Debug overlay: draw the paths of moving pawns #97

Merged
liamjd merged 3 commits from 82-debug-path-overlay into main 2026-09-14 07:19:20 +01:00
Owner

Closes #82

Adds DebugPathOverlay: a MeshInstance3D beside Pawns in game.tscn that rebuilds an ImmediateMesh each frame from every MOVING pawn's remaining_path(), toggled by GameState.debug_mode_changed alongside the existing DebugLayer.

Ribbons, not lines

The issue proposed PRIMITIVE_LINES. That turned out to be unusable: Godot 4 exposes no width for 3D line primitives, so a path is one physical pixel at any zoom and is all but invisible under an orthographic camera. Each segment is now two triangles, its corners pushed half a width perpendicular to the direction of travel — in the ground plane only, so the ribbon climbs a terrace without banking over on the step. Width is a ratio of tile_size (path_width_ratio, default 0.08), following the same reasoning as PlacementController's arrow.

Two smaller decisions worth flagging:

  • The trail starts at the pawn's position flattened to ground level. PawnView floats its origin half a body up so the capsule sits on the terrain, while waypoints sit on the surface; mixing the two conventions ramps the first quad down from the pawn's middle while the rest lie flat.
  • The ribbon is built into a local array before the surface is opened. surface_end() errors if no vertices were added, and every segment can be skipped when move_to() resolves to the tile the pawn already stands on — reachable via debug_scatter_pawns, since random_walkable_position() can return the pawn's own cell.

Verification

Headed capture via tools/screenshot_path_overlay.gd (screenshots attached). Three frames: trails at full length, the same trails after ~4s of walking, and debug mode off.

pawn 1 pawn 2 pawn 3 (idle) surfaces visible processing
early 39 46 0 2 true true
later 34 41 0 2 true true
debug off 33 40 0 2 false false

One surface per moving pawn and none for the idle one; waypoint counts falling as they walk; the toggle stopping both drawing and processing. surfaces staying at 2 after the toggle is the distinction the issue asked for — hidden and not redrawn, rather than cleared.

GUT suite green: 342 passing, 1 pre-existing pending, unchanged from baseline. The overlay itself has no unit coverage — it is render-only, and gl_compatibility draws nothing headless.

Deletes tools/probe_pawn_terrace.gd

It has been dead since #95 put pawn_view.gd on GameClock, and nothing reported it. A -s script is compiled before the autoloads are registered, so its var _view: PawnView forced pawn_view.gd to compile too early and fail on Identifier not found: GameClock. It then fails soft: the script stays broken, the probe spawns a scriptless node, and it produces a plausible but wrong image rather than stopping. It was marked THROWAWAY - delete after use and #80 is closed.

The new capture script sidesteps the same trap by naming no project types at all — documented in its header and now in CLAUDE.md, since the existing note framed this as a get_node() lookup workaround rather than a compile-order failure. Worth knowing that a green suite cannot catch this class of breakage: GUT's runner never names a project type, so it is structurally immune.

Caveats and things left undone

  • No Y lift on the ribbon. Deliberate: no_depth_test = true means there is no depth comparison to lose, so it makes no visible difference today. It becomes load-bearing if depth testing is ever turned back on.
  • no_depth_test draws paths over everything, hills and buildings included. Right for a debug overlay, but it means the overlay cannot be used to judge occlusion — only route shape. Worth revisiting once buildings sit on these routes.
  • Segments do not mitre. Each is independent, so a sharp turn leaves a small notch on the outside. Barely visible at A* turn angles and not worth the maths for a debug tool.
  • The capture is not fully deterministic. The seed fixes the terrain, but game.gd::_reset_stub_pawns() seeds its own RandomNumberGenerator.new() from entropy, so the routes differ between runs.
  • Also corrects the CLAUDE.md credential notes to match what is actually configured on the laptop — the wincred override had never been applied here, which is why pushing hit the #1408 OAuth failure again.

🤖 Generated with Claude Code

https://claude.ai/code/session_01H1i49KZ7HMn2uFk3PdTsav

Closes #82 Adds `DebugPathOverlay`: a `MeshInstance3D` beside `Pawns` in `game.tscn` that rebuilds an `ImmediateMesh` each frame from every `MOVING` pawn's `remaining_path()`, toggled by `GameState.debug_mode_changed` alongside the existing `DebugLayer`. ## Ribbons, not lines The issue proposed `PRIMITIVE_LINES`. That turned out to be unusable: Godot 4 exposes no width for 3D line primitives, so a path is one physical pixel at any zoom and is all but invisible under an orthographic camera. Each segment is now two triangles, its corners pushed half a width perpendicular to the direction of travel — in the ground plane only, so the ribbon climbs a terrace without banking over on the step. Width is a ratio of `tile_size` (`path_width_ratio`, default `0.08`), following the same reasoning as `PlacementController`'s arrow. Two smaller decisions worth flagging: - The trail starts at the pawn's position **flattened to ground level**. `PawnView` floats its origin half a body up so the capsule sits on the terrain, while waypoints sit on the surface; mixing the two conventions ramps the first quad down from the pawn's middle while the rest lie flat. - The ribbon is built into a local array **before** the surface is opened. `surface_end()` errors if no vertices were added, and every segment can be skipped when `move_to()` resolves to the tile the pawn already stands on — reachable via `debug_scatter_pawns`, since `random_walkable_position()` can return the pawn's own cell. ## Verification Headed capture via `tools/screenshot_path_overlay.gd` (screenshots attached). Three frames: trails at full length, the same trails after ~4s of walking, and debug mode off. | | pawn 1 | pawn 2 | pawn 3 (idle) | surfaces | visible | processing | |---|---|---|---|---|---|---| | early | 39 | 46 | 0 | 2 | true | true | | later | 34 | 41 | 0 | 2 | true | true | | debug off | 33 | 40 | 0 | 2 | **false** | **false** | One surface per moving pawn and none for the idle one; waypoint counts falling as they walk; the toggle stopping both drawing and processing. `surfaces` staying at 2 after the toggle is the distinction the issue asked for — hidden and *not redrawn*, rather than cleared. GUT suite green: 342 passing, 1 pre-existing pending, unchanged from baseline. The overlay itself has no unit coverage — it is render-only, and `gl_compatibility` draws nothing headless. ## Deletes `tools/probe_pawn_terrace.gd` It has been dead since #95 put `pawn_view.gd` on `GameClock`, and nothing reported it. A `-s` script is compiled **before** the autoloads are registered, so its `var _view: PawnView` forced `pawn_view.gd` to compile too early and fail on `Identifier not found: GameClock`. It then fails *soft*: the script stays broken, the probe spawns a scriptless node, and it produces a plausible but wrong image rather than stopping. It was marked `THROWAWAY - delete after use` and #80 is closed. The new capture script sidesteps the same trap by naming no project types at all — documented in its header and now in CLAUDE.md, since the existing note framed this as a `get_node()` lookup workaround rather than a compile-order failure. Worth knowing that a green suite cannot catch this class of breakage: GUT's runner never names a project type, so it is structurally immune. ## Caveats and things left undone - **No Y lift on the ribbon.** Deliberate: `no_depth_test = true` means there is no depth comparison to lose, so it makes no visible difference today. It becomes load-bearing if depth testing is ever turned back on. - **`no_depth_test` draws paths over everything**, hills and buildings included. Right for a debug overlay, but it means the overlay cannot be used to judge occlusion — only route shape. Worth revisiting once buildings sit on these routes. - **Segments do not mitre.** Each is independent, so a sharp turn leaves a small notch on the outside. Barely visible at A\* turn angles and not worth the maths for a debug tool. - **The capture is not fully deterministic.** The seed fixes the terrain, but `game.gd::_reset_stub_pawns()` seeds its own `RandomNumberGenerator.new()` from entropy, so the routes differ between runs. - Also corrects the CLAUDE.md credential notes to match what is actually configured on the laptop — the `wincred` override had never been applied here, which is why pushing hit the #1408 OAuth failure again. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01H1i49KZ7HMn2uFk3PdTsav
Adds DebugPathOverlay from #82: a MeshInstance3D beside Pawns in
game.tscn that rebuilds an ImmediateMesh each frame from every MOVING
pawn's remaining_path(). It hangs off GameState.debug_mode_changed, the
same signal DebugLayer uses, and the handler toggles processing as well
as visibility - hiding a node in Godot stops it drawing but not ticking,
so otherwise the overlay would keep rebuilding geometry nobody can see.

Paths are flat triangle ribbons rather than PRIMITIVE_LINES. Godot 4
exposes no width for 3D line primitives, so a line is one physical pixel
at any zoom and is all but invisible under an orthographic camera. Each
segment becomes two triangles, its corners pushed half a width out
perpendicular to the direction of travel - in the ground plane only, so
the ribbon climbs a terrace without banking over on the step.

The ribbon is built into a local array before the surface is opened,
because surface_end() errors if no vertices were added and every segment
can be skipped when a path resolves to the tile the pawn already stands
on. The trail also starts at the pawn's position flattened to ground
level: PawnView floats its origin half a body up so the capsule sits on
the terrain, while waypoints sit on the surface, and mixing the two
conventions ramps the first quad down from the pawn's middle.

Colour is per pawn, the hue rotated by the golden ratio from the pawn id
so consecutive ids land far apart on the wheel.

Adds tools/screenshot_path_overlay.gd, the headed capture behind the AC.
It loads the real game.tscn rather than assembling a world, since the
overlay resolves its siblings by path and a hand-built scene would only
restate the wiring under test. It is deliberately untyped, against the
repo convention: a -s script is compiled before the autoloads are
registered, so naming a project type forces pawns.gd and pawn_view.gd to
compile at that moment and both fail on GameState and GameClock.

Deletes tools/probe_pawn_terrace.gd, dead since #95 put pawn_view.gd on
GameClock, for exactly that reason - it declares `var _view: PawnView`,
so the script never compiles and the probe spawns a scriptless node. It
was marked THROWAWAY and #80 is closed; left in place it produces a
plausible but wrong image rather than failing loudly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1i49KZ7HMn2uFk3PdTsav
The existing note framed this as a get_node() lookup workaround, which
understates it. A -s script is compiled before the autoloads are
registered, so a type annotation naming a project class is enough to
compile that class too early and fail on GameState or GameClock.

It fails soft, which is the dangerous part: the scripts stay broken, the
nodes load without them, and the harness produces a plausible but wrong
result rather than stopping. probe_pawn_terrace.gd was silently dead from
the moment #95 landed, and no test could have caught it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1i49KZ7HMn2uFk3PdTsav
The provider lines are gone from the laptop's ~/.gitconfig rather than
merely known to be useless, and the stale LAN entry was .105, not .109.
The wincred override is now applied here for real; it had never been set
on this machine, which is why the OAuth path was still in play.

Also records that GCM can leave only the refresh_token entry behind, with
the bare-host credential already erased. That is still #1408 and still
needs the refresh token deleted - a single entry rather than two is not a
different problem, and reads like one when you are looking for a pair.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1i49KZ7HMn2uFk3PdTsav
liamjd merged commit fd913b2cb0 into main 2026-09-14 07:19:20 +01:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
liamjd/UntitledColonyBuilder!97
No description provided.