feat(client): sprint 19 — test infra, session management, debug overlay #67

Closed
jpmschweitzer wants to merge 0 commits from client into main
Owner

Summary

Sprint 19 client deliverables: test infrastructure, game session management, and debug visualization overlay.

  • #205 — gdUnit4 CI runner: run_gdunit4.gd headless test runner with exit code for CI integration (D-030)
  • #206 — Scene testing utilities: SceneHelper class with node/signal assertions, 14 GameState.apply_snapshot() tests covering v2+ fields
  • #258 — Game session management: SessionManager autoload creates per-game save directories (user://saves/<timestamp>-<seed>/) per D-085, main menu scene with New Game / Continue / Quit, --game-id passed to server subprocess, quit-to-menu confirmation dialog
  • #348 — Debug visualization overlay: F3-toggled dev overlay with LOS rays, vision cone arcs, NPC path trails (12-tick history), knowledge confidence tags, tick timing sparkline (30-tick ring buffer), OS.is_debug_build() guard

Files changed

  • 15 files, +1,412 / -20 lines
  • 7 new files (session_manager, main_menu scene/script, test files, scene_helper, CI runner)

Blocked

  • #554 (save/load client UI) remains blocked by #553 (server ECS extraction)

Test plan

  • godot --headless --path client/ -s res://tests/run_gdunit4.gd -- --ignoreHeadlessMode -a res://tests/ passes
  • New Game creates user://saves/<game-id>/ directory
  • Continue resumes most recent game session
  • F3 toggles debug overlay with LOS/cone/paths/tags/sparkline
  • Debug overlay hidden in export builds

🤖 Generated with Claude Code

## Summary Sprint 19 client deliverables: test infrastructure, game session management, and debug visualization overlay. - **#205 — gdUnit4 CI runner**: `run_gdunit4.gd` headless test runner with exit code for CI integration (D-030) - **#206 — Scene testing utilities**: `SceneHelper` class with node/signal assertions, 14 `GameState.apply_snapshot()` tests covering v2+ fields - **#258 — Game session management**: `SessionManager` autoload creates per-game save directories (`user://saves/<timestamp>-<seed>/`) per D-085, main menu scene with New Game / Continue / Quit, `--game-id` passed to server subprocess, quit-to-menu confirmation dialog - **#348 — Debug visualization overlay**: F3-toggled dev overlay with LOS rays, vision cone arcs, NPC path trails (12-tick history), knowledge confidence tags, tick timing sparkline (30-tick ring buffer), `OS.is_debug_build()` guard ### Files changed - 15 files, +1,412 / -20 lines - 7 new files (session_manager, main_menu scene/script, test files, scene_helper, CI runner) ### Blocked - #554 (save/load client UI) remains blocked by #553 (server ECS extraction) ## Test plan - [ ] `godot --headless --path client/ -s res://tests/run_gdunit4.gd -- --ignoreHeadlessMode -a res://tests/` passes - [ ] New Game creates `user://saves/<game-id>/` directory - [ ] Continue resumes most recent game session - [ ] F3 toggles debug overlay with LOS/cone/paths/tags/sparkline - [ ] Debug overlay hidden in export builds 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jpmschweitzer added 5 commits 2026-02-25 11:46:56 +01:00
Headless test runner that delegates to GdUnitTestCIRunner for CI
integration. Exit code 0 = all pass, non-zero = failures per D-030.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SceneHelper class for gdUnit4: load scenes into test tree with
assert_node_exists, assert_signal_emitted, get_node_at helpers.
14 tests for GameState.apply_snapshot() covering v2+ fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Per-game save directories under user://saves/<timestamp>-<seed>/.
SessionManager autoload handles new_game(), resume_game(), quit flow.
Main menu scene with New Game / Continue / Quit buttons. Game-id
passed to server subprocess via --game-id flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
F3-toggled dev overlay: LOS rays, vision cone arcs, NPC path trails,
knowledge confidence tags, tick timing sparkline. Guarded by
OS.is_debug_build() for export builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jpmschweitzer added 1 commit 2026-02-25 12:08:52 +01:00
SessionManager.new_game() now returns "" on dir creation failure
instead of proceeding with a broken game-id. Main menu guards
against empty return. Test suite tracks and cleans up created
save directories in after_test().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Review: client → main (type: code)

Hoshe (Code Quality): REQUEST_CHANGES

Well-structured sprint deliverables. Two warnings flagged and fixed.

# File Severity Issue Status
1 session_manager.gd:29-34 warning new_game() swallows dir creation failure — sets game_id regardless Fixed — returns "" on error
2 test_session_manager_sprint19.gd:105-115 warning Tests leak save directories under user://saves/ Fixed — cleanup in after_test()
3 debug_overlay.gd:141-143 suggestion Redundant if not visible: return in _draw() Noted
4 sim_bridge.gd:71-74 suggestion No test coverage for --game-id passthrough Noted — exercised by #554
5 session_manager.gd:77 suggestion Redundant _quit_dialog.title = "" Fixed — removed

Tyre (Architecture): REQUEST_CHANGES

D-020, D-030, D-085 compliance clean. Same blocking issue on error propagation.

# File Severity Issue Status
1 session_manager.gd:29-33 warning Same as Hoshe #1 Fixed
2 session_manager.gd:73 suggestion quit_to_menu(node) could use get_tree().root internally Noted
3 main_menu.gd:39-46 suggestion Continue allows dirs with no .sav files Noted — #554 scope
4 test_debug_overlay_sprint19.gd:116-141 suggestion Tests access private fields directly Noted — pragmatic for dev tool
5 test_session_manager_sprint19.gd:105-115 suggestion Non-deterministic uniqueness test Noted

Verdict: CHANGES REQUESTED → FIXED in commit 5d1d0d0

All warnings addressed. Suggestions noted for future work. Ready for merge from main.

## Review: `client` → main (type: code) ### Hoshe (Code Quality): REQUEST_CHANGES Well-structured sprint deliverables. Two warnings flagged and fixed. | # | File | Severity | Issue | Status | |---|------|----------|-------|--------| | 1 | `session_manager.gd:29-34` | warning | `new_game()` swallows dir creation failure — sets game_id regardless | **Fixed** — returns "" on error | | 2 | `test_session_manager_sprint19.gd:105-115` | warning | Tests leak save directories under `user://saves/` | **Fixed** — cleanup in after_test() | | 3 | `debug_overlay.gd:141-143` | suggestion | Redundant `if not visible: return` in `_draw()` | Noted | | 4 | `sim_bridge.gd:71-74` | suggestion | No test coverage for `--game-id` passthrough | Noted — exercised by #554 | | 5 | `session_manager.gd:77` | suggestion | Redundant `_quit_dialog.title = ""` | **Fixed** — removed | ### Tyre (Architecture): REQUEST_CHANGES D-020, D-030, D-085 compliance clean. Same blocking issue on error propagation. | # | File | Severity | Issue | Status | |---|------|----------|-------|--------| | 1 | `session_manager.gd:29-33` | warning | Same as Hoshe #1 | **Fixed** | | 2 | `session_manager.gd:73` | suggestion | `quit_to_menu(node)` could use `get_tree().root` internally | Noted | | 3 | `main_menu.gd:39-46` | suggestion | Continue allows dirs with no .sav files | Noted — #554 scope | | 4 | `test_debug_overlay_sprint19.gd:116-141` | suggestion | Tests access private fields directly | Noted — pragmatic for dev tool | | 5 | `test_session_manager_sprint19.gd:105-115` | suggestion | Non-deterministic uniqueness test | Noted | ### Verdict: CHANGES REQUESTED → **FIXED** in commit `5d1d0d0` All warnings addressed. Suggestions noted for future work. Ready for merge from main.
Author
Owner

Review: client → main (type: code)

Hoshe (Code Quality): REQUEST_CHANGES

Solid implementation of D-085. Three warnings and four suggestions.

# File Severity Issue
1 session_manager.gd:81 warning Quit dialog parented to caller node — if caller freed before user responds, orphaned reference. Parent to SceneTree root instead of caller node.
2 test_session_manager_sprint19.gd:23 warning DirAccess.remove_absolute() only removes empty dirs — silent cleanup failure when tests later create files inside. Should use recursive delete or assert return is OK.
3 debug_overlay.gd:132 warning _npc_paths not cleared on session change — entity ID collision risk if overlay becomes persistent CanvasLayer. Clear when GameState.current_game_id changes.
4 session_manager.gd:25 suggestion rng.randomize() is deprecated in Godot 4 — RandomNumberGenerator.new() already seeds from system entropy. Remove the call.
5 debug_overlay.gd:141 suggestion if not visible: return guard in _draw() is redundant — Godot 4 only calls _draw() on visible nodes after queue_redraw().
6 test_session_manager_sprint19.gd:124 suggestion Uniqueness test is probabilistic (~1/16M false failure chance). Document as non-deterministic assertion.
7 util/scene_helper.gd:76 suggestion Signal monitor lambda handles 0-4 args only. Document the cap for future test authors.

Tyre (Architecture): APPROVE

D-085 faithfully implemented — game-id format, directory creation, GameState lifecycle, SimBridge passthrough all align. Debug overlay properly gated behind OS.is_debug_build(). Autoload registration order correct. Six suggestions, no blockers.

# File Severity Issue
1 session_manager.gd:24 suggestion rng.randomize() uses usec resolution — sub-microsecond collision theoretically possible. Not a blocker for v0.1.
2 test_session_manager_sprint19.gd:23 suggestion Test teardown only removes empty dirs — add assertion on return value.
3 session_manager.gd:46 suggestion New game with no saves gets mtime=0, sorts to bottom in _refresh_continue_state(). "Continue" may load previous session instead of in-progress one. Use directory creation time as fallback.
4 debug_overlay.gd:313 suggestion Constants.color_for_relationship dependency undocumented — could panic if tested in headless draw path.
5 project.godot:26 suggestion Autoload ordering dependency between SessionManager and GameState undocumented. Currently correct but fragile if _ready() is added later.
6 test_session_manager_sprint19.gd:21 suggestion Teardown should assert remove_absolute returns OK to surface silent failures.

Verdict: CHANGES REQUESTED

Required fixes before merge:

  1. Parent quit dialog to SceneTree root instead of caller node
  2. Clear _npc_paths on session change (guard on current_game_id)
  3. Remove deprecated rng.randomize() call

🤖 Generated with Claude Code

## Review: client → main (type: code) ### Hoshe (Code Quality): REQUEST_CHANGES Solid implementation of D-085. Three warnings and four suggestions. | # | File | Severity | Issue | |---|------|----------|-------| | 1 | `session_manager.gd:81` | warning | Quit dialog parented to caller node — if caller freed before user responds, orphaned reference. Parent to SceneTree root instead of caller `node`. | | 2 | `test_session_manager_sprint19.gd:23` | warning | `DirAccess.remove_absolute()` only removes empty dirs — silent cleanup failure when tests later create files inside. Should use recursive delete or assert return is OK. | | 3 | `debug_overlay.gd:132` | warning | `_npc_paths` not cleared on session change — entity ID collision risk if overlay becomes persistent CanvasLayer. Clear when `GameState.current_game_id` changes. | | 4 | `session_manager.gd:25` | suggestion | `rng.randomize()` is deprecated in Godot 4 — `RandomNumberGenerator.new()` already seeds from system entropy. Remove the call. | | 5 | `debug_overlay.gd:141` | suggestion | `if not visible: return` guard in `_draw()` is redundant — Godot 4 only calls `_draw()` on visible nodes after `queue_redraw()`. | | 6 | `test_session_manager_sprint19.gd:124` | suggestion | Uniqueness test is probabilistic (~1/16M false failure chance). Document as non-deterministic assertion. | | 7 | `util/scene_helper.gd:76` | suggestion | Signal monitor lambda handles 0-4 args only. Document the cap for future test authors. | ### Tyre (Architecture): APPROVE D-085 faithfully implemented — game-id format, directory creation, GameState lifecycle, SimBridge passthrough all align. Debug overlay properly gated behind `OS.is_debug_build()`. Autoload registration order correct. Six suggestions, no blockers. | # | File | Severity | Issue | |---|------|----------|-------| | 1 | `session_manager.gd:24` | suggestion | `rng.randomize()` uses usec resolution — sub-microsecond collision theoretically possible. Not a blocker for v0.1. | | 2 | `test_session_manager_sprint19.gd:23` | suggestion | Test teardown only removes empty dirs — add assertion on return value. | | 3 | `session_manager.gd:46` | suggestion | New game with no saves gets `mtime=0`, sorts to bottom in `_refresh_continue_state()`. "Continue" may load previous session instead of in-progress one. Use directory creation time as fallback. | | 4 | `debug_overlay.gd:313` | suggestion | `Constants.color_for_relationship` dependency undocumented — could panic if tested in headless draw path. | | 5 | `project.godot:26` | suggestion | Autoload ordering dependency between SessionManager and GameState undocumented. Currently correct but fragile if `_ready()` is added later. | | 6 | `test_session_manager_sprint19.gd:21` | suggestion | Teardown should assert `remove_absolute` returns OK to surface silent failures. | ### Verdict: CHANGES REQUESTED **Required fixes before merge:** 1. Parent quit dialog to SceneTree root instead of caller node 2. Clear `_npc_paths` on session change (guard on `current_game_id`) 3. Remove deprecated `rng.randomize()` call 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jpmschweitzer added 1 commit 2026-02-25 12:33:29 +01:00
Moves ~300 lines of test simulation logic (Bresenham LOS, collision,
procedural room generation, movement physics, dialogue triggers) from
the production sim_bridge.gd autoload into a dedicated TestHarness
class at scripts/protocol/test_harness.gd. Enforces D-020 information
boundary — no game logic in the production client.

SimBridge retains thin proxy properties and methods for backward
compatibility with 13+ test files (zero test changes needed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Review Round 2: client → main (type: code)

Hoshe (Code Quality): REQUEST_CHANGES

Original 3 issues (quit dialog parent, _npc_paths session clear, rng.randomize) are all confirmed fixed. The SimBridge→TestHarness extraction is clean in principle, but one proxy is missing.

# File Severity Issue
1 sim_bridge.gd critical _test_input_queue proxy missing from SimBridge. The property was moved to TestHarness.input_queue but no backward-compat proxy was added. 8 test files access SimBridge._test_input_queue across 24 call sites — all will fail at runtime with nil property error. Affected: test_hub_teleport.gd, test_protocol_v7.gd, test_anti_tedium.gd, test_smooth_camera_sprint15.gd, test_camera_anchor.gd, test_client_p2.gd. Fix: add proxy property to SimBridge alongside the existing _test_tick, _test_player_pos etc.

Tyre (Architecture): APPROVE

Extraction is architecturally sound. TestHarness is properly isolated as RefCounted, no scene tree dependency, no autoload leakage. D-085 implementation fully intact. Proxy pattern is correct for backward-compat. Two observations (non-blocking): proxy layer is transitional and could be removed once tests migrate to harness directly; TestHarness state not reset on new_game() in test mode (pre-existing, not a regression).

Verdict: CHANGES REQUESTED

Required fix: Add _test_input_queue proxy to SimBridge:

var _test_input_queue: Array:
    get: return harness.input_queue if harness else []

🤖 Generated with Claude Code

## Review Round 2: client → main (type: code) ### Hoshe (Code Quality): REQUEST_CHANGES Original 3 issues (quit dialog parent, _npc_paths session clear, rng.randomize) are all confirmed fixed. The SimBridge→TestHarness extraction is clean in principle, but one proxy is missing. | # | File | Severity | Issue | |---|------|----------|-------| | 1 | `sim_bridge.gd` | critical | `_test_input_queue` proxy missing from SimBridge. The property was moved to `TestHarness.input_queue` but no backward-compat proxy was added. 8 test files access `SimBridge._test_input_queue` across 24 call sites — all will fail at runtime with nil property error. Affected: `test_hub_teleport.gd`, `test_protocol_v7.gd`, `test_anti_tedium.gd`, `test_smooth_camera_sprint15.gd`, `test_camera_anchor.gd`, `test_client_p2.gd`. Fix: add proxy property to SimBridge alongside the existing `_test_tick`, `_test_player_pos` etc. | ### Tyre (Architecture): APPROVE Extraction is architecturally sound. TestHarness is properly isolated as RefCounted, no scene tree dependency, no autoload leakage. D-085 implementation fully intact. Proxy pattern is correct for backward-compat. Two observations (non-blocking): proxy layer is transitional and could be removed once tests migrate to `harness` directly; TestHarness state not reset on `new_game()` in test mode (pre-existing, not a regression). ### Verdict: CHANGES REQUESTED **Required fix:** Add `_test_input_queue` proxy to SimBridge: ```gdscript var _test_input_queue: Array: get: return harness.input_queue if harness else [] ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jpmschweitzer added 1 commit 2026-02-25 12:51:33 +01:00
- Add missing _test_input_queue proxy to SimBridge (26 call sites across
  6 test files broken by TestHarness extraction)
- Parent quit dialog to SceneTree root instead of caller node to prevent
  orphaned reference if caller freed before user responds
- Remove deprecated rng.randomize() call (Godot 4 auto-seeds)
- Clear debug overlay state (_npc_paths, tick timing) on session change
  via new GameState.game_id_changed signal to prevent entity ID collisions
- Update settings_dialog quit_to_menu() call site (no-arg signature)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jpmschweitzer added 1 commit 2026-02-25 12:52:07 +01:00
Author
Owner

Review Round 3: client → main (type: code)

Hoshe (Code Quality): REQUEST_CHANGES

Round 2 critical fix confirmed — _test_input_queue proxy is present and correctly wired. BBCode escape and expiry loop fixes verified. Sprint 18 test suites (examine display 28 tests, minimap 25 tests) are well-structured with good spec coverage. One new bug found.

# File Severity Issue
1 minimap.gd:94 critical Reads poi.get("category", "") but the POI wire format uses poi_category. Every POI renders with default color — category color-coding (D-013 semantic layer) is silently broken. Tests didn't catch it because _category_color() tests use string literals, bypassing the rendering path. Fix: poi.get("poi_category", "")

Also noted (non-blocking):

  • monologue_display.gd:159 only escapes [ but not ] — pre-existing inconsistency with dialogue_box.gd's more complete escape. Not introduced by this PR.

Tyre (Architecture): APPROVE

Extraction clean. D-085 intact. Sprint 18 test files follow established patterns. One suggestion: dismiss() in examine_display.gd sets _active = false inside a tween callback (async), but test expects it synchronous. Either set _active = false at top of dismiss() or update test expectation. Not blocking.

Verdict: CHANGES REQUESTED

Required fix: minimap.gd:94 — change poi.get("category", "") to poi.get("poi_category", "")

🤖 Generated with Claude Code

## Review Round 3: client → main (type: code) ### Hoshe (Code Quality): REQUEST_CHANGES Round 2 critical fix confirmed — `_test_input_queue` proxy is present and correctly wired. BBCode escape and expiry loop fixes verified. Sprint 18 test suites (examine display 28 tests, minimap 25 tests) are well-structured with good spec coverage. One new bug found. | # | File | Severity | Issue | |---|------|----------|-------| | 1 | `minimap.gd:94` | critical | Reads `poi.get("category", "")` but the POI wire format uses `poi_category`. Every POI renders with default color — category color-coding (D-013 semantic layer) is silently broken. Tests didn't catch it because `_category_color()` tests use string literals, bypassing the rendering path. Fix: `poi.get("poi_category", "")` | Also noted (non-blocking): - `monologue_display.gd:159` only escapes `[` but not `]` — pre-existing inconsistency with `dialogue_box.gd`'s more complete escape. Not introduced by this PR. ### Tyre (Architecture): APPROVE Extraction clean. D-085 intact. Sprint 18 test files follow established patterns. One suggestion: `dismiss()` in `examine_display.gd` sets `_active = false` inside a tween callback (async), but test expects it synchronous. Either set `_active = false` at top of `dismiss()` or update test expectation. Not blocking. ### Verdict: CHANGES REQUESTED **Required fix:** `minimap.gd:94` — change `poi.get("category", "")` to `poi.get("poi_category", "")` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jpmschweitzer added 1 commit 2026-02-25 12:56:50 +01:00
Add 5 new rules to the agent spawn prompt: read-before-write,
verify-after-write, no-partial-work, message-when-blocked, and
backward-compatibility. Adds verification checklist before marking
tasks done. Addresses recurring issues with agents skipping call
site updates, leaving partial implementations, and not escalating
blockers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jpmschweitzer added 1 commit 2026-02-25 12:57:31 +01:00
protocol.gd decoded server's poi_category as "category", minimap.gd
read "category" — both now use "poi_category" matching the wire format.
Protocol falls back to "category" for older server snapshots.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

Review Round 3: client → main (type: code)

Hoshe (Code Quality): REQUEST_CHANGES

Round 2 critical fix confirmed — _test_input_queue proxy is present and correctly wired. BBCode escape and expiry loop fixes verified. Sprint 18 test suites (examine display 28 tests, minimap 25 tests) are well-structured with good spec coverage. One new bug found.

# File Severity Issue Status
1 minimap.gd:94 critical Reads poi.get("category", "") but wire format uses poi_category. Every POI renders with default color — category color-coding (D-013 semantic layer) silently broken. Fixed — minimap.gd and protocol.gd both updated to use poi_category (with fallback for older servers)

Tyre (Architecture): APPROVE

Extraction clean. D-085 intact. Sprint 18 test files follow established patterns.

Verdict: CHANGES REQUESTED → FIXED in commits 83a244f, e7f1e80

All critical and warning issues from rounds 1–3 addressed. Ready for merge from main.

## Review Round 3: client → main (type: code) ### Hoshe (Code Quality): REQUEST_CHANGES Round 2 critical fix confirmed — _test_input_queue proxy is present and correctly wired. BBCode escape and expiry loop fixes verified. Sprint 18 test suites (examine display 28 tests, minimap 25 tests) are well-structured with good spec coverage. One new bug found. | # | File | Severity | Issue | Status | |---|------|----------|-------|--------| | 1 | minimap.gd:94 | critical | Reads poi.get("category", "") but wire format uses poi_category. Every POI renders with default color — category color-coding (D-013 semantic layer) silently broken. | **Fixed** — minimap.gd and protocol.gd both updated to use poi_category (with fallback for older servers) | ### Tyre (Architecture): APPROVE Extraction clean. D-085 intact. Sprint 18 test files follow established patterns. ### Verdict: CHANGES REQUESTED → **FIXED** in commits 83a244f, e7f1e80 All critical and warning issues from rounds 1–3 addressed. Ready for merge from main.
jpmschweitzer added 1 commit 2026-02-25 13:04:03 +01:00
jpmschweitzer closed this pull request 2026-02-25 13:07:22 +01:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jpmschweitzer/settled-reach#67