Add session shell and close world presentation follow-ups
Full build / Import + headless tests (push) Successful in 24s
Full build / Full desktop build + optional release (push) Skipped

Session shell
- Startup venue menu (src/game/ui/start_menu.gd): keyboard + mouse, replaces
  the live-shift boot and V as the player-facing venue path (V stays a debug bind).
- Pause overlay on Esc (src/game/ui/pause_overlay.gd): resume, restart shift,
  settings, quit to menu.
- Settings panel (src/game/ui/settings_panel.gd): mouse look sensitivity and
  HUD font scale, persisted via Save into user://settings.json and loaded on boot.
- Shift review "Run another shift" now returns to the venue menu.
- New smoke: src/test/smoke_session_shell.gd (menu -> shift -> pause -> settings
  -> menu), passes headless and windowed.

World and presentation
- Skyline backdrop restored with a transparent asset and an alpha-scissor
  unshaded material; fixes the opaque black slab from the earlier attempt and
  the unimported texture path.
- Steps and landing are a StaticBody3D with per-tread colliders, so the guard
  and NPCs walk up instead of clipping through.
- Marquee Label3D scaled up (72 -> 160) so STEP LINE reads from the sidewalk.
- Focus torus and name tag enlarged for 2-3 m readability.
- Routine traffic despawns after an idle lifetime (NpcAvatar.ephemeral), so the
  16-NPC cap no longer fills with standers; authored events stay for the shift.

Docs and tests
- PLAN.md test/CI section corrected to the custom headless runner and Gitea Actions.
- README controls table, smoke checklist, test count, and status lists refreshed.
- TODO/DONE/MEMORY updated; completed sections moved to DONE.md.
- Headless suite 77 -> 88 passing (settings round-trip, step colliders, skyline
  alpha, session-shell UI behavior, traffic lifetime).
This commit is contained in:
zwitschi committed 2026-10-31 20:19:22 +01:00
1 parent f11b441c07
commit be429cab29
20 files changed
+989 -90

No files matched your search

+30
View File
@@ -78,6 +78,36 @@ func test_asset_textures_present_and_pow2() -> bool:
return TestUtils.fail("asset not power-of-two: %s (%dx%d)" % [path, w, h])
return true
func test_steps_have_colliders() -> bool:
## Steps were visual-only and the guard walked through them (TODO).
var root := Node3D.new()
WorldBuilder.new(root).build()
var steps := root.find_child("Steps", true, false)
var a := TestUtils.check(steps != null, "Steps node exists")
var b := TestUtils.check(steps is StaticBody3D, "Steps are a physics body")
var shape_count := 0
if steps is StaticBody3D:
var body := steps as StaticBody3D
shape_count = body.get_shape_owners().size()
var c := TestUtils.check(shape_count >= 5, "four treads + landing have colliders (got %d)" % shape_count)
root.free()
return a and b and c
func test_skyline_backdrop_uses_alpha() -> bool:
## The opaque skyline texture made the plane render as a black slab (TODO).
var root := Node3D.new()
var builder := WorldBuilder.new(root)
builder.build()
var backdrop := root.find_child("SkylineBackdrop", true, false)
var a := TestUtils.check(backdrop != null, "SkylineBackdrop exists")
var mat: StandardMaterial3D = builder._skyline_mat
var b := TestUtils.check(mat != null, "skyline material captured")
var c := TestUtils.check(
mat != null and mat.transparency != BaseMaterial3D.TRANSPARENCY_DISABLED,
"skyline material uses alpha, not an opaque slab")
root.free()
return a and b and c
func test_door_indicator_built() -> bool:
var root := Node3D.new()
var builder := WorldBuilder.new(root)