Merge remote-tracking branch 'origin/main' into ocean-guard-synthetic
@@ -3148,3 +3148,66 @@ AUDIT COMPLETE (2026-07-26). Set was 11 files, not 12-13 (tiger.md deleted in 0a
|
||||
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 ('06FSW30GPFFEB05M6EXD1WFANW', 'status', 'in_progress', 'done', NULL, '2026-07-26 11:49:42', '2026-07-26 11:49:42.508', '2026-07-26 11:49:42.508', NULL, 'dced5473823b32b586d691d67cb0d726', 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 ('06FMZZ0184R5B8YMFJW5NWPNXW', 'status', 'in_progress', 'review', NULL, '2026-07-26 12:13:13', '2026-07-26 12:13:13.762', '2026-07-26 12:13:13.762', NULL, '55eb94032833d9e19a7a60116bf300f5', 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 ('06FMZZ0184R5B8YMFJW5NWPNXW', 'status', 'review', 'done', NULL, '2026-07-26 12:29:54', '2026-07-26 12:29:54.219', '2026-07-26 12:29:54.219', NULL, 'cd9fa62db8d8d1d359024df22c19d2e7', 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 ('06FSWCYA60VHFK44J09WW78FY0', 'status', 'backlog', 'in_progress', NULL, '2026-07-26 12:33:36', '2026-07-26 12:33:36.682', '2026-07-26 12:33:36.682', NULL, '126ba2bbb5114ed57d1aa1facb156709', 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 ('06FSP3GEJYTA966BSSR9354YC4', 'status', 'backlog', 'in_progress', NULL, '2026-07-26 12:33:36', '2026-07-26 12:33:36.687', '2026-07-26 12:33:36.687', NULL, '5c62d31cdde3da8bba4160924929d6c7', 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 ('06FSWCYA60VHFK44J09WW78FY0', 'assigned_to', NULL, 'dudley', NULL, '2026-07-26 12:33:41', '2026-07-26 12:33:41.592', '2026-07-26 12:33:41.592', NULL, 'c08a8b496d3a91055577832000605428', 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 ('06FSP3GEJYTA966BSSR9354YC4', 'assigned_to', NULL, 'stig', NULL, '2026-07-26 12:33:42', '2026-07-26 12:33:42.082', '2026-07-26 12:33:42.082', NULL, 'c401604d7344f97f2aaf943be53f48eb', 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 ('06FSP3GEJYTA966BSSR9354YC4', 'status', 'in_progress', 'review', NULL, '2026-07-26 12:46:59', '2026-07-26 12:46:59.992', '2026-07-26 12:46:59.992', NULL, 'f82201a615351e0dccc4a585bbdb32ee', 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 ('06FSWYQA1XKYKEQ114CZKEDAFW', 'description', 'test_fog_shader.gd::test_visibility_texture_update_performance asserts <0.5ms wall-clock and flaked TWICE on 2026-07-26 gate runs on the shared dev box (0.549ms during a heavily contended run racing live captures; 0.503ms — a 0.6% miss — during a lightly loaded run), passing 46/46 solo immediately after both times. A wall-clock assertion with sub-1% headroom cannot be trusted on a machine that runs cargo builds, godot imports, and live capture servers alongside gates — each flake costs a full gate re-run (~3-4 min plus diagnosis). Fix options, pick at pickup: (a) median-of-N (e.g. best-of-5 or median-of-9) sampling inside the test so a single scheduler hiccup cannot fail it — preferred, keeps the budget honest; (b) a modest budget raise with a comment recording the two incidents; (c) an environment-aware multiplier (worst option — hides regressions). Whatever lands must keep the test able to catch a REAL 2x regression. The companion test_full_fog_update_under_1ms has 2x the headroom and has never flaked — evidence the budget, not the code, is the problem. Gate logs: /tmp/sr-run-godot.638004.log (0.549), /tmp/sr-run-godot.1028571.log (0.503).', 'test_fog_shader.gd::test_visibility_texture_update_performance asserts <0.5ms wall-clock and flaked TWICE on 2026-07-26 gate runs on the shared dev box (0.549ms during a heavily contended run racing live captures; 0.503ms — a 0.6% miss — during a lightly loaded run), passing 46/46 solo immediately after both times. A wall-clock assertion with sub-1% headroom cannot be trusted on a machine that runs cargo builds, godot imports, and live capture servers alongside gates — each flake costs a full gate re-run (~3-4 min plus diagnosis). Fix options, pick at pickup: (a) median-of-N (e.g. best-of-5 or median-of-9) sampling inside the test so a single scheduler hiccup cannot fail it — preferred, keeps the budget honest; (b) a modest budget raise with a comment recording the two incidents; (c) an environment-aware multiplier (worst option — hides regressions). Whatever lands must keep the test able to catch a REAL 2x regression. The companion test_full_fog_update_under_1ms has 2x the headroom and has never flaked — evidence the budget, not the code, is the problem. Gate logs: /tmp/sr-run-godot.638004.log (0.549), /tmp/sr-run-godot.1028571.log (0.503).
|
||||
|
||||
FIXED (2026-07-26, direct on main): the test already used median-of-5 (T-1092''s earlier mitigation) and still flaked three times in one day (0.549/0.503/0.638) when concurrent cargo builds inflated all five samples together. Superseded with MINIMUM-of-7: the assertion asks whether the CODE meets the D-059 budget, and load can only inflate wall time, never deflate it — the minimum is the least-noise estimator of code capability while a real regression shifts the minimum too. Budget unchanged at 0.5ms (regression-catching power preserved). Verified 46/46. Closing.', NULL, '2026-07-26 13:17:28', '2026-07-26 13:17:28.575', '2026-07-26 13:17:28.575', NULL, '130fa8e2d5fc4d4925eb0911c0dd8c21', 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 ('06FSWYQA1XKYKEQ114CZKEDAFW', 'status', 'backlog', 'done', NULL, '2026-07-26 13:17:35', '2026-07-26 13:17:35.790', '2026-07-26 13:17:35.790', NULL, '4320a9e32c27bc27bb28a71b3bf909ce', 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 ('06FSWCYA60VHFK44J09WW78FY0', 'description', 'Found during the PR #215 review fix round (T-1116, Tyre finding 3 follow-through): attractor_matching.rs::synthetic_attractor (the D-211 Phase-4 overflow path, PlainCenter at min-spacing) takes no heightmap/TerrainAnalysis argument — it derives positions by pure grid arithmetic (grid_h/2, grid_w/4, spacing-walk) with zero ocean_mask check. Every EXTRACTED attractor type is land-guaranteed (features.rs::extract_attractors filters !ocean_mask, D-209), but a synthetic-overflow settlement can be placed in open water — visible as an ocean-stranded settlement marker on the Atlas. T-1116''s routing relaxation degrades this case gracefully (surrogate anchor within ring 3, else an isolated unrouted node — never a panic or a water road), but the placement guarantee itself is the gap. Documented in road_graph.rs''s anchor comment and the D-210 amendment (PR #215). Fix shape: thread the ocean mask (or TerrainAnalysis) into synthetic_attractor and nudge/reject water candidates deterministically — mind D-211''s seed-derived-position promise (any nudge must be a pure function of seed + terrain, no RNG state change). Verify by hunting a real body whose synthetic overflow currently lands in water (none observed yet — GJ251c/GJ380c placements are all extracted attractors) or a synthetic fixture.', 'Found during the PR #215 review fix round (T-1116, Tyre finding 3 follow-through): attractor_matching.rs::synthetic_attractor (the D-211 Phase-4 overflow path, PlainCenter at min-spacing) takes no heightmap/TerrainAnalysis argument — it derives positions by pure grid arithmetic (grid_h/2, grid_w/4, spacing-walk) with zero ocean_mask check. Every EXTRACTED attractor type is land-guaranteed (features.rs::extract_attractors filters !ocean_mask, D-209), but a synthetic-overflow settlement can be placed in open water — visible as an ocean-stranded settlement marker on the Atlas. T-1116''s routing relaxation degrades this case gracefully (surrogate anchor within ring 3, else an isolated unrouted node — never a panic or a water road), but the placement guarantee itself is the gap. Documented in road_graph.rs''s anchor comment and the D-210 amendment (PR #215). Fix shape: thread the ocean mask (or TerrainAnalysis) into synthetic_attractor and nudge/reject water candidates deterministically — mind D-211''s seed-derived-position promise (any nudge must be a pure function of seed + terrain, no RNG state change). Verify by hunting a real body whose synthetic overflow currently lands in water (none observed yet — GJ251c/GJ380c placements are all extracted attractors) or a synthetic fixture.
|
||||
|
||||
Implementation complete on branch ocean-guard-synthetic (worktree .worktrees/ocean-guard-synthetic).
|
||||
|
||||
COMPATIBILITY ANSWER (requirement 1): confirmed via a live scan of every real
|
||||
body with a heightmap (267 bodies, world seeds 42 and "yolo") plus a targeted
|
||||
before/after comparison — the guard does NOT move any currently-land synthetic
|
||||
placement. Direct evidence: 63 real land-arithmetic synthetic placements
|
||||
checked across all bodies, 63 unmoved (byte-identical position). Full
|
||||
`cargo test` also confirms every existing golden/determinism fixture
|
||||
(cascade_golden, golden_seed_determinism_regression, believability_determinism_and_golden,
|
||||
window_derivation_golden_regression) still passes unchanged.
|
||||
|
||||
FIX SHAPE: synthetic_attractor(placed, grid_w, grid_h, terrain: Option<&TerrainAnalysis>)
|
||||
-> Option<GeographicAttractor>. terrain=None reproduces the exact pre-T-1206
|
||||
behavior (existing callers/tests untouched). When Some: land-passthrough (an
|
||||
already-land arithmetic position returns unchanged), water-only-correction via
|
||||
a new nearest_land_cell() ring-walk (T-1116 nearest_passable_cell pattern at
|
||||
native resolution, row-major tie-break, column wrap / row clamp matching every
|
||||
other grid walk in the cascade), bounded by MAX_LAND_SEARCH_RING = 128 cells
|
||||
(sized empirically from the scan: real bodies'' nearest land to the arithmetic
|
||||
(0,0) default was as far as 125 cells away due to polar/high-latitude ocean
|
||||
bands — grid_h/2 = 128 is the natural ceiling).
|
||||
|
||||
DEGRADATION RULING (requirement 2): no land within MAX_LAND_SEARCH_RING ->
|
||||
skip the synthetic attractor entirely (match_cities'' Phase 4 loop does not
|
||||
push a placement for that city). Reported via the EXISTING Phase-5
|
||||
name-fulfillment warning ("atlas city was not placed") -- no new error path,
|
||||
never a panic, never a fabricated water position. At MAX_LAND_SEARCH_RING=128
|
||||
this degradation path does not trigger on any of the 267 scanned real bodies
|
||||
at either seed tested; it is exercised only in unit tests (tiny all-ocean
|
||||
grids).
|
||||
|
||||
VERIFICATION (requirement 3): wrote a scratch scan (deleted after use, tree
|
||||
clean) that ran the real cascade for all 267 bodies with a heightmap. Found a
|
||||
REAL, reproducible repro: 46 of 109 synthetic-overflow placements landed in
|
||||
open water pre-fix (world seed 42), e.g. GJ903c city_id=24375 at (0,0) which
|
||||
is ocean. Post-fix: 0 in water, 109 total synthetic placements preserved
|
||||
(same count as before -- no unexplained drops), confirmed at a second seed
|
||||
("yolo") too.
|
||||
|
||||
Files changed: server/src/atlas/attractor_matching.rs (synthetic_attractor +
|
||||
nearest_land_cell + match_cities signature + 9 new unit tests),
|
||||
server/src/atlas/cascade.rs (thread terrain_analysis through run_layer3),
|
||||
server/src/atlas/road_graph.rs (anchor comment updated to CLOSED),
|
||||
governance/decisions/architecture.md (D-210 amendment addendum, dated
|
||||
2026-07-26, validated via pql decisions validate --vault).
|
||||
|
||||
Full cargo test: 2221 passed, 0 failed (one gen_queue.rs worker-pool timing
|
||||
test flaked once under full-suite parallel load, confirmed unrelated and
|
||||
non-reproducible in isolation / module-scoped / --lib-only runs, and absent on
|
||||
a clean full-suite rerun). No golden fixtures changed.', NULL, '2026-07-26 13:23:55', '2026-07-26 13:23:55.540', '2026-07-26 13:23:55.540', NULL, '8af7979612b90a17bce6b8bafca84fb4', 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 ('06FSWCYA60VHFK44J09WW78FY0', 'status', 'in_progress', 'review', NULL, '2026-07-26 13:23:59', '2026-07-26 13:23:59.369', '2026-07-26 13:23:59.369', NULL, 'ed0a9656e3b0ada543a2f58e17eda9f5', 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 ('06FSP3GEJYTA966BSSR9354YC4', 'status', 'review', 'done', NULL, '2026-07-26 13:24:15', '2026-07-26 13:24:15.122', '2026-07-26 13:24:15.122', NULL, '37112750b5d1aeec9384cb9a1f5544ae', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -113,3 +113,14 @@ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSP3GEJYTA966BSSR9354YC4', 'T-1197', '2026-07-25 20:53:37.565', '2026-07-25 20:53:37.565', NULL, '68df0f63118c14b41a1e5a39a94c28f2', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSP3H9BVF26YM6PPSMEHX7Z0', 'T-1198', '2026-07-25 20:53:44.415', '2026-07-25 20:53:44.415', NULL, '8572e83ee7c743eff84e7929c2afabbf', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSP4MX7C66N76V31EB88YC78', 'T-1199', '2026-07-25 20:58:36.219', '2026-07-25 20:58:36.219', NULL, 'b943579b967b5fda8e0c2a9c35f6043a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSP7EJPBDP1JB4FFMG0BV11M', 'T-1200', '2026-07-25 21:10:50.802', '2026-07-25 21:10:50.802', NULL, '52d3aac571f9fe6e5fe67505d9584f50', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSP7F5THEPJWGV04B4542SFR', 'T-1201', '2026-07-25 21:10:55.700', '2026-07-25 21:10:55.700', NULL, 'f69426731f605e7d181f1b1234e78f90', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSP7FZ4F20AMWCXSP23Q5H54', 'T-1202', '2026-07-25 21:11:02.180', '2026-07-25 21:11:02.180', NULL, '279d8d2eb166c33b088bf1ff9a31f148', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSP7GX9J2SSHE7ZJB3PH0FEW', 'T-1203', '2026-07-25 21:11:09.900', '2026-07-25 21:11:09.900', NULL, 'b95f16e6f63a5aabcd691e9747317f80', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSP7HT5ES8D83VZA6ETACRAW', 'T-1204', '2026-07-25 21:11:17.291', '2026-07-25 21:11:17.291', NULL, '557ec52cec927e38336b9b48f45d9820', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSW30GPFFEB05M6EXD1WFANW', 'T-1205', '2026-07-26 10:50:18.675', '2026-07-26 10:50:18.675', NULL, '9f8b63e8e4e05cc7c65e019a3205f3a9', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSWCYA60VHFK44J09WW78FY0', 'T-1206', '2026-07-26 11:33:42.067', '2026-07-26 11:33:42.067', NULL, 'a5399659597f7ddcb110c0e699556c08', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSWE6BPT85W6WW7PKB4TEZ3R', 'T-1207', '2026-07-26 11:39:10.134', '2026-07-26 11:39:10.134', NULL, '66f36b1d68b6389cef3354e5fb4a617a', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSWGG5738Z62MBWD4NQ9D2T0', 'T-1208', '2026-07-26 11:49:14.680', '2026-07-26 11:49:14.680', NULL, 'edb9af23869cbdd56223d62060ec1e43', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSWGHKXRFDPRZQC3VF49KSC8', 'T-1209', '2026-07-26 11:49:26.638', '2026-07-26 11:49:26.638', NULL, 'fc68169cd09d2189576d3dd9d94500db', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSWYQA1XKYKEQ114CZKEDAFW', 'T-1210', '2026-07-26 12:51:23.282', '2026-07-26 12:51:23.282', NULL, 'ee984bb4262b08362e4d3908acbfab6b', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_idmap.updated_at OR (excluded.updated_at = ticket_idmap.updated_at AND excluded.hash > ticket_idmap.hash);
|
||||
|
||||
@@ -5333,3 +5333,120 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
|
||||
AUDIT COMPLETE (2026-07-26). Set was 11 files, not 12-13 (tiger.md deleted in 0a9b0d00a, Tiger off the roster). 66 unique D-ids body-matched across all 11. THREE new slip-family members, all in gestalt.md, each verified by full body-match and lead-spot-checked: D-130 written where D-131 (broad life-verb vocabulary) was meant (x3: lines 20/48/62) and D-118 written where D-133 (skills affect outcome) was meant (x2: lines 22/43). All five fixed on main. The other 10 briefings are citation-clean. Context findings recorded on follow-ups: T-1208 (eight wholesale-stale briefings, rewrite backlog, qatux.md + tyre.md highest value; also the systemic superseded-cluster and D-123/D-138 notes). Final slip-family tally across T-1199+T-1205: 16 mis-citations in 6 of 18 briefings, all from the single f4c72e148 commit, all fixed.', 'done', 'medium', 'clerk', 'server', NULL, '2026-07-26 10:50:18.675', '2026-07-26 11:49:42.508', NULL, '805203e281a681f4f5aee386d365941f', 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 ('06FMZZ0184R5B8YMFJW5NWPNXW', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Interstitial fill — tile character between building footprints (setback-driven open space vs. built ops-surface) at chunk derive', 'Deferred slice of T-959 (Layer 5 fill), split out during /whats-next review 2026-07-08. T-988 landed BuildingExteriorTag.setback_tier + BlockSkeleton.interstitial_character (OpenSpace|OperationsSurface, D-233 bulk-driven), and FillChunk now reads materials onto Wall/Roof shell voxels. What remains: actually FILL the between-footprint tiles at chunk-derive time — resolve each non-footprint ground tile to its character (setback-driven interstitial type: void/court/garden/plaza/dock_slip/market_pad/open_lawn per D-235; or the block''s OperationsSurface tag for bulk industry per D-233/T-1097).\n\nWHY DEFERRED (Dudley''s rationale, PR review): FillChunk''s work item today carries only Vec<BuildingPropertyTag> (per-building) with zero block-level metadata; setback_tier is per-building on exterior, but ''the gap between buildings'' is block-shaped. And ''what is an interstitial tile inside a 64m chunk derive'' is a real geometry question (the BSP leaves that lost the coverage roll in subdivide_block_footprints? a separate ground-fill pass?) that needs a design pass, not a guess. Nothing consumes FillChunk in production until Phase 5, so this blocks nothing today.\n\nSCOPE: (1) thread block-level metadata (interstitial_character + setback/density) into the FillChunk work item; (2) decide + implement the ground-fill geometry (which chunk tiles are interstitial vs footprint-covered vs street); (3) resolve each to its D-235 interstitial type / D-233 ops-surface tag; (4) stays pure/cache-free (T-987). Write a short brief pinning the geometry model before implementing.', 'review', 'medium', 'dudley', 'server', 'D-235', '2026-07-11 07:02:23.297', '2026-07-26 12:13:13.762', NULL, 'a1cca37f45b6dd0644785bb2db398942', 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 ('06FMZZ0184R5B8YMFJW5NWPNXW', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Interstitial fill — tile character between building footprints (setback-driven open space vs. built ops-surface) at chunk derive', 'Deferred slice of T-959 (Layer 5 fill), split out during /whats-next review 2026-07-08. T-988 landed BuildingExteriorTag.setback_tier + BlockSkeleton.interstitial_character (OpenSpace|OperationsSurface, D-233 bulk-driven), and FillChunk now reads materials onto Wall/Roof shell voxels. What remains: actually FILL the between-footprint tiles at chunk-derive time — resolve each non-footprint ground tile to its character (setback-driven interstitial type: void/court/garden/plaza/dock_slip/market_pad/open_lawn per D-235; or the block''s OperationsSurface tag for bulk industry per D-233/T-1097).\n\nWHY DEFERRED (Dudley''s rationale, PR review): FillChunk''s work item today carries only Vec<BuildingPropertyTag> (per-building) with zero block-level metadata; setback_tier is per-building on exterior, but ''the gap between buildings'' is block-shaped. And ''what is an interstitial tile inside a 64m chunk derive'' is a real geometry question (the BSP leaves that lost the coverage roll in subdivide_block_footprints? a separate ground-fill pass?) that needs a design pass, not a guess. Nothing consumes FillChunk in production until Phase 5, so this blocks nothing today.\n\nSCOPE: (1) thread block-level metadata (interstitial_character + setback/density) into the FillChunk work item; (2) decide + implement the ground-fill geometry (which chunk tiles are interstitial vs footprint-covered vs street); (3) resolve each to its D-235 interstitial type / D-233 ops-surface tag; (4) stays pure/cache-free (T-987). Write a short brief pinning the geometry model before implementing.', 'done', 'medium', 'dudley', 'server', 'D-235', '2026-07-11 07:02:23.297', '2026-07-26 12:29:54.214', NULL, 'af93331f15b043918cee4603267a41fe', 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 ('06FSWCYA60VHFK44J09WW78FY0', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Synthetic-overflow settlement placement has no ocean-mask guard — can land in open water', 'Found during the PR #215 review fix round (T-1116, Tyre finding 3 follow-through): attractor_matching.rs::synthetic_attractor (the D-211 Phase-4 overflow path, PlainCenter at min-spacing) takes no heightmap/TerrainAnalysis argument — it derives positions by pure grid arithmetic (grid_h/2, grid_w/4, spacing-walk) with zero ocean_mask check. Every EXTRACTED attractor type is land-guaranteed (features.rs::extract_attractors filters !ocean_mask, D-209), but a synthetic-overflow settlement can be placed in open water — visible as an ocean-stranded settlement marker on the Atlas. T-1116''s routing relaxation degrades this case gracefully (surrogate anchor within ring 3, else an isolated unrouted node — never a panic or a water road), but the placement guarantee itself is the gap. Documented in road_graph.rs''s anchor comment and the D-210 amendment (PR #215). Fix shape: thread the ocean mask (or TerrainAnalysis) into synthetic_attractor and nudge/reject water candidates deterministically — mind D-211''s seed-derived-position promise (any nudge must be a pure function of seed + terrain, no RNG state change). Verify by hunting a real body whose synthetic overflow currently lands in water (none observed yet — GJ251c/GJ380c placements are all extracted attractors) or a synthetic fixture.', 'in_progress', 'medium', NULL, 'server', NULL, '2026-07-26 11:33:42.064', '2026-07-26 12:33:36.681', NULL, '024114313664da355c50c920ebf8339f', 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 ('06FSP3GEJYTA966BSSR9354YC4', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Atlas header readout ghosted in Quarter capture — rung-transition alpha artifact', 'Araminta''s T-1196 secondary observation (first GJ1002b captures, 2026-07-25): the ''ATLAS - CAPARICA / QUARTER'' header readout above the legend rendered faded/ghosted in the Quarter frame (scratchpad t1196/gj1002b_quarter.png), while the District and Region frames from the same session and capture path rendered it fully legible. Suspect a z-order or alpha transition state caught mid-render on rung change (the capture settles on StepCanvasRequest.is_pending() then waits 2 frames — a header fade animation may outlast the data settle). Reproduce with a T-1157-harness jump_to capture at any Quarter rung and inspect header alpha across post-settle frames; if it is a fade animation racing the capture, decide whether the fix is harness-side (settle until animations idle) or UI-side (no fade on rung change). Likely cosmetic; the only frame of the three with a legibility defect.', 'in_progress', 'low', NULL, 'client', NULL, '2026-07-25 20:53:37.559', '2026-07-26 12:33:36.687', NULL, '61c09dcec6f2765796beb7a85fa45550', 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 ('06FSWCYA60VHFK44J09WW78FY0', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Synthetic-overflow settlement placement has no ocean-mask guard — can land in open water', 'Found during the PR #215 review fix round (T-1116, Tyre finding 3 follow-through): attractor_matching.rs::synthetic_attractor (the D-211 Phase-4 overflow path, PlainCenter at min-spacing) takes no heightmap/TerrainAnalysis argument — it derives positions by pure grid arithmetic (grid_h/2, grid_w/4, spacing-walk) with zero ocean_mask check. Every EXTRACTED attractor type is land-guaranteed (features.rs::extract_attractors filters !ocean_mask, D-209), but a synthetic-overflow settlement can be placed in open water — visible as an ocean-stranded settlement marker on the Atlas. T-1116''s routing relaxation degrades this case gracefully (surrogate anchor within ring 3, else an isolated unrouted node — never a panic or a water road), but the placement guarantee itself is the gap. Documented in road_graph.rs''s anchor comment and the D-210 amendment (PR #215). Fix shape: thread the ocean mask (or TerrainAnalysis) into synthetic_attractor and nudge/reject water candidates deterministically — mind D-211''s seed-derived-position promise (any nudge must be a pure function of seed + terrain, no RNG state change). Verify by hunting a real body whose synthetic overflow currently lands in water (none observed yet — GJ251c/GJ380c placements are all extracted attractors) or a synthetic fixture.', 'in_progress', 'medium', 'dudley', 'server', NULL, '2026-07-26 11:33:42.064', '2026-07-26 12:33:41.587', NULL, 'f5e116e6900eaad013ca4a2200586e35', 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 ('06FSP3GEJYTA966BSSR9354YC4', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Atlas header readout ghosted in Quarter capture — rung-transition alpha artifact', 'Araminta''s T-1196 secondary observation (first GJ1002b captures, 2026-07-25): the ''ATLAS - CAPARICA / QUARTER'' header readout above the legend rendered faded/ghosted in the Quarter frame (scratchpad t1196/gj1002b_quarter.png), while the District and Region frames from the same session and capture path rendered it fully legible. Suspect a z-order or alpha transition state caught mid-render on rung change (the capture settles on StepCanvasRequest.is_pending() then waits 2 frames — a header fade animation may outlast the data settle). Reproduce with a T-1157-harness jump_to capture at any Quarter rung and inspect header alpha across post-settle frames; if it is a fade animation racing the capture, decide whether the fix is harness-side (settle until animations idle) or UI-side (no fade on rung change). Likely cosmetic; the only frame of the three with a legibility defect.', 'in_progress', 'low', 'stig', 'client', NULL, '2026-07-25 20:53:37.559', '2026-07-26 12:33:42.081', NULL, '125ff7ed1722e7e21e2c98d8817c1d25', 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 ('06FSP3GEJYTA966BSSR9354YC4', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Atlas header readout ghosted in Quarter capture — rung-transition alpha artifact', 'Araminta''s T-1196 secondary observation (first GJ1002b captures, 2026-07-25): the ''ATLAS - CAPARICA / QUARTER'' header readout above the legend rendered faded/ghosted in the Quarter frame (scratchpad t1196/gj1002b_quarter.png), while the District and Region frames from the same session and capture path rendered it fully legible. Suspect a z-order or alpha transition state caught mid-render on rung change (the capture settles on StepCanvasRequest.is_pending() then waits 2 frames — a header fade animation may outlast the data settle). Reproduce with a T-1157-harness jump_to capture at any Quarter rung and inspect header alpha across post-settle frames; if it is a fade animation racing the capture, decide whether the fix is harness-side (settle until animations idle) or UI-side (no fade on rung change). Likely cosmetic; the only frame of the three with a legibility defect.', 'review', 'low', 'stig', 'client', NULL, '2026-07-25 20:53:37.559', '2026-07-26 12:46:59.991', NULL, 'c427777602f2f1ed6b898b6d172b913c', 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 ('06FSWYQA1XKYKEQ114CZKEDAFW', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Fog visibility-texture perf test flakes under machine load — 0.5ms budget has no headroom', 'test_fog_shader.gd::test_visibility_texture_update_performance asserts <0.5ms wall-clock and flaked TWICE on 2026-07-26 gate runs on the shared dev box (0.549ms during a heavily contended run racing live captures; 0.503ms — a 0.6% miss — during a lightly loaded run), passing 46/46 solo immediately after both times. A wall-clock assertion with sub-1% headroom cannot be trusted on a machine that runs cargo builds, godot imports, and live capture servers alongside gates — each flake costs a full gate re-run (~3-4 min plus diagnosis). Fix options, pick at pickup: (a) median-of-N (e.g. best-of-5 or median-of-9) sampling inside the test so a single scheduler hiccup cannot fail it — preferred, keeps the budget honest; (b) a modest budget raise with a comment recording the two incidents; (c) an environment-aware multiplier (worst option — hides regressions). Whatever lands must keep the test able to catch a REAL 2x regression. The companion test_full_fog_update_under_1ms has 2x the headroom and has never flaked — evidence the budget, not the code, is the problem. Gate logs: /tmp/sr-run-godot.638004.log (0.549), /tmp/sr-run-godot.1028571.log (0.503).', 'backlog', 'medium', NULL, 'client', NULL, '2026-07-26 12:51:23.279', '2026-07-26 12:51:23.279', NULL, '0d7374a5cdc7058a66e734fdaaf246db', 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 ('06FSWYQA1XKYKEQ114CZKEDAFW', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Fog visibility-texture perf test flakes under machine load — 0.5ms budget has no headroom', 'test_fog_shader.gd::test_visibility_texture_update_performance asserts <0.5ms wall-clock and flaked TWICE on 2026-07-26 gate runs on the shared dev box (0.549ms during a heavily contended run racing live captures; 0.503ms — a 0.6% miss — during a lightly loaded run), passing 46/46 solo immediately after both times. A wall-clock assertion with sub-1% headroom cannot be trusted on a machine that runs cargo builds, godot imports, and live capture servers alongside gates — each flake costs a full gate re-run (~3-4 min plus diagnosis). Fix options, pick at pickup: (a) median-of-N (e.g. best-of-5 or median-of-9) sampling inside the test so a single scheduler hiccup cannot fail it — preferred, keeps the budget honest; (b) a modest budget raise with a comment recording the two incidents; (c) an environment-aware multiplier (worst option — hides regressions). Whatever lands must keep the test able to catch a REAL 2x regression. The companion test_full_fog_update_under_1ms has 2x the headroom and has never flaked — evidence the budget, not the code, is the problem. Gate logs: /tmp/sr-run-godot.638004.log (0.549), /tmp/sr-run-godot.1028571.log (0.503).
|
||||
|
||||
FIXED (2026-07-26, direct on main): the test already used median-of-5 (T-1092''s earlier mitigation) and still flaked three times in one day (0.549/0.503/0.638) when concurrent cargo builds inflated all five samples together. Superseded with MINIMUM-of-7: the assertion asks whether the CODE meets the D-059 budget, and load can only inflate wall time, never deflate it — the minimum is the least-noise estimator of code capability while a real regression shifts the minimum too. Budget unchanged at 0.5ms (regression-catching power preserved). Verified 46/46. Closing.', 'backlog', 'medium', NULL, 'client', NULL, '2026-07-26 12:51:23.279', '2026-07-26 13:17:28.575', NULL, '6095c1a7e7642d50530d6389f5833a48', 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 ('06FSWYQA1XKYKEQ114CZKEDAFW', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Fog visibility-texture perf test flakes under machine load — 0.5ms budget has no headroom', 'test_fog_shader.gd::test_visibility_texture_update_performance asserts <0.5ms wall-clock and flaked TWICE on 2026-07-26 gate runs on the shared dev box (0.549ms during a heavily contended run racing live captures; 0.503ms — a 0.6% miss — during a lightly loaded run), passing 46/46 solo immediately after both times. A wall-clock assertion with sub-1% headroom cannot be trusted on a machine that runs cargo builds, godot imports, and live capture servers alongside gates — each flake costs a full gate re-run (~3-4 min plus diagnosis). Fix options, pick at pickup: (a) median-of-N (e.g. best-of-5 or median-of-9) sampling inside the test so a single scheduler hiccup cannot fail it — preferred, keeps the budget honest; (b) a modest budget raise with a comment recording the two incidents; (c) an environment-aware multiplier (worst option — hides regressions). Whatever lands must keep the test able to catch a REAL 2x regression. The companion test_full_fog_update_under_1ms has 2x the headroom and has never flaked — evidence the budget, not the code, is the problem. Gate logs: /tmp/sr-run-godot.638004.log (0.549), /tmp/sr-run-godot.1028571.log (0.503).
|
||||
|
||||
FIXED (2026-07-26, direct on main): the test already used median-of-5 (T-1092''s earlier mitigation) and still flaked three times in one day (0.549/0.503/0.638) when concurrent cargo builds inflated all five samples together. Superseded with MINIMUM-of-7: the assertion asks whether the CODE meets the D-059 budget, and load can only inflate wall time, never deflate it — the minimum is the least-noise estimator of code capability while a real regression shifts the minimum too. Budget unchanged at 0.5ms (regression-catching power preserved). Verified 46/46. Closing.', 'done', 'medium', NULL, 'client', NULL, '2026-07-26 12:51:23.279', '2026-07-26 13:17:35.789', NULL, '3f5c596053a4f615e65b192cb0887a3a', 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 ('06FSWCYA60VHFK44J09WW78FY0', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Synthetic-overflow settlement placement has no ocean-mask guard — can land in open water', 'Found during the PR #215 review fix round (T-1116, Tyre finding 3 follow-through): attractor_matching.rs::synthetic_attractor (the D-211 Phase-4 overflow path, PlainCenter at min-spacing) takes no heightmap/TerrainAnalysis argument — it derives positions by pure grid arithmetic (grid_h/2, grid_w/4, spacing-walk) with zero ocean_mask check. Every EXTRACTED attractor type is land-guaranteed (features.rs::extract_attractors filters !ocean_mask, D-209), but a synthetic-overflow settlement can be placed in open water — visible as an ocean-stranded settlement marker on the Atlas. T-1116''s routing relaxation degrades this case gracefully (surrogate anchor within ring 3, else an isolated unrouted node — never a panic or a water road), but the placement guarantee itself is the gap. Documented in road_graph.rs''s anchor comment and the D-210 amendment (PR #215). Fix shape: thread the ocean mask (or TerrainAnalysis) into synthetic_attractor and nudge/reject water candidates deterministically — mind D-211''s seed-derived-position promise (any nudge must be a pure function of seed + terrain, no RNG state change). Verify by hunting a real body whose synthetic overflow currently lands in water (none observed yet — GJ251c/GJ380c placements are all extracted attractors) or a synthetic fixture.
|
||||
|
||||
Implementation complete on branch ocean-guard-synthetic (worktree .worktrees/ocean-guard-synthetic).
|
||||
|
||||
COMPATIBILITY ANSWER (requirement 1): confirmed via a live scan of every real
|
||||
body with a heightmap (267 bodies, world seeds 42 and "yolo") plus a targeted
|
||||
before/after comparison — the guard does NOT move any currently-land synthetic
|
||||
placement. Direct evidence: 63 real land-arithmetic synthetic placements
|
||||
checked across all bodies, 63 unmoved (byte-identical position). Full
|
||||
`cargo test` also confirms every existing golden/determinism fixture
|
||||
(cascade_golden, golden_seed_determinism_regression, believability_determinism_and_golden,
|
||||
window_derivation_golden_regression) still passes unchanged.
|
||||
|
||||
FIX SHAPE: synthetic_attractor(placed, grid_w, grid_h, terrain: Option<&TerrainAnalysis>)
|
||||
-> Option<GeographicAttractor>. terrain=None reproduces the exact pre-T-1206
|
||||
behavior (existing callers/tests untouched). When Some: land-passthrough (an
|
||||
already-land arithmetic position returns unchanged), water-only-correction via
|
||||
a new nearest_land_cell() ring-walk (T-1116 nearest_passable_cell pattern at
|
||||
native resolution, row-major tie-break, column wrap / row clamp matching every
|
||||
other grid walk in the cascade), bounded by MAX_LAND_SEARCH_RING = 128 cells
|
||||
(sized empirically from the scan: real bodies'' nearest land to the arithmetic
|
||||
(0,0) default was as far as 125 cells away due to polar/high-latitude ocean
|
||||
bands — grid_h/2 = 128 is the natural ceiling).
|
||||
|
||||
DEGRADATION RULING (requirement 2): no land within MAX_LAND_SEARCH_RING ->
|
||||
skip the synthetic attractor entirely (match_cities'' Phase 4 loop does not
|
||||
push a placement for that city). Reported via the EXISTING Phase-5
|
||||
name-fulfillment warning ("atlas city was not placed") -- no new error path,
|
||||
never a panic, never a fabricated water position. At MAX_LAND_SEARCH_RING=128
|
||||
this degradation path does not trigger on any of the 267 scanned real bodies
|
||||
at either seed tested; it is exercised only in unit tests (tiny all-ocean
|
||||
grids).
|
||||
|
||||
VERIFICATION (requirement 3): wrote a scratch scan (deleted after use, tree
|
||||
clean) that ran the real cascade for all 267 bodies with a heightmap. Found a
|
||||
REAL, reproducible repro: 46 of 109 synthetic-overflow placements landed in
|
||||
open water pre-fix (world seed 42), e.g. GJ903c city_id=24375 at (0,0) which
|
||||
is ocean. Post-fix: 0 in water, 109 total synthetic placements preserved
|
||||
(same count as before -- no unexplained drops), confirmed at a second seed
|
||||
("yolo") too.
|
||||
|
||||
Files changed: server/src/atlas/attractor_matching.rs (synthetic_attractor +
|
||||
nearest_land_cell + match_cities signature + 9 new unit tests),
|
||||
server/src/atlas/cascade.rs (thread terrain_analysis through run_layer3),
|
||||
server/src/atlas/road_graph.rs (anchor comment updated to CLOSED),
|
||||
governance/decisions/architecture.md (D-210 amendment addendum, dated
|
||||
2026-07-26, validated via pql decisions validate --vault).
|
||||
|
||||
Full cargo test: 2221 passed, 0 failed (one gen_queue.rs worker-pool timing
|
||||
test flaked once under full-suite parallel load, confirmed unrelated and
|
||||
non-reproducible in isolation / module-scoped / --lib-only runs, and absent on
|
||||
a clean full-suite rerun). No golden fixtures changed.', 'in_progress', 'medium', 'dudley', 'server', NULL, '2026-07-26 11:33:42.064', '2026-07-26 13:23:55.539', NULL, '714a3e42c0b30bad10bb62c7b3416992', 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 ('06FSWCYA60VHFK44J09WW78FY0', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Synthetic-overflow settlement placement has no ocean-mask guard — can land in open water', 'Found during the PR #215 review fix round (T-1116, Tyre finding 3 follow-through): attractor_matching.rs::synthetic_attractor (the D-211 Phase-4 overflow path, PlainCenter at min-spacing) takes no heightmap/TerrainAnalysis argument — it derives positions by pure grid arithmetic (grid_h/2, grid_w/4, spacing-walk) with zero ocean_mask check. Every EXTRACTED attractor type is land-guaranteed (features.rs::extract_attractors filters !ocean_mask, D-209), but a synthetic-overflow settlement can be placed in open water — visible as an ocean-stranded settlement marker on the Atlas. T-1116''s routing relaxation degrades this case gracefully (surrogate anchor within ring 3, else an isolated unrouted node — never a panic or a water road), but the placement guarantee itself is the gap. Documented in road_graph.rs''s anchor comment and the D-210 amendment (PR #215). Fix shape: thread the ocean mask (or TerrainAnalysis) into synthetic_attractor and nudge/reject water candidates deterministically — mind D-211''s seed-derived-position promise (any nudge must be a pure function of seed + terrain, no RNG state change). Verify by hunting a real body whose synthetic overflow currently lands in water (none observed yet — GJ251c/GJ380c placements are all extracted attractors) or a synthetic fixture.
|
||||
|
||||
Implementation complete on branch ocean-guard-synthetic (worktree .worktrees/ocean-guard-synthetic).
|
||||
|
||||
COMPATIBILITY ANSWER (requirement 1): confirmed via a live scan of every real
|
||||
body with a heightmap (267 bodies, world seeds 42 and "yolo") plus a targeted
|
||||
before/after comparison — the guard does NOT move any currently-land synthetic
|
||||
placement. Direct evidence: 63 real land-arithmetic synthetic placements
|
||||
checked across all bodies, 63 unmoved (byte-identical position). Full
|
||||
`cargo test` also confirms every existing golden/determinism fixture
|
||||
(cascade_golden, golden_seed_determinism_regression, believability_determinism_and_golden,
|
||||
window_derivation_golden_regression) still passes unchanged.
|
||||
|
||||
FIX SHAPE: synthetic_attractor(placed, grid_w, grid_h, terrain: Option<&TerrainAnalysis>)
|
||||
-> Option<GeographicAttractor>. terrain=None reproduces the exact pre-T-1206
|
||||
behavior (existing callers/tests untouched). When Some: land-passthrough (an
|
||||
already-land arithmetic position returns unchanged), water-only-correction via
|
||||
a new nearest_land_cell() ring-walk (T-1116 nearest_passable_cell pattern at
|
||||
native resolution, row-major tie-break, column wrap / row clamp matching every
|
||||
other grid walk in the cascade), bounded by MAX_LAND_SEARCH_RING = 128 cells
|
||||
(sized empirically from the scan: real bodies'' nearest land to the arithmetic
|
||||
(0,0) default was as far as 125 cells away due to polar/high-latitude ocean
|
||||
bands — grid_h/2 = 128 is the natural ceiling).
|
||||
|
||||
DEGRADATION RULING (requirement 2): no land within MAX_LAND_SEARCH_RING ->
|
||||
skip the synthetic attractor entirely (match_cities'' Phase 4 loop does not
|
||||
push a placement for that city). Reported via the EXISTING Phase-5
|
||||
name-fulfillment warning ("atlas city was not placed") -- no new error path,
|
||||
never a panic, never a fabricated water position. At MAX_LAND_SEARCH_RING=128
|
||||
this degradation path does not trigger on any of the 267 scanned real bodies
|
||||
at either seed tested; it is exercised only in unit tests (tiny all-ocean
|
||||
grids).
|
||||
|
||||
VERIFICATION (requirement 3): wrote a scratch scan (deleted after use, tree
|
||||
clean) that ran the real cascade for all 267 bodies with a heightmap. Found a
|
||||
REAL, reproducible repro: 46 of 109 synthetic-overflow placements landed in
|
||||
open water pre-fix (world seed 42), e.g. GJ903c city_id=24375 at (0,0) which
|
||||
is ocean. Post-fix: 0 in water, 109 total synthetic placements preserved
|
||||
(same count as before -- no unexplained drops), confirmed at a second seed
|
||||
("yolo") too.
|
||||
|
||||
Files changed: server/src/atlas/attractor_matching.rs (synthetic_attractor +
|
||||
nearest_land_cell + match_cities signature + 9 new unit tests),
|
||||
server/src/atlas/cascade.rs (thread terrain_analysis through run_layer3),
|
||||
server/src/atlas/road_graph.rs (anchor comment updated to CLOSED),
|
||||
governance/decisions/architecture.md (D-210 amendment addendum, dated
|
||||
2026-07-26, validated via pql decisions validate --vault).
|
||||
|
||||
Full cargo test: 2221 passed, 0 failed (one gen_queue.rs worker-pool timing
|
||||
test flaked once under full-suite parallel load, confirmed unrelated and
|
||||
non-reproducible in isolation / module-scoped / --lib-only runs, and absent on
|
||||
a clean full-suite rerun). No golden fixtures changed.', 'review', 'medium', 'dudley', 'server', NULL, '2026-07-26 11:33:42.064', '2026-07-26 13:23:59.368', NULL, 'd6affd4f4bef642ca148ee93e6a80a84', 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 ('06FSP3GEJYTA966BSSR9354YC4', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'Atlas header readout ghosted in Quarter capture — rung-transition alpha artifact', 'Araminta''s T-1196 secondary observation (first GJ1002b captures, 2026-07-25): the ''ATLAS - CAPARICA / QUARTER'' header readout above the legend rendered faded/ghosted in the Quarter frame (scratchpad t1196/gj1002b_quarter.png), while the District and Region frames from the same session and capture path rendered it fully legible. Suspect a z-order or alpha transition state caught mid-render on rung change (the capture settles on StepCanvasRequest.is_pending() then waits 2 frames — a header fade animation may outlast the data settle). Reproduce with a T-1157-harness jump_to capture at any Quarter rung and inspect header alpha across post-settle frames; if it is a fade animation racing the capture, decide whether the fix is harness-side (settle until animations idle) or UI-side (no fade on rung change). Likely cosmetic; the only frame of the three with a legibility defect.', 'done', 'low', 'stig', 'client', NULL, '2026-07-25 20:53:37.559', '2026-07-26 13:24:15.122', NULL, 'babf8069d36e51be946855ab64334042', 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);
|
||||
|
||||
@@ -19,6 +19,7 @@ Format based on [Keep a Changelog](https://keepachangelog.com/).
|
||||
- **Internal: legacy map-window field retired** (T-1159) — the obsolete duplicate zoom-granularity field was removed from the map wire protocol and caches; no player-visible change
|
||||
|
||||
### Fixed
|
||||
- **The Atlas header is legible on any terrain** (T-1197) — the map screen's title readout was the one piece of implant chrome drawn without its backing panel, so its light text vanished against pale terrain (the ghosting spotted in the first Caparica captures). It now sits on the standard implant panel like everything else, and the legend below it positions itself from the header's real measured height instead of a hardcoded offset — with structural tests pinning the relationship so the panels can never silently overlap again
|
||||
- **The ground between buildings now has a defined character** (T-1098) — every tile in a built block that isn't a building or a street-margin now resolves to a real ground type (court, garden, plaza, open lawn, industrial operations surface, or genuine void) driven by how densely the block is built — previously that space carried no data at all. A conflict rule is enforced in code (building footprints always win), and a latent bug where certain degenerate lots vanished from the world data entirely is fixed. Data-only until Phase 5 draws the world; this is the second of the two walkability gaps closed this weekend
|
||||
- **Road networks now form on water-heavy worlds** (T-1116, D-210 amendment) — island and coastal bodies were generating settlements but zero roads: a coastal town's map cell could count as majority-water at routing resolution, and the pathfinder refused to start there at all. Routes now anchor to the nearest passable land cell at a small, honestly-priced access cost — never crossing water — so island worlds get their road networks back in the world data. Not yet visible on the Atlas: road drawing belongs to the deferred civilizational overlay wave (nature layer first, T-1207), and the map will show these roads when that wave lands
|
||||
- **The Atlas golden-capture harness works again** (T-1157) — the curated map-screenshot suite, dead since the stepped-Atlas rework, is rebuilt on the real navigation path (the same code a player's click runs): 13 golden shots across 7 bodies now cover every zoom rung, captured live and proven stable — byte-identical across consecutive runs and regardless of map-cache temperature. Every capture logs its full view transform so a future pixel mismatch is diagnosable instead of mysterious
|
||||
|
||||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 656 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
@@ -193,20 +193,24 @@ func test_visibility_texture_update_performance() -> void:
|
||||
fog_state.update_from_state()
|
||||
|
||||
# Measure the steady-state per-frame update (same positions, no resize).
|
||||
# Median of 5 runs: a single wall-clock sample flakes when the machine is
|
||||
# loaded (T-1092 — the pre-push gate shares the box with live sessions);
|
||||
# the median keeps the D-059 budget intent while absorbing load spikes.
|
||||
# MINIMUM of 7 runs (T-1210, superseding T-1092's median-of-5): the
|
||||
# assertion asks whether the CODE meets the D-059 budget, and wall-clock
|
||||
# load can only inflate a sample, never deflate it — so the minimum is
|
||||
# the least-noise estimator of code capability, while a real regression
|
||||
# shifts the minimum too. Median-of-5 still flaked three times on
|
||||
# 2026-07-26 when a cargo build saturated every core and inflated all
|
||||
# five samples together (0.549/0.503/0.638 vs the 0.5 budget).
|
||||
var samples: Array[float] = []
|
||||
for _i in range(5):
|
||||
for _i in range(7):
|
||||
var start := Time.get_ticks_usec()
|
||||
fog_state.update_from_state()
|
||||
samples.append((Time.get_ticks_usec() - start) / 1000.0)
|
||||
samples.sort()
|
||||
var median_ms: float = samples[2]
|
||||
var best_ms: float = samples[0]
|
||||
|
||||
# D-059: Visibility texture upload budget: 0.1ms
|
||||
# Allow margin for test environment overhead
|
||||
assert_that(median_ms).is_less(0.5)
|
||||
assert_that(best_ms).is_less(0.5)
|
||||
|
||||
GameState.visible_positions.clear()
|
||||
|
||||
|
||||
@@ -73,13 +73,30 @@ func test_refresh_reflects_the_active_overlay_toggle() -> void:
|
||||
).is_greater(rows_before)
|
||||
|
||||
|
||||
func test_reposition_sets_a_fixed_panel_margin_position() -> void:
|
||||
## T-1197 PR #217 review (both reviewers, pixel-proven): this used to assert
|
||||
## a hardcoded Vector2(PANEL_MARGIN, 60.0) — the same "60.0" constant whose
|
||||
## drift out of sync with the header panel's REAL grown footprint (once
|
||||
## step_canvas_viewer.gd wrapped the header in its own ImplantPanel) caused
|
||||
## the two panels to fuse into one double-height box. The fix removed that
|
||||
## hardcoded Y from production code (step_canvas_legend.gd's reposition() now
|
||||
## derives it from StepCanvasViewer.get_header_panel_bottom_y() +
|
||||
## get_header_legend_gap_px()) — this test must assert the SAME derived
|
||||
## relationship, not a second copy of the old magic number, or it would go on
|
||||
## silently enforcing the exact drift-prone shape production code just
|
||||
## stopped doing (test_step_canvas_viewer.gd's
|
||||
## test_legend_panel_never_overlaps_the_header_panel_vertically/
|
||||
## test_legend_panel_leaves_a_real_gap_below_the_header_panel are the fuller
|
||||
## non-overlap/gap proof against the REAL viewer-owned legend; this one stays
|
||||
## a narrow smoke check on a standalone legend instance, matching this file's
|
||||
## own "smoke coverage" scope note at the top).
|
||||
func test_reposition_derives_y_from_the_headers_measured_bottom_plus_gap() -> void:
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
var legend = LegendScript.new(v)
|
||||
auto_free(legend)
|
||||
legend.reposition()
|
||||
assert_that(legend.position).is_equal(Vector2(LegendScript.PANEL_MARGIN, 60.0))
|
||||
var expected_y: float = v.get_header_panel_bottom_y() + v.get_header_legend_gap_px()
|
||||
assert_that(legend.position).is_equal(Vector2(LegendScript.PANEL_MARGIN, expected_y))
|
||||
|
||||
|
||||
## T-1192 review fix: RESERVED_COLUMN_PX is now a direct read of
|
||||
|
||||
@@ -837,3 +837,81 @@ func test_get_current_canvas_summary_counts_match_a_fixture_canvas() -> void:
|
||||
var by_class: Dictionary = summary.get("course_count_by_class", {})
|
||||
assert_int(int(by_class.get(0, 0))).is_equal(2)
|
||||
assert_int(int(by_class.get(2, 0))).is_equal(1)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# T-1197 PR #217 review (Hoshe): header-panel-vs-legend-panel vertical
|
||||
# non-overlap — the exact regression this review round caught. Mirrors the
|
||||
# T-1192 precedent above (test_global_canvas_left_edge_never_overlaps_the_legend_column,
|
||||
# line ~674): a geometric non-overlap invariant against the REAL viewer
|
||||
# wiring, not a hand-computed expected pixel value that could silently drift
|
||||
# out of sync with the production layout the same way the old hardcoded
|
||||
# Vector2(PANEL_MARGIN, 60.0) drifted out of sync with the header's real
|
||||
# grown footprint.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
## The screen header panel and the legend panel must never vertically
|
||||
## overlap: the legend's TOP edge (position.y) must be at or below the
|
||||
## header's BOTTOM edge (position.y + size.y). Before the T-1197 PR #217 fix,
|
||||
## step_canvas_legend.gd's reposition() hardcoded Y=60.0 — a constant tuned
|
||||
## for the OLD bare-ImplantHeader footprint — so once the header grew its own
|
||||
## ImplantPanel wrapper (border + content margins), the legend's fixed Y sat
|
||||
## INSIDE the header panel's new, taller footprint: the two fused into one
|
||||
## unbroken double-height box with zero terrain gap between them (pixel-
|
||||
## proven independently by both PR #217 reviewers). This test pins the
|
||||
## invariant directly against the real _screen_header_panel/_legend_panel
|
||||
## Controls the production layout builds, not a copy of the geometry math.
|
||||
func test_legend_panel_never_overlaps_the_header_panel_vertically() -> void:
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
|
||||
# Both panels are manually positioned (implant_panel.gd's own doc: "not
|
||||
# itself inside a parent Container, so nothing else forces a re-measure"),
|
||||
# and reset_to_content_size()/reposition() are deferred — award one idle
|
||||
# frame so the REAL settled sizes are in place before asserting, exactly
|
||||
# like the deferred-resize idiom both panels already rely on in production
|
||||
# (see _build_screen_header()'s own call to reset_to_content_size(), and
|
||||
# _ready()'s own deferred reposition() call added alongside this test).
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
|
||||
var header_top: float = v._screen_header_panel.position.y
|
||||
var header_bottom: float = header_top + v._screen_header_panel.size.y
|
||||
var legend_top: float = v._legend_panel.position.y
|
||||
|
||||
assert_float(legend_top).override_failure_message(
|
||||
(
|
||||
"the legend panel's top edge (y=%.1f) must be AT OR BELOW the header"
|
||||
+ " panel's measured bottom edge (y=%.1f) — a smaller value means the"
|
||||
+ " two panels overlap/fuse into one box, the exact PR #217 regression"
|
||||
)
|
||||
% [legend_top, header_bottom]
|
||||
).is_greater_equal(header_bottom - 0.01)
|
||||
|
||||
|
||||
## The gap must be a REAL, visible gap — not just "touching at exactly the
|
||||
## same pixel" (which would still satisfy >= but reads as fused on screen).
|
||||
## Pins the fixed HEADER_LEGEND_GAP_PX constant is actually being applied,
|
||||
## not merely that overlap happens to be avoided by coincidence of content
|
||||
## size on this particular test body.
|
||||
func test_legend_panel_leaves_a_real_gap_below_the_header_panel() -> void:
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
|
||||
var header_bottom: float = v._screen_header_panel.position.y + v._screen_header_panel.size.y
|
||||
var legend_top: float = v._legend_panel.position.y
|
||||
var gap: float = legend_top - header_bottom
|
||||
|
||||
assert_float(gap).override_failure_message(
|
||||
(
|
||||
"expected a visible gap of at least %.1fpx between the header panel's"
|
||||
+ " bottom (y=%.1f) and the legend panel's top (y=%.1f), got %.1fpx —"
|
||||
+ " panels that merely touch still read as one fused box on screen"
|
||||
)
|
||||
% [v.get_header_legend_gap_px(), header_bottom, legend_top, gap]
|
||||
).is_greater_equal(v.get_header_legend_gap_px() - 0.01)
|
||||
|
||||
@@ -52,8 +52,21 @@ func _init(viewer_ref = null) -> void:
|
||||
visible = false
|
||||
|
||||
|
||||
## T-1197 PR #217 review (both reviewers, pixel-proven): this used to hardcode
|
||||
## Vector2(PANEL_MARGIN, 60.0) — a constant tuned for the bare-ImplantHeader
|
||||
## era, before T-1197 wrapped the header in its own ImplantPanel (border +
|
||||
## content margins grew its real footprint). The two panels fused into one
|
||||
## unbroken double-height box with zero terrain gap. Now DERIVED from the
|
||||
## header panel's own MEASURED height (StepCanvasViewer.get_header_panel_bottom_y(),
|
||||
## which reads `_screen_header_panel.size.y` after that panel's own
|
||||
## reset_to_content_size() has settled) plus a fixed gap constant — never a
|
||||
## second hardcoded Y that can drift out of sync with the header again.
|
||||
func reposition() -> void:
|
||||
position = Vector2(PANEL_MARGIN, 60.0)
|
||||
if _viewer == null:
|
||||
position = Vector2(PANEL_MARGIN, 60.0) # pre-viewer fallback, unreachable in practice (_init always takes a viewer)
|
||||
return
|
||||
var header_bottom_y: float = _viewer.get_header_panel_bottom_y()
|
||||
position = Vector2(PANEL_MARGIN, header_bottom_y + _viewer.get_header_legend_gap_px())
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
|
||||
@@ -145,6 +145,7 @@ var _canvas: Node2D = null
|
||||
var _terrain_layer: StepCanvasTerrainLayer = null
|
||||
var _annotation_layer: StepCanvasAnnotationLayer = null
|
||||
var _screen_header: ImplantHeader = null
|
||||
var _screen_header_panel = null # ImplantPanel — T-1197 backing scrim (untyped: no class_name cycle needed)
|
||||
var _overlay_bar = null
|
||||
var _legend_panel = null
|
||||
var _request = null # StepCanvasRequest
|
||||
@@ -193,6 +194,19 @@ func _ready() -> void:
|
||||
_build_overlay_bar()
|
||||
_build_legend_panel()
|
||||
|
||||
# T-1197 PR #217 review: the legend's FIRST reposition() (called
|
||||
# synchronously above, inside _build_legend_panel()'s refresh()) reads
|
||||
# _screen_header_panel.size.y before that panel's own deferred
|
||||
# reset_to_content_size() has fired (call_deferred runs at end-of-frame,
|
||||
# per implant_panel.gd's own doc) — so it can still see a pre-layout
|
||||
# height on the very first frame. Deferring ONE more reposition() call
|
||||
# after this _ready() returns guarantees it re-reads the header panel's
|
||||
# real settled height at least once, the same "defer past the transient
|
||||
# wrong value" idiom implant_panel.gd already uses for exactly this class
|
||||
# of problem.
|
||||
if _legend_panel:
|
||||
_legend_panel.reposition.call_deferred()
|
||||
|
||||
_disk_sweep_timer = Timer.new()
|
||||
_disk_sweep_timer.name = "DiskSweepTimer"
|
||||
_disk_sweep_timer.wait_time = DISK_SWEEP_INTERVAL_SEC
|
||||
@@ -691,14 +705,64 @@ func _apply_transform() -> void:
|
||||
# =============================================================================
|
||||
|
||||
|
||||
## T-1197: the header used to be a bare ImplantHeader added straight to this
|
||||
## Control, drawn directly over the terrain canvas with no backing — every
|
||||
## OTHER implant chrome element (the legend below it) gets its scrim from
|
||||
## ImplantPanel's `panel_bg` StyleBoxFlat, but a lone ImplantHeader has no
|
||||
## panel of its own (see implant_header.gd — just two Labels). Its fixed
|
||||
## opaque text color reads fine against the near-black viewer background or
|
||||
## dark ocean terrain (the common case at rung 0's letterboxed corner and most
|
||||
## fixed-rung captures), but against pale/light terrain — Quarter's white
|
||||
## upland scatter, District's olive/tan dry terrain — the contrast collapses
|
||||
## to the point of unreadability (Araminta's T-1196 observation,
|
||||
## scratchpad t1196/gj1002b_quarter.png; reproduced baked into the committed
|
||||
## golden atlas_GJ820Bc_District.png). This is a static contrast defect, not a
|
||||
## timing race — there is no fade/tween anywhere in this cluster (confirmed by
|
||||
## reading every script under client/ui/implant), so no harness settle change
|
||||
## could fix it. Wrapping in an ImplantPanel (same component every other
|
||||
## header-bearing screen in the implant UI already uses — index_screen.gd,
|
||||
## planet_screen.gd, system_screen.gd, reach_screen.gd, kind_menu_screen.gd,
|
||||
## detail_screen.gd, overview_screen.gd, character_creation.gd, and the
|
||||
## legend right below THIS header — step_canvas_viewer.gd's bare add_child()
|
||||
## was the only outlier) gives the header the same opaque scrim as every
|
||||
## sibling chrome element, guaranteeing contrast regardless of what terrain
|
||||
## renders underneath.
|
||||
## T-1197 PR #217 review (both reviewers, pixel-proven): the ImplantPanel wrap
|
||||
## below grew the header's on-screen footprint (border + content_margin_* from
|
||||
## the theme's panel stylebox) beyond the bare ImplantHeader's old, smaller
|
||||
## size — but step_canvas_legend.gd's own reposition() still hardcoded its Y
|
||||
## at PANEL_MARGIN + 60.0, a constant tuned for the OLD bare-header height.
|
||||
## Result: the two panels fused into one unbroken double-height box, zero
|
||||
## terrain gap, in every capture and all 13 goldens. Fixed via
|
||||
## HEADER_PANEL_TOP_Y/HEADER_LEGEND_GAP_PX below + get_header_panel_bottom_y()
|
||||
## — the legend now DERIVES its Y from the header panel's own MEASURED height
|
||||
## (never a second hardcoded constant that would drift again the next time
|
||||
## either panel's content changes shape).
|
||||
const HEADER_PANEL_TOP_Y: float = 16.0
|
||||
const HEADER_LEGEND_GAP_PX: float = 12.0
|
||||
|
||||
|
||||
func _build_screen_header() -> void:
|
||||
var PanelScript := load("res://ui/implant/implant_panel.gd")
|
||||
_screen_header_panel = PanelScript.new()
|
||||
_screen_header_panel.name = "ScreenHeaderPanel"
|
||||
_screen_header_panel.position = Vector2(PANEL_MARGIN, HEADER_PANEL_TOP_Y)
|
||||
_screen_header_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_screen_header_panel.custom_minimum_size.x = 320.0
|
||||
_screen_header_panel.theme_resource = _implant_theme # set BEFORE add_child(), matching _build_legend_panel()'s own ordering
|
||||
add_child(_screen_header_panel)
|
||||
|
||||
_screen_header = ImplantHeader.new()
|
||||
_screen_header.position = Vector2(PANEL_MARGIN, 16.0)
|
||||
_screen_header.custom_minimum_size.x = 320.0
|
||||
_screen_header.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_screen_header)
|
||||
if _implant_theme:
|
||||
_screen_header.apply_implant_theme(_implant_theme)
|
||||
_screen_header_panel.add_component(_screen_header)
|
||||
|
||||
# Same "manually positioned, not inside a parent Container, so nothing else
|
||||
# forces a re-measure" situation implant_panel.gd's own
|
||||
# reset_to_content_size() doc describes for the legend — this panel needs
|
||||
# the identical deferred re-measure so get_header_panel_bottom_y() reads a
|
||||
# settled `size.y`, not a transient pre-layout value.
|
||||
_screen_header_panel.reset_to_content_size()
|
||||
|
||||
|
||||
func _refresh_screen_header() -> void:
|
||||
@@ -709,6 +773,35 @@ func _refresh_screen_header() -> void:
|
||||
var title := "ATLAS — %s" % name_label.to_upper()
|
||||
var subtitle := "%s · %.3f km/gridunit" % [_held_rung.to_upper(), spacing_km]
|
||||
_screen_header.set_content(title, subtitle)
|
||||
# Title/subtitle text length never changes the panel's HEIGHT (autowrap is
|
||||
# word-smart but both lines are short, fixed-shape content — only the
|
||||
# legend's row COUNT genuinely varies), but re-measuring here is free
|
||||
# insurance against exactly the class of drift this review round caught,
|
||||
# and keeps the legend's derived Y correct even if header content ever
|
||||
# grows a line.
|
||||
_screen_header_panel.reset_to_content_size()
|
||||
if _legend_panel:
|
||||
_legend_panel.reposition()
|
||||
|
||||
|
||||
## T-1197 PR #217 review: the ONE place step_canvas_legend.gd's reposition()
|
||||
## reads the header panel's footprint from — never a second hardcoded
|
||||
## constant. Falls back to HEADER_PANEL_TOP_Y-only (as if the header panel had
|
||||
## zero height) before the header panel exists, mirroring
|
||||
## _centered_view_offset()'s own "nothing to measure yet" pre-arrival pattern.
|
||||
func get_header_panel_bottom_y() -> float:
|
||||
if _screen_header_panel == null:
|
||||
return HEADER_PANEL_TOP_Y
|
||||
return _screen_header_panel.position.y + _screen_header_panel.size.y
|
||||
|
||||
|
||||
## Plain-method accessor for HEADER_LEGEND_GAP_PX (not a bare const read) —
|
||||
## step_canvas_legend.gd holds `_viewer` UNTYPED (its own doc: "avoid cyclic
|
||||
## ref"), and this cluster's convention is a method call across that boundary,
|
||||
## matching get_header_panel_bottom_y()/get_held_rung()/is_overlay_visible()
|
||||
## rather than relying on GDScript's duck-typed const-through-Variant access.
|
||||
func get_header_legend_gap_px() -> float:
|
||||
return HEADER_LEGEND_GAP_PX
|
||||
|
||||
|
||||
func _build_overlay_bar() -> void:
|
||||
|
||||