Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
310 lines
58 KiB
SQL
310 lines
58 KiB
SQL
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 ('06FXF1VDVQDQ8EFGTXX787M90R', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Atlas rivers vanish at native resolution — 375 courses arrive, 0 drawn', 'Found 2026-08-06 when the visual capture resolution was raised from 960x540 to the native 3440x1440. On Ferrath (GJ820Bc) Global the wire delivers 375 river courses and the annotation layer draws NONE: ''courses=375 drawn=0'' in the view-transform readout. At 960x540 the SAME build drew them as visible strokes, so this is resolution-dependent, not a river-generation failure -- the courses are present and correct on the wire. Suspect the D-261 cull (drop a course below 15 px of on-screen length, 3x the 5 px stroke) or the water-truncation step: T-1237 computes the clipped/culled polylines ONCE on canvas adoption (set_frame) rather than per draw, so if adoption runs against a pre-layout or stale viewport the screen-length measurement is wrong for every course at once -- which matches an all-or-nothing drawn=0 rather than a partial cull. Note the scale moved the RIGHT way for visibility (46.792 km/gridunit at 960x540 vs 35.267 at 3440x1440 -- more gridunits across the body, so a river spans MORE of them), which makes a legitimate cull an unlikely explanation. Reproduce: tests/run-visual --screenshot atlas_GJ820Bc_Global and read the drawn= count.', 'backlog', 'high', NULL, 'client', 'D-261', '2026-08-06 14:43:24.765', '2026-08-06 14:43:24.765', NULL, '9597ed74aa67fb7ebeef78f60374d89c', 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;
|
|
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 ('06FT0TX2W0BA10PRR7NMJ2362M', 'epic', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Rung-0.5 expanded layer — whole-body hydrology + biome-from-orbit base (D-258)', 'D-258: the Atlas cascade gains a single expanded layer (''rung 0.5''), generated once per body from the rung-0 input pair (heightmap.png + reliefmap.png, never displayed), and every zoom tier below it (Region/District/Quarter/Block/Chunk) derives from that layer instead of independently re-deriving from the source files. Hydrology (drainage, course routing, lake fill) resolves ONCE on rung 0.5 and nowhere else -- it is a whole-body computation, not derivable per-window. Global displays rung 0.5 directly (biome-from-orbit, not a photograph). Same-session amendment: lake shorelines run the same shore-morphology code as ocean shorelines (coastline warp applied to both surfaces in the single rung-0.5 pass; shore-morphology gates key on proximity to water, not to ocean; sea-flavored types like TidalFlat/Estuarine-vs-Delta separated by tidal energy, a derived quantity, never by an is-it-the-ocean switch; salinity excluded from morphology entirely). This is a named, principled carve-out from D-227 derive-don''t-store: a whole-body flow solve is not locally computable, so it cannot be re-derived per window at any price -- storage here buys correctness, not convenience. Not yet ticketed prior to this epic (D-258''s own Implementation note). See governance/decisions/architecture.md#d-258.
|
|
|
|
RE-SCOPE REQUIRED BEFORE ANY CHILD STARTS (2026-07-27, D-258 amendment). The structural rationale for this epic was disproven on evidence the day after it was written. D-258 claimed hydrology ''was not derivable at all'' per-window; in fact layer1.rs::run_layer1_with_moisture already solves drainage AND settled-equilibrium hydrology once per body, folds the filled surface into TerrainAnalysis, and every rung bilinearly samples it -- the code''s own comment calls it ''a coarse continuous primitive computed once, sampled fresh at every rung, never re-solved'' (mechanism B, D-255(f)). Compute-once-sample-everywhere already exists. What actually made Global look flat was serve_step_canvas_request zeroing Global''s wire extent (a pre-extent-inversion sentinel), producing a 2x1 canvas -- fixed 2026-07-27; once sized correctly Global reads as a world with no hydrology work at all. Rivers at Global were measured as negligible: 375 courses present, 458 of 518,400 pixels different vs courses-off, because at ~39.7 km/gridunit most courses are shorter than one gridunit. SURVIVES: biome un-summarisation (reliefmap as plurality), composition-on-descent, the conservation invariant, and the lake-shore amendment. WEAKENED: the stored expanded layer and its D-227 carve-out. The live question is no longer ''what does rung 0.5 cost'' but ''does biome un-summarisation need a stored layer at all, or does it ride the existing sample-fresh-at-every-rung mechanism''. Re-scope this epic and T-1212 against that question first.
|
|
|
|
---
|
|
RE-SCOPED 2026-08-06 (Jeroen''s call) — the epic is unblocked, and its question has changed again.
|
|
|
|
EVIDENCE. A descent ladder was captured on Ferrath (GJ820Bc) at native 3440x1440,
|
|
anchored on land, one shot per rung, no overlays (scenarios atlas_GJ820Bc_land_*
|
|
in tests/visual.json). Ferrath''s heightmap is 1024x512 over a 38,089 km
|
|
circumference = 37.2 km per source pixel. Against that:
|
|
|
|
Global 35.267 km/gridunit ~1:1 with the source pixel a real map
|
|
Region 0.379 km/gridunit 98x finer flat wash
|
|
District 0.0038 km/gridunit 9,800x finer flat wash
|
|
Quarter/Block/Chunk finer still flat
|
|
|
|
The Atlas is legible exactly where it SAMPLES the heightmap and flat everywhere
|
|
it must INVENT. Every rung below Global is a single uniform colour field with
|
|
dither noise; the courses/settlements readout is 0 at all of them.
|
|
|
|
WHAT THIS SETTLES. D-258''s amendment (5) framed the live question as "does biome
|
|
un-summarisation need a stored layer at all, or does it ride the existing
|
|
sample-fresh-at-every-rung mechanism". The ladder answers a PRIOR question:
|
|
un-summarisation is not happening in ANY form. There is no expansion to decide
|
|
the storage policy for. Storage is therefore a downstream optimisation, not the
|
|
decision this epic turns on.
|
|
|
|
NEW SCOPE. Build the expansion first as a PURE FUNCTION, following the mechanism
|
|
that already exists rather than inventing a second one: layer1.rs''s
|
|
compute-once-sample-everywhere primitive (D-255(f) mechanism B) is the model, and
|
|
the amendment established hydrology already works that way. Measure it. Add a
|
|
stored layer ONLY if the measured cost forces it — and if it does, that is when
|
|
the D-227 carve-out argument gets made, on numbers rather than on the disproven
|
|
"not locally computable" claim.
|
|
|
|
Order: T-1213 (the un-summarisation generator) is now the first child and is
|
|
UNBLOCKED. T-1214 (hydrology onto rung 0.5) stays parked — the amendment showed
|
|
that solve already runs once per body and does not need to move. T-1216 (Global
|
|
biome-from-orbit palette) and T-1217 (lake shores through the shared shore path)
|
|
survive unchanged; both were independent of the storage question.
|
|
|
|
T-1212 does not gate this any more (see its own note).', 'backlog', 'high', NULL, NULL, 'D-258', '2026-07-26 21:53:56.448', '2026-08-06 15:59:03.757', NULL, '923892406469abdd3fb6ee9041ae1cbf', 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;
|
|
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 ('06FT0TYBD74TQNVKTJMKA8D9KM', 'task', '06FT0TX2W0BA10PRR7NMJ2362M', 'Measure rung-0.5 cost/size BEFORE implementing (compute + disk, whole-body)', 'MUST run and be reviewed before any other T-1211 child starts -- this measurement could reshape the rung-0.5 design, per the pair session''s explicit sequencing note. Measure, at minimum: (1) per-body derive cost for the expanded layer at a resolution sized so a whole body draws at 2 screen px per gridunit on a large display (the D-258 sizing rule) -- both single-body cold-derive time and the full ~271-body population sum; (2) per-body and total disk footprint if the layer is cached/stored (rung 0.5 is a named D-227 carve-out -- storage is deliberate, but its size must be known, not assumed); (3) whole-body hydrology solve cost on this layer (drainage + course routing + lake fill) at the same resolution, since D-258 requires this to run exactly once per body and nowhere else. Reference point: D-255''s own rung-0 always-keep tier estimate went from ~8.85 MB (measured against a stale ~18K-cell/body figure) to an estimated 226 MB (1080p) / ~900 MB (4K) once the extent inversion made Global viewport-sized -- D-255 amendment item 6 explicitly says ''re-measure against rung 0.5, not against this record.'' This ticket is that re-measurement. Report back to the team before T-1211''s other children are started; if the numbers are structurally bad (e.g. rung 0.5 at the sizing D-258 specifies costs an order of magnitude more than the old rung-0 model), that is grounds to revisit the resolution target with Jeroen before writing generator code. See governance/decisions/architecture.md#d-258 (rationale + Implementation note).
|
|
|
|
SCOPE INVALIDATED 2026-07-27 (see T-1211 and the D-258 amendment). This ticket was written to measure the cost of moving a whole-body hydrology solve onto rung 0.5. That solve does not need to move -- it already runs once per body in layer1.rs and is sampled fresh at every rung. Do NOT run this measurement as written; it would price work that is not required. If a measurement is still wanted after T-1211 is re-scoped, the question is narrower: what does BIOME un-summarisation cost, and does it need storing at all.
|
|
|
|
---
|
|
RETIRED AS A GATE 2026-08-06. This no longer blocks T-1213 or the T-1211 epic.
|
|
|
|
It was already SCOPE INVALIDATED (2026-07-27) for pricing a hydrology move that
|
|
does not need to happen. The 2026-08-06 descent ladder (see T-1211) closes the
|
|
remaining reason to keep it as a gate: every rung below Global renders a flat
|
|
wash, so biome un-summarisation is not happening at all. There is no artefact
|
|
whose cost or disk footprint can be measured, because none is produced.
|
|
|
|
The measurement question survives, but it is now DOWNSTREAM of T-1213 rather
|
|
than upstream of it: once the un-summarisation exists as a pure function, measure
|
|
THAT, and only then decide whether a stored layer is warranted. Re-scope this
|
|
ticket to that measurement when T-1213 lands, or close it and let T-1213 carry
|
|
its own measurement step.', 'backlog', 'high', NULL, NULL, 'D-258', '2026-07-26 21:54:06.825', '2026-08-06 15:59:12.825', NULL, 'e0d88ae134ccd57737d8eab2627e2b88', 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;
|
|
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 ('06FT0TZC9MJV6KZTSRVRYQ327M', 'story', '06FT0TX2W0BA10PRR7NMJ2362M', 'Rung-0.5 expanded-layer generator (deterministic un-summarisation of rung 0)', 'Build the deterministic expansion from rung 0 (heightmap.png 1024x512 16-bit elevation + reliefmap.png 1024x512 colour biome) to rung 0.5, sized per D-258''s 2-px-per-gridunit-at-large-display rule. Same body + same seed must produce the same layer every time (byte-identical, per D-227''s determinism discipline extended by this carve-out). The reliefmap is a PLURALITY, not ground truth: each reliefmap cell already voted-and-discarded the dominant biome across ~38 km of ground, so this generator un-summarises it -- it does not upscale/interpolate it. Three binding invariants from D-258: (1) biome edges are gradients, never lines -- transitions blend so no boundary falls on a rung-0 cell edge (the D-243 climate edge-fuzz rule applied to biome); (2) descending the ladder reveals COMPOSITION not sharpness -- a cell reading ''forest'' globally must be able to contain clearings/marsh/rock/scrub the vote suppressed, emerging deterministically as the ladder descends; (3) CONSERVATIVE invention is the binding acceptance gate -- downsampling rung 0.5 must reproduce the rung-0 summary it came from (a forest cell may gain marsh pockets but must still read as forest from orbit). Blocked on T-1212 (cost/size measurement) landing first. Depends on: nothing else in this epic to start scaffolding, but hydrology (sibling ticket) and this generator are tightly coupled -- coordinate sequencing with whoever picks up hydrology. See governance/decisions/architecture.md#d-258.
|
|
|
|
---
|
|
UNBLOCKED 2026-08-06 (T-1211 re-scope, Jeroen''s call). The T-1212 blocker edge is
|
|
removed: that measurement priced a hydrology move that is not happening, and the
|
|
descent ladder showed there is no expansion artefact to measure yet anyway.
|
|
|
|
BUILD IT AS A PURE FUNCTION FIRST, not as a stored layer. The stored-layer half of
|
|
D-258 was materially weakened by its own 2026-07-27 amendment (the "not locally
|
|
computable" argument for the D-227 carve-out does not hold, because the whole-body
|
|
solve it cited already runs once per body in layer1.rs and is sampled at every
|
|
rung). So follow the mechanism that exists — D-255(f) mechanism B,
|
|
compute-once-sample-everywhere — measure it, and only argue for storage on those
|
|
numbers. Do NOT open with a cache.
|
|
|
|
WHAT "FLAT" MEANS CONCRETELY, so the fix has a target. Ferrath''s heightmap is
|
|
1024x512 over a 38,089 km circumference: 37.2 km per source pixel. Global draws at
|
|
35.267 km/gridunit, roughly 1:1 with the source, and reads as a real map. Region
|
|
draws at 0.379 km/gridunit — 98x finer than any stored datum — and is a single
|
|
uniform colour with dither. District is 0.0038 km/gridunit, ~9,800x finer, also
|
|
uniform. So the acceptance bar is not subtle: at Region, ~98 gridunits across a
|
|
single source pixel must carry visible, deterministic, non-repeating composition
|
|
that still downsamples back to that pixel''s summary (D-258''s conservation
|
|
invariant, the binding gate).
|
|
|
|
VERIFY BY CAPTURE, NOT BY REASONING. The scenarios exist: atlas_GJ820Bc_land_*
|
|
(Region/District/Quarter/Block/Chunk, one land-anchored world point, no overlays)
|
|
in tests/visual.json. Captures run offscreen under gamescope at native 3440x1440
|
|
via tests/run-visual --screenshot <name> — they do not steal the desktop. Re-shoot
|
|
the ladder and look at it; a green unit test proves nothing here.
|
|
|
|
Note the ladder currently also reports courses=0 at every rung below Global —
|
|
rivers vanish on descent. That is tracked separately as T-1239 and is NOT this
|
|
ticket''s scope, but it will be visible in the same captures, so do not mistake it
|
|
for a failure of the un-summarisation work.', 'backlog', 'high', NULL, NULL, 'D-258', '2026-07-26 21:54:15.245', '2026-08-06 15:59:39.623', NULL, 'd9bfabaf6b4f1751a9261995f84c0b7b', 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;
|
|
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 ('06FT0TZC9MJV6KZTSRVRYQ327M', 'story', '06FT0TX2W0BA10PRR7NMJ2362M', 'Rung-0.5 expanded-layer generator (deterministic un-summarisation of rung 0)', 'Build the deterministic expansion from rung 0 (heightmap.png 1024x512 16-bit elevation + reliefmap.png 1024x512 colour biome) to rung 0.5, sized per D-258''s 2-px-per-gridunit-at-large-display rule. Same body + same seed must produce the same layer every time (byte-identical, per D-227''s determinism discipline extended by this carve-out). The reliefmap is a PLURALITY, not ground truth: each reliefmap cell already voted-and-discarded the dominant biome across ~38 km of ground, so this generator un-summarises it -- it does not upscale/interpolate it. Three binding invariants from D-258: (1) biome edges are gradients, never lines -- transitions blend so no boundary falls on a rung-0 cell edge (the D-243 climate edge-fuzz rule applied to biome); (2) descending the ladder reveals COMPOSITION not sharpness -- a cell reading ''forest'' globally must be able to contain clearings/marsh/rock/scrub the vote suppressed, emerging deterministically as the ladder descends; (3) CONSERVATIVE invention is the binding acceptance gate -- downsampling rung 0.5 must reproduce the rung-0 summary it came from (a forest cell may gain marsh pockets but must still read as forest from orbit). Blocked on T-1212 (cost/size measurement) landing first. Depends on: nothing else in this epic to start scaffolding, but hydrology (sibling ticket) and this generator are tightly coupled -- coordinate sequencing with whoever picks up hydrology. See governance/decisions/architecture.md#d-258.
|
|
|
|
---
|
|
UNBLOCKED 2026-08-06 (T-1211 re-scope, Jeroen''s call). The T-1212 blocker edge is
|
|
removed: that measurement priced a hydrology move that is not happening, and the
|
|
descent ladder showed there is no expansion artefact to measure yet anyway.
|
|
|
|
BUILD IT AS A PURE FUNCTION FIRST, not as a stored layer. The stored-layer half of
|
|
D-258 was materially weakened by its own 2026-07-27 amendment (the "not locally
|
|
computable" argument for the D-227 carve-out does not hold, because the whole-body
|
|
solve it cited already runs once per body in layer1.rs and is sampled at every
|
|
rung). So follow the mechanism that exists — D-255(f) mechanism B,
|
|
compute-once-sample-everywhere — measure it, and only argue for storage on those
|
|
numbers. Do NOT open with a cache.
|
|
|
|
WHAT "FLAT" MEANS CONCRETELY, so the fix has a target. Ferrath''s heightmap is
|
|
1024x512 over a 38,089 km circumference: 37.2 km per source pixel. Global draws at
|
|
35.267 km/gridunit, roughly 1:1 with the source, and reads as a real map. Region
|
|
draws at 0.379 km/gridunit — 98x finer than any stored datum — and is a single
|
|
uniform colour with dither. District is 0.0038 km/gridunit, ~9,800x finer, also
|
|
uniform. So the acceptance bar is not subtle: at Region, ~98 gridunits across a
|
|
single source pixel must carry visible, deterministic, non-repeating composition
|
|
that still downsamples back to that pixel''s summary (D-258''s conservation
|
|
invariant, the binding gate).
|
|
|
|
VERIFY BY CAPTURE, NOT BY REASONING. The scenarios exist: atlas_GJ820Bc_land_*
|
|
(Region/District/Quarter/Block/Chunk, one land-anchored world point, no overlays)
|
|
in tests/visual.json. Captures run offscreen under gamescope at native 3440x1440
|
|
via tests/run-visual --screenshot <name> — they do not steal the desktop. Re-shoot
|
|
the ladder and look at it; a green unit test proves nothing here.
|
|
|
|
Note the ladder currently also reports courses=0 at every rung below Global —
|
|
rivers vanish on descent. That is tracked separately as T-1239 and is NOT this
|
|
ticket''s scope, but it will be visible in the same captures, so do not mistake it
|
|
for a failure of the un-summarisation work.', 'in_progress', 'high', NULL, NULL, 'D-258', '2026-07-26 21:54:15.245', '2026-08-07 11:36:05.551', NULL, 'a3edd5306633b6247c1cd67bf700db69', 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;
|
|
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 ('06FT0TZC9MJV6KZTSRVRYQ327M', 'story', '06FT0TX2W0BA10PRR7NMJ2362M', 'Rung-0.5 expanded-layer generator (deterministic un-summarisation of rung 0)', 'Build the deterministic expansion from rung 0 (heightmap.png 1024x512 16-bit elevation + reliefmap.png 1024x512 colour biome) to rung 0.5, sized per D-258''s 2-px-per-gridunit-at-large-display rule. Same body + same seed must produce the same layer every time (byte-identical, per D-227''s determinism discipline extended by this carve-out). The reliefmap is a PLURALITY, not ground truth: each reliefmap cell already voted-and-discarded the dominant biome across ~38 km of ground, so this generator un-summarises it -- it does not upscale/interpolate it. Three binding invariants from D-258: (1) biome edges are gradients, never lines -- transitions blend so no boundary falls on a rung-0 cell edge (the D-243 climate edge-fuzz rule applied to biome); (2) descending the ladder reveals COMPOSITION not sharpness -- a cell reading ''forest'' globally must be able to contain clearings/marsh/rock/scrub the vote suppressed, emerging deterministically as the ladder descends; (3) CONSERVATIVE invention is the binding acceptance gate -- downsampling rung 0.5 must reproduce the rung-0 summary it came from (a forest cell may gain marsh pockets but must still read as forest from orbit). Blocked on T-1212 (cost/size measurement) landing first. Depends on: nothing else in this epic to start scaffolding, but hydrology (sibling ticket) and this generator are tightly coupled -- coordinate sequencing with whoever picks up hydrology. See governance/decisions/architecture.md#d-258.
|
|
|
|
---
|
|
UNBLOCKED 2026-08-06 (T-1211 re-scope, Jeroen''s call). The T-1212 blocker edge is
|
|
removed: that measurement priced a hydrology move that is not happening, and the
|
|
descent ladder showed there is no expansion artefact to measure yet anyway.
|
|
|
|
BUILD IT AS A PURE FUNCTION FIRST, not as a stored layer. The stored-layer half of
|
|
D-258 was materially weakened by its own 2026-07-27 amendment (the "not locally
|
|
computable" argument for the D-227 carve-out does not hold, because the whole-body
|
|
solve it cited already runs once per body in layer1.rs and is sampled at every
|
|
rung). So follow the mechanism that exists — D-255(f) mechanism B,
|
|
compute-once-sample-everywhere — measure it, and only argue for storage on those
|
|
numbers. Do NOT open with a cache.
|
|
|
|
WHAT "FLAT" MEANS CONCRETELY, so the fix has a target. Ferrath''s heightmap is
|
|
1024x512 over a 38,089 km circumference: 37.2 km per source pixel. Global draws at
|
|
35.267 km/gridunit, roughly 1:1 with the source, and reads as a real map. Region
|
|
draws at 0.379 km/gridunit — 98x finer than any stored datum — and is a single
|
|
uniform colour with dither. District is 0.0038 km/gridunit, ~9,800x finer, also
|
|
uniform. So the acceptance bar is not subtle: at Region, ~98 gridunits across a
|
|
single source pixel must carry visible, deterministic, non-repeating composition
|
|
that still downsamples back to that pixel''s summary (D-258''s conservation
|
|
invariant, the binding gate).
|
|
|
|
VERIFY BY CAPTURE, NOT BY REASONING. The scenarios exist: atlas_GJ820Bc_land_*
|
|
(Region/District/Quarter/Block/Chunk, one land-anchored world point, no overlays)
|
|
in tests/visual.json. Captures run offscreen under gamescope at native 3440x1440
|
|
via tests/run-visual --screenshot <name> — they do not steal the desktop. Re-shoot
|
|
the ladder and look at it; a green unit test proves nothing here.
|
|
|
|
Note the ladder currently also reports courses=0 at every rung below Global —
|
|
rivers vanish on descent. That is tracked separately as T-1239 and is NOT this
|
|
ticket''s scope, but it will be visible in the same captures, so do not mistake it
|
|
for a failure of the un-summarisation work.', 'in_progress', 'high', NULL, NULL, 'D-258', '2026-07-26 21:54:15.245', '2026-08-07 11:36:20.464', NULL, '94ccf78431914ea836ba5d59170a3395', 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;
|
|
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 ('06FSJWSX11WV3C1XXZEV88Q3P0', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Biome/relief stipple-density layer on the terrain build (RimWorld technique 4)', 'Follow-up from T-1175''s assess-only item (2026-07-25, stig''s write-up): a stipple/texture-density layer carrying hills/mountains/forest on top of hue, driven by the already-wire-carried vegetation and elev_q L8 planes — presentation-only, D-255(e)-legal (texture-space dithering of already-derived per-cell values, deterministically seeded per cell coordinate + value so it is stable across cache hit/miss; never invents samples between server cells). Would live as a post-process in step_canvas_terrain_layer.gd::rebuild_from_canvas()''s Image.set_pixel build. Design questions to settle at pickup: (a) stipple dots inline in the existing per-cell loop (cheap, same O(wxh) pass) vs a second overlay pass (simpler code, doubles pixel-touch cost); (b) density from vegetation class directly vs a combination with elev_q — relief hachures and forest texture are two different visual grammars in the RimWorld reference, not one slider; (c) own legend toggle (TMP/MST/VEG overlay-bar pattern) vs always-on like the elevation lightness modifier. Reference: docs/design/references/rimworld-world-map-fluency.jpg. Related: T-1175, T-1162 (vegetation patchiness fields), D-255(e).', 'in_progress', 'low', NULL, 'client', NULL, '2026-07-25 13:24:54.152', '2026-08-07 12:44:21.620', NULL, 'c1560b624bd9a9d79fe919f282620f22', 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;
|
|
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 ('06FXRSY7QWD8J5X6G1N86WMKEC', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'MIN_WL_BANDS_M octave cutoffs are pre-extent-inversion (stale, currently inert)', 'Found during T-1213 (2026-08-07). MIN_WL_BANDS_M (layer_proxy.rs) is built from 2*DISTRICT_M (4,096 m) and 2*QUARTER_M (1,024 m) -- Nyquist for a sample spacing equal to the rung''s CELL SIZE. That was correct while a rung fixed SPACING; after D-255''s extent inversion a rung fixes EXTENT and District''s spacing is 2048/540 = 3.8 m, so its true Nyquist floor is ~7.6 m. The bands are off by roughly the canvas cell count (~540x). This is the same class of defect as the Global 2x1 wire-extent sentinel the D-258 amendment documents: a constant that was correct under the pre-inversion model and silently outlived it. The code even states the consequence as though intended -- district_profile.rs''s comment reads ''At District''s real Nyquist floor (4,096 m) every VOXEL_OCTAVE_WAVELENGTHS_M entry is truncated, so relief is always exactly 0.0 there''. IMPORTANT SCOPE NOTE, verified before filing: this is currently INERT for the step canvas. step_canvas_viewer._fire_request() calls request_now(body, rung, center, extent) with no min_wl_m, so it defaults to 0, and quantize_min_wl_m(0) returns 0 (the leading sentinel band) -- no truncation happens on the served path. It therefore only affects the legacy layer_proxy district-window consumer. It is NOT the cause of the flat District/Quarter rungs; that is elev_q''s 80 m quantisation (0-100 across MAX_REGION_ELEVATION_KM = 8.0 km), measured at d1 mean 0.02 with the cutoff already disabled. Fix: derive the cutoff from the resolved canvas spacing rather than the rung cell size. Check the layer_proxy consumer''s expectations first -- MIN_WL_BANDS_M is shared, carries a const assert tying band 4 to OCTAVE_WAVELENGTHS_M[3], and is part of the cache key, so a change there is not local.', 'backlog', 'medium', NULL, 'server', 'D-255', '2026-08-07 13:26:56.703', '2026-08-07 13:26:56.703', NULL, '45cb7553effbe05042fe7f3eb09e6483', 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;
|
|
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 ('06FXF1VDVQDQ8EFGTXX787M90R', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Atlas rivers vanish at native resolution — 375 courses arrive, 0 drawn', 'Found 2026-08-06 when the visual capture resolution was raised from 960x540 to the native 3440x1440. On Ferrath (GJ820Bc) Global the wire delivers 375 river courses and the annotation layer draws NONE: ''courses=375 drawn=0'' in the view-transform readout. At 960x540 the SAME build drew them as visible strokes, so this is resolution-dependent, not a river-generation failure -- the courses are present and correct on the wire. Suspect the D-261 cull (drop a course below 15 px of on-screen length, 3x the 5 px stroke) or the water-truncation step: T-1237 computes the clipped/culled polylines ONCE on canvas adoption (set_frame) rather than per draw, so if adoption runs against a pre-layout or stale viewport the screen-length measurement is wrong for every course at once -- which matches an all-or-nothing drawn=0 rather than a partial cull. Note the scale moved the RIGHT way for visibility (46.792 km/gridunit at 960x540 vs 35.267 at 3440x1440 -- more gridunits across the body, so a river spans MORE of them), which makes a legitimate cull an unlikely explanation. Reproduce: tests/run-visual --screenshot atlas_GJ820Bc_Global and read the drawn= count.', 'in_progress', 'high', NULL, 'client', 'D-261', '2026-08-06 14:43:24.765', '2026-08-13 22:12:59.623', NULL, 'a52ba3f39408c80765dca1eb58e79ae6', 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;
|
|
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 ('06FXF1VDVQDQ8EFGTXX787M90R', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Atlas rivers vanish at native resolution — 375 courses arrive, 0 drawn', 'Found 2026-08-06 when the visual capture resolution was raised from 960x540 to the native 3440x1440. On Ferrath (GJ820Bc) Global the wire delivers 375 river courses and the annotation layer draws NONE: ''courses=375 drawn=0'' in the view-transform readout. At 960x540 the SAME build drew them as visible strokes, so this is resolution-dependent, not a river-generation failure -- the courses are present and correct on the wire. Suspect the D-261 cull (drop a course below 15 px of on-screen length, 3x the 5 px stroke) or the water-truncation step: T-1237 computes the clipped/culled polylines ONCE on canvas adoption (set_frame) rather than per draw, so if adoption runs against a pre-layout or stale viewport the screen-length measurement is wrong for every course at once -- which matches an all-or-nothing drawn=0 rather than a partial cull. Note the scale moved the RIGHT way for visibility (46.792 km/gridunit at 960x540 vs 35.267 at 3440x1440 -- more gridunits across the body, so a river spans MORE of them), which makes a legitimate cull an unlikely explanation. Reproduce: tests/run-visual --screenshot atlas_GJ820Bc_Global and read the drawn= count.
|
|
|
|
---
|
|
DIAGNOSED 2026-08-14. Not a client rendering bug. Both suspects in the original
|
|
report are wrong, and so is the `team: client` label — the defect is a stale
|
|
client-side disk cache (T-1183/D-255), invalidated by nothing that changed.
|
|
|
|
MEASUREMENT. Same build, same scenario, same native 3440x1440, only the cache
|
|
differs:
|
|
|
|
stale cache courses=375 runs=180 longest=6.0px (~106 km) drawn=0
|
|
cold cache courses=73 runs=23 longest=93.2px (~1,644 km) drawn=18
|
|
|
|
Server-side, at the same moment the cold capture ran:
|
|
`river_cells=615 paths=123 courses=73 longest_path_cells=27 ta_w=512 ta_h=256`.
|
|
The network is exactly as designed. During the STALE capture the server logged
|
|
NO course production at all — the canvas never came from it.
|
|
|
|
WHY IT LOOKED RESOLUTION-DEPENDENT. It isn''t. 960x540 resolves to an 814x407
|
|
canvas, a cache key never written before, so it MISSED and re-derived correctly
|
|
(73 courses, 1,644 km trunk). 3440x1440 resolves to 1080x540, which HAD a cached
|
|
entry from 2026-08-06 — written before T-1237 (4e503c356) replaced one-course-
|
|
per-D8-hop with one-course-per-river. Every `.dat` payload for GJ820Bc predates
|
|
that fix; the oldest is 2026-07-28. So the "high resolution" capture was
|
|
replaying a pre-fix canvas: 375 hop fragments, none clearing D-261''s
|
|
read-as-a-line floor, hence drawn=0. The ticket''s own note that the scale "moved
|
|
the RIGHT way for visibility" was correct and was the clue — a legitimate cull
|
|
could not explain it, because the cull was never the actor.
|
|
|
|
The 375 / 180 / 0 chain also matches, digit for digit, the pre-fix measurement
|
|
already written into `_cull_short`''s doc comment. That number was being re-read
|
|
off a cache, not re-measured.
|
|
|
|
ROOT CAUSE. The disk cache''s only invalidation signal is `project.yaml: version`
|
|
(step_canvas_disk_cache.gd `current_schema_version()`). 4e503c356 changed how
|
|
canvases are GENERATED but touched only the annotation layer, river_course.rs and
|
|
step_canvas.rs — never project.yaml — so every hop-shaped entry stayed "valid".
|
|
All 13 stale GJ820Bc entries are stamped 0.4.5, the then-current version. This is
|
|
the fourth instance of the same class: project.yaml''s own comments record 0.4.2,
|
|
0.4.3, 0.4.4 and 0.4.5 as bumps forced by exactly this failure.
|
|
|
|
FIX, three parts:
|
|
1. project.yaml 0.4.5 -> 0.4.6, forcing every pre-T-1237 entry to miss. This is
|
|
what repairs a real player''s Atlas; clearing a local cache is not a fix.
|
|
2. tests/run-visual isolates `user://` per capture (XDG_DATA_HOME into
|
|
.cache/visual-user-data, recreated each run). The harness was reading the
|
|
developer''s persistent cache, so a capture could render a canvas built by a
|
|
build that no longer existed — and every golden shot in that window silently
|
|
inherited it. A visual test must exercise the tree it is run against.
|
|
3. tests/run-visual keeps server stderr (was `2>/dev/null` into an unlinked
|
|
mktemp file). No tracing output from a capture was reachable, which is why
|
|
"the server produced nothing" was invisible for eight days.
|
|
|
|
Diagnostic left in place: the capture readout now prints `runs=` and `longest=`
|
|
between `courses=` and `drawn=`, so the three stages of "arrived -> survived the
|
|
water clip -> survived the length cull" can be told apart from a single capture.
|
|
That distinction is what made this solvable, and its absence is what made the
|
|
original report guess between two wrong suspects.
|
|
|
|
FOLLOW-UPS worth their own tickets, not done here:
|
|
- `current_schema_version()` reads `res://../project.yaml`, which does not exist
|
|
in an exported build — it returns the "?.?.?" fallback, identical for every
|
|
build, so a shipped game''s cache would never invalidate on version at all.
|
|
- Nothing enforces the generation-change/version-bump pairing. Four occurrences
|
|
suggests a check (e.g. a pre-push rule: canvas-generation paths touched =>
|
|
project.yaml version must move) rather than a fifth comment.', 'in_progress', 'high', NULL, 'client', 'D-261', '2026-08-06 14:43:24.765', '2026-08-14 21:13:15.779', NULL, 'ee86b57855c05540234c4b0f04ca2a18', 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;
|
|
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 ('06G0495WRHF8ADK82VR8CH1J8R', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Atlas disk cache never invalidates in an exported build — current_schema_version() falls back to ''?.?.?''', 'step_canvas_disk_cache.gd::current_schema_version() reads ProjectSettings.globalize_path(''res://'') + ''/../project.yaml''. That resolves to the repo-root file in a dev run (res:// = client/), but an exported build has no project.yaml one level above res://, so the function returns its ''?.?.?'' fallback. Every exported build therefore stamps and compares the SAME sentinel version, which means the schema-version invalidation path — the cache''s only invalidation signal — is inert in a shipped game: a canvas cached by one build is served forever by every later build. Found while diagnosing T-1239, where the same mechanism failed in dev for a different reason (the version simply was not bumped). Fix direction: bake the version into the client at export time (a generated const, ProjectSettings application/config/version, or an exported resource) rather than reading a repo file at runtime. Note test_current_schema_version_reads_project_yaml passes in dev and would not catch this — it asserts the non-fallback path, in the only environment where that path works.', 'backlog', 'medium', NULL, 'client', 'D-255', '2026-08-14 21:19:17.188', '2026-08-14 21:19:17.188', NULL, '7880dfa6b9038360c78843ae9f6ca2e2', 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;
|
|
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 ('06G04975H3S7GRVQXHKYCR7BKR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Enforce the canvas-generation/project.yaml-version pairing — four silent stale-cache regressions and counting', 'project.yaml''s version is the Atlas disk cache''s only invalidation signal, and nothing enforces that a change to canvas GENERATION also moves it. The file''s own comment block now records four bumps forced after the fact by exactly this failure: 0.4.2 (lake_margin_q semantics), 0.4.3 (coast_warp_px at orbital sampling), 0.4.4 (D-255 extent inversion), 0.4.5 (Global sentinel), and now 0.4.6 (T-1237 one-course-per-river, diagnosed as T-1239 eight days after it shipped). The failure is silent and machine-dependent: it reproduces only where a warm cache exists, so the author with a cold checkout sees nothing wrong. Direction: a pre-push check in .config/hooks/pre-push — if the push touches the canvas-generation paths (server/src/atlas/step_canvas.rs, river_course.rs, layer1.rs, district_profile.rs, the client step_canvas layers) and project.yaml''s version line is unchanged in the same range, reject with the reason. Registry-driven like tooling/generator_sources.py rather than a hand-kept path list in the hook. A false positive is cheap (bump the version, entries miss once); a false negative is another week of a wrong map.', 'backlog', 'medium', NULL, 'client', 'D-255', '2026-08-14 21:19:27.624', '2026-08-14 21:19:27.624', NULL, '5386de8d8c8120e17373cc1c1a59a9cd', 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;
|
|
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 ('06FXF1VDVQDQ8EFGTXX787M90R', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Atlas rivers vanish at native resolution — 375 courses arrive, 0 drawn', 'Found 2026-08-06 when the visual capture resolution was raised from 960x540 to the native 3440x1440. On Ferrath (GJ820Bc) Global the wire delivers 375 river courses and the annotation layer draws NONE: ''courses=375 drawn=0'' in the view-transform readout. At 960x540 the SAME build drew them as visible strokes, so this is resolution-dependent, not a river-generation failure -- the courses are present and correct on the wire. Suspect the D-261 cull (drop a course below 15 px of on-screen length, 3x the 5 px stroke) or the water-truncation step: T-1237 computes the clipped/culled polylines ONCE on canvas adoption (set_frame) rather than per draw, so if adoption runs against a pre-layout or stale viewport the screen-length measurement is wrong for every course at once -- which matches an all-or-nothing drawn=0 rather than a partial cull. Note the scale moved the RIGHT way for visibility (46.792 km/gridunit at 960x540 vs 35.267 at 3440x1440 -- more gridunits across the body, so a river spans MORE of them), which makes a legitimate cull an unlikely explanation. Reproduce: tests/run-visual --screenshot atlas_GJ820Bc_Global and read the drawn= count.
|
|
|
|
---
|
|
DIAGNOSED 2026-08-14. Not a client rendering bug. Both suspects in the original
|
|
report are wrong, and so is the `team: client` label — the defect is a stale
|
|
client-side disk cache (T-1183/D-255), invalidated by nothing that changed.
|
|
|
|
MEASUREMENT. Same build, same scenario, same native 3440x1440, only the cache
|
|
differs:
|
|
|
|
stale cache courses=375 runs=180 longest=6.0px (~106 km) drawn=0
|
|
cold cache courses=73 runs=23 longest=93.2px (~1,644 km) drawn=18
|
|
|
|
Server-side, at the same moment the cold capture ran:
|
|
`river_cells=615 paths=123 courses=73 longest_path_cells=27 ta_w=512 ta_h=256`.
|
|
The network is exactly as designed. During the STALE capture the server logged
|
|
NO course production at all — the canvas never came from it.
|
|
|
|
WHY IT LOOKED RESOLUTION-DEPENDENT. It isn''t. 960x540 resolves to an 814x407
|
|
canvas, a cache key never written before, so it MISSED and re-derived correctly
|
|
(73 courses, 1,644 km trunk). 3440x1440 resolves to 1080x540, which HAD a cached
|
|
entry from 2026-08-06 — written before T-1237 (4e503c356) replaced one-course-
|
|
per-D8-hop with one-course-per-river. Every `.dat` payload for GJ820Bc predates
|
|
that fix; the oldest is 2026-07-28. So the "high resolution" capture was
|
|
replaying a pre-fix canvas: 375 hop fragments, none clearing D-261''s
|
|
read-as-a-line floor, hence drawn=0. The ticket''s own note that the scale "moved
|
|
the RIGHT way for visibility" was correct and was the clue — a legitimate cull
|
|
could not explain it, because the cull was never the actor.
|
|
|
|
The 375 / 180 / 0 chain also matches, digit for digit, the pre-fix measurement
|
|
already written into `_cull_short`''s doc comment. That number was being re-read
|
|
off a cache, not re-measured.
|
|
|
|
ROOT CAUSE. The disk cache''s only invalidation signal is `project.yaml: version`
|
|
(step_canvas_disk_cache.gd `current_schema_version()`). 4e503c356 changed how
|
|
canvases are GENERATED but touched only the annotation layer, river_course.rs and
|
|
step_canvas.rs — never project.yaml — so every hop-shaped entry stayed "valid".
|
|
All 13 stale GJ820Bc entries are stamped 0.4.5, the then-current version. This is
|
|
the fourth instance of the same class: project.yaml''s own comments record 0.4.2,
|
|
0.4.3, 0.4.4 and 0.4.5 as bumps forced by exactly this failure.
|
|
|
|
FIX, three parts:
|
|
1. project.yaml 0.4.5 -> 0.4.6, forcing every pre-T-1237 entry to miss. This is
|
|
what repairs a real player''s Atlas; clearing a local cache is not a fix.
|
|
2. tests/run-visual isolates `user://` per capture (XDG_DATA_HOME into
|
|
.cache/visual-user-data, recreated each run). The harness was reading the
|
|
developer''s persistent cache, so a capture could render a canvas built by a
|
|
build that no longer existed — and every golden shot in that window silently
|
|
inherited it. A visual test must exercise the tree it is run against.
|
|
3. tests/run-visual keeps server stderr (was `2>/dev/null` into an unlinked
|
|
mktemp file). No tracing output from a capture was reachable, which is why
|
|
"the server produced nothing" was invisible for eight days.
|
|
|
|
Diagnostic left in place: the capture readout now prints `runs=` and `longest=`
|
|
between `courses=` and `drawn=`, so the three stages of "arrived -> survived the
|
|
water clip -> survived the length cull" can be told apart from a single capture.
|
|
That distinction is what made this solvable, and its absence is what made the
|
|
original report guess between two wrong suspects.
|
|
|
|
FOLLOW-UPS worth their own tickets, not done here:
|
|
- `current_schema_version()` reads `res://../project.yaml`, which does not exist
|
|
in an exported build — it returns the "?.?.?" fallback, identical for every
|
|
build, so a shipped game''s cache would never invalidate on version at all.
|
|
- Nothing enforces the generation-change/version-bump pairing. Four occurrences
|
|
suggests a check (e.g. a pre-push rule: canvas-generation paths touched =>
|
|
project.yaml version must move) rather than a fifth comment.', 'done', 'high', NULL, 'client', 'D-261', '2026-08-06 14:43:24.765', '2026-08-14 21:20:27.835', NULL, 'fa2402988b35d9f0db6ffcf7cf7a81c2', 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;
|
|
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 ('06G0495WRHF8ADK82VR8CH1J8R', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Atlas disk cache never invalidates in an exported build — current_schema_version() falls back to ''?.?.?''', 'step_canvas_disk_cache.gd::current_schema_version() reads ProjectSettings.globalize_path(''res://'') + ''/../project.yaml''. That resolves to the repo-root file in a dev run (res:// = client/), but an exported build has no project.yaml one level above res://, so the function returns its ''?.?.?'' fallback. Every exported build therefore stamps and compares the SAME sentinel version, which means the schema-version invalidation path — the cache''s only invalidation signal — is inert in a shipped game: a canvas cached by one build is served forever by every later build. Found while diagnosing T-1239, where the same mechanism failed in dev for a different reason (the version simply was not bumped). Fix direction: bake the version into the client at export time (a generated const, ProjectSettings application/config/version, or an exported resource) rather than reading a repo file at runtime. Note test_current_schema_version_reads_project_yaml passes in dev and would not catch this — it asserts the non-fallback path, in the only environment where that path works.', 'in_progress', 'medium', NULL, 'client', 'D-255', '2026-08-14 21:19:17.188', '2026-08-14 21:21:35.097', NULL, '0e36862b9cbd420d1f0d8114f7673cc9', 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;
|
|
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 ('06G0495WRHF8ADK82VR8CH1J8R', 'bug', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Atlas disk cache never invalidates in an exported build — current_schema_version() falls back to ''?.?.?''', 'step_canvas_disk_cache.gd::current_schema_version() reads ProjectSettings.globalize_path(''res://'') + ''/../project.yaml''. That resolves to the repo-root file in a dev run (res:// = client/), but an exported build has no project.yaml one level above res://, so the function returns its ''?.?.?'' fallback. Every exported build therefore stamps and compares the SAME sentinel version, which means the schema-version invalidation path — the cache''s only invalidation signal — is inert in a shipped game: a canvas cached by one build is served forever by every later build. Found while diagnosing T-1239, where the same mechanism failed in dev for a different reason (the version simply was not bumped). Fix direction: bake the version into the client at export time (a generated const, ProjectSettings application/config/version, or an exported resource) rather than reading a repo file at runtime. Note test_current_schema_version_reads_project_yaml passes in dev and would not catch this — it asserts the non-fallback path, in the only environment where that path works.', 'done', 'medium', NULL, 'client', 'D-255', '2026-08-14 21:19:17.188', '2026-08-14 21:35:35.623', NULL, '3bd4d5eaacf6a4792e00a644a3333535', 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;
|
|
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 ('06G04975H3S7GRVQXHKYCR7BKR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Enforce the canvas-generation/project.yaml-version pairing — four silent stale-cache regressions and counting', 'project.yaml''s version is the Atlas disk cache''s only invalidation signal, and nothing enforces that a change to canvas GENERATION also moves it. The file''s own comment block now records four bumps forced after the fact by exactly this failure: 0.4.2 (lake_margin_q semantics), 0.4.3 (coast_warp_px at orbital sampling), 0.4.4 (D-255 extent inversion), 0.4.5 (Global sentinel), and now 0.4.6 (T-1237 one-course-per-river, diagnosed as T-1239 eight days after it shipped). The failure is silent and machine-dependent: it reproduces only where a warm cache exists, so the author with a cold checkout sees nothing wrong. Direction: a pre-push check in .config/hooks/pre-push — if the push touches the canvas-generation paths (server/src/atlas/step_canvas.rs, river_course.rs, layer1.rs, district_profile.rs, the client step_canvas layers) and project.yaml''s version line is unchanged in the same range, reject with the reason. Registry-driven like tooling/generator_sources.py rather than a hand-kept path list in the hook. A false positive is cheap (bump the version, entries miss once); a false negative is another week of a wrong map.', 'in_progress', 'medium', NULL, 'client', 'D-255', '2026-08-14 21:19:27.624', '2026-08-14 21:41:02.683', NULL, 'efd83e7e6e689653b411d77629c19985', 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;
|
|
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 ('06G04975H3S7GRVQXHKYCR7BKR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Enforce the canvas-generation/project.yaml-version pairing — four silent stale-cache regressions and counting', 'project.yaml''s version is the Atlas disk cache''s only invalidation signal, and nothing enforces that a change to canvas GENERATION also moves it. The file''s own comment block now records four bumps forced after the fact by exactly this failure: 0.4.2 (lake_margin_q semantics), 0.4.3 (coast_warp_px at orbital sampling), 0.4.4 (D-255 extent inversion), 0.4.5 (Global sentinel), and now 0.4.6 (T-1237 one-course-per-river, diagnosed as T-1239 eight days after it shipped). The failure is silent and machine-dependent: it reproduces only where a warm cache exists, so the author with a cold checkout sees nothing wrong. Direction: a pre-push check in .config/hooks/pre-push — if the push touches the canvas-generation paths (server/src/atlas/step_canvas.rs, river_course.rs, layer1.rs, district_profile.rs, the client step_canvas layers) and project.yaml''s version line is unchanged in the same range, reject with the reason. Registry-driven like tooling/generator_sources.py rather than a hand-kept path list in the hook. A false positive is cheap (bump the version, entries miss once); a false negative is another week of a wrong map.', 'in_progress', 'medium', NULL, 'client', 'D-255', '2026-08-14 21:19:27.624', '2026-08-14 21:41:17.977', NULL, 'cb2da99199993e9c60322845ceaf22b8', 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;
|
|
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 ('06G04975H3S7GRVQXHKYCR7BKR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Enforce the canvas-generation/project.yaml-version pairing — four silent stale-cache regressions and counting', 'project.yaml''s version is the Atlas disk cache''s only invalidation signal, and nothing enforces that a change to canvas GENERATION also moves it. The file''s own comment block now records four bumps forced after the fact by exactly this failure: 0.4.2 (lake_margin_q semantics), 0.4.3 (coast_warp_px at orbital sampling), 0.4.4 (D-255 extent inversion), 0.4.5 (Global sentinel), and now 0.4.6 (T-1237 one-course-per-river, diagnosed as T-1239 eight days after it shipped). The failure is silent and machine-dependent: it reproduces only where a warm cache exists, so the author with a cold checkout sees nothing wrong. Direction: a pre-push check in .config/hooks/pre-push — if the push touches the canvas-generation paths (server/src/atlas/step_canvas.rs, river_course.rs, layer1.rs, district_profile.rs, the client step_canvas layers) and project.yaml''s version line is unchanged in the same range, reject with the reason. Registry-driven like tooling/generator_sources.py rather than a hand-kept path list in the hook. A false positive is cheap (bump the version, entries miss once); a false negative is another week of a wrong map.', 'done', 'medium', NULL, 'client', 'D-255', '2026-08-14 21:19:27.624', '2026-08-14 22:06:55.412', NULL, 'eb7ca0296fcb5255bdb5cc94e66c45d5', 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;
|