Files
zfxaction26_1/SLICE3.MD
2026-04-21 21:16:30 +02:00

32 KiB

Add Debug Foundation

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 3 without reading prior chat history.

Purpose / Big Picture

After this slice, the project will have shared debug tools that later hero, weapon, enemy, cluster, mission, boss, and highscore slices can reuse. A developer will be able to boot directly into a debug sandbox, pause and resume time, step one frame, change time scale, switch difficulty, set seed, toggle test flags, request actor spawns by content id, jump to a timeline marker, restart the sandbox, reload the scene, and see the active debug state in an overlay.

The observable result is concrete: from D:\Code\zfxaction26_1, dotnet test SideScrollerGame.sln validates the debug command service, dotnet build SideScrollerGame.sln succeeds, and .\godot --headless --path godot -- --debug-boot=debug-sandbox --debug-script=foundation-smoke --seed=333 boots a Godot debug sandbox, runs a scripted sequence of debug commands, prints the commands and resulting state, prints Debug foundation smoke succeeded, and exits with code 0.

This slice does not implement the real hero, weapons, enemies, clusters, or mission runner. It creates the control surface and fake sandbox targets that later slices can replace with real gameplay handlers while keeping the same debug command names and fast testing workflow.

Progress

  • (2026-04-21 17:43Z) Read repository rules, Windows rules, PLANS.md, CODE.md, SLICE2.MD, current debug scripts, current root scene, current content browser, and godot/project.godot.
  • (2026-04-21 17:43Z) Verified that Slice 2 has committed content definitions, validation tests, content browser boot mode, and a clean worktree.
  • (2026-04-21 17:43Z) Created this Slice 3 ExecPlan.
  • (2026-04-21 19:18Z) Implemented pure debug runtime state and command service.
  • (2026-04-21 19:18Z) Added unit tests for debug commands and validation behavior.
  • (2026-04-21 19:18Z) Added Godot debug command node, upgraded overlay, clickable debug panel, and debug sandbox scene.
  • (2026-04-21 19:18Z) Added debug sandbox boot mode and input actions.
  • (2026-04-21 19:18Z) Added headless debug foundation smoke script.
  • (2026-04-21 19:18Z) Ran formatting for touched C# files with jb cleanupcode --build=False.
  • (2026-04-21 19:18Z) Validated with .NET tests, .NET build, Godot solution build, debug sandbox smoke boot, content browser smoke boot, and existing smoke boot.
  • (2026-04-21 19:18Z) 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, and all successful Godot commands use .\godot --path godot ... from D:\Code\zfxaction26_1.

  • Observation: The current debug overlay only shows boot mode, seed, scene id, and debug build state. Evidence: godot/scripts/debug/DebugOverlay.cs contains SetStatus(DebugSettings settings, string loadedSceneId) and a single label.

  • Observation: The current root boot modes are Menu, Smoke, and ContentBrowser. Evidence: godot/scripts/debug/DebugBootMode.cs contains those three enum values, and godot/scripts/bootstrap/GameRoot.cs switches between menu, smoke, and content browser scenes.

  • Observation: Content definitions and tests are available for difficulty ids, enemy ids, and mission timeline marker names. Evidence: godot/scripts/content/samples/SampleContent.cs creates difficulty.easy, difficulty.normal, difficulty.hard, enemy.serial, enemy.parallel, and mission markers such as cluster.opening.

  • Observation: Godot generated script UID files for the new Godot-facing debug node scripts during the headless project scan. Evidence: godot/scripts/debug/DebugCommandNode.cs.uid, godot/scripts/debug/DebugPanelController.cs.uid, and godot/scripts/debug/DebugSandboxController.cs.uid appeared after .\godot --headless --path godot --build-solutions --quit.

Decision Log

  • Decision: Implement debug command rules as plain C# first, with a Godot node wrapper for engine effects. Rationale: Command validity, flags, selected difficulty, time scale values, spawn requests, and marker jumps are pure rules that can be tested quickly with dotnet test. Godot-specific work such as pausing the scene tree and reloading scenes belongs in the node wrapper. Date/Author: 2026-04-21 / Codex.

  • Decision: Use one shared debug command service instead of wiring shortcuts directly inside the sandbox scene. Rationale: Later slices need the same commands from keyboard shortcuts, clickable debug UI, tests, future automation, and gameplay sandboxes. A shared service avoids duplicating behavior in every scene. Date/Author: 2026-04-21 / Codex.

  • Decision: Make Slice 3 prove spawn and timeline commands with fake sandbox objects. Rationale: Real enemies, projectiles, clusters, and mission timeline systems do not exist yet. A fake actor and fake marker jump still prove the debug command surface and preserve the same handler interfaces for later real systems. Date/Author: 2026-04-21 / Codex.

  • Decision: Add DebugSandbox as a boot mode instead of expanding ContentBrowser. Rationale: The content browser is for inspecting definitions. The debug sandbox is for exercising runtime commands. Keeping them separate prevents the content browser from becoming a mixed-purpose tool. Date/Author: 2026-04-21 / Codex.

Outcomes & Retrospective

Completed. Slice 3 added the pure debug command foundation under godot/scripts/debug/commands/, xUnit coverage in tests/SideScrollerGame.Content.Tests/DebugCommandServiceTests.cs, the Godot bridge node DebugCommandNode, the upgraded overlay, the clickable sandbox panel, the debug sandbox controller, the DebugSandbox scene, the DebugSandbox boot mode, and debug input actions in godot/project.godot.

Validation completed from D:\Code\zfxaction26_1:

dotnet test SideScrollerGame.sln
  Passed! - Failed: 0, Passed: 22, Skipped: 0, Total: 22

dotnet build SideScrollerGame.sln
  Build succeeded.
  0 Warning(s)
  0 Error(s)

.\godot --headless --path godot --build-solutions --quit
  Exited successfully after project scan, .NET build, and script class registration.

.\godot --headless --path godot -- --debug-boot=debug-sandbox --debug-script=foundation-smoke --seed=333
  Debug foundation smoke succeeded

.\godot --headless --path godot -- --debug-boot=content-browser --content-validate-only
  Content validation succeeded

.\godot --headless --path godot -- --debug-boot=smoke --seed=12345
  Smoke scene loaded

Remaining risk: frame-step behavior is covered through the command service request counter and debug node bridge, but it has not been manually observed in the editor UI in this headless-only iteration.

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 sets run/main_scene="res://scenes/bootstrap/GameRoot.tscn", the C# assembly name, and input actions for movement, primary fire, secondary fire, special fire, pause, debug overlay, and quick restart.
  • SideScrollerGame.sln: Visual Studio solution. It contains the Godot C# project and the tests/SideScrollerGame.Content.Tests xUnit project.
  • godot/scripts/bootstrap/GameRoot.cs: root scene script. It reads DebugSettings, seeds Godot randomness, prints boot mode and seed, and loads a scene based on DebugBootMode.
  • godot/scenes/bootstrap/GameRoot.tscn: main scene. It wires MenuScene, SmokeScene, ContentBrowserScene, and a DebugOverlay child.
  • godot/scripts/debug/DebugBootMode.cs: debug boot enum. It currently contains Menu, Smoke, and ContentBrowser.
  • godot/scripts/debug/DebugSettings.cs: reads --debug-boot=<value> and --seed=<integer> from user command-line arguments. It already supports hyphenated boot names such as content-browser.
  • godot/scripts/debug/DebugOverlay.cs: small overlay label. It must become a reusable debug status overlay in this slice.
  • godot/scripts/debug/ContentBrowserController.cs: existing debug content browser that can headlessly validate sample content.
  • godot/scripts/content/samples/SampleContent.cs: theme-neutral sample content. Slice 3 should use it to validate difficulty ids, actor definition ids, and mission marker names.
  • tests/SideScrollerGame.Content.Tests/: xUnit tests. Slice 3 can add debug tests here or rename the project later, but the simplest implementation is to add debug tests to this existing test project.

Definitions used in this plan:

  • A debug command is a named action used only during development, such as Pause, FrameStep, SetTimeScale, SpawnActor, or JumpToMarker.
  • A debug command service is a C# object that receives debug commands, updates debug state, and notifies registered handlers. A handler is a callback function that a scene provides when it knows how to do a command-specific action, such as spawning a fake actor in the sandbox.
  • Debug state is the current shared debug information, such as whether the game is paused, the selected difficulty, current time scale, random seed, active timeline marker, and toggled flags.
  • A debug sandbox is a Godot scene made only for development. It contains visible placeholder nodes and buttons so commands can be tested without real gameplay systems.
  • A frame step means advancing the game by one rendered/process frame while the game is otherwise paused.
  • Time scale means Godot's global simulation speed multiplier. 1.0 is normal speed, 0.5 is half speed, and 2.0 is double speed.
  • Headless mode means Godot runs without opening a window. This plan uses headless mode to prove the debug sandbox from the command line.

Plan of Work

First, add pure debug command types under godot/scripts/debug/commands/. Add DebugCommandId, an enum for commands such as ToggleOverlay, Pause, Resume, TogglePause, FrameStep, SetTimeScale, ReloadScene, RestartMission, SetDifficulty, SetSeed, SpawnActor, JumpToMarker, ToggleCollisionShapes, ToggleGameplayBounds, ToggleInvulnerability, ToggleInfiniteSpecialAmmo, and ToggleNoEnemyFire. Add DebugCommandResult, a small result object with Succeeded, Message, and optional CommandId. Add DebugRuntimeState, a plain C# class or record that stores overlay visibility, pause state, time scale, active difficulty id, seed, last spawned actor id, spawned actor count, current marker id, collision shape visibility flag, gameplay bounds flag, invulnerability flag, infinite special ammo flag, no enemy fire flag, reload request count, restart request count, and frame step request count.

Next, add DebugCommandService as a pure C# service in godot/scripts/debug/commands/. It should accept a ContentRegistry in its constructor so it can validate difficulty ids, actor ids, and mission marker ids against SampleContent.CreateRegistry(). It should expose DebugRuntimeState State, DebugCommandResult Execute(DebugCommandId commandId, string? argument = null), and events such as StateChanged and CommandExecuted. It should allow handler registration for sandbox-specific actions: a spawn handler for actor ids, a timeline jump handler for marker ids, a reload handler, and a restart handler. If no handler is registered, the command should still update the request count and return a clear message rather than crashing. Unknown difficulty ids, unknown actor ids, and unknown marker ids should fail with clear messages.

Then add unit tests in tests/SideScrollerGame.Content.Tests/DebugCommandServiceTests.cs. These tests should cover at least: pause/resume/toggle pause updates state, time scale accepts the supported values 0.25, 0.5, 1, 2, and 4, invalid time scale fails, difficulty can switch to difficulty.hard, missing difficulty fails, seed can be set, toggle flags work, spawn actor validates enemy.serial, missing actor fails, marker jump validates cluster.opening, missing marker fails, restart request increments the restart counter, and command events fire. These tests should not launch Godot.

Next, add a Godot wrapper node godot/scripts/debug/DebugCommandNode.cs. It should extend Node, own one DebugCommandService, and apply engine-level effects. When the service state says paused, set GetTree().Paused. When time scale changes, set Engine.TimeScale. When frame step is requested, briefly unpause for one process frame and then pause again. When reload is requested, call GetTree().ReloadCurrentScene() unless the current scene is the root scene and the command came from the headless smoke script; in the smoke script, record the reload request without disrupting the script. Keep the wrapper small and let the pure service own validation and state.

Update godot/scripts/bootstrap/GameRoot.cs and godot/scenes/bootstrap/GameRoot.tscn so the root scene owns one DebugCommandNode child and passes the debug settings seed into it. Expose the command node to loaded scenes by node path, for example /root/GameRoot/DebugCommandNode. Do not make it a global autoload in this slice; the root already exists and can own shared runtime services.

Upgrade godot/scripts/debug/DebugOverlay.cs. It should be toggleable and should display active boot mode, loaded scene id, seed, active difficulty, paused state, time scale, current marker, spawned actor count, and flags for invulnerability, infinite special ammo, no enemy fire, collision shapes, and gameplay bounds. Add a method such as Bind(DebugCommandService service, DebugSettings settings, string loadedSceneId). The overlay should subscribe to StateChanged and refresh itself. Keep SetStatus only if it remains useful as a compatibility wrapper.

Add a clickable debug panel for the sandbox. Create godot/scripts/debug/DebugPanelController.cs and use a simple Control node in the sandbox scene. The panel should have buttons or option controls for pause/resume, frame step, time scale choices, difficulty choices, spawn enemy.serial, spawn enemy.parallel, jump intro, jump cluster.opening, toggle invulnerability, toggle infinite special ammo, toggle no enemy fire, toggle collision shapes, toggle gameplay bounds, restart sandbox, and reload scene. If building all buttons as .tscn children is verbose, the controller may create buttons in _Ready() in code for this slice.

Add godot/scenes/debug/DebugSandbox.tscn and godot/scripts/debug/DebugSandboxController.cs. The sandbox should be a Node2D or Control scene with a title, a simple playfield area, the debug panel, and a log label. It should find the root DebugCommandNode, register spawn, marker jump, reload, and restart handlers, and update the log whenever a command executes. The spawn handler should create a visible placeholder node with the requested actor id as text and increment a visible count. The marker jump handler should update a visible marker label. The restart handler should clear spawned actors and reset the marker label. The reload handler may just log in the sandbox because the root command wrapper owns actual scene reload behavior.

Add headless smoke support to the sandbox. If Godot is headless and the user command-line contains --debug-script=foundation-smoke, the sandbox controller should run a deterministic command sequence after one process frame: pause, set time scale to 0.5, set difficulty to difficulty.hard, set seed to the command-line seed, spawn enemy.serial, jump to cluster.opening, toggle invulnerability, toggle infinite special ammo, toggle no enemy fire, toggle collision shapes, toggle gameplay bounds, restart sandbox, and finally spawn enemy.parallel. It should verify the resulting state and printed log, print Debug foundation smoke succeeded, and quit with exit code 0. If any command fails unexpectedly, print Debug foundation smoke failed and quit with exit code 1.

Update debug boot wiring. Add DebugSandbox to DebugBootMode. Add an exported PackedScene? DebugSandboxScene to GameRoot. Add the res://scenes/debug/DebugSandbox.tscn reference to godot/scenes/bootstrap/GameRoot.tscn. Update GameRoot.LoadBootScene so --debug-boot=debug-sandbox loads the sandbox.

Update input actions in godot/project.godot. Keep existing actions and add these actions: debug_pause, debug_frame_step, debug_time_slower, debug_time_faster, debug_spawn_actor, debug_jump_marker, debug_toggle_invulnerability, debug_toggle_infinite_special_ammo, debug_toggle_no_enemy_fire, debug_toggle_collision_shapes, and debug_toggle_gameplay_bounds. The sandbox controller should handle these actions through _UnhandledInput and call DebugCommandNode.Execute(...) so keyboard and clickable panel use the same service.

Finally, update this ExecPlan as work proceeds. Do not edit DESIGN.md unless the user explicitly asks for design changes. If SideScrollerGame.sln, godot/SideScrollerGame.Godot.csproj, or test project files have unrelated user edits, preserve them and stage only Slice 3 changes.

Concrete Steps

Run all commands from D:\Code\zfxaction26_1.

  1. Confirm the current state before editing:

    git status --short
    dotnet test SideScrollerGame.sln
    dotnet build SideScrollerGame.sln
    .\godot --headless --path godot -- --debug-boot=content-browser --content-validate-only
    

    Expected result: the worktree should be clean unless the user has made new edits. Tests should report 8 or more passing tests. The content browser should print loaded definitions and Content validation succeeded.

  2. Create the debug command folder:

    godot/scripts/debug/commands
    

    Creating a directory that already exists is safe.

  3. Add pure debug command files under godot/scripts/debug/commands/:

    DebugCommandId.cs
    DebugCommandResult.cs
    DebugRuntimeState.cs
    DebugCommandService.cs
    

    Keep these files free of Godot node dependencies. They may use System, System.Collections.Generic, and the content registry types.

  4. Add tests in:

    tests/SideScrollerGame.Content.Tests/DebugCommandServiceTests.cs
    

    Use SampleContent.CreateRegistry() as the registry input. The tests must prove the service works without launching Godot.

  5. Add Godot debug runtime scripts:

    godot/scripts/debug/DebugCommandNode.cs
    godot/scripts/debug/DebugPanelController.cs
    godot/scripts/debug/DebugSandboxController.cs
    

    The command node bridges service state to Godot pause and time scale. The panel and sandbox call into the command node.

  6. Add the sandbox scene:

    godot/scenes/debug/DebugSandbox.tscn
    

    The scene should contain the sandbox controller, a label for current marker, a label or container for spawned actors, a log label, and a simple panel node for debug buttons.

  7. Update existing debug boot and root files:

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

    Wire the DebugSandbox boot mode, add the command node child, bind the overlay to the command service, and preserve Menu, Smoke, and ContentBrowser boot behavior.

  8. Update godot/project.godot input actions. Add only missing debug actions. Preserve existing actions and settings.

  9. Format touched C# files. Include every C# file touched by this slice. Use separate path arguments:

    jb cleanupcode --build=False godot\scripts\debug\commands\DebugCommandId.cs godot\scripts\debug\commands\DebugCommandResult.cs godot\scripts\debug\commands\DebugRuntimeState.cs godot\scripts\debug\commands\DebugCommandService.cs godot\scripts\debug\DebugCommandNode.cs godot\scripts\debug\DebugPanelController.cs godot\scripts\debug\DebugSandboxController.cs godot\scripts\debug\DebugOverlay.cs godot\scripts\debug\DebugBootMode.cs godot\scripts\bootstrap\GameRoot.cs tests\SideScrollerGame.Content.Tests\DebugCommandServiceTests.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.

  10. Validate the slice:

    dotnet test SideScrollerGame.sln dotnet build SideScrollerGame.sln .\godot --headless --path godot --build-solutions --quit .\godot --headless --path godot -- --debug-boot=debug-sandbox --debug-script=foundation-smoke --seed=333 .\godot --headless --path godot -- --debug-boot=content-browser --content-validate-only .\godot --headless --path godot -- --debug-boot=smoke --seed=12345

Expected result: tests pass, the solution builds with 0 errors, the Godot solution build exits without a stuck process, the debug sandbox smoke prints Debug foundation smoke succeeded, the content browser still validates sample content, and the existing smoke scene still prints Smoke scene loaded.

  1. Check the diff:

    git status --short git diff -- SLICE3.MD godot/project.godot godot/scripts/debug godot/scripts/bootstrap godot/scenes/debug godot/scenes/bootstrap tests

Expected result: the diff includes only Slice 3 files and minimal edits to shared debug/root files. User changes outside the slice must not be reverted.

  1. Commit this slice after validation:

    git add SLICE3.MD godot/project.godot godot/scripts/debug godot/scripts/bootstrap godot/scenes/debug godot/scenes/bootstrap tests git commit -m "Add debug foundation"

Validation and Acceptance

This slice is accepted when the following observable behaviors are true.

Running 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 the existing content validation tests plus new debug command service tests.

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 debug sandbox can be booted directly and can exercise the command service headlessly:

.\godot --headless --path godot -- --debug-boot=debug-sandbox --debug-script=foundation-smoke --seed=333

Expected output includes:

Debug boot: DebugSandbox
Seed: 333
Debug foundation smoke loaded
Command executed: Pause
Command executed: SetTimeScale 0.5
Command executed: SetDifficulty difficulty.hard
Actor spawned: enemy.serial
Timeline marker: cluster.opening
Command executed: RestartMission
Actor spawned: enemy.parallel
Debug foundation smoke succeeded

Existing direct boots must still work:

.\godot --headless --path godot -- --debug-boot=content-browser --content-validate-only

Expected output includes:

Content validation succeeded

And:

.\godot --headless --path godot -- --debug-boot=smoke --seed=12345

Expected output includes:

Debug boot: Smoke
Seed: 12345
Smoke scene loaded

Opening the editor should show the project and root scene:

.\godot --editor --path godot

In the editor, running with -- --debug-boot=debug-sandbox should show the sandbox. The overlay should be visible by default, the buttons should change the overlay state, keyboard shortcuts should call the same commands, spawned fake actors should appear in the sandbox, restart should clear them, and the reload command should reload the scene.

Idempotence and Recovery

The implementation is mostly additive and safe to repeat. Creating directories that already exist should do nothing. Re-running tests, builds, and smoke commands should produce the same result.

If godot/project.godot already contains one of the planned debug input actions, keep the existing action and add only missing actions. Do not duplicate action blocks.

If .\godot --headless --path godot --build-solutions --quit generates new .cs.uid files for scripts, keep them if Godot generated them during the project scan. Do not hand-edit generated UID values.

If the debug sandbox smoke command hangs, inspect running processes and stop only the Godot process whose command line contains --debug-boot=debug-sandbox --debug-script=foundation-smoke and this repository path. Record the command and process detail in Surprises & Discoveries, then fix the smallest affected script.

If frame-step behavior is unreliable in headless mode, keep unit tests focused on the frame step request counter and validate the visual one-frame advance manually in the editor. Record the limitation in Surprises & Discoveries and Outcomes & Retrospective.

If user changes appear in files outside this slice, leave them untouched. If user changes conflict with godot/project.godot, GameRoot.tscn, or shared debug scripts, read the 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 generated files are unwanted, leave them unstaged unless the user explicitly asks for cleanup.

Artifacts and Notes

Current successful Slice 2 commands before Slice 3 implementation:

dotnet test SideScrollerGame.sln
  Passed!  - Failed: 0, Passed: 8, Skipped: 0, Total: 8

.\godot --headless --path godot -- --debug-boot=content-browser --content-validate-only
  Debug boot: ContentBrowser
  Seed: 1
  Loaded content definitions:
  behavior.enemy.parallel
  behavior.enemy.serial
  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

The debug sandbox smoke output after this slice should resemble:

Debug boot: DebugSandbox
Seed: 333
Debug foundation smoke loaded
Command executed: Pause
Command executed: SetTimeScale 0.5
Command executed: SetDifficulty difficulty.hard
Command executed: SpawnActor enemy.serial
Actor spawned: enemy.serial
Command executed: JumpToMarker cluster.opening
Timeline marker: cluster.opening
Command executed: ToggleInvulnerability
Command executed: ToggleInfiniteSpecialAmmo
Command executed: ToggleNoEnemyFire
Command executed: ToggleCollisionShapes
Command executed: ToggleGameplayBounds
Command executed: RestartMission
Command executed: SpawnActor enemy.parallel
Actor spawned: enemy.parallel
Debug foundation smoke 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 debug command types should avoid Godot node dependencies so xUnit tests can run without launching the engine.

At the end of this slice, these public C# types should exist. The exact supporting members may be adjusted during implementation, but the names, responsibilities, and observable behaviors should remain stable.

In godot/scripts/debug/commands/DebugCommandId.cs:

namespace SideScrollerGame.Debug.Commands;

public enum DebugCommandId
{
    ToggleOverlay,
    Pause,
    Resume,
    TogglePause,
    FrameStep,
    SetTimeScale,
    ReloadScene,
    RestartMission,
    SetDifficulty,
    SetSeed,
    SpawnActor,
    JumpToMarker,
    ToggleCollisionShapes,
    ToggleGameplayBounds,
    ToggleInvulnerability,
    ToggleInfiniteSpecialAmmo,
    ToggleNoEnemyFire
}

In godot/scripts/debug/commands/DebugRuntimeState.cs:

namespace SideScrollerGame.Debug.Commands;

public sealed class DebugRuntimeState
{
    public bool OverlayVisible { get; }
    public bool IsPaused { get; }
    public double TimeScale { get; }
    public string ActiveDifficultyId { get; }
    public int Seed { get; }
    public string CurrentMarkerId { get; }
    public string LastSpawnedActorId { get; }
    public int SpawnedActorCount { get; }
    public bool ShowCollisionShapes { get; }
    public bool ShowGameplayBounds { get; }
    public bool Invulnerable { get; }
    public bool InfiniteSpecialAmmo { get; }
    public bool NoEnemyFire { get; }
    public int ReloadSceneRequestCount { get; }
    public int RestartMissionRequestCount { get; }
    public int FrameStepRequestCount { get; }
}

The implementation may use mutable properties if that keeps the code simple, but callers should treat the state as read-only outside the service.

In godot/scripts/debug/commands/DebugCommandService.cs:

namespace SideScrollerGame.Debug.Commands;

public sealed class DebugCommandService
{
    public DebugCommandService(ContentRegistry registry, int seed);
    public DebugRuntimeState State { get; }
    public DebugCommandResult Execute(DebugCommandId commandId, string? argument = null);
    public event Action<DebugRuntimeState>? StateChanged;
    public event Action<DebugCommandResult>? CommandExecuted;
}

The service should validate difficulty ids against registry.Difficulties, actor ids against registry.EnemyTypes, and marker ids against mission mission.test timeline markers. If a later slice changes the active mission, it can extend the service to select a different mission id.

In godot/scripts/debug/DebugCommandNode.cs:

namespace SideScrollerGame.Debug;

public partial class DebugCommandNode : Node
{
    public DebugCommandService Service { get; }
    public void Initialize(DebugSettings settings);
    public DebugCommandResult Execute(DebugCommandId commandId, string? argument = null);
}

In godot/scripts/debug/DebugOverlay.cs, add:

public void Bind(DebugCommandService service, DebugSettings settings, string loadedSceneId);

The overlay should refresh when the service state changes and should hide or show itself based on the overlay visibility flag.

In godot/scripts/debug/DebugSandboxController.cs:

namespace SideScrollerGame.Debug;

public partial class DebugSandboxController : Node
{
    public override void _Ready();
    public override void _UnhandledInput(InputEvent @event);
}

The sandbox should use DebugCommandNode.Execute(...) for both keyboard and button commands. It should not maintain separate command rules.

In godot/scripts/debug/DebugBootMode.cs, extend the enum to include:

DebugSandbox

In godot/scripts/bootstrap/GameRoot.cs, extend boot loading so:

--debug-boot=debug-sandbox

loads res://scenes/debug/DebugSandbox.tscn.

Revision note 2026-04-21: Created this ExecPlan from the current Slice 2 project state. The plan scopes Slice 3 to shared debug command infrastructure, overlay upgrades, keyboard and clickable sandbox controls, and headless smoke validation while leaving real gameplay systems to later slices.