fix(client): the Atlas was replaying a cache from a build that no longer existed (T-1239)

Ferrath's Global map drew no rivers at native resolution: 375 courses arrived
and 0 were drawn. The report suspected the D-261 length cull or the water
truncation. Both were innocent, and so was the renderer.

The client served the canvas from its own disk cache (T-1183). Every payload
for GJ820Bc predated T-1237 (4e503c356), which replaced one-course-per-D8-hop
with one-course-per-river -- so the map was drawing 375 hop fragments whose
longest run was 106 km, all of them under D-261's read-as-a-line floor. Same
build, same scenario, same 3440x1440, cache the only difference:

  stale   courses=375  runs=180  longest=6.0px  (~106 km)   drawn=0
  cold    courses=73   runs=23   longest=93.2px (~1,644 km) drawn=18

It looked resolution-dependent because it wasn't a resolution at all: 960x540
resolves to an 814x407 canvas, a key never cached, so it missed and re-derived
correctly. 3440x1440 resolves to 1080x540, which had an entry from 2026-08-06.
During the stale capture the server logged no course production whatsoever --
the canvas never came from it.

The cache's only invalidation signal is project.yaml's version, and 4e503c356
changed how canvases are generated without touching it, so hop-shaped entries
stayed valid. All 13 stale entries are stamped 0.4.5. 0.4.6 forces them to miss;
that, not clearing a local directory, is what repairs a player's Atlas.

The harness let this hide for eight days, in two ways now fixed. It ran against
the developer's persistent user:// cache, so a capture could render a canvas
built by a build that no longer existed -- and any golden shot in that window
silently inherited it; user:// is now isolated per run. And it sent server
stderr to /dev/null via an already-unlinked mktemp file, so no tracing from a
capture was ever reachable; the log now lives at .cache/visual-server.log.

The capture readout gained runs= and longest= between courses= and drawn=,
because "375 arrived, 0 drawn" is not one fact but three stages, and telling
them apart is what turned a guess between two suspects into a measurement.

Follow-ups filed: T-1241 (current_schema_version() returns its ?.?.? fallback in
an exported build, so a shipped game never invalidates on version at all) and
T-1242 (nothing enforces the generation-change/version-bump pairing -- this is
the fourth bump forced after the fact).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-14 23:20:16 +02:00
co-authored by Claude Opus 5
parent 16348e2e89
commit 07ed2a47ab
9 changed files with 244 additions and 11 deletions
+16 -1
View File
@@ -19,7 +19,22 @@ name: The Settled Reach
# every Global request resolved to a two-cell canvas regardless of viewport.
# Those entries were also cached under a key that ignored extent, so they
# answered every later request forever. 0.4.5 forces them to miss.
version: 0.4.5
# 0.4.5-tagged canvases carry pre-T-1237 river courses: one course per D8 HOP
# rather than one per river (build_paths walks whole headwater->mouth rivers
# now). The hop-shaped entries survived that fix because it changed only server
# and client code, never this version — so Ferrath Global replayed 375 fragments
# with a 106 km longest run, all of them below D-261's read-as-a-line floor, and
# drew NOTHING, while a cold cache on the same build produced 73 whole rivers
# with a 1,644 km trunk and drew 18. Diagnosed as T-1239. 0.4.6 forces them to
# miss.
#
# THE PATTERN, now four bumps deep: a change to how a canvas is GENERATED is
# only half the change — the other half is this line. Nothing enforces the
# pairing, so a canvas-generation commit that leaves this version alone ships a
# silent regression that reproduces only on machines with a warm cache. The
# capture harness no longer hides it (tests/run-visual isolates user:// per run,
# T-1239), but a real player's cache is still governed by this number alone.
version: 0.4.6
repository: settled-reach