diff --git a/SLICE1.MD b/SLICE1.MD index 0d6b275..8461732 100644 --- a/SLICE1.MD +++ b/SLICE1.MD @@ -15,10 +15,10 @@ The observable result is small but concrete: from `D:\Code\zfxaction26_1`, `dotn - [x] (2026-04-21 16:36Z) Read repository rules, Windows rules, `PLANS.md`, `CODE.md`, and current Godot project files. - [x] (2026-04-21 16:36Z) Verified that the Godot project currently lives in `godot/project.godot`, not at the repository root. - [x] (2026-04-21 16:36Z) Verified that `SideScrollerGame.sln` and `godot/SideScrollerGame.Godot.csproj` currently build with `dotnet build SideScrollerGame.sln` after the user's fixes. -- [ ] Implement root scene, placeholder scene, smoke scene, input actions, and debug boot code. -- [ ] Run formatting for touched C# files with `jb cleanupcode --build=False`. -- [ ] Validate with .NET build, Godot solution build, and headless smoke boot. -- [ ] Commit the completed slice. +- [x] (2026-04-21 16:52Z) Implemented root scene, placeholder scene, smoke scene, input actions, and debug boot code. +- [x] (2026-04-21 16:52Z) Ran formatting for touched C# files with `jb cleanupcode --build=False`. +- [x] (2026-04-21 16:52Z) Validated with .NET build, Godot solution build, and headless smoke boot. +- [x] (2026-04-21 16:52Z) Commit the completed slice. ## Surprises & Discoveries @@ -31,6 +31,12 @@ The observable result is small but concrete: from `D:\Code\zfxaction26_1`, `dotn - Observation: Running `dotnet build SideScrollerGame.sln` and `dotnet build godot\SideScrollerGame.Godot.csproj` at the same time can race on the same Godot temp assembly. Evidence: the parallel build attempt failed with `CS2012: Cannot open ... SideScrollerGame.Godot.dll for writing`. A serial `dotnet build SideScrollerGame.sln` immediately afterward succeeded. +- Observation: `godot/project.godot` had `project/assembly_name="SideScrollerGame"`, while the fixed C# project builds `SideScrollerGame.Godot.dll`. + Evidence: the first smoke boot did not instantiate C# scripts, and `.\godot --headless --path godot --quit` reported that `GameRoot.cs` and `DebugOverlay.cs` classes could not be found. Updating the Godot assembly name to `SideScrollerGame.Godot` fixed script loading. + +- Observation: Quoting the semicolon-separated `jb cleanupcode` file list made JetBrains treat the full list as one path. + Evidence: `jb cleanupcode --build=False "file1;file2;..."` exited with "No items were found to cleanup." Running `jb cleanupcode --build=False file1 file2 ...` formatted all touched C# files. + ## Decision Log - Decision: Keep `godot/project.godot` as the canonical Godot project entrypoint for this slice. @@ -49,9 +55,30 @@ The observable result is small but concrete: from `D:\Code\zfxaction26_1`, `dotn Rationale: Command-line boot modes make sandbox and smoke testing fast without editor interaction. Project setting fallback keeps editor boot predictable. Date/Author: 2026-04-21 / Codex. +- Decision: Match `godot/project.godot`'s .NET assembly name to the existing C# project output, `SideScrollerGame.Godot`. + Rationale: Godot resolves attached C# scripts through the configured assembly name. The project already builds `SideScrollerGame.Godot.dll`, and changing the Godot setting avoids touching the user's fixed `.csproj`. + 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. +Implemented a bootable Godot project shell under `godot/`. Created the root scene, menu placeholder scene, smoke scene, debug boot settings reader, debug overlay, and smoke controller. Added project input actions and configured `res://scenes/bootstrap/GameRoot.tscn` as the main scene. + +Validation completed: + + dotnet build SideScrollerGame.sln + Build succeeded. + 0 Warning(s) + 0 Error(s) + + .\godot --headless --path godot --build-solutions --quit + Exited successfully. + + .\godot --headless --path godot -- --debug-boot=smoke --seed=12345 + Debug boot: Smoke + Seed: 12345 + Smoke scene loaded + +Remaining risk: editor visual layout was not manually inspected in a window during this slice. The headless boot path and script loading are validated. ## Context and Orientation diff --git a/godot/project.godot b/godot/project.godot index e123a6a..4fc37d5 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -11,9 +11,69 @@ config_version=5 [application] config/name="SideScrollerGame" +run/main_scene="res://scenes/bootstrap/GameRoot.tscn" +run/debug_boot_mode="menu" +run/debug_seed=1 config/features=PackedStringArray("4.5", "C#", "Forward Plus") config/icon="res://icon.svg" [dotnet] -project/assembly_name="SideScrollerGame" +project/assembly_name="SideScrollerGame.Godot" + +[input] + +move_up={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":87,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +move_down={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +move_left={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":65,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +move_right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":68,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +fire_primary={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +fire_secondary={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":70,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +fire_special={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":69,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +pause_game={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +debug_overlay={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194336,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +quick_restart={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":82,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} diff --git a/godot/scenes/bootstrap/GameRoot.tscn b/godot/scenes/bootstrap/GameRoot.tscn new file mode 100644 index 0000000..d630113 --- /dev/null +++ b/godot/scenes/bootstrap/GameRoot.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=5 format=3 uid="uid://b1fxc23gkbqre"] + +[ext_resource type="Script" path="res://scripts/bootstrap/GameRoot.cs" id="1_game_root"] +[ext_resource type="Script" path="res://scripts/debug/DebugOverlay.cs" id="2_debug_overlay"] +[ext_resource type="PackedScene" path="res://scenes/menu/MenuPlaceholder.tscn" id="3_menu"] +[ext_resource type="PackedScene" path="res://scenes/debug/SmokeScene.tscn" id="4_smoke"] + +[node name="GameRoot" type="Node"] +script = ExtResource("1_game_root") +MenuScene = ExtResource("3_menu") +SmokeScene = ExtResource("4_smoke") + +[node name="DebugOverlay" type="CanvasLayer" parent="."] +script = ExtResource("2_debug_overlay") diff --git a/godot/scenes/debug/SmokeScene.tscn b/godot/scenes/debug/SmokeScene.tscn new file mode 100644 index 0000000..0a86082 --- /dev/null +++ b/godot/scenes/debug/SmokeScene.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=3 uid="uid://dpsx0hxc4vd5s"] + +[ext_resource type="Script" path="res://scripts/debug/SmokeSceneController.cs" id="1_smoke_scene_controller"] + +[node name="SmokeScene" type="Node2D"] +script = ExtResource("1_smoke_scene_controller") + +[node name="HeroPlaceholder" type="Polygon2D" parent="."] +position = Vector2(160, 180) +color = Color(0.25, 0.85, 1, 1) +polygon = PackedVector2Array(48, 0, -32, -24, -12, 0, -32, 24) + +[node name="SmokeLabel" type="Label" parent="."] +offset_left = 96.0 +offset_top = 224.0 +offset_right = 384.0 +offset_bottom = 256.0 +text = "Smoke Scene" diff --git a/godot/scenes/menu/MenuPlaceholder.tscn b/godot/scenes/menu/MenuPlaceholder.tscn new file mode 100644 index 0000000..c18023b --- /dev/null +++ b/godot/scenes/menu/MenuPlaceholder.tscn @@ -0,0 +1,46 @@ +[gd_scene load_steps=2 format=3 uid="uid://cg86dxl2ys1vh"] + +[ext_resource type="Script" path="res://scripts/menu/MenuPlaceholder.cs" id="1_menu_placeholder"] + +[node name="MenuPlaceholder" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_menu_placeholder") + +[node name="Title" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -210.0 +offset_top = -32.0 +offset_right = 210.0 +offset_bottom = 0.0 +grow_horizontal = 2 +grow_vertical = 2 +horizontal_alignment = 1 +vertical_alignment = 1 +text = "SideScrollerGame - Menu Placeholder" + +[node name="Hint" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -240.0 +offset_top = 8.0 +offset_right = 240.0 +offset_bottom = 40.0 +grow_horizontal = 2 +grow_vertical = 2 +horizontal_alignment = 1 +vertical_alignment = 1 +text = "Run with -- --debug-boot=smoke for smoke scene" diff --git a/godot/scripts/bootstrap/GameRoot.cs b/godot/scripts/bootstrap/GameRoot.cs new file mode 100644 index 0000000..a9ba209 --- /dev/null +++ b/godot/scripts/bootstrap/GameRoot.cs @@ -0,0 +1,59 @@ +#nullable enable + +using Godot; +using SideScrollerGame.Debug; + +namespace SideScrollerGame.Bootstrap; + +public partial class GameRoot : Node +{ + public override void _Ready() + { + m_Settings = DebugSettings.Load(); + GD.Seed((ulong)m_Settings.Seed); + + GD.Print($"Debug boot: {m_Settings.BootMode}"); + GD.Print($"Seed: {m_Settings.Seed}"); + + LoadBootScene(m_Settings.BootMode); + } + + public void LoadBootScene(DebugBootMode bootMode) + { + PackedScene? scene = bootMode switch + { + DebugBootMode.Smoke => SmokeScene, + _ => MenuScene + }; + + string loadedSceneId = bootMode.ToString(); + if (scene is null) + { + GD.PushError($"No scene configured for debug boot mode '{bootMode}'."); + return; + } + + if (m_LoadedScene is not null) + { + m_LoadedScene.QueueFree(); + } + + m_LoadedScene = scene.Instantiate(); + AddChild(m_LoadedScene); + + DebugOverlay? overlay = GetNodeOrNull("DebugOverlay"); + if (overlay is not null && m_Settings is not null) + { + overlay.SetStatus(m_Settings, loadedSceneId); + } + } + + [Export] + public PackedScene? MenuScene { get; set; } + + [Export] + public PackedScene? SmokeScene { get; set; } + + private Node? m_LoadedScene; + private DebugSettings? m_Settings; +} \ No newline at end of file diff --git a/godot/scripts/bootstrap/GameRoot.cs.uid b/godot/scripts/bootstrap/GameRoot.cs.uid new file mode 100644 index 0000000..78c9152 --- /dev/null +++ b/godot/scripts/bootstrap/GameRoot.cs.uid @@ -0,0 +1 @@ +uid://b28od0hdkj1kx diff --git a/godot/scripts/debug/DebugBootMode.cs b/godot/scripts/debug/DebugBootMode.cs new file mode 100644 index 0000000..8fa40a9 --- /dev/null +++ b/godot/scripts/debug/DebugBootMode.cs @@ -0,0 +1,7 @@ +namespace SideScrollerGame.Debug; + +public enum DebugBootMode +{ + Menu, + Smoke +} \ No newline at end of file diff --git a/godot/scripts/debug/DebugBootMode.cs.uid b/godot/scripts/debug/DebugBootMode.cs.uid new file mode 100644 index 0000000..7efcb14 --- /dev/null +++ b/godot/scripts/debug/DebugBootMode.cs.uid @@ -0,0 +1 @@ +uid://cebwe1o0qw160 diff --git a/godot/scripts/debug/DebugOverlay.cs b/godot/scripts/debug/DebugOverlay.cs new file mode 100644 index 0000000..34fa2c2 --- /dev/null +++ b/godot/scripts/debug/DebugOverlay.cs @@ -0,0 +1,43 @@ +#nullable enable + +using Godot; + +namespace SideScrollerGame.Debug; + +public partial class DebugOverlay : CanvasLayer +{ + public override void _Ready() + { + Layer = 100; + ProcessMode = ProcessModeEnum.Always; + EnsureLabel(); + } + + public void SetStatus(DebugSettings settings, string loadedSceneId) + { + Label label = EnsureLabel(); + label.Text = $"Debug boot: {settings.BootMode}\nSeed: {settings.Seed}\nScene: {loadedSceneId}\nDebug: {OS.IsDebugBuild()}"; + } + + private Label EnsureLabel() + { + if (m_StatusLabel is not null) + { + return m_StatusLabel; + } + + m_StatusLabel = GetNodeOrNull