27 KiB
Add Core Data Definitions
This ExecPlan is a living document. The sections Progress, Surprises & Discoveries, Decision Log, and Outcomes & Retrospective must be kept up to date as work proceeds.
This plan follows PLANS.md in the repository root. It is intentionally self-contained so a developer with only this repository and this file can implement Slice 2 without reading prior chat history.
Purpose / Big Picture
After this slice, the project will have a small content definition layer for the side-scrolling shooter. A developer will be able to describe a mission, difficulty, camera path, parallax layers, enemies, behavior tracks, clusters, collectibles, weapons, special weapons, and squadron mate types without hardcoding those choices into gameplay scenes.
The observable result is concrete: from D:\Code\zfxaction26_1, dotnet test SideScrollerGame.sln validates the sample content and intentionally broken content, dotnet build SideScrollerGame.sln succeeds, and .\godot --headless --path godot -- --debug-boot=content-browser --content-validate-only loads the project, validates the sample registry, prints a short list of loaded definition ids, prints Content validation succeeded, and exits with code 0.
This slice does not implement gameplay movement, enemy spawning, weapons, or the mission runner. It creates the contracts and validation needed so those later systems can load data confidently and fail fast when sample content is incomplete or contradictory.
Progress
- (2026-04-21 17:04Z) Read repository rules, Windows rules,
PLANS.md,DESIGN.md,CODE.md,SLICE1.MD, current Godot scripts, andgodot/project.godot. - (2026-04-21 17:04Z) Verified that Slice 1 has committed a bootable Godot shell with
MenuandSmokedebug boot modes. - (2026-04-21 17:04Z) Created this Slice 2 ExecPlan.
- Implement C# content definition types and validation result types.
- Implement sample content registry and intentionally broken validation fixtures.
- Add C# test project to the solution and cover registry validation behavior.
- Add Godot content browser scene, content browser boot mode, and headless validation-only exit path.
- Run formatting for touched C# files with
jb cleanupcode --build=False. - Validate with .NET tests, .NET build, Godot solution build, and headless content browser boot.
- Commit the completed slice.
Surprises & Discoveries
-
Observation: The active Godot project is under
godot/, not at the repository root. Evidence:godot/project.godotis the configured project file, the wrapper command is run from the repository root as.\godot --path godot ..., and Slice 1 validation uses that layout. -
Observation: The current boot shell has only
MenuandSmokedebug boot modes. Evidence:godot/scripts/debug/DebugBootMode.cscontains onlyMenuandSmoke, andgodot/scripts/bootstrap/GameRoot.csswitches every non-smoke mode to the menu placeholder. -
Observation: There are no existing gameplay content definitions or tests in the repository. Evidence:
rg --filesshows only bootstrap, menu, debug smoke scripts and scenes undergodot/; there is notests/directory.
Decision Log
-
Decision: Implement Slice 2 definitions as plain C# records and small enums inside the existing Godot C# project first, not as Godot
Resourceassets. Rationale: Plain C# records are fast to write, easy to unit test without launching Godot, easy to refactor during the jam, and can later be backed by Godot resources or JSON if editor authoring becomes more important than code-side iteration. Date/Author: 2026-04-21 / Codex. -
Decision: Add a separate .NET test project for content validation. Rationale: Definition validation is pure rule logic. Running it through
dotnet testis faster and more reliable than launching the Godot editor for every validation case. Date/Author: 2026-04-21 / Codex. -
Decision: Add a Godot content browser debug boot mode even though the unit tests cover validation. Rationale: Later slices need a fast in-engine way to confirm that designers can see loaded content ids and validation errors before entering gameplay. This also proves the definitions can be consumed from Godot scenes, not only from tests. Date/Author: 2026-04-21 / Codex.
-
Decision: Keep sample definitions theme-neutral. Rationale: The jam topic is unknown. Stable ids such as
mission.test,enemy.serial, andweapon.primary.basicprove system wiring without committing to a visual or story theme. Date/Author: 2026-04-21 / Codex.
Outcomes & Retrospective
Not started. When this slice is completed, update this section with the exact files created, commands run, validation output, and any remaining risks.
Context and Orientation
The repository root is D:\Code\zfxaction26_1. The Godot project root is D:\Code\zfxaction26_1\godot. The repo-local wrapper D:\Code\zfxaction26_1\godot.cmd launches Godot 4.5.1 .NET, so Godot commands are run from the repository root with .\godot --path godot ....
Current important files are:
godot/project.godot: Godot project configuration. It currently setsrun/main_scene="res://scenes/bootstrap/GameRoot.tscn"andproject/assembly_name="SideScrollerGame.Godot".godot/SideScrollerGame.Godot.csproj: the Godot C# project. It currently targetsnet8.0, usesGodot.NET.Sdk/4.5.1, and buildsSideScrollerGame.Godot.dll.SideScrollerGame.sln: the Visual Studio solution. It currently contains the Godot C# project.godot/scripts/bootstrap/GameRoot.cs: the root scene script. It readsDebugSettings, seeds Godot randomness, prints the boot mode and seed, then loads either the menu placeholder or smoke scene.godot/scripts/debug/DebugBootMode.cs: the enum for debug boot modes. It currently containsMenuandSmoke.godot/scripts/debug/DebugSettings.cs: command-line and project-setting reader for--debug-boot=<value>and--seed=<integer>.godot/scripts/debug/DebugOverlay.cs: tiny top-left debug label used by the root scene.godot/scenes/bootstrap/GameRoot.tscn: main scene withGameRoot.cs,MenuScene,SmokeScene, and aDebugOverlaychild.CODE.md: the broader implementation plan. Its Slice 2 section asks for definitions, sample data, a content registry, validation methods, and a debug content browser.DESIGN.md: the systems design source. It describes the data-driven content objects and rules that Slice 2 must represent. Do not rewrite this file in Slice 2 unless the user explicitly asks for design changes.SLICE1.MD: the previous ExecPlan. It documents the boot shell and validation commands used by this repo.
Definitions used in this plan:
- A definition is immutable content data that describes what something is, such as a weapon id, enemy health, cluster spawn time, or difficulty multiplier. A definition should not own live scene nodes or mutable gameplay state.
- A stable id is a string that other definitions can reference. Stable ids should use lowercase dotted names, such as
mission.testorweapon.primary.basic, so sample content is easy to search and compare. - A content registry is a central object that exposes all known definitions by stable id. Later gameplay code will ask the registry for definitions instead of constructing content directly.
- Validation means checking content for broken references and invalid numbers before gameplay starts. For example, a mission that references
cluster.missingshould produce a clear validation error naming that missing id. - A content browser is a debug scene that lists loaded definition ids and validation messages. It is not the final game UI; it is a fast testing tool.
- Headless mode means Godot runs without opening a window. This plan uses headless mode to prove the content browser and validation can run from the command line.
Plan of Work
First, create a pure content model under godot/scripts/content/. Use subfolders so the file locations communicate intent: godot/scripts/content/definitions/ for definition records and enums, godot/scripts/content/validation/ for validation results, and godot/scripts/content/samples/ for hardcoded sample content. Keep one primary public type per file, with class names matching filenames.
Add definition records for the Slice 2 objects named in CODE.md and DESIGN.md: MissionDefinition, DifficultyDefinition, CameraPathDefinition, LevelLayerDefinition, EnemyTypeDefinition, EnemyBehaviorDefinition, EnemyClusterDefinition, CollectibleDefinition, WeaponDefinition, SpecialWeaponDefinition, and SquadronMateTypeDefinition. Also add small supporting records and enums where they keep the main definitions readable, such as DefinitionId, SpawnScheduleEntryDefinition, CameraPathPointDefinition, LayerKind, BehaviorTrackMode, BehaviorEventKind, CollectibleKind, WeaponKind, SpecialWeaponKind, SquadronMateFormationKind, ClusterEscapeRule, and DifficultyModifierSet.
Keep the data minimal but useful. Do not model every future parameter in detail. Each definition should contain the stable id, display name or debug name, and the fields needed to validate cross-references and starter balance. For example, EnemyClusterDefinition should include an id, a reward score, an escape rule, and spawn entries that reference enemy type ids. MissionDefinition should include an id, a default difficulty id, camera path id, background and foreground layer ids, cluster ids, collectible ids, special weapon ids, and timeline marker names. WeaponDefinition should include kind, damage, fire cadence seconds, projectile speed, projectile count, and whether it consumes enemy projectiles.
Next, implement validation in godot/scripts/content/validation/. Add ContentValidationSeverity with Info, Warning, and Error. Add ContentValidationMessage with severity, code, message, and optional definition id. Add ContentValidationResult with an IReadOnlyList<ContentValidationMessage>, a HasErrors property, and helper constructors. Add ContentValidator with a method Validate(ContentRegistry registry) that checks every sample definition collection. Validation should report missing ids, duplicate ids, empty required lists, invalid timings, invalid multipliers, invalid health and damage values, invalid weapon slots, broken mission references, cluster spawn entries that reference missing enemies, behavior tracks with no events, event durations below zero, and difficulties with non-positive multipliers.
Then add ContentRegistry under godot/scripts/content/. It should own read-only dictionaries keyed by stable id. It should expose typed lookup methods such as TryGetMission, TryGetDifficulty, and TryGetEnemyType, plus an AllDefinitionIds() method that returns stable ids grouped or sorted for display. Add SampleContent.CreateRegistry() in godot/scripts/content/samples/ to create one test mission and enough referenced content for validation to pass.
The sample content must prove the real vertical slice shape without implementing gameplay. Include at least these ids:
- Difficulty ids:
difficulty.easy,difficulty.normal,difficulty.hard. - Camera path id:
camera.test.path. - Layer ids:
layer.background.stars,layer.foreground.clouds. - Enemy behavior ids:
behavior.enemy.serial,behavior.enemy.parallel. - Enemy type ids:
enemy.serial,enemy.parallel. - Cluster id:
cluster.opening. - Collectible ids:
collectible.points.small,collectible.squadron.orbit. - Primary weapon id:
weapon.primary.basic. - Secondary weapon id:
weapon.secondary.vertical. - Special weapon id:
weapon.special.bomb. - Squadron mate type id:
squadron.orbit. - Mission id:
mission.test.
Add an intentionally broken sample builder, such as BrokenSampleContent.CreateRegistryWithMissingEnemyReference(), that is used only by tests. This avoids weakening the main sample content while proving validation catches bad data.
Next, add a test project under tests/SideScrollerGame.Content.Tests/. Use xUnit or another standard .NET test framework already available through NuGet. If no test framework is already present, choose xUnit because it works cleanly with dotnet test and does not require Godot to launch. Add the test project to SideScrollerGame.sln. The test project should reference godot/SideScrollerGame.Godot.csproj or, if that proves awkward because of Godot SDK behavior, create a small plain class library src/SideScrollerGame.Content/ and move the pure content model there. Prefer the smallest working approach, but record any project-structure decision in this plan.
Cover these behaviors with tests:
SampleContent.CreateRegistry()validates with no errors.- Duplicate ids produce an error naming the duplicated id.
- A mission with a missing cluster id produces an error naming the mission and missing cluster.
- A cluster with a missing enemy type id produces an error naming the cluster and missing enemy.
- A behavior track with no events produces an error naming the behavior.
- A difficulty with a non-positive multiplier produces an error naming the difficulty and field.
- The registry can look up
mission.test,enemy.serial,weapon.primary.basic, anddifficulty.normal.
Then add a Godot content browser. Create godot/scenes/debug/ContentBrowser.tscn and godot/scripts/debug/ContentBrowserController.cs. The scene should be a Control with a title label and a multiline text label. The controller should create the sample registry, validate it, list definitions by id, and show validation messages. If Godot is headless and the command line contains --content-validate-only, it should print all loaded definition ids, print either Content validation succeeded or Content validation failed, then quit with exit code 0 for success and 1 for validation errors.
Update godot/scripts/debug/DebugBootMode.cs to add ContentBrowser. Update godot/scripts/bootstrap/GameRoot.cs to export a PackedScene? ContentBrowserScene and load it when the boot mode is ContentBrowser. Update godot/scenes/bootstrap/GameRoot.tscn to reference res://scenes/debug/ContentBrowser.tscn. Keep existing Menu and Smoke behavior unchanged.
Finally, update this ExecPlan as work proceeds. Do not edit DESIGN.md as part of this slice. If SideScrollerGame.sln or godot/SideScrollerGame.Godot.csproj have unrelated user edits, preserve them and stage only the minimal solution or project changes required for tests.
Concrete Steps
Run all commands from D:\Code\zfxaction26_1.
-
Confirm the current state before editing:
git status --short rg --files godot dotnet build SideScrollerGame.sln .\godot --headless --path godot -- --debug-boot=smoke --seed=12345Expected result:
git status --shortshould show only changes the user intentionally left in the worktree, if any. The .NET build should end withBuild succeeded. The smoke boot should printDebug boot: Smoke,Seed: 12345, andSmoke scene loaded. -
Create content folders:
godot/scripts/content godot/scripts/content/definitions godot/scripts/content/validation godot/scripts/content/samplesCreating directories that already exist is safe.
-
Add the definition records and enums described in the Plan of Work. Keep the public namespace
SideScrollerGame.Content.Definitionsfor definition types. UseSideScrollerGame.Content.Validationfor validation types. UseSideScrollerGame.Content.Samplesfor sample builders. -
Add
godot/scripts/content/ContentRegistry.cs. It should accept all definition collections in its constructor, build dictionaries keyed by id, and expose read-only collections for validators and the debug browser. It should not depend on Godot scene nodes. -
Add
godot/scripts/content/validation/ContentValidator.cs. It should validate a whole registry and returnContentValidationResult. Do not throw exceptions for normal content mistakes; return validation messages so the browser and tests can display them. -
Add
godot/scripts/content/samples/SampleContent.cswith one valid test mission and all referenced definitions. Add broken sample helpers in test code or ingodot/scripts/content/samples/BrokenSampleContent.csif shared fixtures reduce duplication. -
Add a test project:
dotnet new xunit -n SideScrollerGame.Content.Tests -o tests\SideScrollerGame.Content.Tests dotnet sln SideScrollerGame.sln add tests\SideScrollerGame.Content.Tests\SideScrollerGame.Content.Tests.csproj dotnet add tests\SideScrollerGame.Content.Tests\SideScrollerGame.Content.Tests.csproj reference godot\SideScrollerGame.Godot.csprojIf referencing the Godot project makes
dotnet testfail because of Godot-specific build behavior, createsrc\SideScrollerGame.Content\SideScrollerGame.Content.csproj, move the pure content files there, reference that project from both the Godot project and the test project, and record the reason inSurprises & DiscoveriesandDecision Log. -
Add tests for validation and registry lookup behavior. Use test names that describe the expected behavior, such as
SampleContent_ValidatesWithoutErrorsandValidate_ClusterWithMissingEnemyType_ReportsMissingEnemy. -
Add
godot/scenes/debug/ContentBrowser.tscnandgodot/scripts/debug/ContentBrowserController.cs. The browser should display all ids fromSampleContent.CreateRegistry()and validation messages fromContentValidator. -
Extend debug boot:
godot/scripts/debug/DebugBootMode.cs godot/scripts/bootstrap/GameRoot.cs godot/scenes/bootstrap/GameRoot.tscn
Add the
ContentBrowserenum value, export and wireContentBrowserScene, and load it when requested by--debug-boot=content-browser. -
Format touched C# files. Include every C# file touched by this slice. Use separate path arguments because quoting a semicolon-separated list can be interpreted as one path by JetBrains Cleanup Code:
jb cleanupcode --build=False godot\scripts\content\ContentRegistry.cs godot\scripts\content\definitions\MissionDefinition.cs godot\scripts\content\validation\ContentValidator.cs godot\scripts\content\samples\SampleContent.cs godot\scripts\debug\ContentBrowserController.cs godot\scripts\debug\DebugBootMode.cs godot\scripts\bootstrap\GameRoot.cs tests\SideScrollerGame.Content.Tests\ContentValidationTests.cs
Adjust the file list to include the real touched files. If
jb cleanupcodeis unavailable, record the exact error inSurprises & Discoveries, keep formatting manually consistent, and continue validation. -
Validate the slice:
dotnet test SideScrollerGame.sln dotnet build SideScrollerGame.sln .\godot --headless --path godot --build-solutions --quit .\godot --headless --path godot -- --debug-boot=content-browser --content-validate-only
Expected result: tests pass, the solution builds with 0 errors, the Godot solution build exits without a stuck process, and the content browser prints loaded ids plus
Content validation succeeded. -
Check the diff:
git status --short git diff -- SLICE2.MD godot/scripts/content godot/scripts/debug godot/scripts/bootstrap godot/scenes/debug godot/scenes/bootstrap SideScrollerGame.sln tests src
Expected result: the diff includes only Slice 2 files and the minimal solution/project updates required for tests. User changes outside the slice must not be reverted.
-
Commit this slice after validation:
git add SLICE2.MD godot/scripts/content godot/scripts/debug godot/scripts/bootstrap godot/scenes/debug godot/scenes/bootstrap SideScrollerGame.sln tests src git commit -m "Add core content definitions"
If
srcis not created, omit it. IfSideScrollerGame.slnis dirty from user work unrelated to the test project addition, stage only the hunks that add the test project.
Validation and Acceptance
This slice is accepted when the following observable behaviors are true.
Running content tests succeeds:
dotnet test SideScrollerGame.sln
Expected final output should include a passed test summary and no failed tests. The exact number of tests may change, but it should include tests for valid sample content, missing references, duplicate ids, invalid difficulty multipliers, invalid behavior tracks, and registry lookup.
Building from the repository root succeeds:
dotnet build SideScrollerGame.sln
Expected final lines include:
Build succeeded.
0 Error(s)
Godot can build the C# solution and exit:
.\godot --headless --path godot --build-solutions --quit
The command should exit without a stuck Godot process. If it prints warnings that do not fail the process, record them in this plan.
The content browser can be booted directly and used as a validation smoke test:
.\godot --headless --path godot -- --debug-boot=content-browser --content-validate-only
Expected output includes:
Debug boot: ContentBrowser
Loaded content definitions:
mission.test
difficulty.normal
enemy.serial
cluster.opening
weapon.primary.basic
Content validation succeeded
Opening the editor should still show the project and root scene:
.\godot --editor --path godot
In the editor, running with no command-line arguments should still show the menu placeholder. Running with -- --debug-boot=content-browser should show the content browser with loaded ids and validation status. Running with -- --debug-boot=smoke --seed=12345 should still print Smoke scene loaded and exit in headless mode.
Idempotence and Recovery
The implementation is mostly additive and safe to repeat. Creating directories that already exist should do nothing. Re-running dotnet test, dotnet build, Godot solution build, and content browser smoke boot should produce the same result.
If test project creation fails because files already exist, inspect tests/SideScrollerGame.Content.Tests/SideScrollerGame.Content.Tests.csproj and continue from the existing project instead of deleting it. If the solution already contains the test project, do not add it twice.
If referencing godot/SideScrollerGame.Godot.csproj from tests causes restore or build problems, move pure content definitions into a plain class library under src/SideScrollerGame.Content/ and reference that library from both the Godot project and tests. This is a safe course change because it keeps Godot scene scripts in the Godot project while making pure rules easy to test.
If .\godot --headless --path godot --build-solutions --quit hangs, stop only the stuck Godot process whose command line contains this repository path and this exact command. Record the command and process details in Surprises & Discoveries. Validate with dotnet build SideScrollerGame.sln plus the content browser smoke boot before continuing.
If user changes appear in files outside this slice, leave them untouched. If user changes conflict with SideScrollerGame.sln, godot/SideScrollerGame.Godot.csproj, or godot/project.godot, read the current file and merge only the smallest required change.
Do not use git restore, git checkout --, reset commands, or equivalent rollback commands to discard local changes. If a generated file is unwanted, leave it unstaged unless the user explicitly asks for cleanup.
Artifacts and Notes
Current project shape before Slice 2 implementation:
godot\project.godot
godot\SideScrollerGame.Godot.csproj
godot\scenes\bootstrap\GameRoot.tscn
godot\scenes\debug\SmokeScene.tscn
godot\scenes\menu\MenuPlaceholder.tscn
godot\scripts\bootstrap\GameRoot.cs
godot\scripts\debug\DebugBootMode.cs
godot\scripts\debug\DebugOverlay.cs
godot\scripts\debug\DebugSettings.cs
godot\scripts\debug\SmokeSceneController.cs
godot\scripts\menu\MenuPlaceholder.cs
Current successful Slice 1 smoke command:
.\godot --headless --path godot -- --debug-boot=smoke --seed=12345
Debug boot: Smoke
Seed: 12345
Smoke scene loaded
The content browser output after this slice should resemble:
Debug boot: ContentBrowser
Seed: 1
Loaded content definitions:
camera.test.path
cluster.opening
collectible.points.small
collectible.squadron.orbit
difficulty.easy
difficulty.hard
difficulty.normal
enemy.parallel
enemy.serial
layer.background.stars
layer.foreground.clouds
mission.test
squadron.orbit
weapon.primary.basic
weapon.secondary.vertical
weapon.special.bomb
Content validation succeeded
Interfaces and Dependencies
The project uses Godot 4.5.1 .NET through Godot.NET.Sdk/4.5.1 and targets net8.0 for desktop builds. New Godot-facing scripts must use the Godot namespace and partial Godot classes where required by the engine. Pure content model types should avoid Godot dependencies unless an engine type clearly saves complexity.
At the end of this slice, these public C# types should exist. The exact supporting fields may be adjusted during implementation, but the names, responsibilities, and validation behaviors should remain stable.
In godot/scripts/content/ContentRegistry.cs:
namespace SideScrollerGame.Content;
public sealed class ContentRegistry
{
public IReadOnlyDictionary<string, MissionDefinition> Missions { get; }
public IReadOnlyDictionary<string, DifficultyDefinition> Difficulties { get; }
public IReadOnlyDictionary<string, CameraPathDefinition> CameraPaths { get; }
public IReadOnlyDictionary<string, LevelLayerDefinition> LevelLayers { get; }
public IReadOnlyDictionary<string, EnemyTypeDefinition> EnemyTypes { get; }
public IReadOnlyDictionary<string, EnemyBehaviorDefinition> EnemyBehaviors { get; }
public IReadOnlyDictionary<string, EnemyClusterDefinition> EnemyClusters { get; }
public IReadOnlyDictionary<string, CollectibleDefinition> Collectibles { get; }
public IReadOnlyDictionary<string, WeaponDefinition> Weapons { get; }
public IReadOnlyDictionary<string, SpecialWeaponDefinition> SpecialWeapons { get; }
public IReadOnlyDictionary<string, SquadronMateTypeDefinition> SquadronMateTypes { get; }
public IEnumerable<string> AllDefinitionIds();
}
In godot/scripts/content/validation/ContentValidator.cs:
namespace SideScrollerGame.Content.Validation;
public sealed class ContentValidator
{
public ContentValidationResult Validate(ContentRegistry registry);
}
In godot/scripts/content/validation/ContentValidationResult.cs:
namespace SideScrollerGame.Content.Validation;
public sealed class ContentValidationResult
{
public IReadOnlyList<ContentValidationMessage> Messages { get; }
public bool HasErrors { get; }
}
In godot/scripts/content/samples/SampleContent.cs:
namespace SideScrollerGame.Content.Samples;
public static class SampleContent
{
public static ContentRegistry CreateRegistry();
}
In godot/scripts/debug/ContentBrowserController.cs:
namespace SideScrollerGame.Debug;
public partial class ContentBrowserController : Control
{
public override void _Ready();
}
The content browser controller must print validation results and quit when headless and --content-validate-only is present in OS.GetCmdlineUserArgs().
In godot/scripts/debug/DebugBootMode.cs, extend the enum to include:
ContentBrowser
In godot/scripts/bootstrap/GameRoot.cs, extend boot loading so:
--debug-boot=content-browser
loads res://scenes/debug/ContentBrowser.tscn.
Revision note 2026-04-21: Created this ExecPlan from the current Slice 1 project shell. The plan scopes Slice 2 to pure data definitions, validation, sample content, tests, and an in-engine content browser smoke path while leaving gameplay runtime systems to later slices.