Files
zfxaction26_1/SLICE2.MD

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, and godot/project.godot.
  • (2026-04-21 17:04Z) Verified that Slice 1 has committed a bootable Godot shell with Menu and Smoke debug 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.godot is 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 Menu and Smoke debug boot modes. Evidence: godot/scripts/debug/DebugBootMode.cs contains only Menu and Smoke, and godot/scripts/bootstrap/GameRoot.cs switches every non-smoke mode to the menu placeholder.

  • Observation: There are no existing gameplay content definitions or tests in the repository. Evidence: rg --files shows only bootstrap, menu, debug smoke scripts and scenes under godot/; there is no tests/ 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 Resource assets. 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 test is 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, and weapon.primary.basic prove 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 sets run/main_scene="res://scenes/bootstrap/GameRoot.tscn" and project/assembly_name="SideScrollerGame.Godot".
  • godot/SideScrollerGame.Godot.csproj: the Godot C# project. It currently targets net8.0, uses Godot.NET.Sdk/4.5.1, and builds SideScrollerGame.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 reads DebugSettings, 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 contains Menu and Smoke.
  • 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 with GameRoot.cs, MenuScene, SmokeScene, and a DebugOverlay child.
  • 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.test or weapon.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.missing should 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, and difficulty.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.

  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=12345
    

    Expected result: git status --short should show only changes the user intentionally left in the worktree, if any. The .NET build should end with Build succeeded. The smoke boot should print Debug boot: Smoke, Seed: 12345, and Smoke scene loaded.

  2. Create content folders:

    godot/scripts/content
    godot/scripts/content/definitions
    godot/scripts/content/validation
    godot/scripts/content/samples
    

    Creating directories that already exist is safe.

  3. Add the definition records and enums described in the Plan of Work. Keep the public namespace SideScrollerGame.Content.Definitions for definition types. Use SideScrollerGame.Content.Validation for validation types. Use SideScrollerGame.Content.Samples for sample builders.

  4. 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.

  5. Add godot/scripts/content/validation/ContentValidator.cs. It should validate a whole registry and return ContentValidationResult. Do not throw exceptions for normal content mistakes; return validation messages so the browser and tests can display them.

  6. Add godot/scripts/content/samples/SampleContent.cs with one valid test mission and all referenced definitions. Add broken sample helpers in test code or in godot/scripts/content/samples/BrokenSampleContent.cs if shared fixtures reduce duplication.

  7. 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.csproj
    

    If referencing the Godot project makes dotnet test fail because of Godot-specific build behavior, create src\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 in Surprises & Discoveries and Decision Log.

  8. Add tests for validation and registry lookup behavior. Use test names that describe the expected behavior, such as SampleContent_ValidatesWithoutErrors and Validate_ClusterWithMissingEnemyType_ReportsMissingEnemy.

  9. Add godot/scenes/debug/ContentBrowser.tscn and godot/scripts/debug/ContentBrowserController.cs. The browser should display all ids from SampleContent.CreateRegistry() and validation messages from ContentValidator.

  10. Extend debug boot:

    godot/scripts/debug/DebugBootMode.cs godot/scripts/bootstrap/GameRoot.cs godot/scenes/bootstrap/GameRoot.tscn

    Add the ContentBrowser enum value, export and wire ContentBrowserScene, and load it when requested by --debug-boot=content-browser.

  11. 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 cleanupcode is unavailable, record the exact error in Surprises & Discoveries, keep formatting manually consistent, and continue validation.

  12. 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.

  13. 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.

  14. 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 src is not created, omit it. If SideScrollerGame.sln is 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.