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)
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>
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>
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:
Parent quit dialog to SceneTree root instead of caller node
Clear _npc_paths on session change (guard on current_game_id)
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>
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:
## 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)
- 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>
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", "")
## 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)
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>
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>
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Sprint 19 client deliverables: test infrastructure, game session management, and debug visualization overlay.
run_gdunit4.gdheadless test runner with exit code for CI integration (D-030)SceneHelperclass with node/signal assertions, 14GameState.apply_snapshot()tests covering v2+ fieldsSessionManagerautoload creates per-game save directories (user://saves/<timestamp>-<seed>/) per D-085, main menu scene with New Game / Continue / Quit,--game-idpassed to server subprocess, quit-to-menu confirmation dialogOS.is_debug_build()guardFiles changed
Blocked
Test plan
godot --headless --path client/ -s res://tests/run_gdunit4.gd -- --ignoreHeadlessMode -a res://tests/passesuser://saves/<game-id>/directory🤖 Generated with Claude Code
Review:
client→ main (type: code)Hoshe (Code Quality): REQUEST_CHANGES
Well-structured sprint deliverables. Two warnings flagged and fixed.
session_manager.gd:29-34new_game()swallows dir creation failure — sets game_id regardlesstest_session_manager_sprint19.gd:105-115user://saves/debug_overlay.gd:141-143if not visible: returnin_draw()sim_bridge.gd:71-74--game-idpassthroughsession_manager.gd:77_quit_dialog.title = ""Tyre (Architecture): REQUEST_CHANGES
D-020, D-030, D-085 compliance clean. Same blocking issue on error propagation.
session_manager.gd:29-33session_manager.gd:73quit_to_menu(node)could useget_tree().rootinternallymain_menu.gd:39-46test_debug_overlay_sprint19.gd:116-141test_session_manager_sprint19.gd:105-115Verdict: CHANGES REQUESTED → FIXED in commit
5d1d0d0All warnings addressed. Suggestions noted for future work. Ready for merge from main.
Review: client → main (type: code)
Hoshe (Code Quality): REQUEST_CHANGES
Solid implementation of D-085. Three warnings and four suggestions.
session_manager.gd:81node.test_session_manager_sprint19.gd:23DirAccess.remove_absolute()only removes empty dirs — silent cleanup failure when tests later create files inside. Should use recursive delete or assert return is OK.debug_overlay.gd:132_npc_pathsnot cleared on session change — entity ID collision risk if overlay becomes persistent CanvasLayer. Clear whenGameState.current_game_idchanges.session_manager.gd:25rng.randomize()is deprecated in Godot 4 —RandomNumberGenerator.new()already seeds from system entropy. Remove the call.debug_overlay.gd:141if not visible: returnguard in_draw()is redundant — Godot 4 only calls_draw()on visible nodes afterqueue_redraw().test_session_manager_sprint19.gd:124util/scene_helper.gd:76Tyre (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.session_manager.gd:24rng.randomize()uses usec resolution — sub-microsecond collision theoretically possible. Not a blocker for v0.1.test_session_manager_sprint19.gd:23session_manager.gd:46mtime=0, sorts to bottom in_refresh_continue_state(). "Continue" may load previous session instead of in-progress one. Use directory creation time as fallback.debug_overlay.gd:313Constants.color_for_relationshipdependency undocumented — could panic if tested in headless draw path.project.godot:26_ready()is added later.test_session_manager_sprint19.gd:21remove_absolutereturns OK to surface silent failures.Verdict: CHANGES REQUESTED
Required fixes before merge:
_npc_pathson session change (guard oncurrent_game_id)rng.randomize()call🤖 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.
sim_bridge.gd_test_input_queueproxy missing from SimBridge. The property was moved toTestHarness.input_queuebut no backward-compat proxy was added. 8 test files accessSimBridge._test_input_queueacross 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_posetc.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
harnessdirectly; TestHarness state not reset onnew_game()in test mode (pre-existing, not a regression).Verdict: CHANGES REQUESTED
Required fix: Add
_test_input_queueproxy to SimBridge:🤖 Generated with Claude Code
Review Round 3: client → main (type: code)
Hoshe (Code Quality): REQUEST_CHANGES
Round 2 critical fix confirmed —
_test_input_queueproxy 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.minimap.gd:94poi.get("category", "")but the POI wire format usespoi_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:159only escapes[but not]— pre-existing inconsistency withdialogue_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()inexamine_display.gdsets_active = falseinside a tween callback (async), but test expects it synchronous. Either set_active = falseat top ofdismiss()or update test expectation. Not blocking.Verdict: CHANGES REQUESTED
Required fix:
minimap.gd:94— changepoi.get("category", "")topoi.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.
Tyre (Architecture): APPROVE
Extraction clean. D-085 intact. Sprint 18 test files follow established patterns.
Verdict: CHANGES REQUESTED → FIXED in commits
83a244f,e7f1e80All critical and warning issues from rounds 1–3 addressed. Ready for merge from main.
Pull request closed