The pre-push gate rejected the T-1213 push on a wall-clock fog budget (0.606 vs 0.5 ms) that passes 23/23 in isolation on the same build. Second hardening cycle for the same failure mode: min-of-7 defends against one slow sample, not the sustained core saturation the gate itself creates by running cargo and tooling suites immediately before it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
614 lines
82 KiB
SQL
614 lines
82 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;
|
|
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.
|
|
|
|
---
|
|
LADDER RE-SHOT COLD 2026-08-16 — the first ladder whose numbers can be trusted.
|
|
Every previous ladder, including the one this ticket''s re-scope was written from,
|
|
was captured against the developer''s persistent user:// cache (T-1239). The
|
|
harness now isolates user:// per capture, so these are fresh derives.
|
|
|
|
RESULT: the acceptance bar is NOT met. Region and below are still flat.
|
|
|
|
Flatness, measured rather than eyeballed (.cache/t1213_flatness.py samples the
|
|
terrain area, excluding the panels):
|
|
|
|
rung distinct R std G std B std lum p1-p99
|
|
Global 1581 24.05 35.75 36.21 145.69
|
|
Region 2923 4.80 8.31 4.19 33.59
|
|
District 53 2.56 4.90 2.56 13.72
|
|
Quarter 46 2.42 4.78 2.42 11.01
|
|
|
|
Note Region carries MORE distinct colours than Global (2923 vs 1581) while
|
|
holding a quarter of the structure. That is the dither/stipple pass adding colour
|
|
noise, not information — so distinct-count is precisely the wrong metric, and the
|
|
percentile luminance spread is the honest one. Structure falls ~92% from Global
|
|
to Quarter.
|
|
|
|
WHY IT IS FLAT — a channel mismatch, not a missing generator.
|
|
|
|
Composition IS working in the data. The conservation test, run explicitly against
|
|
real terrain, reports over a 2,048 m patch at the ladder''s own anchor:
|
|
|
|
conservation: majority class 3 at 98.9% across 2 classes {1: 175, 3: 16209}
|
|
|
|
So D-258 invariant 3 (conservation) holds and invariant 2 (composition) is
|
|
happening: 1.07% of the patch is a minority class.
|
|
|
|
But composition perturbs `moisture_q` and `slope_q`, which resolve into
|
|
`vegetation_class`. The base map draws neither. step_canvas_colorize.gd''s own
|
|
comment states it: "The base layer reads hue from morphology and lightness from
|
|
elev_q, and on a real body below Global that collapses: morphology resolves to
|
|
ONE zone". The ladder scenarios pass `[]` for overlays deliberately
|
|
(visual_scenarios.gd: "No overlays, deliberately: a colour ramp on top would mask
|
|
the very thing being judged"), so the composed fields are never rendered in the
|
|
shots that judge this work.
|
|
|
|
The un-summarisation lands in channels the default view does not draw. That is
|
|
why perfectly correct composition and a flat map coexist.
|
|
|
|
THE CONSERVATION TEST WAS NOT PROTECTING ANYTHING (fixed here).
|
|
|
|
Its "not a monoculture" assertion read:
|
|
|
|
assert!(tally.len() > 1 || share == 1.0, ...)
|
|
|
|
which is a TAUTOLOGY — a single-class tally has a 100% share by definition, so
|
|
both branches are always satisfiable and the check could never fail, including in
|
|
the exact case its message names ("or nothing was composed"). It was the only
|
|
thing standing behind invariant 2, and it stood behind nothing. Now split into
|
|
the two bounds the invariant actually has: majority > 50% (conservation, the
|
|
ceiling on invention) and minority >= 0.1% (composition, the floor). Measured
|
|
1.07% against a 0.1% floor.
|
|
|
|
It is also still `#[ignore]`d, so the binding acceptance gate for this epic runs
|
|
only when someone types it. Worth moving into a harness that runs — the
|
|
believability and window-derivation harnesses already load real bodies in the
|
|
normal `cargo test` path, so the precedent exists.
|
|
|
|
courses=0 BELOW GLOBAL IS NOT A BUG — it is arithmetic, and the ticket''s note
|
|
attributing it to T-1239 is superseded. Global now reports courses=73 drawn=18
|
|
(T-1239 fixed). Region/District/Quarter report courses=0 because the hydrology
|
|
network is 615 river cells on a 512x256 grid (74.4 km/cell) and a Region window
|
|
is 489 x 205 km = 0.0217% of a 462M km2 body: expected river cells in view = 615
|
|
x 0.000217 = 0.13. Zero is the likely outcome, not a failure. The real
|
|
observation underneath it is that 615 river cells on an Earth-sized body is a
|
|
sparse network (~45,000 km of total watercourse), which is a believability
|
|
question for hydrology, not a rendering one.
|
|
|
|
THE OPEN DECISION — three readings, materially different work:
|
|
|
|
(A) Make the DISPLAYED channels carry sub-source-pixel composition. The literal
|
|
reading of this ticket''s acceptance bar ("at Region, ~98 gridunits across a
|
|
single source pixel must carry visible, deterministic, non-repeating
|
|
composition"). Means morphology and/or elev_q gain a fine tier, which is
|
|
D-227/D-258 territory and the largest change.
|
|
(B) Accept that the base view is morphology + elevation, and judge composition
|
|
through the overlays — i.e. the ladder gains VEG/MST companion shots. Test
|
|
change only, but it concedes that the default map stays flat below Global.
|
|
(C) Raise inclusion density so the existing vegetation texture reads at map
|
|
scale. Tuning only; 1.07% is near-invisible.
|
|
|
|
Not chosen here — it needs Jeroen''s call, and it touches D-258.', 'in_progress', 'high', NULL, NULL, 'D-258', '2026-07-26 21:54:15.245', '2026-08-16 10:32:31.457', NULL, '21d816226e574f3b6489810453220b5e', 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.
|
|
|
|
---
|
|
LADDER RE-SHOT COLD 2026-08-16 — the first ladder whose numbers can be trusted.
|
|
Every previous ladder, including the one this ticket''s re-scope was written from,
|
|
was captured against the developer''s persistent user:// cache (T-1239). The
|
|
harness now isolates user:// per capture, so these are fresh derives.
|
|
|
|
RESULT: the acceptance bar is NOT met. Region and below are still flat.
|
|
|
|
Flatness, measured rather than eyeballed (.cache/t1213_flatness.py samples the
|
|
terrain area, excluding the panels):
|
|
|
|
rung distinct R std G std B std lum p1-p99
|
|
Global 1581 24.05 35.75 36.21 145.69
|
|
Region 2923 4.80 8.31 4.19 33.59
|
|
District 53 2.56 4.90 2.56 13.72
|
|
Quarter 46 2.42 4.78 2.42 11.01
|
|
|
|
Note Region carries MORE distinct colours than Global (2923 vs 1581) while
|
|
holding a quarter of the structure. That is the dither/stipple pass adding colour
|
|
noise, not information — so distinct-count is precisely the wrong metric, and the
|
|
percentile luminance spread is the honest one. Structure falls ~92% from Global
|
|
to Quarter.
|
|
|
|
WHY IT IS FLAT — a channel mismatch, not a missing generator.
|
|
|
|
Composition IS working in the data. The conservation test, run explicitly against
|
|
real terrain, reports over a 2,048 m patch at the ladder''s own anchor:
|
|
|
|
conservation: majority class 3 at 98.9% across 2 classes {1: 175, 3: 16209}
|
|
|
|
So D-258 invariant 3 (conservation) holds and invariant 2 (composition) is
|
|
happening: 1.07% of the patch is a minority class.
|
|
|
|
But composition perturbs `moisture_q` and `slope_q`, which resolve into
|
|
`vegetation_class`. The base map draws neither. step_canvas_colorize.gd''s own
|
|
comment states it: "The base layer reads hue from morphology and lightness from
|
|
elev_q, and on a real body below Global that collapses: morphology resolves to
|
|
ONE zone". The ladder scenarios pass `[]` for overlays deliberately
|
|
(visual_scenarios.gd: "No overlays, deliberately: a colour ramp on top would mask
|
|
the very thing being judged"), so the composed fields are never rendered in the
|
|
shots that judge this work.
|
|
|
|
The un-summarisation lands in channels the default view does not draw. That is
|
|
why perfectly correct composition and a flat map coexist.
|
|
|
|
THE CONSERVATION TEST WAS NOT PROTECTING ANYTHING (fixed here).
|
|
|
|
Its "not a monoculture" assertion read:
|
|
|
|
assert!(tally.len() > 1 || share == 1.0, ...)
|
|
|
|
which is a TAUTOLOGY — a single-class tally has a 100% share by definition, so
|
|
both branches are always satisfiable and the check could never fail, including in
|
|
the exact case its message names ("or nothing was composed"). It was the only
|
|
thing standing behind invariant 2, and it stood behind nothing. Now split into
|
|
the two bounds the invariant actually has: majority > 50% (conservation, the
|
|
ceiling on invention) and minority >= 0.1% (composition, the floor). Measured
|
|
1.07% against a 0.1% floor.
|
|
|
|
It is also still `#[ignore]`d, so the binding acceptance gate for this epic runs
|
|
only when someone types it. Worth moving into a harness that runs — the
|
|
believability and window-derivation harnesses already load real bodies in the
|
|
normal `cargo test` path, so the precedent exists.
|
|
|
|
courses=0 BELOW GLOBAL IS NOT A BUG — it is arithmetic, and the ticket''s note
|
|
attributing it to T-1239 is superseded. Global now reports courses=73 drawn=18
|
|
(T-1239 fixed). Region/District/Quarter report courses=0 because the hydrology
|
|
network is 615 river cells on a 512x256 grid (74.4 km/cell) and a Region window
|
|
is 489 x 205 km = 0.0217% of a 462M km2 body: expected river cells in view = 615
|
|
x 0.000217 = 0.13. Zero is the likely outcome, not a failure. The real
|
|
observation underneath it is that 615 river cells on an Earth-sized body is a
|
|
sparse network (~45,000 km of total watercourse), which is a believability
|
|
question for hydrology, not a rendering one.
|
|
|
|
THE OPEN DECISION — three readings, materially different work:
|
|
|
|
(A) Make the DISPLAYED channels carry sub-source-pixel composition. The literal
|
|
reading of this ticket''s acceptance bar ("at Region, ~98 gridunits across a
|
|
single source pixel must carry visible, deterministic, non-repeating
|
|
composition"). Means morphology and/or elev_q gain a fine tier, which is
|
|
D-227/D-258 territory and the largest change.
|
|
(B) Accept that the base view is morphology + elevation, and judge composition
|
|
through the overlays — i.e. the ladder gains VEG/MST companion shots. Test
|
|
change only, but it concedes that the default map stays flat below Global.
|
|
(C) Raise inclusion density so the existing vegetation texture reads at map
|
|
scale. Tuning only; 1.07% is near-invisible.
|
|
|
|
Not chosen here — it needs Jeroen''s call, and it touches D-258.
|
|
|
|
---
|
|
FIXED 2026-08-16 (option A). The un-summarisation was reaching the wire and
|
|
falling off it one line before the renderer.
|
|
|
|
ROOT CAUSE: `step_canvas_protocol.gd`''s decode dictionary listed every dense
|
|
plane EXCEPT relief_q. The server has encoded it since 5eb394b36
|
|
(step_canvas.rs:1234) and the terrain layer has asked for it by name ever since
|
|
(`canvas.get("relief_q")`), but the key was never put in the decoded dictionary,
|
|
so the plane arrived nowhere. The server half of relief_q landed; the protocol
|
|
half did not.
|
|
|
|
That is why the deep rungs were flat. relief_q is the ONE field with signal below
|
|
District — elev_q''s 80 m steps quantise sub-district detail away — and it was
|
|
absent. Measured plane variety at District, before: {morphology: 1, elev_q: 11,
|
|
relief_q: 0, moisture_q: 25, vegetation: 3}. A 0 there means ABSENT, not merely
|
|
constant; the distinction is what made this diagnosable, and it did not exist
|
|
until this session added the plane-variety readout.
|
|
|
|
TWO CHANGES:
|
|
1. Decode relief_q (one line, client/scripts/protocol/step_canvas_protocol.gd).
|
|
2. Shade with it, SIGNED and continuously (step_canvas_colorize.gd). It was
|
|
previously spent only as a stipple PROBABILITY, so a ridge and a plain
|
|
differed in dot density, which at one pixel per cell reads as noise rather
|
|
than landform — and `_ruggedness()` took absf(relief_q - 50), discarding the
|
|
sign the server deliberately preserved ("a hollow and a rise are different
|
|
ground"). Rises now lighten, hollows darken, under the stipple rather than
|
|
instead of it.
|
|
|
|
LADDER, before -> after (tooling/atlas-flatness, lum p1-p99):
|
|
|
|
Global 145.69 -> 145.69 unchanged, and correct: relief_q is flat 50 at
|
|
orbital rungs by construction
|
|
Region 33.59 -> 71.01 2.1x
|
|
District 13.72 -> 77.01 5.6x
|
|
Quarter 11.01 -> 42.56 3.9x
|
|
|
|
Structure retention Global->Quarter went from 7.6% to 29%. Plane variety after:
|
|
Region relief_q 101 values, District 91, Quarter 33.
|
|
|
|
HONEST STATE. The acceptance bar ("at Region, ~98 gridunits across a single
|
|
source pixel must carry visible, deterministic, non-repeating composition") is
|
|
MET. It is not yet cartographically finished: Region reads as heavy speckle
|
|
because ruggedness is now real data rather than an elev_q-gradient fallback, so
|
|
far more cells earn a stipple mark than the T-1194 tuning assumed (17,599
|
|
distinct colours there). District reads as soft blobby relief — form without
|
|
directionality, since the field is isotropic noise with no hillshade direction
|
|
and no drainage alignment. Both are tuning/grammar follow-ups on top of a channel
|
|
that now carries signal, not blockers.
|
|
|
|
REGRESSION COVER ADDED. Every protocol test passed throughout the weeks the plane
|
|
was missing, because each asserted a field it already knew about and none
|
|
asserted the SET. There is now a test that walks all eight dense planes of
|
|
EncodedStepCanvas and fails if any is absent from the decode — verified by
|
|
disabling the fix and watching it fail by name.', 'in_progress', 'high', NULL, NULL, 'D-258', '2026-07-26 21:54:15.245', '2026-08-16 13:05:35.162', NULL, 'e611e465ff1fd8de7b71b3de10702fec', 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 ('06G0P0F9C3RKKK23ZHHTANCPZW', 'bug', '06FBPPMZNNEV052DBYYY3A897C', 'test_visibility_texture_update_performance flakes under gate load — min-of-7 was not enough', 'Wall-clock perf assertion in client/tests/test_fog_shader.gd:213 (best-of-7 < 0.5 ms, D-059 budget) failed the pre-push gate on 2026-08-16 at 0.606 ms, then passed 23/23 in isolation minutes later on the same build. The failing run was a full-suite pass inside the hook, which runs AFTER the tooling suite and cargo checks, so every one of the seven samples was inflated together by a loaded machine. My manual full-suite run of the identical tree passed 1830/0. Nothing in the pushed change touches the fog path (Atlas protocol decode, colorize, terrain layer, tooling). This is the SECOND recorded hardening cycle for the same failure mode: the estimator is already min-of-7 (T-1210) after median-of-5 flaked three times on 2026-07-26 when a cargo build saturated the cores (0.549/0.503/0.638). Taking the minimum defends against ONE slow sample, not against sustained saturation, which is precisely the condition the gate creates by design. Direction: measure CPU time rather than wall clock (Time.get_ticks_usec measures elapsed, so it prices the scheduler in), or make the budget assertion load-aware, or move the perf assertions out of the correctness gate into a dedicated bench run where the machine is quiet. Do NOT simply raise the 0.5 threshold -- that discards the budget D-059 set without deciding to.', 'backlog', 'medium', NULL, 'client', NULL, '2026-08-16 14:37:49.792', '2026-08-16 14:37:49.792', NULL, '513d27f28c0ff1a50bd36692fb96fa46', 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;
|