fix(client): the Atlas map scrolled itself off the world

Jeroen: 'it scrolls out of screen automatically.' It did, with no input.

_gui_input only fires while the pointer is over the Control, so
_last_mouse_pos freezes at wherever it was last seen. Leaving the map ALWAYS
means crossing an edge, so the frozen value is always inside the 24px edge
margin — and the viewer went on believing the cursor was held there, panning
forever. Moving the mouse elsewhere could not stop it, because 'elsewhere'
generates no events this Control ever hears.

NOTIFICATION_MOUSE_EXIT now resets to the same (-1,-1) sentinel the field is
born with, making 'pointer is not over the map' and 'pointer has never been
over the map' the same state. Neither should scroll, and there was already a
test asserting the second case — the first had no equivalent.

Considered and rejected: reading get_local_mouse_position() live instead of
caching. It is arguably cleaner, but it cannot be injected in a headless test,
so it would have traded a bug for the inability to prove the fix — and the
existing edge-scroll suite drives _last_mouse_pos directly.

This also explains the drift I had blamed on the capture harness (T-1236):
same defect, and the harness was simply exercising it faithfully.

Client suite 1833 / 1807 passed / 0 failed / 26 skipped.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-27 21:36:32 +02:00
co-authored by Claude
parent 144d35d2a5
commit dac64a8a40
4 changed files with 88 additions and 13 deletions
@@ -3273,3 +3273,6 @@ RE-SCOPE REQUIRED BEFORE ANY CHILD STARTS (2026-07-27, D-258 amendment). The str
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FT0TYBD74TQNVKTJMKA8D9KM', 'description', 'MUST run and be reviewed before any other T-1211 child starts -- this measurement could reshape the rung-0.5 design, per the pair session''s explicit sequencing note. Measure, at minimum: (1) per-body derive cost for the expanded layer at a resolution sized so a whole body draws at 2 screen px per gridunit on a large display (the D-258 sizing rule) -- both single-body cold-derive time and the full ~271-body population sum; (2) per-body and total disk footprint if the layer is cached/stored (rung 0.5 is a named D-227 carve-out -- storage is deliberate, but its size must be known, not assumed); (3) whole-body hydrology solve cost on this layer (drainage + course routing + lake fill) at the same resolution, since D-258 requires this to run exactly once per body and nowhere else. Reference point: D-255''s own rung-0 always-keep tier estimate went from ~8.85 MB (measured against a stale ~18K-cell/body figure) to an estimated 226 MB (1080p) / ~900 MB (4K) once the extent inversion made Global viewport-sized -- D-255 amendment item 6 explicitly says ''re-measure against rung 0.5, not against this record.'' This ticket is that re-measurement. Report back to the team before T-1211''s other children are started; if the numbers are structurally bad (e.g. rung 0.5 at the sizing D-258 specifies costs an order of magnitude more than the old rung-0 model), that is grounds to revisit the resolution target with Jeroen before writing generator code. See governance/decisions/architecture.md#d-258 (rationale + Implementation note).', 'MUST run and be reviewed before any other T-1211 child starts -- this measurement could reshape the rung-0.5 design, per the pair session''s explicit sequencing note. Measure, at minimum: (1) per-body derive cost for the expanded layer at a resolution sized so a whole body draws at 2 screen px per gridunit on a large display (the D-258 sizing rule) -- both single-body cold-derive time and the full ~271-body population sum; (2) per-body and total disk footprint if the layer is cached/stored (rung 0.5 is a named D-227 carve-out -- storage is deliberate, but its size must be known, not assumed); (3) whole-body hydrology solve cost on this layer (drainage + course routing + lake fill) at the same resolution, since D-258 requires this to run exactly once per body and nowhere else. Reference point: D-255''s own rung-0 always-keep tier estimate went from ~8.85 MB (measured against a stale ~18K-cell/body figure) to an estimated 226 MB (1080p) / ~900 MB (4K) once the extent inversion made Global viewport-sized -- D-255 amendment item 6 explicitly says ''re-measure against rung 0.5, not against this record.'' This ticket is that re-measurement. Report back to the team before T-1211''s other children are started; if the numbers are structurally bad (e.g. rung 0.5 at the sizing D-258 specifies costs an order of magnitude more than the old rung-0 model), that is grounds to revisit the resolution target with Jeroen before writing generator code. See governance/decisions/architecture.md#d-258 (rationale + Implementation note).
SCOPE INVALIDATED 2026-07-27 (see T-1211 and the D-258 amendment). This ticket was written to measure the cost of moving a whole-body hydrology solve onto rung 0.5. That solve does not need to move -- it already runs once per body in layer1.rs and is sampled fresh at every rung. Do NOT run this measurement as written; it would price work that is not required. If a measurement is still wanted after T-1211 is re-scoped, the question is narrower: what does BIOME un-summarisation cost, and does it need storing at all.', NULL, '2026-07-27 18:51:51', '2026-07-27 18:51:51.056', '2026-07-27 18:51:51.056', NULL, 'c9374708c339c12f68f1121972578a7d', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FT0VDA5Q7JZFXG7XYWFR6M3C', 'description', 'Pre-existing lint-cap violation, made worse by the 2026-07-26 pair session''s fetch-overlay wiring work (still net additive to a file already over cap). Split step_canvas_viewer.gd so it comes back under the 1,000-line gdlint limit -- likely candidate: extract the fetch-overlay wiring / cache-store plumbing into a separate component now that step_canvas_fetch_overlay.gd exists as a dedicated node, reducing what step_canvas_viewer.gd itself needs to own directly. Verify gdlint passes clean after the split and that the viewer test suite (92/92 as of this session) stays green.', 'Pre-existing lint-cap violation, made worse by the 2026-07-26 pair session''s fetch-overlay wiring work (still net additive to a file already over cap). Split step_canvas_viewer.gd so it comes back under the 1,000-line gdlint limit -- likely candidate: extract the fetch-overlay wiring / cache-store plumbing into a separate component now that step_canvas_fetch_overlay.gd exists as a dedicated node, reducing what step_canvas_viewer.gd itself needs to own directly. Verify gdlint passes clean after the split and that the viewer test suite (92/92 as of this session) stays green.
ALSO test_step_canvas_viewer.gd, which crossed the 1,000-line gdlint cap on 2026-07-27 (now 1,094) as regression tests accumulated during the Global debugging session. Same cap, same file cluster, so fix together: the viewer suite splits naturally along the seams its own section comments already mark (rung transport / Global sizing + letterbox / cache-key contract / edge-scroll + pan). Note the production file and the test file hit the cap for opposite reasons — the viewer because it does too much, the suite because it covers a lot — so the remedies differ: extract collaborators from the viewer, split the suite by concern.', NULL, '2026-07-27 19:36:19', '2026-07-27 19:36:19.691', '2026-07-27 19:36:19.691', NULL, 'aec2754271feda9dab0ac68296663bc7', 2) ON CONFLICT(hash) DO NOTHING;
+3
View File
@@ -5569,3 +5569,6 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
SCOPE INVALIDATED 2026-07-27 (see T-1211 and the D-258 amendment). This ticket was written to measure the cost of moving a whole-body hydrology solve onto rung 0.5. That solve does not need to move -- it already runs once per body in layer1.rs and is sampled fresh at every rung. Do NOT run this measurement as written; it would price work that is not required. If a measurement is still wanted after T-1211 is re-scoped, the question is narrower: what does BIOME un-summarisation cost, and does it need storing at all.', 'backlog', 'high', NULL, NULL, 'D-258', '2026-07-26 21:54:06.825', '2026-07-27 18:51:51.056', NULL, 'c5e77cd4de6e9ec9647bbfe12d0ed6e2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FTA26EM8YXW086EV2K0N2WZR', 'bug', '06FBPPMZNNEV052DBYYY3A897C', 'visual_capture drifts the Atlas view — edge-scroll fires during the settle', 'Found 2026-07-27 while verifying the Global fill fix. An atlas capture reported canvas_position=(292.0, 657.3336) where the letterbox centre should be a whole-pixel value near y=133-277. The Y is both wrong AND fractional, which rules out center_offset() -- that floors to whole pixels for texel-exactness. The cause is edge scroll: the capture harness leaves the mouse at the viewport corner, StepCanvasViewer reads it as a held edge-scroll, and pans continuously through the 240 settle ticks, pushing the canvas down and right (x pinned at the legend column, y drifting). Consequences: (1) every Atlas golden is captured mid-pan, so the baselines encode an arbitrary drift offset rather than the canonical centred frame; (2) a real centring regression would be invisible against them; (3) captures are not reproducible run-to-run if tick timing varies. Fix shape: the capture harness should suppress edge-scroll (park the mouse centre-screen, or expose a viewer flag the harness sets), then assert the settled position IS the centred letterbox. Worth doing before any Atlas golden is trusted for centring -- note the goldens were ALSO degenerate for a separate reason until today (missing body_radius_km), so this rung''s baselines have never been meaningful.', 'backlog', 'medium', NULL, 'client', NULL, '2026-07-27 19:24:05.666', '2026-07-27 19:24:05.666', NULL, '5834a65f27af46030249f4b79a3b019a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FT0VDA5Q7JZFXG7XYWFR6M3C', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'step_canvas_viewer.gd is 1,133 lines against the 1,000-line gdlint cap', 'Pre-existing lint-cap violation, made worse by the 2026-07-26 pair session''s fetch-overlay wiring work (still net additive to a file already over cap). Split step_canvas_viewer.gd so it comes back under the 1,000-line gdlint limit -- likely candidate: extract the fetch-overlay wiring / cache-store plumbing into a separate component now that step_canvas_fetch_overlay.gd exists as a dedicated node, reducing what step_canvas_viewer.gd itself needs to own directly. Verify gdlint passes clean after the split and that the viewer test suite (92/92 as of this session) stays green.
ALSO test_step_canvas_viewer.gd, which crossed the 1,000-line gdlint cap on 2026-07-27 (now 1,094) as regression tests accumulated during the Global debugging session. Same cap, same file cluster, so fix together: the viewer suite splits naturally along the seams its own section comments already mark (rung transport / Global sizing + letterbox / cache-key contract / edge-scroll + pan). Note the production file and the test file hit the cap for opposite reasons — the viewer because it does too much, the suite because it covers a lot — so the remedies differ: extract collaborators from the viewer, split the suite by concern.', 'backlog', 'low', NULL, NULL, NULL, '2026-07-26 21:56:09.390', '2026-07-27 19:36:19.691', NULL, '706b91aeeb64634aa9a40e473b56efab', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);