From 41b6ceb47ef2306507d0cecc6f6bceb5d6c5e22a Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Tue, 28 Jul 2026 17:11:39 +0200 Subject: [PATCH] =?UTF-8?q?chore(meta):=20close=20T-1236=20=E2=80=94=20the?= =?UTF-8?q?=20capture=20drift=20was=20the=20product=20bug,=20not=20the=20h?= =?UTF-8?q?arness?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filed blaming the capture harness for parking the mouse in a corner. The harness was faithfully reproducing a real edge-scroll defect, fixed in dac64a8a4. No harness change was needed. Kept rather than deleted because the mis-attribution is the useful part: a tooling explanation was reached for before the product was checked. Co-Authored-By: Claude --- .pql/changelog/ticket_history/2026-07.sql | 4 ++++ .pql/changelog/tickets/2026-07.sql | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/.pql/changelog/ticket_history/2026-07.sql b/.pql/changelog/ticket_history/2026-07.sql index 7ca895254..77affaac0 100644 --- a/.pql/changelog/ticket_history/2026-07.sql +++ b/.pql/changelog/ticket_history/2026-07.sql @@ -3276,3 +3276,7 @@ SCOPE INVALIDATED 2026-07-27 (see T-1211 and the D-258 amendment). This ticket w 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; +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 ('06FTA26EM8YXW086EV2K0N2WZR', 'description', '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.', '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. + +RESOLVED 2026-07-27 by the edge-scroll fix (commit dac64a8a4) — this ticket''s diagnosis was wrong. I filed it blaming the capture harness for parking the mouse in a corner. The harness was faithfully reproducing a REAL product bug: _gui_input only fires while the pointer is over the Control, so _last_mouse_pos froze wherever it was last seen, and leaving the map always means crossing an edge, so the frozen value was always inside the edge margin. The viewer then panned forever with no input. NOTIFICATION_MOUSE_EXIT now resets to the (-1,-1) sentinel. Evidence: captures previously reported canvas_position=(292.0, 657.3336) — off-centre AND fractional, which ruled out center_offset() since that floors; they now report whole-number positions like (423.0, 0.0) with no drift across runs. No harness change was needed or made. Kept rather than deleted because the mis-attribution is worth seeing: a tooling explanation was reached for before the product was checked.', NULL, '2026-07-28 15:11:31', '2026-07-28 15:11:31.728', '2026-07-28 15:11:31.728', NULL, 'eaf52c758698ad7deace9e670825a90b', 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 ('06FTA26EM8YXW086EV2K0N2WZR', 'status', 'backlog', 'done', NULL, '2026-07-28 15:11:31', '2026-07-28 15:11:31.754', '2026-07-28 15:11:31.754', NULL, '6a20816c1df4d1cb2287c801186fdcf1', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-07.sql b/.pql/changelog/tickets/2026-07.sql index 3cade5fc4..3b4eaaa84 100644 --- a/.pql/changelog/tickets/2026-07.sql +++ b/.pql/changelog/tickets/2026-07.sql @@ -5574,3 +5574,9 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat 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); 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 ('06FTEX51V1CH70BS8WAPZX0HH4', 'story', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Render rivers as cartographic strokes (D-261)', 'Implements D-261 in step_canvas_annotation_layer.gd. All four rules are client-side over existing server data -- no wire change needed. (1) FIXED 5 px screen-space stroke at every rung, replacing the current per-class ladder (0.6/1.2/2.4). (2) CONTIGUOUS polyline through the river''s own cell centres, so geometry is a function of the river rather than the canvas pitch -- this is the actual fix for the fragments, since courses are presently resampled at canvas pitch and a course shorter than one gridunit collapses to a dot. (3) NEVER DRAWN OVER WATER: truncate each course where it meets ocean or lake, using the per-cell classification already present in the adopted canvas. (4) CULL below 15 px of on-screen length (3x the stroke width -- below that it reads as a square, not a line). Measure the VISIBLE extent, not total river length: a course crossing the window always spans it and passes, so only a course lying wholly inside the view and small is culled, and no new wire field is required. PERFORMANCE: compute the clipped/culled polylines ONCE on canvas adoption (set_frame), never per draw -- the layer redraws every frame and a per-point water lookup across ~375 courses per frame is waste for geometry that only changes when a canvas arrives. VERIFY by capture, not by reasoning: the pre-change baseline is 375 courses on Ferrath Global differing from courses-off by 458 of 518,400 pixels. After this, Global should show only major systems (>264 km on Ferrath) as clean unbroken strokes that stop at the coastline, with tributaries appearing on descent. NOTE the per-class width grammar is deliberately flattened here; the thinning-by-size polish is a separate follow-up (see D-261''s Deferred section).', 'backlog', 'medium', NULL, 'client', 'D-261', '2026-07-28 06:41:06.520', '2026-07-28 06:41:06.520', NULL, 'bae3e0eba124da884036c7588523e99f', 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 ('06FTEX6JGRQCTH8S5RYAZ1BBPR', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Polish: river stroke width varies with river size (D-261 deferred)', 'Deferred out of T-1237 by Jeroen: ''I do like your suggestion of thinning shorter rivers (especially if that shows tributaries following those rules) but maybe that is for a future polish iteration.'' D-261 flattens the ratified stream/tributary/trunk width grammar (0.6/1.2/2.4 px, tuned so a tributary joining a trunk visibly reads as a join) to a uniform 5 px. This restores a size-varying width on top of the D-261 stroke model. Worth knowing before starting: width and cull are COUPLED. D-261 derives the visibility threshold as 3x the stroke width, so a thinner stroke lowers its own threshold -- a 2 px tributary would become visible at ~106 km on Ferrath Global where a 5 px trunk needs 264 km. That is arguably the correct behaviour (smaller rivers are both thinner AND appear later) and makes the two rules compose rather than conflict, but it means the cull can no longer be a single per-canvas constant: it becomes per-course, derived from that course''s own width. Blocked in spirit on T-1237 landing first, since it modifies the model that ticket establishes.', 'backlog', 'low', NULL, 'client', 'D-261', '2026-07-28 06:41:18.982', '2026-07-28 06:41:18.982', NULL, '87d7cb4a08bbfce9ddea16e26dc1c2c9', 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. + +RESOLVED 2026-07-27 by the edge-scroll fix (commit dac64a8a4) — this ticket''s diagnosis was wrong. I filed it blaming the capture harness for parking the mouse in a corner. The harness was faithfully reproducing a REAL product bug: _gui_input only fires while the pointer is over the Control, so _last_mouse_pos froze wherever it was last seen, and leaving the map always means crossing an edge, so the frozen value was always inside the edge margin. The viewer then panned forever with no input. NOTIFICATION_MOUSE_EXIT now resets to the (-1,-1) sentinel. Evidence: captures previously reported canvas_position=(292.0, 657.3336) — off-centre AND fractional, which ruled out center_offset() since that floors; they now report whole-number positions like (423.0, 0.0) with no drift across runs. No harness change was needed or made. Kept rather than deleted because the mis-attribution is worth seeing: a tooling explanation was reached for before the product was checked.', 'backlog', 'medium', NULL, 'client', NULL, '2026-07-27 19:24:05.666', '2026-07-28 15:11:31.728', NULL, '507610d424d89db679b5e8bfa8bef090', 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. + +RESOLVED 2026-07-27 by the edge-scroll fix (commit dac64a8a4) — this ticket''s diagnosis was wrong. I filed it blaming the capture harness for parking the mouse in a corner. The harness was faithfully reproducing a REAL product bug: _gui_input only fires while the pointer is over the Control, so _last_mouse_pos froze wherever it was last seen, and leaving the map always means crossing an edge, so the frozen value was always inside the edge margin. The viewer then panned forever with no input. NOTIFICATION_MOUSE_EXIT now resets to the (-1,-1) sentinel. Evidence: captures previously reported canvas_position=(292.0, 657.3336) — off-centre AND fractional, which ruled out center_offset() since that floors; they now report whole-number positions like (423.0, 0.0) with no drift across runs. No harness change was needed or made. Kept rather than deleted because the mis-attribution is worth seeing: a tooling explanation was reached for before the product was checked.', 'done', 'medium', NULL, 'client', NULL, '2026-07-27 19:24:05.666', '2026-07-28 15:11:31.754', NULL, '39a5284eeee2c2637ed19a6c4a080e0a', 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);