Commit Graph
100 Commits
Author SHA1 Message Date
jpmschweitzerandClaude 58cd87d48c chore(meta): record the atlas shutdown leak as intermittent, not constant
Observed twice today with different outcomes. The crash is deterministic —
every shutdown logs the null-instance error at server_process.gd:87 via
_stop_spawned_server. The orphaned server is NOT: one run leaked a process
that had to be killed by hand, the next reaped cleanly with the same error in
the log.

That combination is the awkward one. A fix verified by a single clean
shutdown proves nothing, so T-1224 now says to reproduce by repeated
launch/close while watching for surviving processes, rather than by reading
one log.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 09:05:40 +02:00
jpmschweitzerandClaude 363574d687 fix(config): pre-push names which check failed
The hook incremented a bare counter at 13 sites and ended with "N check(s)
failed. Fix the errors above." — naming nothing. Six of those sites (fmt,
clippy, cargo test, deny, ruff, tooling) print no FAIL line at all, so a
failure was only inferable from the ABSENCE of an "— OK" line.

Hit for real today: a push aborted on cargo fmt, and the verdict was
indistinguishable from any other failure. Finding the cause meant scrolling
past thousands of lines of unrelated test-fixture output, because the one
actionable line said only that something, somewhere, had failed.

Failed checks are now collected by name and printed in a self-contained
final block, so tailing the log always shows WHAT broke — plus a pointer to
grep the failing check's own output, and the reminder that fmt auto-fixes.

Note this is NOT a verbosity reduction, which was the tempting fix. Detail is
exactly what you want when something fails; the defect was that the verdict
carried no information, not that the log carried too much.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 01:43:46 +02:00
jpmschweitzerandClaude 145e3c8b11 style(simulation): cargo fmt the extent-inversion tests
Hand-written test bodies in step_canvas.rs did not match rustfmt. Caught by
the pre-push gate, which is exactly its job — team-patterns.md's note that
fmt auto-fixes and clippy is a quick lead patch, rather than something agents
should pre-emptively duplicate.

No behaviour change.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 01:38:42 +02:00
jpmschweitzerandClaude 885a10af09 chore(meta): close the two harness tickets, file the gaps they exposed
T-1222 and T-1223 shipped today but were still sitting in backlog. Closed,
with what actually landed recorded on each — including that T-1223's title
premise was wrong: godot-cold-parse does not miss client/tests specifically,
it only ever sees the startup path, so the fix was a new tool rather than a
widened filter.

Three gaps opened after the reconciliation pass and had no ticket:

T-1230 — re-enable test_character_visual_sprint28 and fix the per-test
compositor rebuild that made it 37% of the client suite. Skipping it bought
39s; the skip must not become permanent, and the ticket says so with the
deadline (Phase 5 player rendering) and the better fix to prefer.

T-1231 — the enclosed-settlement and Sol GeneratorScope rulings still exist
only in a scratchlog under /tmp with no D-record behind them. Qatux flagged
this and correctly refused to invent the governance itself.

T-1232 — scene_helper.gd turns out to have no importers at all, which is why
its five-month parse breakage cost nothing. Delete or adopt: user's call.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 01:14:03 +02:00
jpmschweitzerandClaude 791600dd24 test(client): skip the 3D character compositor suite while 3D is not in play
Jeroen's call during the Phase-4 Atlas work: nothing in this suite's subject
is being changed, and it is by a wide margin the most expensive thing in the
client suite. Client run drops from 135s to 96s — 29% — from this one file.

The numbers, measured across all 86 suites:
  this suite     50.0s /   26 tests  (~1.9s each)  -> 37% of the whole run
  all 86 suites 127.1s / 1830 tests
  the other 78   ~33s  / 1804 tests
Every test instantiates a fresh CharacterVisual Node3D and loads the skeleton
.glb plus body and skin-tone assets, so the cost is asset loading per test,
not assertion count. The rest of the suite is close to free.

Used gdUnit4's own suite-skip (__is_skipped) rather than a hardcoded pass, as
requested but one level more honest: a test that returns success without
exercising anything reports as COVERAGE. It inflates the pass count and reads,
to anyone scanning a summary, exactly like a suite that ran and was fine.
The skip reports these 26 as SKIPPED in the statistics — and run-godot now
parses that field and excludes it from passed — so the omission stays visible
in every run rather than being laundered into a green number. It also
short-circuits before the test bodies, so the 50s is genuinely reclaimed
rather than merely hidden.

No test was modified. Deleting the _init() restores the suite exactly as it
was, and the comment says so, along with when to do it (Phase 5 player
rendering at the latest) and the better fix to prefer then — sharing the
compositor instead of rebuilding it per test.

Pair session with Jeroen, 2026-07-27.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 00:57:15 +02:00
jpmschweitzerandClaude 263a98f3ed fix(config): run-godot reported double the real test count, and could not see skips
Two parsing bugs in the summary, found while measuring suite times.

DOUBLE COUNT. gdUnit4 prints one "Statistics:" line per suite and then a
single "Overall Summary:" line whose numbers are the sum of all of them. The
pattern matched both shapes and summed all 87 lines, so every total was
exactly twice the truth: a full run reported 3,660 tests against an actual
1,830, and a 26-test suite reported 52. It was invisible because it doubled
UNIFORMLY — nothing ever looked inconsistent, only large. Every count quoted
from this harness, in this session and before it, was 2x.

Now prefers the Overall Summary, which is gdUnit4's own arithmetic over the
whole run and so cannot disagree with itself; per-suite summing survives only
as a fallback for a run that dies before printing it.

ANSI. gdUnit4 colourises output and the escape sequences sit BETWEEN the
fields of the summary line, so patterns matching the raw log silently fell
through to the weaker "Executed test cases" fallback — which cannot see skips
and reported a fully skipped suite as 26 FAILED. All parsing now runs against
a de-ANSI'd copy, including the load-error guards.

SKIPS are now parsed and surfaced as their own JSON field, and excluded from
passed. Counting a skipped test as passing is the same false-green shape the
harness guards exist to prevent, and it stops being hypothetical the moment a
suite is deliberately skipped.

Verified against a fully-skipped suite (26 total / 0 passed / 0 failed / 26
skipped, was 26 FAILED) and a full run (1,830 total / 1,804 passed / 0 failed
/ 26 skipped, was 3,660/3,660).

Pair session with Jeroen, 2026-07-27.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 00:56:47 +02:00
jpmschweitzerandClaude bf1976613f chore(config): enforce the parse sweep at the push gate, ahead of the suite
Placed in the pre-push hook rather than /pr-process, because the hook is where
enforcement actually lives — and notably the hook never ran godot-cold-parse
at all, so until now nothing enforced "does this script parse" for any file
outside the startup path.

Ordered BEFORE the test suite deliberately. That makes failures cheaper rather
than the gate slower: a script that does not parse is caught in ~4s instead of
after ~135s of tests that could never have covered it. A clean push pays 3.7s;
a broken one saves over two minutes.

Not redundant with the suite. gdUnit4 reports the suites that DID load as a
clean pass, so an unparseable file reads as success — guarded now in
tests/run-godot, but only for test files. The sweep covers all 226 scripts,
including the roughly half of the codebase no test ever loads.

/pr-process gains a scope note instead of a second invocation: cold-parse sees
only the startup path and filters "Cannot infer the type" (which hid a
genuinely broken file for five months), so it must not be read as a general
parse check. Per team-patterns.md the skill does not duplicate the gate.

Hooks run from .config/hooks via core.hooksPath, so this is live without an
install step.

Pair session with Jeroen, 2026-07-27.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 00:42:19 +02:00
jpmschweitzerandClaude a005e48405 feat(config): parse sweep — verify every project script parses, not just the startup path
godot-cold-parse only ever sees scripts on the STARTUP path: autoloads and
the main scene chain. That is the correct scope for the job it was built for
(Sprint 36's `Could not find base class "MetaScreen"`, a registration-ORDER
bug), but it is far narrower than the name suggests, and most of the codebase
is invisible to it. Verified by deliberately breaking a non-startup UI script
and a test file in turn: cold-parse reported "clean", exit 0, for both.

That is the second half of today's false green. A parse error in
test_step_canvas_annotation_layer.gd survived cold-parse AND survived
gdUnit4, which reports the suites that DID load as a clean pass. Two gates,
one blind spot: neither verified that a file it never opened was openable.

godot-parse-sweep opens every .gd in the project (226 today, addons and
.godot excluded) and fails on any that will not parse.

The split between the two halves is forced, not stylistic. No Godot API
reports GDScript parse failure reliably:

  - ResourceLoader.load(path, "GDScript", CACHE_MODE_IGNORE) SEGFAULTS the
    engine on a script that fails to parse — it dies on exactly the input the
    tool exists to find.
  - GDScript.new() + source_code + reload() returns a clean error code but
    detaches the script from its resource_path, so class_name, preload() and
    relative extends stop resolving: it reported 150 of 226 healthy scripts
    as broken.
  - Plain ResourceLoader.load() neither crashes nor false-positives, but
    returns a NON-null object for a broken script, so its return value is
    useless.

The engine's own stderr is the only honest signal. So the GDScript half just
opens files and makes no verdict; the wrapper scrapes the diagnosis. The
wrapper also refuses to pass unless the sweep reported completion, so a
future break in the walk cannot itself become a false green.

Unlike cold-parse, "Cannot infer the type" is NOT filtered. That filter is
precisely why cold-parse stayed silent about the file below.

First run found a real one: client/tests/util/scene_helper.gd has not parsed
since 2026-02-25 — five months — because `func(a := null, ...)` cannot infer
a type from null. Fixed with explicit `: Variant` params. Blast radius is
zero (the helper has no importers, so nothing else was taken out with it),
but it went unseen by two gates for five months, which is the point.

Full suite green at 3660.

Pair session with Jeroen, 2026-07-27.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 00:32:42 +02:00
jpmschweitzerandClaude 6547482e6d docs(meta): reconcile CLAUDE.md + CHANGELOG with the extent inversion; file 19 tickets
CLAUDE.md's Phase-4 row described the pre-inversion ladder — "every step a
server-derived data canvas at its native gridunit spacing" — which the D-255
amendment reversed. Corrected, with rung 0.5 noted as ruled (D-258) but not
implemented rather than restated there. The D-243 scale-ladder section is
deliberately untouched: scale.rs still holds the old constants, so it is
still accurate, and amending it now would make it wrong in the other
direction.

CHANGELOG gains three player-facing entries for today's shipped work, with
the whole-body-map fix carrying an explicit "still open: no rivers or lakes
yet" caveat so it does not read as finished.

Tickets T-1211..T-1229 filed: the rung-0.5 epic with its cost measurement
gating every child, the scale-constant change, Sol's GeneratorScope, the two
test-harness false greens, the make-atlas shutdown bug, two data gaps and
three cleanups. Golden regeneration is blocked on both the rung-0.5 epic and
the scale-constant change so the revalidation is paid once.

Review corrections applied to the delegated pass:

- The blocker graph was reported but never created — all 8 claimed edges were
  absent. Added. `pql ticket list --under T-1211 --unblocked` now correctly
  returns only the measurement, which was the structural point of the epic.
- A changelog entry credited T-1206, which is an unrelated open bug about
  synthetic settlements landing in open water. Re-attributed to the D-255
  amendment.
- Tickets have no --decision link to D-258/D-255. Not repairable: --decision
  exists only on `ticket new` and `refine write` rejects it. Filed upstream as
  pql FR-5 rather than worked around; the descriptions reference the records
  in prose meanwhile.

Pair session with Jeroen, 2026-07-27.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 00:18:47 +02:00
jpmschweitzerandClaude b2ef73256a fix(config): run-godot reported a suite that never ran as a pass
Found by walking into it. test_step_canvas_annotation_layer.gd had a parse
error from an earlier edit in this session, so gdUnit4 could not load it and
ran the other suites instead. The harness printed 3610 passed / 0 failed and
exit 0. Fifty tests had not run for hours and nothing said so — the full
suite reports 3660 with the file repaired, and that difference was invisible.

Two states are now hard harness failures rather than test results:

  load_error — a suite failed to LOAD. Any pass count excludes it, so a green
  number is a lie. The hint names the offending file.

  no_tests  — zero tests executed. A run that executes nothing can never be
  a pass; previously a mistyped --filter printed "Tests passed".

Both add a "harness_error" field to the summary JSON and exit 2. The exit
code cannot inherit gdUnit4's, which returns 0 in both states — that is
precisely why they were invisible.

Verified by injecting each failure rather than by reasoning about it. The
load_error guard was checked in the case that actually matters: one broken
file among many, where total stays large and failed stays zero. That run now
reports 3610/0 WITH harness_error and exits 2, where before it was
indistinguishable from success.

Also repairs the file itself: a missed set_frame() argument (the parse error),
and a cell-placement test still asserting pre-inversion spacing. Rewritten to
assert the invariant that survives the extent inversion, the viewport aspect
ratio and panning — half the SHORT axis is half a rung cell — instead of a
literal. Two things it deliberately does not assert, both of which the
previous version got wrong: "the corner is half a district away" holds only
on a square canvas, and the canvas is one district WIDE without sitting ON a
district. It is a free-floating window centred wherever the player panned;
zoom is stepped, pan is continuous. A rung names a scale, not a cell you are
inside. A second test pins that with a deliberately unaligned world centre,
so a future change that snaps the canvas to the rung lattice — making pan
step instead of slide — fails here.

Pair session with Jeroen, 2026-07-27.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 00:12:26 +02:00
jpmschweitzerandClaude d58397c59f docs(meta): D-255 amendment — the extent inversion, shipped today
D-255 described a system that stopped existing this morning. It said a rung
fixes gridunit SPACING and that spacing is "never viewport-derived"; both are
now exactly backwards. Anything reading it — a refinement agent, a reviewer,
a future session — would have built against a fiction with no way to tell.

Records the inversion (a rung fixes EXTENT, the shorter viewport axis spans
one cell of that level, spacing falls out), Global moving from the body's
region grid to a viewport-sized 2:1 canvas, Region leaving the orbital derive
set, and the display-ratio band collapsing to a uniform 2x2.

Two corrections matter beyond bookkeeping.

D-255 justified Global's D-226(d) legality by it being COARSER than the region
grid. It is now finer — 40.8 km against 204.8 km. The conclusion survives,
since D-226(d) prohibits tile-level maps and caps at settlement/quarter
granularity and 40.8 km is twenty times coarser than a district, but the
premise is dead and nothing downstream should lean on it.

And the always-keep cache figure is invalidated. The "~8.85 MB across 267
bodies, trivially process-resident" number assumed ~18,073 cells per body; a
viewport-sized Global is 460,800 on a 1080p display, which is 25x — about
226 MB, and roughly 900 MB on a 4K display, with per-body derive going from
~16-21 ms to about half a second. An always-keep tier whose size scales with
the user's monitor is the wrong shape, which is an independent argument for
D-258: rung 0.5 is fixed-resolution and baked, and Global becomes a view of
it rather than a canvas retained in its own right.

Also records the two retired mechanisms (the S2 station-spacing floor,
cap_extent_to_body superseded by rung liveness) and states plainly that
Global is still broken — correctly sized now, but with no hydrology until
rung 0.5 lands. Region is eyeball-confirmed working.

Pair session with Jeroen, 2026-07-26.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 23:49:25 +02:00
jpmschweitzerandClaude 1c45cd2ec8 fix(client): Global rung derived its canvas from a pre-layout viewport
Eyeballed on Lendel: the Atlas opened on a Global map that was literally two
cells — one green, one blue — stretched across the window, reporting
19,598.512 km/gridunit, which is exactly half the body's circumference.

Two bugs, both of which the D-255 extent inversion turned from harmless into
fatal.

enter() fires its first request BEFORE this Control is laid out, and a
not-yet-laid-out size is not always exactly Vector2.ZERO — a few stray pixels
sailed past the `== Vector2.ZERO` guard, so the viewer asked for a 2x2
gridunit canvas and the server's 2:1 fit floored it to 2x1. That never
mattered while Global discarded the requested extent and took its cell counts
from the body's region grid; the moment the request became the canvas size, a
transient layout artefact became the map. Any viewport below a plausible
panel size is now treated as not-laid-out.

And Global was excluded from the refetch settle entirely, so a canvas born at
the wrong size could never heal however the window was resized. That
exclusion was correct when no viewport could change Global's extent. Global
now takes the SIZE refit like every other rung, but still never the pan
re-float — its canvas is whole-body and origin-anchored, and the server
ignores `center` for it.

Both have regression tests. The second asserts on _world_center rather than
_view_offset, because _recompute_canvas_transform() legitimately re-centres
the offset on any canvas adoption and would have made the test pass for the
wrong reason.

Worth noting for the class: no test written today could have caught this.
Every one supplies an explicit viewport. The bug lived entirely in the gap
between "scene loads" and "layout completes" — a seam a live launch
exercises and a unit test does not.

Also stages governance/README.md's pql-maintained record index (D-258).

Pair session with Jeroen, 2026-07-26.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 23:43:32 +02:00
jpmschweitzerandClaude 02fe71e9f3 docs(meta): D-258 amendment — one shore, not two; tidal energy over salinity
Jeroen, eyeballing the lakes: "they did not seem to run the same coastline
code as ocean does". Correct, in two separate ways.

The coastline warp was ocean-only. invent_primitives displaces the sample
through coast_warp_px before reading the ocean mask, but the lake test read
the UNWARPED position, so ocean coasts got invented bays and capes while lake
shores traced the bare elevation contour. It cannot be fixed by warping the
lake sample alone: a lake is where a filled surface sits above terrain, two
reads that must agree, so moving one and not the other puts water on
hillsides or holes inside lakes. Both surfaces move together in the rung-0.5
pass, or neither does.

And shore morphology was structurally unreachable at a lake edge. Every gate
keyed on ocean_fraction_q, which is always 0 in a lake basin because lakes sit
above sea level. Ruled: lakes get full shore morphology — cliffs, beaches,
deltas. Gates key on proximity to water, not to ocean. No new vocabulary
needed; MorphologyZone already carries Fjord, Delta, Wetland, CliffCoast and
DuneStrand.

The interesting part is what separates the sea-flavoured types, because it
isn't salinity. A delta builds land outward where the river deposits faster
than the water removes; an estuary is the inverse, a drowned valley widening
seaward. The discriminator is tidal energy: the microtidal Mediterranean is
ringed with deltas (Nile, Rhone, Po) despite being salt, while the macrotidal
Atlantic gives estuaries (Thames, Severn, Gironde). So lakes always resolve to
Delta — and so does a tideless sea, which an ocean-vs-lake switch would have
got wrong. Tidal energy governs TidalFlat too, so one derived quantity
replaces two stipulations and no "is it the ocean" branch survives.

Salinity is a property of water, not a landform, and is excluded from
morphology entirely. Derive it from below-sea-level connectivity if gameplay
ever needs it. Parked.

Pair session with Jeroen, 2026-07-26.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 23:38:18 +02:00
jpmschweitzerandClaude 8a9877c4bf docs(meta): D-258 — rung-0.5 expanded layer, one derived base for the ladder
Every Atlas rung currently re-derives from the heightmap independently. D-258
inserts one deterministic whole-body layer between the baked inputs and the
ladder, and points every deeper rung at it instead of at the source files.

Two failures forced it. The Global rung was deriving a five-class hue map
while a per-body artefact labelled "clean color hypsometric render (display /
Atlas)" sat unused beside it. And hydrology was not derivable at all: flow is
a global solve, so no per-window derivation could produce a coherent water
system — Region carried no courses and lakes could not fill.

The record also fixes what the reliefmap IS. It is a plurality, not a
classification: each cell names the biome dominating ~38 km, a vote already
counted and discarded. So rung 0.5 un-summarises it rather than upscaling it,
which binds three consequences — biome edges are gradients never lines
(D-243's climate rule extended to biome), descending reveals composition
rather than sharpness, and invented detail must downsample back to the
summary it came from. That last one is the acceptance gate for any sub-biome
algorithm.

Rung 0.5 is a stored derived artefact and therefore a named carve-out from
D-227's derive-don't-store. The boundary is principled: D-227 governs what is
LOCALLY computable, where storage is pure cost. A whole-body flow solve is not
locally computable by construction — that is why it must exist — so storage
here buys correctness, not convenience. Everything below rung 0.5 stays
derive-don't-store.

Record precedes implementation; no code changes here. Complements the same
session's D-255 extent inversion, which governs how a canvas is sized rather
than what it is made of.

Pair session with Jeroen, 2026-07-26.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 23:24:47 +02:00
jpmschweitzerandClaude 9c8fcc2f95 feat(client): size the Global rung to the viewport, not the region grid
Global took its cell counts from `global_cell_counts()` — one gridunit per
region — and discarded the requested extent entirely. On GJ380c that produced
a 191x95 canvas built from a heightmap stored at 512x256: roughly seven times
the available cells thrown away before anything was drawn. The count also
shrank as REGION_M grew, so tuning the scale ladder silently degraded the
opener, which is why the top of the ladder got worse rather than better as
the ladder itself was refined.

Global now fits the largest 2:1 canvas inside the requested extent. It cannot
take its ASPECT from the viewport — the canvas is equirectangular whole-body,
360 degrees of longitude by 180 of latitude, and must stay 2:1 or the cells
stop being square and the map shears — so the existing letterbox absorbs the
remainder. A hostile extent is still clamped; sizing to the request is not
trusting the request.

Global also joins the deep display ratio, making the band uniform. At 5 px
per gridunit a 1920 px window asked for 384 cells across a body whose
heightmap holds 512x256 — discarding stored detail to save work already done.
At 2 px it asks for 960, which is heightmap-native: nothing thrown away,
nothing invented, and the same screen area filled either way.

A body with no radius is not a sphere (asteroid belt, oort cloud) and has no
equirectangular surface to fit. Those degrade to the region grid — a visibly
degenerate 1x1 canvas — rather than a plausible-looking lie at whatever size
the viewport happened to ask for.

project.yaml 0.4.3 -> 0.4.4 invalidates the persisted step-canvas disk cache.
District/Quarter/Block/Chunk kept identical 960x540 cell counts through the
extent inversion, so their cache keys are byte-identical while a District
canvas now covers 3.6 km of ground instead of 1,966 km — a warm cache would
silently serve pre-inversion canvases.

Global still rides the orbital derive, so it carries no courses yet; that is
the next step and is deliberately separate, being a cost question over the
whole body rather than a sizing one.

Pair session with Jeroen, 2026-07-26.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 22:45:40 +02:00
jpmschweitzerandClaude 0a0419abcc feat(client): invert the Atlas rung relation — rung sets extent, not spacing
A rung used to fix the gridunit SPACING, with the canvas extent falling out
of spacing x cell count. That is why the top of the ladder was unusable: at
REGION_M spacing a viewport-sized canvas spanned ~251,658 km — six times
around a rocky body — so the Region rung capped to the body and redrew the
Global picture pixel-for-pixel. "Global and region look the same" was not a
rendering bug; it was this relation, stated in metres.

Inverted: a rung fixes the EXTENT and the spacing falls out of the canvas
size. The shorter viewport axis spans exactly one cell of the rung's level,
so a widescreen window shows more ground on the long axis rather than less
on the short one. Every rung now shows the ground its name promises —
Region 262x466 km, District 4.1x7.3 km — and the canvas cell count is
viewport-driven and identical at every rung, so derive cost no longer varies
with depth and resize is free.

Consequences that fell out of the inversion rather than being chosen:

- Region leaves the orbital derive set. It was envelope-only because at
  251,658 km nothing finer made sense; at 262 km it is a genuine provincial
  map and takes the full courses-aware derive. Region having no rivers at
  all was much of why the top of the ladder read flat. It also joins the
  deep display ratio for the same reason.
- The S2 station-spacing floor is deleted, not retuned. It guarded an
  O(1/spacing) blowup that the inversion makes structurally impossible (the
  canvas cell count is now constant across rungs, so stations-per-course is
  bounded however deep you scroll). Kept, it would do active harm in the
  opposite direction: a 2,048 m pitch across a 3.6 km District canvas places
  two stations and draws every river as a straight line. Station placement
  gets its own generator pass.
- cap_extent_to_body is superseded and now a documented no-op. A canvas can
  no longer over-request a body by construction. The residual question —
  whether a rung's cell exceeds the whole body — is liveness, not capping,
  and is_rung_live_on_body() answers it by omitting the rung. Empirically it
  never fires on inhabited content: all six rungs are live on all 271
  populated bodies with a radius.
- snap_to_gridunit no longer truncates its multiplier to int. Post-inversion
  the deep rungs run sub-metre (Chunk ~0.12 m at a 1080 px short axis), where
  int(spacing) floors to zero and would collapse every request centre onto
  the origin.

Both sides derive spacing from the same three inputs (rung, echoed cell
extent, body radius) rather than one telling the other, so there is nothing
to keep in sync beyond the constant table itself. Body radius already
reaches the viewer via enter(); no wire change.

Pair session with Jeroen, 2026-07-26. D-243/D-255 amendments to be backfiled.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 21:38:08 +02:00
jpmschweitzerandClaude f5b34131b0 chore(meta): update changelog
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 17:12:52 +02:00
jpmschweitzerandClaude 36482de5d6 fix(ui): Atlas fetch readout, and one settle timer for resize and pan
Two viewer fixes from the same pair session; they share
step_canvas_viewer.gd so they land together.

FETCH READOUT. A cold derive takes seconds and the map gave no honest
sign of it. Root cause found while building the replacement: a Control
paints its own _draw() BEFORE its children, so everything the viewer
drew itself — the old DERIVING TERRAIN label AND the pending wash —
was painted UNDER the terrain canvas, visible only when no texture
existed at all, i.e. never in the slow-fetch case they existed for.
That has been the state since the stepped viewer shipped. The readout
now lives in its own overlay node added after the canvas: a centered
implant-idiom panel, DOWNLOADING MAP DATA, indeterminate sweep, 250 ms
grace so cache hits never flash it, held canvas still drawing beneath.
Indeterminate by design — the server reports no derive sub-steps, and
a progress fraction we cannot source would be invented.

ONE SETTLE TIMER, THREE TRIGGERS. The viewer never re-requested a
canvas on resize, so one derived for a smaller window letterboxed
forever in a bigger one — the map not filling the frame. And the
pan-edge refetch fired from inside the per-frame pan loop the instant
its threshold was crossed, so a held edge-scroll issued a fresh
request AND snapped the view on every frame past it. Both are the same
event: the user is still moving. A shared one-shot timer now collapses
them into a single request at the resting state, with a hard pan
threshold that still fires immediately when the canvas edge is about
to enter view (waiting there would show empty background), and
drifting back inside the soft threshold cancels the pending request.
Resize reaches this Control identically whether the OS window or a
diegetic in-implant parent changed, so both sources are covered.

Two new tests pin the soft path (schedules, does not refloat or snap)
and its cancellation; the pre-existing threshold test was verified to
still discriminate — its drift trips the new hard threshold — rather
than passing vacuously.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 17:12:05 +02:00
jpmschweitzerandClaude ae03ea2de7 perf(ui): Atlas deep rungs draw one gridunit per 2x2 px block
DISPLAY_RATIO_DEEP 1.0 -> 2.0, so a viewport-fit request at District/
Quarter/Block/Chunk asks for ~4x fewer gridunits (1920x1080: ~2.07M
cells -> ~518K) and the server-side derive cost falls with it. Texel-
exactness is preserved — the ratio stays a whole number of screen px
per gridunit, so every source texel still lands on whole pixels; only
the block size changes. Non-integer ratios are not an option here:
they reintroduce exactly the sub-pixel blur D-255's texel-exactness
exists to prevent.

Judged live by Jeroen against the 1x1 build (pair session): the deep
rungs read as crisp larger pixels rather than blur, and the speedup is
substantial. Looks were the gate.

Both transport tests that pinned the old literal now assert the
RELATIONSHIP instead — deep rungs share the constant, viewport-fit
divides by it — plus a new guard that the ratio stays whole, so the
value remains tunable without editing tests that are not about it.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 17:11:44 +02:00
jpmschweitzerandClaude Fable 5 b58b02c98a chore(meta): pql changelog — idmap row (T-1206 flow)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 15:28:25 +02:00
jpmschweitzerandClaude Fable 5 dc403d7f73 chore(meta): changelog + pql — T-1197 done (PR #217 merged, araminta re-accepted)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 15:24:26 +02:00
jpmschweitzer 3821e6718f Merge remote-tracking branch 'origin/main' into header-ghost-fix 2026-07-26 15:21:11 +02:00
jpmschweitzerandClaude Fable 5 c431780164 fix(client): fog perf test measures min-of-7, not median-of-5 (T-1210 done)
Three flakes in one day (0.549/0.503/0.638 vs the 0.5ms budget) proved
median-of-5 (T-1092's mitigation) insufficient when a concurrent cargo
build inflates all samples together. The assertion asks whether the
CODE meets the D-059 budget — load can only inflate wall time, never
deflate it, so the minimum is the least-noise estimator of code
capability, while a real regression shifts the minimum too. Budget
unchanged; regression-catching power preserved. 46/46 verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 15:17:51 +02:00
jpmschweitzerandClaude Fable 5 a4d9a4fa09 fix(ui): PR #217 review fixes — derived legend offset, 960x540 goldens, non-overlap tests
The legend's reposition() now derives its Y from the header panel's
MEASURED bottom plus a named 12px gap (new accessors on the viewer;
deferred recompute so it reads settled layout, refreshed on header
content change) — the old hardcoded 60.0 sat 17px inside the wrapped
header's real 77px bottom, fusing the panels. Pixel-proven at the
evidence center: bottom=77.0, legend top=89.0, gap=12.0 exact. All 13
goldens regenerated at the DOCUMENTED 960x540 (the 1280x720 rider was
my own CLI flag, not a config; PIL-verified) with two-run byte
stability. Two structural tests pin the header/legend relationship
(T-1192 precedent), proven failing-first against the old constant
(-17.0px reported), and the legacy test that pinned the literal 60.0
now asserts the derived relationship so it cannot re-enforce drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 15:13:11 +02:00
jpmschweitzer 34ecdad46f Merge remote-tracking branch 'origin/main' into header-ghost-fix 2026-07-26 14:47:21 +02:00
jpmschweitzerandClaude Fable 5 0a51f98666 chore(meta): pql changelog — T-1197 to review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 14:47:13 +02:00
jpmschweitzerandClaude Fable 5 41144f317f fix(ui): Atlas header gets its implant panel scrim — legible on any terrain (T-1197)
Root cause was not the suspected fade race — no animation exists
anywhere under client/ui/implant (grep-proven, and settle+2 vs
settle+90 captures were byte-identical). _build_screen_header() was
the single ImplantHeader call site in the codebase that added the
header bare instead of through ImplantPanel.add_component(), so its
fixed light-gray text washed out against pale terrain (Quarter upland
scatter, District olive) while reading fine on dark ocean. Wrapped in
an ImplantPanel like every sibling — the theme's panel_bg scrim makes
it legible everywhere. One committed golden (atlas_GJ820Bc_District)
already carried the ghosting baked in, confirming the bug was static;
all 13 goldens regenerated per the header change, two independent
live-server runs byte-identical (T-1157 stability discipline).
Verified live at the original evidence center: header crisp at
settle+2/+30/+90.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 14:46:55 +02:00
jpmschweitzerandClaude Fable 5 59d6ff57c3 chore(meta): pql changelog — batch 9 activated (T-1206/dudley, T-1197/stig)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 14:36:07 +02:00
jpmschweitzerandClaude Fable 5 47279184d2 chore(meta): changelog + pql — batch 8 closed (T-1098 done via PR #216)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 14:30:05 +02:00
jpmschweitzerandClaude Fable 5 9c5aa79852 fix(simulation): PR #216 review fixes — footprint-wins enforcement, discriminating tests
Finding 2 became a real code fix: interstitial_fill_into now enforces
the footprint-wins conflict rule (column_has_voxel range probe) — the
FilledChunk absence contract was previously a documented promise the
code didn't keep against conflicting inputs; pinned by a fully-
overlapping-leaf test asserting per-tile resolution. The tautological
overlap test replaced with a real rects_overlap() geometric helper
(itself sanity-tested) applied pairwise. The degenerate-setback fix is
now a standalone pure fn shrink_lot_or_interstitial with four boundary
tests — honestly documented as unreachable from live traffic today
(every min_lot exceeds every setback), a robustness guard for future
recalibration. Both sub-chunk clip tests now reconstruct the full
32-tile union across the seam (disjoint + complete), including the
pre-existing footprint clip test (leave-cleaner). Brief's ChunkLayout
claim tightened to the verified no-production-consumer statement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 14:28:40 +02:00
jpmschweitzer 5b5cdc85a3 Merge remote-tracking branch 'origin/main' into interstitial-fill 2026-07-26 14:13:31 +02:00
jpmschweitzerandClaude Fable 5 d49871e22d chore(meta): pql changelog — T-1098 to review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 14:13:24 +02:00
jpmschweitzerandClaude Fable 5 1d676a91f8 feat(simulation): interstitial fill — ground-tile character between building footprints (T-1098)
The BSP leaves that lose the D-233 coverage roll in
subdivide_block_footprints were computed and discarded; they are now
surfaced as the interstitial rect set (BlockSubdivision), making the
ground-plane classification exhaustive by construction: footprint /
interstitial / street-margin-or-reserved. FillChunk carries the leaves
plus a minimal BlockFillContext (interstitial_character + setback_tier,
re-derived at block level via the existing pure fn); FilledChunk gains
a sparse interstitial map whose absence contract is stated on the
struct (missing key = footprint/street/reserved, never unknown). The
pure resolution maps OperationsSurface (D-233) first, else setback_tier
onto five of D-235's seven interstitial values — dock_slip/market_pad
have no specified trigger in the record and point at T-1209 rather
than an invented mapping. Design brief with the geometry model at
docs/architecture/interstitial-fill-t1098.md (lead-approved
checkpoint). Bonus fix: a degenerate setback shrink previously vanished
from BOTH lists silently; it now falls through to interstitial. 14 new
tests; full cargo test green incl. all golden harnesses; purity per
T-987 (plan-time compute, pre-resolved work items, no cache reads).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 14:13:08 +02:00
jpmschweitzerandClaude Fable 5 879c9dfb60 fix(briefings): correct five more f4c72e148 mis-citations in gestalt.md (T-1205 done)
D-130->D-131 (broad life-verb vocabulary, x3) and D-118->D-133 (skills
affect outcome, x2), each verified by full record-body match and lead
spot-check. The T-1205 audit body-matched all 66 unique decision ids
across the remaining 11 briefings — the other 10 are citation-clean.
Final f4c72e148 tally: 16 mis-citations across 6 of 18 briefings, all
fixed (T-1199 + T-1205). Follow-ups: T-1208 (eight wholesale-stale
briefings, rewrite backlog), and tiger.md confirmed deleted/off-roster.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:49:59 +02:00
jpmschweitzerandClaude Fable 5 447033857c chore(meta): pql changelog — batch 8 activated (T-1098/dudley, T-1205/clerk)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:44:16 +02:00
jpmschweitzerandClaude Fable 5 63ec09b089 chore(meta): changelog + pql — batch 7 closed (T-1116 done via PR #215; T-1207 Wave-2 overlays filed)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:40:28 +02:00
jpmschweitzer 8e82bcafd0 Merge remote-tracking branch 'origin/road-routing-coastal-fix' 2026-07-26 13:39:28 +02:00
jpmschweitzerandClaude Fable 5 a378ef862c chore(meta): pql changelog — T-1206 filed (synthetic-overflow ocean-guard gap, from PR #215 review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:36:26 +02:00
jpmschweitzerandClaude Fable 5 de8bcf4ebb fix(simulation): PR #215 review fixes — hop-unit surcharge, D-210 amendment, citation + gap
The surcharge is now COASTAL_ACCESS_SURCHARGE_HOPS_PER_RING=1 added
directly to length_cells (a pure hop count) — the old cost-unit
constant div_ceil'd through MIN_CELL_COST silently produced 4 hops per
ring, worst-case +24 (double the waypoint threshold) for physically
short edges; worst case is now 6. A formula-pinning test asserts both
the arithmetic and the constant. GJ251c's repro tightened to the
documented 2 edges. The always-land citation now points at the real
guarantee (features.rs::extract_attractors, D-209) — and checking the
D-211 Phase-4 synthetic-overflow path exposed a real gap: it has no
ocean-mask guard at all (T-1206 filed); documented, not papered over.
D-210 gains a dated amendment recording the surrogate-anchor-at-cost
carve-out and the relaxation-over-nudge adjudication. The bare 100
dependency dissolved with the unit fix. Edge counts on both repro
bodies verified unchanged (reachability was never affected).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:34:43 +02:00
jpmschweitzerandClaude Fable 5 0039bda184 style(simulation): cargo fmt
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:19:02 +02:00
jpmschweitzerandClaude Fable 5 1ec5cb4fd5 fix(simulation): clippy — erasing_op row-major literals, range-contains in T-1116 tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:16:54 +02:00
jpmschweitzer 5dd703ac6f Merge remote-tracking branch 'origin/main' into road-routing-coastal-fix 2026-07-26 13:13:52 +02:00
jpmschweitzerandClaude Fable 5 8247ba1ded fix(simulation): coastal-cell routing relaxation — roads return to water-heavy bodies (T-1116)
A routing cell folds up to 64 native pixels, so a coastal settlement's
own land pixel (placement always filters !ocean_mask) can sit inside a
water-majority cell that RouteGrid marks IMPASSABLE — and astar()
hard-returned None for every pair touching it, zeroing whole road
graphs (GJ251c: all 3 placements; GJ380c: Sethvale). The fix relaxes
only the start/goal anchor lookup: nearest_passable_cell (ring BFS,
deterministic row-major tie-break, bounded at COASTAL_ANCHOR_MAX_RING=3)
finds a surrogate anchor and prices it via COASTAL_ACCESS_COST_PER_RING
— a short, honestly-costed access road, never a free water crossing.
IMPASSABLE semantics untouched everywhere else (D-210 transit costs,
open-ocean). The placement-nudge alternative was rejected: it would
move Layer-3 state D-211 promises is seed-derived, for no gain.

Boundary semantics pinned by test: exactly-half-water cells stay
passable (strict-majority rule); a settlement with no passable cell
within the search ring degrades to an isolated 0-edge node, never a
panic or fabricated route. Failing-first repro on real bodies
(GJ251c 0->2 edges, GJ380c 0->1) via the real cascade entry point,
plus same-seed determinism. Full cargo test green; believability and
cascade goldens verified unaffected (Layer-2-only change).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:05:35 +02:00
jpmschweitzerandClaude Fable 5 f60cb1b3b9 chore(meta): changelog — T-1204 done (PR #214 merged, first production assets)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:04:55 +02:00
jpmschweitzer c9eefe9efe Merge remote-tracking branch 'origin/furniture-promotion' 2026-07-26 13:04:33 +02:00
jpmschweitzerandClaude Fable 5 82d44cf483 docs(assets): codify the authored-footprint rule (araminta ruling, PR #214)
footprint_tiles is an authored value, never derived from normalized
mesh geometry — [1,1] defaults only for plausibly-single-tile classes;
ordinarily-multi-tile classes require an explicitly stated footprint at
promotion. Her exact wording in furniture-props.md §3; conventions.md
§4 gains the authored-not-derived sentence pointing at the full rule
(and a pre-existing 'Single Sunday-tile' typo fixed in passing). The
[1,1] values shipped for the four promoted items stand as ruled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 13:03:55 +02:00
jpmschweitzerandClaude Fable 5 d20ae38b74 chore(meta): pql changelog — T-1204 to review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 12:55:43 +02:00
jpmschweitzerandClaude Fable 5 2f87c1a65f data(assets): promote the four spike GLBs — first production environment assets (T-1204)
table_baroque, chair_modernist, desk_scifi (furniture, with mask
sidecars) and lion_statue (props, non-tintable) enter
client/assets/models/ under the category-first convention, byte-
identical to the spike sources (MD5-verified; spikes/ untouched,
vw_beetle excluded per the Q-067 deferral). Postprocess verified
already-applied by Blender node-graph inspection (mat_primary,
roughness=1.0/specular=0.0 shader inputs — the convenience properties
read stale defaults, the graph is authoritative), so not re-run. All
four registered in manifest.json at 'planned' with footprint_tiles
[1,1] (a flagged judgment call — normalized spike geometry erases
real-world scale; the brief ruling goes to araminta in review).
In-engine proof: clean headless import + ResourceLoader load +
instantiate for all four. Discovery documented in conventions.md §2 +
glb-gen SKILL.md: Godot externalizes the GLB's embedded texture as a
<model>_Image_0.png sidecar referenced by binary UID — it must be
committed or loading breaks with an undiscoverable dependency error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 12:55:28 +02:00
jpmschweitzerandClaude Fable 5 1f199b9717 fix(briefings): correct eleven decision-id mis-citations from the f4c72e148 pass (T-1199)
The 2026-03-13 'update all 18 briefings' commit transcribed a family of
adjacent-id slips while summarizing the Where's the Fun? workshop batch,
copied forward by every later maintenance pass: D-126<->D-134 swapped,
D-136 written as D-131, D-137 as D-136, D-130 as D-131, D-120 as D-137.
Fixed in gore/mellanie/ozzie/stig/nigel briefings; every correction
verified against the record's actual body (D-130's emergent-moral-arc
and D-120's no-skill-ceiling were resolved beyond the clerk's audit by
body-matching — the nigel D-137 fix would otherwise have created
contradictory citations in one file). Governance records untouched
(they were always correct); sprint archives verified already-correct.
Remaining 13 briefings from the same commit: T-1205.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 12:50:56 +02:00
jpmschweitzerandClaude Fable 5 74e95148ae chore(meta): pql changelog — batch 7 activated (T-1116/dudley, T-1204/justine, T-1199/clerk; Q-119 narrow ruling on T-961)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:16:24 +02:00
jpmschweitzerandClaude Fable 5 3f63ccf8ce chore(meta): changelog + pql — batch 6 closed (T-1049/T-1051/T-1053 done; stories T-1200..T-1204 created under T-961)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:11:44 +02:00
jpmschweitzer 8db1d92733 Merge remote-tracking branch 'origin/asset-class-stories' 2026-07-25 23:10:38 +02:00
jpmschweitzerandClaude Fable 5 660f4a703f docs(assets): PR #213 review fixes — DoorSpec fields, spike inventory, axis honesty, D-154
doors.md now presents D-231's actual struct shape (initial_state
Open/Closed/Locked/Sealed; TemporalWindow lives in the independent
credential field) and attaches the Phase-5 runtime bridge to
initial_state only. furniture-props.md inventories all five spike GLBs
— acceptance is 4 promotable (3 furniture + lion_statue), vw_beetle
explicitly excluded to the Q-067 deferral. deferred.md + station-walls
gain the axis-completeness note: roof/facade/street textures ride the
wall-brief convention as a same-family follow-on, so the register
accounts for all four D-235 axes. The briefing's D-257 pointer
corrected to palette.md §2.1–2.2, and both the briefing and palette.md
(leave-cleaner, gap inherited from PR #211) now carry D-154's amendment
to D-033 — relationship colors display only in the insert/perception
overlay; normal gameplay is a uniform #1a1a1a outline (D-150).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:10:09 +02:00
jpmschweitzerandClaude Fable 5 18349d8619 chore(meta): pql changelog — T-1049/T-1053 to review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:01:23 +02:00
jpmschweitzerandClaude Fable 5 2e9bbcaaf2 docs(briefings): rewrite araminta.md around the Phase-4 landscape (T-1053)
Replaces the 2026-03-13 v0.2-pivot briefing (sprite pipeline, dropped
scope framing, pre-Atlas) with the current shape: palette.md +
conventions.md as the live authorities the briefing points at rather
than restates, her standing rulings (category-first naming, D-257
toon/glazing) marked as hers to extend, D-235 co-maintenance with Miri,
the wardrobe pipeline as reference model, D-255 stepped Atlas as active
co-designed work, and her real open items (T-1049, D-257 shader,
Q-119, T-1198). Every v0.2-era decision citation verified before
dropping: D-114/D-117 superseded; D-119/D-122/D-128/D-135 live but not
hers; D-033/D-045 kept. D-126/D-131 found REPURPOSED to unrelated
content since the era docs cited them — the ID-drift sweep is T-1199.
Lead added the briefs/ + deferred.md pointers (araminta's cross-agent
note; her SendMessage to the author couldn't be delivered).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:01:09 +02:00
jpmschweitzerandClaude Fable 5 2bac820c77 docs(assets): per-class production briefs + template + formal deferral register (T-1049)
The asset-brief template (scope/visual-reference/naming/gen-path/
acceptance, one page) and five real briefs — station walls, rural walls,
doors, floors, furniture/props — each citing its D-235 axis tokens and
palette.md register rows (or the D-257 toon default), conventions.md
naming/mask/footprint rules, and the image-gen -> glb-gen -> promotion
path with concrete acceptance counts. Doors carry the negative
no-state-frame-files constraint (runtime mechanism stays Phase 5);
furniture/props' first acceptance is promoting the three unpromoted
spike GLBs. deferred.md is the formal register for the T-1051 four
(lamp posts, barns, TVs, billboards) + cars/Q-067, with rationale and
revisit triggers — satisfying T-1051's own 'or record formal deferral'
deliverable. Child stories under T-961 are created lead-side at merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 23:00:51 +02:00
jpmschweitzerandClaude Fable 5 e70c3efeee chore(meta): pql changelog — T-1199 filed (D-126/D-131 ID-drift sweep, from T-1053 finding)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:58:44 +02:00
jpmschweitzerandClaude Fable 5 9afcff0b54 chore(meta): pql changelog — batch 6 activated (T-1049/araminta, T-1053/qatux; T-1051 deferral ratified)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:55:21 +02:00
jpmschweitzerandClaude Fable 5 a223af66fb chore(meta): pql changelog — batch 5 closed (T-1196 PASS+done; T-1197/T-1198 filed from araminta observations)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:53:55 +02:00
jpmschweitzerandClaude Fable 5 5d00a1d2ee chore(meta): pql changelog — T-1196 active, capture center probed (GJ1002b massif)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:47:12 +02:00
jpmschweitzerandClaude Fable 5 4df5f132b9 chore(meta): pql changelog — T-1157 done (PR #212 merged)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:38:59 +02:00
jpmschweitzerandClaude Fable 5 72b15ab13c chore(meta): update changelog
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:38:49 +02:00
jpmschweitzer 19bfe811f8 Merge remote-tracking branch 'origin/capture-harness-redesign' 2026-07-25 22:38:21 +02:00
jpmschweitzerandClaude Fable 5 feef7aa1c5 fix(client): PR #212 review fixes — true on-screen footprint_px, self-describing shot coverage
Tyre finding 2: _log_atlas_view_transform's footprint_px now logs the
real on-screen pixel footprint (StepCanvasTransport.canvas_footprint_px
* canvas_scale — the same public pure function the terrain layer uses),
with the old cell-count field kept as canvas_cells. Live-verified the
divergence the fix exposes: Global logs footprint_px=(1528, 760) vs
canvas_cells=191x95 — the T-1192 fit-multiplier class the log exists to
root-cause, previously invisible under the mislabel.

Tyre finding 3: atlas_shots.json's one_shot_per_body note replaced by
rung_coverage stating the true distribution — 13 goldens across 7
bodies (per-rung coverage map, GJ380c's 3 shots as the invariance-proof
body, curated-subset rationale per T-1121). Corrects the prior commit
message's '12 new goldens' miscount: the true count everywhere is 13,
verified against shots array, visual.json entries, and PNGs on disk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:36:03 +02:00
jpmschweitzerandClaude Fable 5 deeec94ec9 chore(meta): pql changelog — T-1157 review transition (write-through landed main-side via FR-4 vault resolution)
Tyre's PR #212 finding 1: the review-status row existed only in a live
DB, unrecorded in any git-tracked changelog — a rebuild reverted it.
Root cause: pql run from the capture worktree resolved the MAIN vault
(FR-4), so the write-through row landed here, uncommitted. Committing
it main-side makes the transition rebuild-safe; branch and main
changelog rows union at merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:22:30 +02:00
jpmschweitzer 28a0b3e2af Merge remote-tracking branch 'origin/main' into capture-harness-redesign 2026-07-25 22:13:49 +02:00
jpmschweitzerandClaude Fable 5 a45d4a5f58 feat(client): capture harness redesigned for the stepped Atlas — (body, rung) goldens (T-1157)
_setup_atlas_golden_shot and _run_atlas_matrix rebuilt on the real D-255
surface: nav.push('regional') through AtlasApp's own body-selection tail,
then StepCanvasViewer.jump_to at a fixed center (inventory item 5) — no
shim over the retired continuous-zoom API. is_pending()-aware bounded
settle (mirroring atlas_agent_driver.gd) and view-transform logging
(inventory item 3) wired into both capture paths. The 12 z2_0/z4_0/zfit
goldens are replaced by 12 (body, rung) goldens captured live against
fresh --test-mode servers; atlas_shots.json/visual.json re-keyed;
atlas_gen_open's stale gen_l1_* overlay ids fixed to gen_dw_temp.

Verification: two consecutive District runs byte-identical, and a
cold-vs-warm disk-cache invariance proof on both terrain draw branches
(Global/NEAREST, District/LINEAR) — byte-identical either way, so
capture output does not depend on the shared user://atlas_cache state.
DEVOPS.md's real-rendering exception note now records the fold-target
mapping for the smoke file T-1182 already deleted (Global + District
goldens exercise its two real-pixel draw branches). Old legacy-tracked
.import sidecars go with their PNGs; new goldens ship bare per
.gitignore's client/**/*.import rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 22:13:37 +02:00
jpmschweitzerandClaude Fable 5 ae45d5e21a chore(meta): pql changelog — batch-6 prep (T-1049 re-scoped, T-1053 refined, T-1196 body pool recorded)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 21:00:46 +02:00
jpmschweitzerandClaude Fable 5 7f704a08c7 chore(meta): pql changelog — visual track closed (T-1050, T-1052 done)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 20:55:15 +02:00
jpmschweitzerandClaude Fable 5 bea6c5fc4f chore(meta): update changelog
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 20:52:27 +02:00
jpmschweitzer 3351c595ab Merge remote-tracking branch 'origin/main' into visual-asset-catalog 2026-07-25 20:48:59 +02:00
jpmschweitzerandClaude Fable 5 08acbcb7df chore(meta): pql changelog — T-1193 first-slice incident record
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 20:46:07 +02:00
jpmschweitzerandClaude Fable 5 9ac98542d1 fix(client): isolate viewer tests from the machine-shared atlas disk cache
user://atlas_cache/ is one directory for every worktree gate run, live
capture driver, and real play session, and T-1183's Tier-2/3 lookup
short-circuits BEFORE test_mode's silent-no-op IPC — so a warm shared
cache delivers real canvases into tests written against 'nothing ever
arrives'. Caught live: a concurrent GJ380c Global capture flipped the
two before-any-canvas viewer tests in another worktree's push gate.

Adds disk_cache_root_override on StepCanvasViewer (threads into
StepCanvasRequest's existing test-injection-only seam; production
leaves it empty) and routes every direct viewer construction in the
viewer + legend suites through a _make_viewer() helper pinning an
isolated root. First slice of T-1193's viewer test seam; screen-built
viewers still share the default root (noted on T-1193).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 20:45:46 +02:00
jpmschweitzerandClaude Fable 5 2ca70d0228 docs(assets): PR #211 review fixes — ratified glazing tokens, D-257, manifest skip contract
Review round (Hoshe + Tyre, both REQUEST_CHANGES) fully addressed:
palette.md's glass carve-out re-keyed from retired never-shipped tokens
(precision_glass/smart_facade) to the ratified glass_curtain_wall (wall)
+ industrial_glazing (facade); the toon/PBR treatment promoted from a
docs-only ruling to D-257 (architecture, cross-refs D-235/D-244/D-043/
D-044/D-033) with palette.md §2 now citing it as authority; D-149 +
D-257 added to decision_refs; manifest.json _comment gains the explicit
underscore-prefix loader skip contract; master pipeline table Visual row
Stub -> Active; conventions.md mask pointer Section 3 -> 5; pre-existing
broken D-066 links in the mood-board transcript repaired (leave-cleaner).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 20:36:47 +02:00
jpmschweitzerandClaude Fable 5 7d5a774fa5 docs(assets): Phase-4 visual style bible in palette.md (T-1052)
Authors the full style bible: camera table separating D-148 gameplay from
D-019 offline-renderer use; the delegated toon-vs-PBR ruling (environment
props share the character toon treatment, with a minimal-PBR carve-out for
glass/polished metal so sightlines read truthfully under occlusion-based
perception); a color/material register for every D-235 ObjectTag (hue,
grain, D-217-keyed weathering); and explicit supersession notes both ways
with visual-grammar-v01.md and the mood-board workshop transcript.
Includes branch-side pql changelog rows (T-1050/T-1052 -> review); the
worktree pre-commit export step failed benignly (write-through had
already refreshed the files) so they are staged explicitly here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 20:20:21 +02:00
jpmschweitzerandClaude Fable 5 f77e15d075 docs(assets): 3D-model+texture catalog, glb-gen production path, conventions (T-1050)
Instantiates the five D-244 catalog files (models/textures/artwork/icons/
effects — icons carries the 13 as-built SVGs, stance icons traced to their
consumer and marked final), creates client/assets/models/{furniture,props}
with a schema-documented manifest.json mirroring the character manifest,
fixes glb-gen SKILL.md's stale 'path does not exist' paragraph, and authors
docs/assets/visual/conventions.md: category-first model naming (araminta
ruling), mask sidecars per the character convention, D-235-token-keyed
texture naming, footprint_tiles as manifest metadata. Door-state and
TileSet conventions are recorded as explicit deferrals (Phase-5 owned).
visual/README.md carries both this ticket's count/link updates and
T-1052's palette-section rewrite (shared file, committed here).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 20:20:04 +02:00
jpmschweitzerandClaude Fable 5 3b3ef392c5 chore(meta): pql changelog — visual cluster re-scoped (T-1050/T-1052 refined+active, T-1051 deferred blocked-by T-1049)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 20:11:55 +02:00
jpmschweitzerandClaude Fable 5 0822390f7a chore(meta): pql changelog — batch 5 activation (T-1157 in_progress/hoshe, T-1196 ready, T-962 gate questions recorded, T-1050 held-note correction)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 20:04:19 +02:00
jpmschweitzerandClaude Fable 5 d14bead60a chore(meta): pql changelog — batch 4 closed (T-964, T-971 done)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:56:59 +02:00
jpmschweitzerandClaude Fable 5 493cd716b6 style(simulation): gate bounce — cargo fmt in fix-round fixture
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:49:56 +02:00
jpmschweitzerandClaude Fable 5 5d1935f8fc style(simulation): gate bounce — clippy identity_op in drainage fixtures
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:45:18 +02:00
jpmschweitzer e4b5c2bc9f Merge remote-tracking branch 'origin/main' into test-hardening 2026-07-25 19:36:39 +02:00
jpmschweitzerandClaude Fable 5 b165c8038d fix(simulation): PR #210 review round — guard boundary, live oasis pinning, unreachability proof (T-964)
Guard becomes land_districts <= 1 (both reviewers converged — a lone
island definitionally cannot show two distinct directions; same
nothing-to-vary condition one value short), with a lone-island vacuous-
pass fixture; golden confirmed untouched. Oasis scaling adjudicated as
LIVE, not future — GRID_W is already 1024 on main, so ring iterations
change 2/4 -> 4/8 today: extracted a pure oasis_ring_iterations()
helper pinned by tests at both 512 and 1024, and traced exactly why the
determinism hash stayed green (it reads only elevation; the rings touch
only biome — a genuinely different array, not a coincidence). The
drainage merge-logic question answered byte-precisely: zero logic
changed vs main (comment-only diff) — and the deeper dig PROVED the
'isolated basin with another basin to escape to' branch is
mathematically unreachable for any connected grid (contracting vertex
groups of a connected graph cannot disconnect it), so the comment now
states that instead of narrating a divergence that never fires; two
direct merge-target tests added regardless. Wrap test renamed to what
it actually pins (non-wrap-awareness). D-010 docstring softened to
same-process purity, naming the cascade golden as the cross-run layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:35:09 +02:00
jpmschweitzer c324875959 Merge remote-tracking branch 'origin/atlas-agent-channel' 2026-07-25 19:33:22 +02:00
jpmschweitzerandClaude Fable 5 6f63cb6f70 fix(ui): agent driver honors SR_PORT — the eyeball's own finding (T-971)
The channel eyeball caught the committed reference driver silently
relying on SimBridge's hardcoded default port, unlike every sibling
real-render driver — a caller starting the server on a chosen port got
20 silent connect retries against the wrong port and a hollow session
whose results had valid shapes but no data. Mirrors visual_capture.gd's
convention exactly: SR_LIVE=1 without SR_PORT is a hard error; SR_PORT
sets SimBridge.server_port before boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:30:29 +02:00
jpmschweitzerandClaude Fable 5 52304d3e37 fix(ui): PR #209 review round — current-screen guards, pending-aware settle, one body guard (T-971)
Every screen-targeted intent now routes through one
_require_current_screen() check and returns the structured error shape
instead of silently mutating an off-screen viewer (hoshe's finding:
scroll_rung from the reach screen fired real IPC and reported ok). The
reference driver's fixed 4-frame settle becomes is_pending()-aware with
a 600-frame bound, the keep-waiting decision extracted as a pure
testable function — restoring the proven eyeball-driver discipline. The
terrain_reference guard moves into AtlasApp._on_body_selected(), the
shared tail for double-click, Enter, AND the intent path — closing a
pre-existing click/Enter divergence hoshe caught this PR formalizing;
the intent layer pre-checks via the new SystemScreen.find_body() and
reports structured errors for unknown ids and terrain-less bodies.
after_test() resets AtlasAgentBridge.current_app (tyre's freed-pending
footgun). Suites 58/58 + 14/14; full suite 3,638.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:14:02 +02:00
jpmschweitzerandClaude Fable 5 d316c1274b chore(meta): pql changelog — batch-4 activation rows (T-948 closed as delivered, T-964/T-971 activation + appends)
Tyre's PR #209 review flagged T-971's re-scope appends as invisible to
the branch-side planning store — same bookkeeping-lag class as PR #208's
T-1195 finding: the write-through rows were awaiting this commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:05:08 +02:00
jpmschweitzerandClaude Fable 5 272d3781d8 style(simulation): gate bounce — clippy unnecessary_cast in test fixture
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:04:47 +02:00
jpmschweitzerandClaude Fable 5 80974dfe5a docs(governance): D-226 amendment — agent-channel vocabulary reconciled to the stepped Atlas (T-971)
Tyre's PR #209 finding: every prior D-226 re-scope carries a dated
amendment in the record, and this vocabulary change existed only in
code comments and ticket appends. Records the drops (select_city,
open_regional — no settlement hit-test affordance post-D-255; one
screen Region..Chunk), the additions (open_atlas, jump_to_center via
the constrained jump_to seam), the summary-field reconciliation, and
the in-process-only transport narrowing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:04:00 +02:00
jpmschweitzerandClaude Fable 5 b929aa27b0 style(simulation): gate bounce — cargo fmt
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 19:01:51 +02:00
jpmschweitzerandClaude Fable 5 e141a595b1 feat(simulation): believability gate learns basin-direction variety (T-964, D-245)
A real D-245 gate-shape strengthening, not just a test: the D-256/T-1174
finding proved the believability golden byte-identical under a total
all-North basin_direction collapse — every scalar contrast field is
structurally blind to the one field that regressed. ContrastMetrics
gains land_districts and basin_directions_distinct (both over ALL
districts, no new derive calls — the evidence-backed pick over the
voxel-transect proxy, which washes out at production sample density),
and evaluate_criteria gains 'basin direction variety': pass when
land_districts == 0 (the drained-body guard — an all-ocean body has no
cells that can cast a D8 vote per the aggregator's own exclusion rule,
so a uniform default is legitimate, mirroring the file's existing
nothing-to-vary idiom) or distinct >= 2. Negative test proves the
criterion catches the land-bearing all-North regression; vacuous-pass
test proves the waterworld guard. Golden regenerated and rerun-stable;
both validation bodies (Arbour, Edict) pass at distinct=2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 18:59:13 +02:00
jpmschweitzerandClaude Fable 5 f81622bbf0 test(simulation): Phase-4 hardening — deferred #953/#963 review gaps (T-964)
The verified-still-open coverage list: per-type attractor reachability
fixtures (LakeShore via enclosed depression, PassEntrance via crafted
saddle, PlainCenter via flat terrain, RiverCrossing via confluence) plus
thin_by_spacing behavior (collision, strict-< boundary, equirectangular
column wrap); heightmap 8-bit decode, sea_level passthrough, downsample
identity and zero-target early-return; drainage area_pct bit-for-bit
determinism plus the isolated-basin-fallback divergence comment (Tyre
N1, citing the pre-#953 behavior it deliberately departs from); the
layer1 mountain-branch pairing test (investigated first — the cascade
test supplies a mountain pool but only ever asserted river counts, a
genuine gap); an importer idempotency test covering atlas_city_names
AND atlas_feature_names plus the Sol exemption, wired into
make test-tooling; and the oasis_water dilation radius scaled by
GRID_W/512 (Tyre N2, hash-stable). One stale item dropped per the
refinement trim (test_sim_determinism wiring — already done).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 18:58:53 +02:00
jpmschweitzerandClaude Fable 5 3689ad0466 chore(meta): update changelog
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 18:52:10 +02:00
jpmschweitzerandClaude Fable 5 34dbac9bf6 feat(ui): AtlasAgentInterface — observe/act named-intent control channel (D-226, T-971)
D-226 layer 4, rebuilt against the post-D-255 stepped Atlas after the
Phase-1 reconciliation (the original intent list targeted the retired
continuous-zoom viewer). Eleven intents, each backed by the exact
production handler a click calls — select/open for systems and bodies
(extracted shared by-id tails so click and intent paths are one code
path), scroll_rung, reset_view, back, open/close_atlas, set_overlay —
plus two new first-class capabilities: jump_to_center (the fixed-center
revisit pattern proven by five eyeball drivers, via a new
StepCanvasViewer.jump_to seam that reuses _scroll_rung's exact request
tail — same extent cap, same cache keys) and get_current_canvas_summary
(allocation-light reads off the raw wire dict, courses-by-class,
draw-matched settlement dedup — no PNG decode). Contract shape: dumb
AtlasAgentBridge autoload holding the app handle (untyped per the
parse-order rule), all logic in the static AtlasAgentInterface class.
observe() is side-effect-free: current state + a generic Control-walk
affordance tree. In-process consumers only this ticket (documented);
the committed reference driver (atlas_agent_driver.gd, InputSwallower +
settle-until-ready from the T-1157 inventory) replaces the scratch
eyeball drivers as the sanctioned headless-drive pattern.
select_city/open_regional dropped with recorded rationale (no
settlement hit-test affordance exists post-D-255) — diff on the ticket.
33 new tests across three suites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 18:51:45 +02:00
jpmschweitzerandClaude Fable 5 e0d120e555 chore(meta): pql changelog — polish batch closed (T-1160/69/59/75 done, T-1196 filed)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 18:21:04 +02:00
jpmschweitzer aafce641db Merge remote-tracking branch 'origin/atlas-feature-names' 2026-07-25 18:15:08 +02:00
jpmschweitzerandClaude Fable 5 077d787c4a fix(simulation): one shared invent_coastal_position — PR #208 review round (T-1160)
Hoshe found the concrete residual: the hand-copied driver block called
derive_temperature_c unconditionally where invent_primitives prefers the
lapse-adjusted region baseline on non-airless bodies — driver_temp feeds
glaciation/moisture into the warp magnitude, so orbital displacement
could still differ from District at the same position (the ticket's
defect class, one step upstream); the copy structurally couldn't branch
right because region_baseline_at_district was computed after it. Tyre
demanded the structural cure: steps 1-3 now live in ONE shared helper
(invent_coastal_position -> warped position + CoastCharacter);
invent_primitives composes helper + detail-scatter; the orbital path
hoists the baseline and makes a single helper call. The audit test's own
'District-style' side turned out to be a THIRD copy carrying the same
bug — rewired to the real helper (raw pre-fix side untouched as the
historical baseline; refreshed: 5.01% disagreement, mean 5,957.9 m,
748.9 ns/cell). Golden verified byte-identical under forced regen, with
the reason traced: the wire temperature never flowed through the buggy
internal branch, and the fixture's probe positions cross no discrete
boundary — the 267-body audit is the instrument that sees the drift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 17:22:16 +02:00
jpmschweitzerandClaude Fable 5 7a55685bf6 chore(meta): pql changelog — T-1194/T-1195 filed, polish batch transitions
T-1194 (biome/relief stipple layer, from T-1175's assessment) and T-1195
(river+mountain label positions, the T-1169 scope adjudication target)
now have their write-through rows in git — tyre's PR #208 review caught
the branch-side store unable to resolve the T-1195 reference because
these rows were awaiting this commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 17:05:47 +02:00
jpmschweitzerandClaude Fable 5 71f70f035e style(simulation): gate bounce — cargo fmt
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 16:53:41 +02:00
jpmschweitzer 497f9dc94d Merge remote-tracking branch 'origin/main' into atlas-feature-names
# Conflicts:
#	CHANGELOG.md
2026-07-25 16:44:51 +02:00