diff --git a/CLAUDE.md b/CLAUDE.md index beee72ed5..38478eeb7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ Development follows a strict cascade. Each phase has a concrete deliverable. **D | 1 | Wiki content complete — all planets, moons, stations, heightmaps, artwork | Implant-ready Godot map of the Reach with click-throughs + wiki/GTTR popups | | 2 | Economics layer — supply/demand, transport, political/social pressure, corporations, supply chains | Economics spreadsheets/graphs with runtime-tweakable simulation | | 3 | Planetary/moon maps & station layouts — cities, rivers, mountains, roads, biomes, rail | Atlas of the Reach (implant app) | -| 4 | World generation (tile/chunk/block) — deterministic multilayer seed→tile cascade; each layer above the tile fill viewed as a **map in the implant Atlas** (no in-world rendering; chunk/tile/voxel output is harness-verified, never Atlas-mapped — D-226 amendment 2026-07-13) | Deterministic walkable-world data + per-layer Atlas maps + asset catalog + **seamless Atlas zoom ladder** (planetary map → tile scale, every level fully derived and sampled at canvas resolution — heightmap PNGs are invisible derivation input, never displayed; no zoom over/undersampling — D-166 amendment 2026-07-21) | +| 4 | World generation (tile/chunk/block) — deterministic multilayer seed→tile cascade; each layer above the tile fill viewed as a **map in the implant Atlas** (no in-world rendering; tile/voxel output is harness-verified, never Atlas-mapped; **chunk (64 m) is the deepest Atlas rung** — D-255, narrowing the D-226 amendment 2026-07-13) | Deterministic walkable-world data + per-layer Atlas maps + asset catalog + the **stepped Atlas ladder** (D-255: six levels — the Global body-surface opener (rung 0, variable extent, always kept) + five fixed rungs Region→District→Quarter→Block→Chunk; every step a server-derived data canvas at its native gridunit spacing, drawn texel-exact by the client map-art function — heightmap PNGs remain invisible derivation input, never displayed) | | 5 | Player control & in-world rendering — character, walls/stairs/doors, lighting, drawn on **generated** tiles (no test map) | Player viewport with final-version assets on the generated world | | 6 | Detail coloring — room-level NPC population, cultural room grammar | Only when the world is walkable | diff --git a/docs/architecture/atlas-zoom-ladder-t1143.md b/docs/architecture/atlas-zoom-ladder-t1143.md index df2f3ce72..07737caa3 100644 --- a/docs/architecture/atlas-zoom-ladder-t1143.md +++ b/docs/architecture/atlas-zoom-ladder-t1143.md @@ -2,13 +2,28 @@ title: "Atlas Zoom Ladder — T-1143 Design Pass" description: Continuous-derivation zoom ladder from planetary map to tile scale, per D-166's 2026-07-21 amendment + corollary type: design -status: draft — pending Jeroen decisions +status: superseded — by D-255 (body-map-viewer stepped render architecture) round: T-1143 created: 2026-07-21 +superseded: 2026-07-24 --- # Atlas Zoom Ladder — T-1143 Design Pass +> **SUPERSEDED (2026-07-24) by [D-255](../../governance/decisions/architecture.md#d-255) +> — the body-map-viewer stepped render architecture** (workshop: +> `docs/workshops/body-map-viewer/`). The continuous canvas-resolution-sampling +> model, the `select_rung`/`MAX_COVERAGE_M` coverage-ceiling walk, the +> granularity-parameter serving sketch, and the "down to tile scale" floor this +> pass designed are all replaced: zoom is **stepped** (six levels — the Global +> body-surface opener + five fixed rungs Region→District→Quarter→Block→Chunk, +> chunk 64 m the deepest; tile/voxel re-closed for the Atlas), content +> determination is server-side per-step data canvases on a tagged-envelope +> carrier, and the client is a map-art function (RTT terrain + screen-space +> annotations — no zoom-scaled canvas). This document remains as the historical +> record of the design pass and the measured §7 cost tables it produced (which +> the D-255 measurement appendix extends). Do not implement from this file. + Contributors: Tyre (architecture), Dudley (derivation/cost), Araminta (encoding), Troblum (adversarial critique), Qatux (synthesis). Grounded in D-166 (+ 2026-07-21 amendment & corollary), D-226 (+ T-1112/T-1124/T-1145 amendments), D-227 (+ T-1125), D-243, D-201, T-1143 full ticket text. --- diff --git a/docs/architecture/river-courses-t1170.md b/docs/architecture/river-courses-t1170.md index cf3c03fda..4bffe84a1 100644 --- a/docs/architecture/river-courses-t1170.md +++ b/docs/architecture/river-courses-t1170.md @@ -7,6 +7,8 @@ round: T-1170 created: 2026-07-23 --- +> **Note (2026-07-24, body-map-viewer workshop — D-255):** the carrier three-way rule below (Ruling 1c / Ruling 6) survives unchanged in substance. Two terminology/scope updates from D-255: **(1)** "the windowed payload" is repointed to "the per-step data-canvas payload — formerly the `district_window` field, now the tagged step-canvas envelope" (D-226 2026-07-23 amendment / D-255 §c). **(2)** Rule (iii) — *rung-indexed invented detail rides the windowed payload regardless of geometric kind* — gains a second vector member alongside `courses`: **`cliffs: Vec`** (carved-gorge geometry: dominant `elevation` + per-segment `channel_depth` + `cliff_edge`), a sparse list zero-length when nothing is carved (T-1177 population survey: zero carved cells across all 267 real bodies), Phase-4 Atlas scope. Governance capture is in the D-226 and D-227 body-map-viewer amendments; this note only glosses the design doc so its "windowed payload" language stays current. + *cracks knuckles* — I've read both tickets in full, the D-226 amendment block (wave-1 carrier note, two-waterline note, T-1137 queue ruling, windowed-family ceiling), D-227, D-243, and the T-1143 zoom-ladder serving model. The audits are code-true and the tension called out in point 1 is real — I created it, and it resolves cleanly once the carrier rule is split on the right axis. Ruling follows. --- diff --git a/governance/README.md b/governance/README.md index 5285f8ab1..2ab8fa71c 100644 --- a/governance/README.md +++ b/governance/README.md @@ -320,6 +320,7 @@ line in place — keep the Q-record for the audit trail rather than deleting it. - [D-252: Facing is view-only — movement no longer writes Facing; NPC gaze is intent](decisions/architecture.md#d-252-facing-is-view-only--movement-no-longer-writes-facing-npc-gaze-is-intent) — _architecture_ - [D-253: Region transient state model — seasonal/tidal/weather/snow phase functions (resolves Q-105)](decisions/architecture.md#d-253-region-transient-state-model--seasonaltidalweathersnow-phase-functions-resolves-q-105) — _architecture_ - [D-254: Standalone Atlas companion app — `make atlas`, dual-connection reader](decisions/architecture.md#d-254-standalone-atlas-companion-app--make-atlas-dual-connection-reader) — _architecture_ +- [D-255: Body Map Viewer — stepped Atlas render architecture (supersedes the T-1143 continuous-ladder mechanism)](decisions/architecture.md#d-255-body-map-viewer--stepped-atlas-render-architecture-supersedes-the-t-1143-continuous-ladder-mechanism) — _architecture_ ## Open questions diff --git a/governance/decisions/architecture.md b/governance/decisions/architecture.md index 530c05bd3..39d050232 100644 --- a/governance/decisions/architecture.md +++ b/governance/decisions/architecture.md @@ -602,10 +602,11 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Amendment (2026-05-22):** Phases 4 and 5 swapped — **world generation now precedes player control**. The rule: no player-control or in-world rendering work begins until the generator can deterministically seed-generate every tile of every world via the full multilayer cascade. The original Phase 4 "2-floor test map" is **dropped** — test layers are produced by the generator itself once layer-drawing begins; we start drawing the world only when generation knows what to draw. Generation progress is viewed as **per-layer maps in the implant Atlas** (the Phase 3 deliverable, already built), not via an in-world renderer. The existing in-world rendering code is **left as-is until Phase 5** — neither built upon nor removed before then. Rationale: building player systems against a throwaway test substrate means rebuilding them against real generated tiles later; gating player work on deterministic generation avoids that waste. Epics T-749 (now Phase 5) and T-750 (now Phase 4) and the CLAUDE.md cascade table are updated to match. - **Amendment (2026-06-12):** Build order **within and after** the cascade clarified (Jeroen). The world is built **outside-in**: (1) geo layer; (2) the economic layer drawn onto the world — cities, buildings, streets, blocks, and the inter-settlement road/rail network; (3) building templates; (4) **door boundary contracts** defining the seam between exteriors and building interiors. Only then (5) the background NPC layer and building interiors (Phase 6), and only after that does gameplay/scenario design resume ("making it a game again" — tracked in the post-cascade gameplay parking epic, outside any phase). **Player control and visual rendering (Phase 5) ramp in parallel once outside generation produces walkable exteriors** — the Phase-5 trigger is "outsides generate deterministically and are walkable", not "all Phase-4 polish complete"; T-962 holds the gate and is updated to this trigger. Guiding statement: *"Before this is a game I want it to be a Reach a character can travel through."* NPCs, scenarios, and gameplay systems are conceived, designed, and built only after the traversable world exists. This amendment absorbs the v0.2 scope cluster — D-114, D-115, D-117, D-118, D-120 are marked superseded by this record, with each one's surviving design substance noted on the record itself. - **Amendment (2026-07-21):** **Seamless zoom ladder is a hard Phase-4 deliverable condition** (Jeroen, reviewing the Groombridge/Lendel Atlas zoom-ladder captures). The guiding statement (BHAG) is deliberately **unchanged** — *"Before this is a game I want it to be a Reach a character can travel through."* What changes is the Phase 4 exit bar: the Atlas must provide a **continuous zoom ladder from the planetary map down to tile scale**, where every zoom level shows **deterministically calculated information at that level's native granularity** ("LoD on the information in view" — the T-1143 ruling), never magnified interpolation of a coarser composite. Evidence that forced the condition: at Lendel's scale one heightmap pixel spans ~19 districts, so the 16-district window is *sub-pixel* relative to its source — beyond fit-zoom the current viewer can only linearly magnify the same 16×16 composite (captures 2026-07-21, `.cache/screenshots/groombridge/`), and the real waterline sat ~36 districts from the pixel-edge estimate, i.e. **all** information at window scale and below is invented detail (D-227) that must be *derived*, not smoothed. T-1143 is the design pass for the ladder (granularity rungs, wire budget, the D-226(d) floor question); the Phase-4 epic (T-750) does not close until the ladder stands. CLAUDE.md's cascade table is updated to carry the condition. **Corollary (Jeroen, same day):** the authored heightmap PNGs are thereby demoted to **invisible input to the seed/derivation calculation** — no zoom level displays them directly, *including the orbital/planetary level*, which must itself be **fully derived**. Display at every rung samples the derivation at **canvas resolution** (the ladder is a continuous field, not a stack of fixed display rasters), which removes both zoom oversampling (magnified interpolation) and undersampling (the ~40–78 km/px source-raster floor) by construction, and lets the map adapt to any viewport size. The `import_heightmaps` bake into systems.db survives as derivation *input* storage only; the AtlasViewer's direct heightmap-texture display path retires when the derived planetary rung lands (in T-1143's design-pass scope). +- **Amendment (2026-07-23, body-map-viewer workshop — stepped ladder supersedes continuous; see [D-255](#d-255)):** the 2026-07-21 corollary sentence — *"Display at every rung samples the derivation at canvas resolution (the ladder is a continuous field, not a stack of fixed display rasters)"* — is **repointed, not deleted.** The zoom mechanism is now **stepped**: the server generates one data canvas per discrete zoom step, sampled at that step's native [D-243](#d-243) gridunit spacing. The corollary's *guarantee* survives, re-expressed per-step: **a gridunit is never derived coarser than its step's own rung floor, and never displayed finer than the display-ratio tunable** (1×1 ideal, ≥5×5 px/gridunit acceptable). The "continuous field" framing is retired as literally false (it never was continuous once server-side per-step canvases replaced the client `_canvas.scale` model). The corollary now reads: *"each zoom step's data canvas is a derivation sampled at that step's native gridunit spacing; display within a step holds that canvas at a fixed, texel-exact ratio; between-step magnification of the held canvas is bounded to one step interval and is the ladder's only sanctioned display-time scaling."* **This amendment owns the one honest new gap it creates:** in the interval just before a step-cross, the held (coarser or adjacent) canvas is magnified to fill the new step's viewport for the fetch duration — the exact operation the original corollary was written against, now a **named, bounded exception** rather than a silent violation. The bound is joint in two knobs — step count (more steps → smaller per-step magnification factor) and the display ratio — and the fetch interval is short (measured: step-canvas texture upload 0.03–4.6 ms at every size, uncached 330K-gridunit step derive+encode ~80 ms — a double-digit-millisecond transient, not a resting display state). **Ladder extent:** the 2026-07-21 amendment's *"down to tile scale"* phrasing is narrowed by [D-255](#d-255) — the Atlas ladder bottoms out at **chunk (64 m)**, not tile (1 m); tile/voxel is Phase-5 in-world content, not an Atlas rung. - **Rationale:** The pattern of negotiating pragmatic v0.2 cuts while discussing room-level detail repeatedly produced superseded decisions, confused agents, and distracted from building the actual product. The cascade enforces a first-things-first discipline: each layer of the game is grounded in the layer below it before detail is added. - **Raised by:** Jeroen, established 2026-03-24 during world generation workshop. - **Dissent:** None. -- **Cross-reference:** Initiative T-745, Epics T-746–751, CLAUDE.md cascade table, `docs/workshops/world-generation/workshop-outcomes.md` +- **Cross-reference:** Initiative T-745, Epics T-746–751, CLAUDE.md cascade table, `docs/workshops/world-generation/workshop-outcomes.md`, [D-255](#d-255) (body-map-viewer stepped render architecture — the corollary's stepped successor). --- @@ -768,9 +769,10 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Rationale:** The version constants were designed for a network deployment where client and server can ship out of sync. Our actual deployment is a subprocess: the Godot client launches the Rust server it was built with. They are *always* in sync at runtime — the version check has never caught a real mismatch in the field, only dev-time forgetfulness. The cost has been measurable: every protocol-shaping sprint requires bumping two constants in lockstep, and we accumulated tautological tests asserting `PROTOCOL_VERSION == N` (deleted in sprint 36 — see ticket from this D-record). Removing the handshake makes the per-sprint cost zero. **Reversibility:** When/if networked multiplayer arrives (no firm date — see [D-005](#d-005-architecture-godot-client--rust-server-via-subprocess)), the natural fit is a one-time handshake at connection time (a single client-version vs. server-version exchange in the connection protocol), not a per-snapshot version stamp. So even the multiplayer path doesn't argue for keeping the per-snapshot field — that field would be doubly redundant once a connection-time check exists. The design space hasn't been narrowed. - **What we lose:** A single eager, human-readable error at connect time ("client v22 ↔ server v23"). A genuine dev-time schema drift will now surface as a downstream decode/missing-field error, possibly seconds into a session rather than at handshake. - **What we keep:** All field-presence and roundtrip tests in `test_protocol_bridge.gd`, `test_signal_sprint24.gd`, etc. — these cover the *behavior* the version constant was meant to gate. Decode failure in `Messagepack.decode()` still rejects malformed payloads. +- **Amendment (2026-07-23, body-map-viewer workshop — persistent-cache scope note; see [D-255](#d-255)):** the co-ship "always in sync at runtime" guarantee holds for the **live wire** but does **not** extend to a **disk-backed persistent cache**. [D-255](#d-255)'s client-side Atlas cache (`user://atlas_cache/`) survives a game update by construction — a cache file written by version N read back by version N+1 crosses exactly the version boundary this record assumes away elsewhere. That boundary is handled in [D-227](#d-227)'s cache schema/version-tag amendment (a mismatch = cache miss, re-fetch, never decode), not here — this note only records that D-192's reasoning is scoped to the live wire and the persistent cache is the one place it does not reach. D-192's live-wire reasoning is unchanged. - **Raised by:** Jeroen, sprint-36 client triage. Triggered by stale `test_protocol_version_is_19` assertions failing across two suites after the v23 bump, requiring mechanical edits in both places to "fix." - **Dissent:** None. -- **Cross-reference:** [D-005](#d-005-architecture-godot-client--rust-server-via-subprocess) (subprocess model — always co-shipped). +- **Cross-reference:** [D-005](#d-005-architecture-godot-client--rust-server-via-subprocess) (subprocess model — always co-shipped), [D-227](#d-227) (persistent-cache version-tag — where the disk-format version boundary is handled), [D-255](#d-255) (body-map-viewer render architecture — the persistent Atlas cache). --- @@ -1534,7 +1536,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Implementation:** T-960 (client viewer + proxy) plus the `AnalyzeBody` activation ticket. New surface: `server/src/atlas/source_resolver.rs`, `server/src/atlas/layer_proxy.rs`; `gen_queue.rs` `run_work_item` activation + `GenCompletion` payload; bridge receive/send branching; client `protocol.gd` / `sim_bridge.gd` / `atlas_viewer.gd`. - **Raised by:** Jeroen (mod-first directive) + Tyre (design pass) + Claude, `/whats-next` refinement of T-960, 2026-05-23. - **Amendment (2026-06-12, extension constraint — fable-ous.md S-10):** `decode_inbound` (`bridge/mod.rs:38-74`) distinguishes the two inbound message types purely by MessagePack shape (array = `Vec`, map = `AtlasLayerRequest`) and serde ignores unknown map fields — so a future second map-shaped inbound type with overlapping required fields would silently mis-decode as an atlas request. Constraint: **the next inbound message type must introduce a tagged envelope** (or a required marker field) rather than extending shape-based detection. -- **Cross-reference:** Q-098 (resolved by this), Q-099 (mod content catalog — spun off), [D-191](#d-191) (Atlas viewer), [D-166](#d-166) (per-layer Atlas progress viewer), [D-200](#d-200) / [D-203](#d-203) (three-tier execution, LRU cache), [D-005](#d-005) / [D-192](#d-192) (client+server co-ship — no version handshake), [D-224](#d-224) (SeedChain — feeds the cascade the proxy runs) +- **Amendment (2026-07-23, body-map-viewer workshop — deferred constraint DISCHARGED; see [D-255](#d-255)):** the 2026-06-12 constraint above ("the next inbound message type must introduce a tagged envelope / a required marker field") is now **executed** by [D-255](#d-255). The step-canvas payload cannot ride the existing `district_window` windowed carrier (measured 21×–563× over the ~30 KB windowed ceiling across the three canvas sizes), so it introduces a **required marker field** (`step_canvas: bool` on a new `StepCanvasRequest` inbound variant + a dedicated `StepCanvasResponse` outbound message), extending the same `star_map`/`city_names`/`browse` discriminated-shape pattern. This is the sanctioned tagged-envelope path D-225 deferred, now built — no note change beyond recording that the constraint is discharged there. +- **Cross-reference:** Q-098 (resolved by this), Q-099 (mod content catalog — spun off), [D-191](#d-191) (Atlas viewer), [D-166](#d-166) (per-layer Atlas progress viewer), [D-200](#d-200) / [D-203](#d-203) (three-tier execution, LRU cache), [D-005](#d-005) / [D-192](#d-192) (client+server co-ship — no version handshake), [D-224](#d-224) (SeedChain — feeds the cascade the proxy runs), [D-255](#d-255) (body-map-viewer render architecture — discharges the tagged-envelope deferral) - **Dissent:** None --- @@ -1670,6 +1673,14 @@ Technical foundation decisions that constrain implementation: engine, client-ser **Amended 2026-07-21 (T-1145 — Jeroen, second companion hands-on, KALLAST window):** three regional-window presentation fixes, all client-only. **Cover-fit supersedes contain:** `fit_window_view()`'s zoom now derives from the LARGER viewport dimension with no margin factor (`max(viewport.x, viewport.y) / composite_native`, not the old `0.9 * min(...)`), so the square district-window composite fills a wide/tall viewport edge to edge instead of leaving side margins, with the shorter axis' data extending into pan-space (the same "cover" concept as CSS `object-fit: cover`) — the existing §4 pan-edge refetch is unaffected (it keys off the screen-center-to-DistrictPos mapping, which any fit already centers on `_held_center` by construction, so no refetch churn at rest). **WASD + edge-scroll supersedes drag-pan:** LMB-drag panning is removed entirely (Jeroen's ruling — drag conflicts with click semantics for the map objects, e.g. settlements, this window will host later); panning is now held WASD/arrow keys (continuous, frame-rate-independent, `_process`-polled, physical-keycode reads to stay independent of the project's existing `move_north`/etc. gameplay-movement InputMap actions bound to the same keys) plus edge-scrolling (cursor within ~24px of a viewport edge, suppressed over UI and while the OS window lacks focus); wheel zoom is unchanged; pole-wall (§5 amendment above) and east-west wrap (T-1142) semantics are preserved unchanged under the new input source. **Smoothing is an interim presentation, pending T-1143:** the composite renders as an `n`×`n` `Image`/`ImageTexture` (one pixel per district, the identical existing per-cell color pipeline) drawn scaled with linear filtering — the same treatment the planetary heightmap already gets — instead of `n`×`n` flat rects, so GPU bilinear sampling reads as a terrain gradient rather than hard blocks; the original crisp per-cell path survives behind a compile-time const specifically so T-1143's design pass can compare both directly, and this smoothing is **not** T-1143's answer to district-tier legibility, only a stopgap ahead of it. **Amended 2026-07-21 (T-1143 design-pass rulings — Jeroen, after the zoom-ladder design pass, `docs/architecture/atlas-zoom-ladder-t1143.md`):** three rulings on the pass's reserved decisions. **(1) The item-(d) ceiling is opened for the Atlas ladder** — Jeroen: *"we set a new BHAG so old restrictions are up for debate."* The D-166 2026-07-21 zoom-ladder condition ("down to tile scale") is read **literally**: the Atlas windowed viewport may descend below quarter (512 m) toward block/tile granularity. This is an explicit ruling, not erosion — exactly the deliberate revisit the T-1112 §2 anti-erosion clause was hardened to force into the open. Item (d)'s substance survives in narrowed form: chunk/tile/voxel output still never appears as a **whole-body planetary map layer**, and the below-quarter rungs are implementation-gated on their own **measurement pass** (costs/wire for block and tile rungs are unmeasured — design pass §2/§7); the harness-verification path for L5 fill remains primary until that pass lands. **(2) Planetary rung wire carrier: progressive capped-density tiling** riding the generalized `district_window` carrier (granularity parameter, §3 of the design pass) — no new dense-raster wire shape, no forced tagged-envelope migration. **(3) The §5 entry click-through cut is superseded by continuous cursor-anchored zoom**: wheel-zoom carries the view from the orbital frame down through regional granularities continuously, anchored at the cursor, **with the condition that a full zoom-out resets to the original canonical planetary frame and location** (the fixed orbital framing is the ladder's top rest state, not a drifted pan state). The click-through descent and rectangle reticle are retired as the *sole* entry (T-1138's shipped mechanic stands until the continuous ladder replaces it in the same change — close inspection is never stranded, same discipline as the §5 fixed-view transition). D-013's "the zoom gesture owns spatial descent" reading is **restored** for this seam. **Wire-contract note (T-1150, PR #191 review — Tyre):** the `window_granularity` field that ruling (2) rides on expresses **finer-than-district integer multiples only** (1 = district, 4 = quarter today; each new rung is a deliberate widening of `resolve_window_granularity`'s whitelist — the single widening point; unknown values fall back to district, never trusted from the wire). Coarser-than-district reuse (the region/orbital rungs of ruling (2)'s progressive tiling) requires the design pass's R5 signed/log-scale-or-enum redesign of the field — a new magic value is not the path. Recorded here so the type's limit is contract, not only a design-doc risk row. **Refinement-semantics note (T-1153, PR #192 review — Tyre):** the ladder's progressive cross-rung refinement (hold the coarse composite, fetch the finer rung, swap in place on arrival; per-tile arrival in the orbital mosaic) **extends** the T-1124 §4 float-on-center/debounce async contract — it does not supersede it. §4 still governs the per-request mechanics unchanged (`district_window: None`-until-derived polling, the 150 ms debounce, float-on-center refetch); rung crossings add a second request class on top, per the design pass §3's progressive-refinement model. This record is the one that governs the swap-on-arrival behavior. The legacy `window_granularity: u32` wire field is now fully shadowed by `window_granularity_v2` (the server always echoes both); it is **scheduled for retirement** once pre-T-1152 wire-compat is confirmed unneeded (single-repo client/server pair — no external clients exist today; ticketed). **Pending-shape protocol note (T-1163, PR #193 review — Tyre):** `AtlasLayerResponse` has **two legal wire shapes for one logical "still deriving, client must re-poll" state**, and both are contract: whole-response `status: Pending` (whole-body cache cold — nothing about this body computed yet) and `status: Ready` with `district_window: null` (body warm, this window still in the derive queue). `Ready` is set **only** by the whole-body cache-hit branch, independent of the window's own derivation. Any `AtlasLayerResponse` consumer must treat BOTH shapes as retry-with-backoff and only `NotFound`/`Error` as terminal — the T-1163 cold-launch starvation (every first launch black) was precisely a client reading `status != Ready` as ignorable. A server refactor that "cleans up" the Pending/Ready-null asymmetry must migrate every consumer in the same change. **Filter-axis note (T-1161, PR #194 review — Tyre):** COMPOSITE_SMOOTH is retained as the compile-time *pipeline* axis (texture vs. per-cell rects, crisp path kept for debug/compare); the ladder's crispness-at-sparse-rungs requirement is met by the per-rung *sampling-filter* policy (`_filter_for_granularity_v2`: Region/orbital-mosaic NEAREST, District/Quarter LINEAR, unknown falls back LINEAR), **not** by deleting the const. The design pass §8 step 6 "retire COMPOSITE_SMOOTH" is errata'd accordingly; T-1155's retirement framing is cancelled, superseded by T-1161. **Wave-1 nature-overlay carrier note (T-1156, 2026-07-23 — Tyre):** river skeletons (rivers/basins/attractors) ride the Atlas zoom ladder on the **existing whole-body `layer1` field** (`RiverNetwork`/`drainage_basins`/`attractors`, already serialized on every `AtlasLayerResponse`), **not** on the windowed `district_window` carrier — so the windowed-family ceiling (§2, [HARD], exactly one windowed field) is untouched and no tagged-envelope migration is triggered. Rationale: the skeleton is discrete vector geometry ((u16,u16) cell chains, boundary polylines, point attractors), computed once per body in the Layer-1 pass and cached "valid forever" (D-227) — categorically the whole-body family, not a per-pan viewport query. Rasterizing rivers into `district_window`'s per-cell arrays is rejected: a 1-cell-wide thalweg is sub-cell at every ladder rung (512 m/cell and coarser), so a presence-byte either over-fattens (the Lendel failure the ladder mandate kills) or drops the river on the sampling grid. **Per-rung refinement is client-side** (trunk at Region → +tributaries at District → +streams at Quarter), a filter on a **new quantized `river_class` per cell added to `RiverNetwork`** (derived from the flow-accumulation `drainage.rs` already computes; additive, `#[serde(default)]`-safe, no ceiling impact) — NOT server-side per-rung re-transmission. This is distinct from T-1162's server-side `min_wavelength_m` cutoff: that truncates a continuous per-metre field at the rung's Nyquist limit; the skeleton is a fixed finite graph with nothing to truncate. **General rule established:** discrete map features (linear + point geometry) ride the whole-body overlay family, filtered per-rung client-side; only continuous per-metre fields ride the windowed per-cell arrays — Wave 2's roads/rail/settlements (already whole-body fields) inherit this carrier unchanged. **Consistency scope:** the river skeleton is upstream of and independent from T-1162's perturbed `moisture_q` (drainage runs on elevation, never samples moisture), so the two cannot disagree; the vegetation layer's riparian response to rivers (`near_perennial_water`) is a **named forward contract, deferred to T-1168** — not fixed in Wave 1. Until it lands, the river overlay draws over terrain whose vegetation layer does not yet respond to it (an accepted nature-layer-first gap). **Visibility-direction note (same PR, Araminta):** the per-rung visibility DIRECTION is Araminta's presentation ruling on the 76 km skeleton-resolution evidence — **fade-down** (Region shows the full skeleton, District trunk-only de-emphasized, Quarter off), consciously inverting the provisional add-as-you-descend mapping the ticket brief carried. This posture is explicitly **pre-T-1170**: it is revisited (in `RIVER_CLASS_VISIBLE_BY_RUNG`, the single client-side revisit point) when course invention gives finer rungs real geometry to reveal. **Two-waterline note (T-1172, 2026-07-23 — Tyre):** the river skeleton is extracted against the **raw heightmap sea level** (`drainage.rs`), a rung-independent graph; the drawn coast is the **derived morphology verdict** — which is **rung-dependent by construction** (the coast-warp crinkle, `coast_invention.rs`, adds octaves at finer rungs, so the drawn coastline is a *family* of curves indexed by rung, not a single curve). There is therefore **no single authoritative server-side waterline** to reconcile the skeleton against — a server-side classification would bake one rung's coast into the wire and be wrong at every other rung. Reconciliation is a **presentation-frame** operation: the draw site clips river dots/confluences/mouths against the arrived composite's per-cell water verdict at the rung being painted (drop-in-water, no snap). The skeleton stays rung-independent (its correct nature per the carrier note); the clip is retired into T-1170 when course invention terminates courses at the invented coast with continuous geometry. **Course-invention carrier note (T-1170, 2026-07-23 — Tyre, full ruling in docs/architecture/river-courses-t1170.md):** river course geometry below D8 resolution is **invention, not skeleton**, and rides the **windowed payload** as a vector field inside `DistrictWindowLayer` (`courses`, additive) — invented server-side per window on the background queue (T-1137 discipline) at the window's rung, terminated server-side against the same rung's morphology water verdict the window's cells carry. This does **not** count against the windowed-family ceiling (§2 [HARD]): the ceiling counts windowed *query* fields on `AtlasLayerResponse`; courses are content of the single existing windowed payload, same echo key, same staleness semantics. The wave-1 carrier rule is hereby refined three-way: **(i)** rung-independent discrete features (skeletons, graphs, markers — computed once, valid forever) ride the whole-body family, filtered per-rung client-side; **(ii)** continuous per-metre fields ride the windowed per-cell arrays; **(iii)** rung-indexed invented detail rides the windowed payload regardless of geometric kind, because rung is a request parameter and only the windowed query carries one — the coast crinkle has always implicitly been (iii); courses are its vector sibling. The two-waterline note's reconciliation resolves per-rung server-side for courses (the terminus is windowed content at a known rung); the Region-rung skeleton path retains the presentation-frame clip until Region itself goes windowed (T-1143 ruling 2), which retires the last clip site. The skeleton itself gains `river_downstream` (additive per-river-cell D8 pointer with `MOUTH`/`EDGE_DRAIN`/reserved-`TERMINAL` sentinels) on `RiverNetwork` — whole-body, rung-independent, per rule (i). Pole-edge drains are grid artifacts, not mouths — excluded from `mouths` at extraction. **Implementation notes (PR #197 review):** (a) Godot's line rasterizer floors stroke widths below ~1.0 canvas units to a 1-px hairline; the client floors compensated stroke widths accordingly (`zoom_compensated_stroke_width`), with the consequence that Ruling 5c's **per-class width differentiation is inert at every shipping fit zoom** — course classes are distinguished by opacity alone until T-1175's per-vertex tapering (Polygon2D strips) supersedes stroke-width rendering; the 'trunk widest' promise is design intent, not current pixels. (b) The pole-row edge-drain branch is **structurally unreachable** (`flow_direction` bounds-checks before assignment); the real EDGE_DRAIN mechanism is the interior `k<0` no-valid-downstream case — discovered by revert-verification, test fixture exercises the reachable path. + **Amended 2026-07-23 (body-map-viewer workshop — stepped render architecture; full record [D-255](#d-255)):** the T-1143 design-pass rulings above (2026-07-21) are revised by the body-map-viewer workshop, which relocated content determination to the server, made the client a map-art function, and made zoom stepped. Six consolidated revisions to this record's amendment chain: + + - **Ceiling re-scope (§2 windowed-family ceiling, [HARD]) — tagged-envelope migration TRIGGERED, superseding T-1143 ruling 2.** T-1143 ruling 2's "progressive capped-density tiling... no forced tagged-envelope migration" is superseded: measurement (T-1179) shows the step-canvas payload at **21×–563× the ~30 KB windowed-payload reference** across the three canvas sizes — a cell-count gap of two-to-three orders of magnitude no encoding closes, so it cannot ride the `district_window` carrier. The ceiling's **purpose survives** (no uncorrelated concurrent windowed *queries*); its **mechanism is re-scoped** to the **legacy `district_window` carrier only**. The step canvas rides a **new tagged-envelope carrier** (D-225's deferred migration, now executed — [D-255](#d-255) §b) that the "exactly one windowed field" rule does not apply to by construction (it is the new shape, not a second field on the old). `district_window` survives byte-unchanged for its existing consumer until that consumer is replaced, then goes cold. + - **Rung selector superseded (T-1143 §6 `select_rung` / `MAX_COVERAGE_M` coverage-walk).** The coverage-ceiling walk (Quarter→District→Region→tile-mode mosaic; `compute_tile_grid`) is replaced by a **discrete step index** into the stepped gridunit ladder ([D-255](#d-255) §c). Rungs-as-derivation-granularity survive; the *selector* is "which step the viewport is on," not a coverage walk. `resolve_window_granularity`'s whitelist-validation discipline (T-1150) is the right shape for the step index's own validation. + - **Zoom transport superseded (T-1143 ruling 3).** Continuous cursor-anchored zoom → **stepped** (one server-canvas fetch per crossed step boundary). Survives unchanged: cursor-anchored centering, edge-scroll pan, and the [HARD] full-zoom-out reset (now to the canonical **Global rung-0 body-surface frame**, per [D-255](#d-255)). + - **Item (d) restated per-request + accumulation cap.** The item-(d) whole-body prohibition is stated as a **per-request / per-derivation** constraint, not aggregate-storage: no single `StepCanvasRequest` derives whole-body coverage at sub-Region spacing. The client-side persistent-cache *accumulation* path (a systematic exhaustive pan assembling the forbidden artifact as N cached files — most plausibly the item-(4) `AtlasAgentInterface` QA sweep) is closed structurally by a **per-body deep-rung client-cache retention cap** ([D-255](#d-255) cache section), not by practical improbability. The rule's purpose is about information content, not file count. + - **Item (d) floor partial-restore at chunk (Jeroen, interview 2 — narrows T-1143 ruling 1).** T-1143 ruling 1 opened the item-(d) floor "toward block/tile"; interview 2 **scopes that opening — it does not reverse it.** Jeroen: *"the actual tile level rung seems unusable. maybe replace with 64?"* The Atlas ladder bottoms out at **chunk (64 m)**; **tile/voxel (1 m) returns to never-Atlas-mapped** (a 10-px-per-tile full-screen view is ~192×108 m of ground — Phase-5 in-world content, not a map). Block (128 m) and chunk (64 m) are legal Atlas rungs, riding the viewport-sized carve-out; tile/voxel L5 fill stays harness-verified, shown in-world in Phase 5, exactly as item (d) originally required. A deliberate scoping of the BHAG, not a walk-back. + - **Courses carrier gloss + cliff sparse-list sibling (river-courses-t1170.md rule (iii)).** The T-1170 course-invention carrier rule survives; "the windowed payload" is repointed to "the per-step data-canvas payload (formerly `district_window`, now the tagged step-canvas envelope)." Rule (iii) (rung-indexed invented detail rides the windowed payload regardless of geometric kind) gains a **second vector member alongside `courses`: `cliffs: Vec`** — carved-gorge geometry (dominant `elevation` + per-segment `channel_depth` + `cliff_edge`, direct solver-output carry), a **sparse list, zero-length when nothing is carved** (T-1177's population survey: **zero carved cells across all 267 real committed bodies** — structurally rare, `#[serde(default)]`). **Phase-4 Atlas scope** (a map showing a smooth shoreline where the settled solver computed a carved channel would misrepresent the "settled hydrology" the workshop premised). The cliffs list is NOT the endorheic-lake cue (0/267 carve — see [D-227](#d-227)'s lake amendment). - **Rationale:** Reusing the real UI — rather than a parallel offline renderer or dumped files — means the debug/review surface never diverges from what ships, and a dropped artifact can't go stale. Agent-navigability converts qualitative "does the synthesis look natural?" review from a manual eyeball pass into an automatable sweep that flags the few outliers for a human. The harness rides seams that already exist (`TickRate::Paused`, the paused-allowlist, `gameplay_occluded`, the bridge framing, the `run-visual` capture primitive) — a naming-and-contract exercise, not a new subsystem. - **New surface:** server pause-gating (run-conditions on the world phases keyed to a pause command); client `AtlasAgentInterface` (`observe`/`act`, Control-tree walker) + its local transport; the generation overlay rendering + selector + legend; interactive capture wired to `run-visual`. - **Implementation:** Phase 4 (epic T-750), built bottom-up — auto-pause substrate, T-969 proxy (D-225), T-960 viewer, agent channel, agent capture. Geography is the first consumer. @@ -1693,8 +1704,14 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Implementation:** Phase 4+ (epic T-750). The caching substrate exists at the atlas level (`BodyWorldStateCache`, D-203/D-225); the tile/voxel cache, mutator overlay, and geology derivation land as the cascade reaches the tile layers. No migration needed pre-save (D-202 `schema_version` lineage covers future drift once saves exist). - **Amended 2026-07-17 (T-1125 — the invention carries geographic content into district classification; two-tier driver model):** the "invented deterministically (interpolation + domain warp + detail-scatter)" clause is now implemented *with character* at the district tier, closing the T-1123 finding that classification consumed only the raw bilinear envelope (pixel-smooth coasts; scatter amplitude slaved to coarse heightmap slope ≈ 0 exactly on low-relief coasts). Mechanism (`atlas/coast_invention.rs` + `district_profile::invent_primitives`, shared by the on-demand `derive_district` AND batch `derive_district_profile` paths so they can never silently diverge): **(a) coastline domain-warp** — every envelope field (elevation, slope, ocean mask) is bilinearly sampled at the same warp-displaced position (C¹ multi-octave value noise, ≈16–262 km band, sub-pixel amplitude cap 0.75 px, distinct salted hash stream — never correlated with the terrain scatter or climate edge-fuzz), inventing bays/capes/fjord inlets while the heightmap stays the truth at its own scale; **(b) slope-independent scatter floor** — the detail-scatter envelope gains a character-driven floor (invention no longer collapses on flat coasts; the old "flat envelope → zero invention" reading is superseded — the envelope rule survives as a *ceiling*: gentle bounded relief, never mountains on an authored plain); **(c) shoreline carving** — ridged character contributes real slope in shoreline patches so the steep coastal families (Fjord/CliffCoast) can fire where glacially/tectonically justified. **Crinkle varies (Jeroen's ruling): two driver tiers, zero new authored data.** Tier 1 (body personality envelope): `planet_class` (via `TectonicClass`) + `hydrosphere` + `atmosphere` + `body_radius_km` (via the pixel⇄metre seam) only — **D-240 stands: no orbital/tilt inputs**; erosion-proneness is *derived* (more ocean → wetter/rainier → higher erosion → smoother mature coasts; dry/thin-atmosphere → sharp young coasts). Tier 2 (position): latitude, driver-tier `GlaciationGrade` (fjordy high-latitude glaciated coasts), local wetness, and a seeded ~100–400 km heterogeneity field so stretches of the same coast differ; longitude participates via absolute world-metre noise keying. **Circularity ruling:** the driver-tier climate (glaciation/moisture) reads the *unwarped* raw-bilinear primitives — one-step-stale by design, documented at the call site. All pure `(seed, body, position)` (D-010); character never steps on a district/region line (D-243 edge-fuzz discipline). - **Amended 2026-07-23 (T-1170 — river courses join the invention family; full ruling in docs/architecture/river-courses-t1170.md):** the "invented deterministically" clause now covers linear features: river courses between D8 cells are pure `(seed, body, edge, rung)` functions — a rung-independent valley-seeking coarse path (bilinear `TerrainAnalysis` proxy, never full re-derivation) plus rung-indexed perpendicular warp octaves under the `min_wavelength_m` truncation discipline, distinct salted stream, amplitude tapered to zero at cell-centre anchors (confluence continuity), stations at global arc-length positions (window-independent). Round-1 relief reconciliation is course-follows-terrain (valley preference); terrain-carves-for-course is the deferred converse, targeted at block/tile rungs. +- **Amended 2026-07-24 (body-map-viewer workshop — four cache/derive additions; full record [D-255](#d-255)):** the derive-don't-store model gains four additive amendments from the body-map-viewer render architecture. All four leave derive-don't-store's core intact; none makes any derived value a function of mutable state. + + - **(1) Map-time TTL-split + staleness-vs-storage as distinct eviction axes (Jeroen).** The Atlas map shows current state via a **TTL-split**: static geometry (morphology/elevation/moisture/vegetation/glaciation/height — everything this record covers) is cached **indefinitely-fresh** (determinism → re-derivation is byte-identical, so "stale" does not apply); **sim-state planes** (frozen/flooded, [D-253](#d-253)-driven) are separately-cached **short-TTL** planes re-requested as sim time advances. Two distinct eviction axes (Jeroen, verbatim: *"we still may also want to evict non global level geometry based on time to save storage for planets the player visits but never goes back to"*): **staleness-eviction** applies only to sim-state planes (they genuinely go stale); **storage-eviction** applies to all sub-global cache entries including geometry — evicted on time-since-last-visit purely as a **storage-budget policy**, not because the data is wrong (a re-derive on next visit is byte-identical and cheap). The **global tier alone (the rung-0 Global body-surface canvas, [D-255](#d-255) §c) is keep-always**, exempt from both axes. + - **(2) Persistent client-cache schema/version tag (the one place [D-192](#d-192)'s co-ship guarantee does not reach).** [D-255](#d-255)'s disk-backed client cache (`user://atlas_cache/`) survives a game update — a cache file written by version N read back by N+1 crosses a version boundary D-192 assumes away for the live wire. **Requirement:** every persistent cache entry carries a **schema/version tag** (the game's `project.yaml` version or a `generator_sha`-style stamp), checked at read time — a **mismatch is treated as a cache miss, re-fetched, never decoded**. Consistent with this record's "cache never truth, evict → recompute always valid": a version-mismatched entry is just another eviction case. + - **(3) Seed-chaining as a "cache-accelerated pure function" (Jeroen ratified — [D-255](#d-255) §a.10).** A finer step may **consume a coarser step's resolved output** (the outline's "serves as seed information for the deeper cascade") **without weakening derive-don't-store**: the DEFINITION stays pure (`derive(seed, position)`, byte-identical every time); the IMPLEMENTATION may read a **resident coarser canvas as an OPTIMIZATION** with a **derive-fresh fallback**. This is an optimization, not a semantic dependency, because the coarser canvas is itself evictable derived data — a pure function of the same seed — so correctness never depends on the cache being warm (evict → derive fresh → **byte-identical** input, this record's own "eviction → recompute, always valid" test). What is consumed is the coarser rung's **continuous primitive baseline** (the shipped district-reads-region pattern), **never** its resolved categorical classification (that would violate the per-rung dominant-mode re-derivation discipline). A **mandatory determinism test** (cache-hit path == cache-miss path, byte-exact) is the correctness gate; the ①②③ benched costs are the **cache-cold worst-case ceiling** (the fallback path), so the shipped system is never slower than measured. No chain-reaction on eviction: each rung's fresh-derive fallback is self-contained (`derive(seed, position)` takes nothing but those two inputs). + - **(4) Lakes sourced from settled hydrology (data-source fix, no wire/vocabulary change — [D-255](#d-255) §a.11).** `MorphologyZone::Lake` (discriminant 1, already in [D-239](#d-239) §6's frozen 17-zone set — **not** a new zone) is sourced from `HydrologyResult`'s (T-1177) settled-equilibrium basins instead of the crude `ocean_fraction_q >= 60` heightmap heuristic, falling through to that heuristic where no basin exists. Water derives from the **continuous `filled_scaled` field sampled bilinearly per rung** (mechanism B above — the same way `sea_level` is sampled), **not** projected basin-cell membership (which would give a blocky, non-refining lake edge — the D-166-corollary magnified-composite artifact); so lake edges *refine* with zoom. Lake basin geometry is **static** (settled equilibrium, cached indefinitely-fresh), distinct from the short-TTL flooded sim-state plane of (1). **Endorheic-vs-overflow reads via OUTFLOW-COURSE PRESENCE** — an overflow basin's outlet edge appears in `courses` (T-1170), an endorheic basin's does not — **no wire bit, no 18th zone** (proportionality: 4.63% endorheic = 1,030/22,270 basins does not justify permanently widening the frozen vocabulary; the outlet-wiring is required regardless for hydrologic honesty; and the inference is exact — every `Overflow` basin has a non-empty `outlet_path`, every `Endorheic` none). The cliffs list is NOT the cue (T-1177 survey: zero carved cells across all 267 real bodies). **Honest sequencing:** until the basin-outlet→D8 wiring ticket ships (additive, pre-cleared by T-1170 Ruling 7b's reserved `TERMINAL` sentinel), the map shows lakes but not the drains-vs-closed distinction. - **Raised by:** Jeroen (derive-don't-store, volumetric, drop-the-floor-cap directives) + Claude, atlas-derivation workshop, 2026-05-25. -- **Cross-reference:** [D-010](#d-010) (determinism — now save-critical), [D-222](#d-222) (subtile/tile/chunk hierarchy), [D-110](#d-110) (signed z-levels), [D-225](#d-225) (layer-stream proxy + cache pattern), [D-203](#d-203) (LRU cache tier), [D-224](#d-224) (SeedChain — feeds `derive`), [D-228](#d-228) (composite tile schema — the derived value type), [Q-101](../questions/architecture.md#q-101) (refinement contract), [Q-103](../questions/architecture.md#q-103) (mutator op schema), [Q-104](../questions/architecture.md#q-104) (floor↔voxel-z mapping) +- **Cross-reference:** [D-010](#d-010) (determinism — now save-critical), [D-222](#d-222) (subtile/tile/chunk hierarchy), [D-110](#d-110) (signed z-levels), [D-225](#d-225) (layer-stream proxy + cache pattern), [D-203](#d-203) (LRU cache tier), [D-224](#d-224) (SeedChain — feeds `derive`), [D-228](#d-228) (composite tile schema — the derived value type), [D-253](#d-253) (transient sim-state — the short-TTL plane the map-time split carries), [D-255](#d-255) (body-map-viewer render architecture — the four amendments above), [Q-101](../questions/architecture.md#q-101) (refinement contract), [Q-103](../questions/architecture.md#q-103) (mutator op schema), [Q-104](../questions/architecture.md#q-104) (floor↔voxel-z mapping) - **Dissent:** None --- @@ -1977,10 +1994,11 @@ Technical foundation decisions that constrain implementation: engine, client-ser **(5) Vocabulary** — fixed, and recorded in CLAUDE.md. "Region" now means **only** the ~205 km top hard block. The old 1 km "RegionProfile" scale is **removed** — its terrain/climate carrier role moves onto the **district** (2 km), aligning it with [D-239](#d-239) §2's existing climate district. [D-201](#d-201)'s tier-4 "Region" (50–500 km watershed/political) is renamed **Province** (its actual content — drainage basins, [D-205](#d-205), territory), an **overlay** painted across regions, *not* a containment rung. Region (a fixed metric grid cell) and Province (an irregular lore-bearing boundary) sit at overlapping scales but are different kinds and must stay distinct in the docs. No new words are invented (`sector` is reserved by `perception::VisibilitySector`; `tract` and the rest were rejected). +- **Amendment (2026-07-24, body-map-viewer workshop — `gridunit` vocabulary added; full record [D-255](#d-255)):** **`gridunit` (at zoom step)** is added to the locked vocabulary — additive, no rung changes. A gridunit is the **per-step data-canvas cell** of the Atlas stepped zoom ladder ([D-255](#d-255)) — **not a new spatial rung**, a *role* name for whichever of this ladder's rungs a given zoom step is pinned to. **At every *fixed* rung, gridunit spacing equals one of this ladder's fixed metre values and never floats with viewport size or display resolution** — a fixed step's gridunit is a rung above, full stop. The Atlas ladder's fixed rungs run from **Region (~205 km, rung 1) down to chunk (64 m, rung 5, the deepest Atlas rung)**; **tile/voxel (1 m) is NOT an Atlas gridunit** (Phase-5 in-world content — [D-255](#d-255) / [D-166](#d-166) 2026-07-23 amendment). The **one exception to fixed-metre snapping is rung 0 (Global)**, the *variable* map opener: one gridunit per region, so its gridunit is a whole region (~205 km) but its **canvas extent floats with the body's region count — the elastic seam of this record made visible** (region count = `round(2πR/204.8 km)`). Global's gridunit is still not viewport-derived (it is the body's region grid, a fixed body property), so the "never floats with viewport/display" discipline holds; only the body's own region count varies. Separately, the **display ratio** (screen-px per gridunit — 1×1 ideal, ≥5×5 acceptable) is a free, client-side, viewport-dependent parameter, kept architecturally separate from gridunit spacing: two monitors requesting the same step get the **same** absolute-metre canvas (same cache entry) and merely display it at different px ratios (a GPU resize, never a re-derivation) — which keeps the derivation cache key free of any presentation parameter. - **Rationale:** The cascade glued absolute voxel/chunk scales to the body-relative heightmap with three contradictory assumptions and no record fixing metres-per-pixel, so nothing downstream could be metre-precise (Q-110). A rigid absolute ladder with a single elastic seam removes the contradiction at the source: the only body-specific function in the whole chain is region↔planet via `body_radius_km`; everything below is fixed integer math, unblocking the production wiring (T-1046). Every rung earns a job — the region in particular is not mere addressing: it is the natural lockdown scale for weather/season/climate (the Q-105 dynamism source) and the sane planetary grid (~195×98 regions on an Earth-sized body, vs an unusable ~19,500 districts). Confining invented terrain detail to a deterministic scatter layer, and climate variation to an edge-fuzzed continuous blend, honours derive-don't-store ([D-227](#d-227)) while keeping both believable — the grid never shows and the planetary seam is never walked. - **Raised by:** Jeroen, 2026-06-14 — driving Q-110 during the cascade-spine sequencing (next: wire the tier into production, T-1046). - **Implementation:** unblocks **T-1046** (`ChunkPos → … → RegionPos` is now defined); the region↔heightmap interpolation + detail-scatter synthesis + climate edge-fuzz is the elastic-seam stage (new ticket). [D-201](#d-201) amended (Region→Province). The canonical ladder is recorded in **CLAUDE.md**. Refines [D-239](#d-239) §2 (district temperature now modulates a region baseline) and contributes the region-clock answer toward [Q-105](../questions/architecture.md#q-105). -- **Cross-reference:** [D-201](#d-201) (spatial hierarchy — tier-4 Region→Province, amended here), [D-202](#d-202) (heightmap 1024×512), [D-204](#d-204) (`body_radius_km`), [D-220](#d-220)/[D-222](#d-222) (locked sub-settlement dims — chunk/block/quarter/district), [D-205](#d-205) (province boundaries — the overlay), [D-225](#d-225)/[D-227](#d-227) (derive-on-demand / don't-store), [D-239](#d-239) §2/§3/§4/§7/§10 (climate primitive refined; warp; seams; budget reframes to per-active-derivation), [Q-105](../questions/architecture.md#q-105) (region seasonal/clock state — answered at the region). +- **Cross-reference:** [D-201](#d-201) (spatial hierarchy — tier-4 Region→Province, amended here), [D-202](#d-202) (heightmap 1024×512), [D-204](#d-204) (`body_radius_km`), [D-220](#d-220)/[D-222](#d-222) (locked sub-settlement dims — chunk/block/quarter/district), [D-205](#d-205) (province boundaries — the overlay), [D-225](#d-225)/[D-227](#d-227) (derive-on-demand / don't-store), [D-239](#d-239) §2/§3/§4/§7/§10 (climate primitive refined; warp; seams; budget reframes to per-active-derivation), [D-255](#d-255) (body-map-viewer render architecture — `gridunit` vocabulary + the Global rung-0 elastic-seam view), [Q-105](../questions/architecture.md#q-105) (region seasonal/clock state — answered at the region). - **Dissent:** None --- @@ -2240,4 +2258,29 @@ Technical foundation decisions that constrain implementation: engine, client-ser --- -*108 decisions (D-001 through D-254, excluding gaps). Last updated: 2026-07-17 (D-254 — standalone Atlas companion app: dedicated entry scene, ConnectionRole-gated reader connection (0-1 Player + 0-N Reader), wire-only data browser extending the T-949 proxy pattern, save/load and trading seams recorded not built).* +### D-255: Body Map Viewer — stepped Atlas render architecture (supersedes the T-1143 continuous-ladder mechanism) + +- **Date:** 2026-07-24 +- **Decision:** The Atlas map handler is rebuilt on three locked premises: **(1) content determination is server-side** — the CPU/Rust server answers "what is at this world coordinate at this zoom step" as a per-step data canvas; the client never invents geometry (GPU is presentation only). **(2) The client is a map-art function** — it colorizes, styles, and annotates the server's canvas via a render-to-texture terrain layer (texel-exact, drawn at the display ratio) plus an unscaled screen-space sibling layer for vector annotations. **(3) Zoom is stepped** — discrete gridunit-spacing levels, one server-canvas fetch per crossed step boundary, cursor-anchored, with edge-scroll pan and a hard full-zoom-out reset to the canonical **Global (rung-0)** body-surface frame. This dissolves the T-1143 error class (the client compensating for a zoom-scaled `_canvas.scale` — `_zs`/`_zs_stroke`/`_zs_ring_radius`, the line-rasterizer floor) at the root: there is no zoom-scaled canvas anymore, so per-call compensation cannot occur. + + **(a) The step ladder — six levels: the Global map opener + five fixed metre rungs.** **Global (rung 0, body-surface view) → Region (204.8 km, rung 1) → District (2,048 m, rung 2) → Quarter (512 m, rung 3) → Block (128 m, rung 4) → Chunk (64 m, rung 5, deepest)** — the "Option D" ladder, every fixed rung measured. **Rung 0 (Global) is the map opener and is *variable*-extent, not a fixed metre rung** (Jeroen, post-briefing correction): it is the whole body surface at **one gridunit per region**, so its canvas *is* the body's region grid — variable per body (~19K gridunits on an Earth-class body; the [D-243](#d-243) elastic seam made visible, region count floats per `body_radius_km`), and it is the sole always-kept tier. **Region (rung 1) is the largest *fixed*-size rung** — viewport-sized like every fixed rung below it, evictable, **not** the canonical tier. Every fixed rung's gridunit spacing is one of [D-243](#d-243)'s absolute-metre rungs, never viewport-derived. **Tile/voxel (1 m) is dropped from the Atlas ladder** (a 10-px-per-tile full-screen view is ~192×108 m of ground — in-world viewport content, Phase-5's scope, not an Atlas map); voxel is reserved for Phase-5 in-world rendering. **Deepest bottom-out rule: 1 screen px per 64 m gridunit, no magnification margin** — the old "10 px per tile" margin does not carry over (it existed only because a 1 m unit is sub-readable at 1×1; a 64 m gridunit is already a legible map feature, so chunk uses the plain fixed-canvas budget with no display-ratio-sized exception). The **display ratio** (screen-px per gridunit, 1×1 ideal → ≥5×5 acceptable) is a free client-side presentation parameter, decoupled from spacing (see the [D-243](#d-243) gridunit amendment). + + **(b) Canvas policy — rung 0 canonical/always-kept, every fixed rung viewport-sized.** Rung 0 (Global) is the **sole canonical, always-keep tier** — the body-surface region-grid canvas, [D-226](#d-226)(d)-legal by construction (at one gridunit per region it is *coarser* than the region grid, never a sub-region metre-resolution whole-body derivation). **Every fixed rung — Region through Chunk — is viewport-sized and evictable**, bounded to a fixed canvas-pixel budget (not a per-monitor echo), which keeps the deep ladder legal under [D-226](#d-226)(d) **per request** by construction. The D-226(d) prohibition is stated as a **per-request / per-derivation** constraint, not aggregate-storage (D-226 2026-07-23 amendment); the client-cache accumulation path is closed by a per-body deep-rung retention cap (below), not by improbability. + + **(c) Wire — a tagged-envelope carrier, executing D-225's deferred migration.** The step-canvas payload cannot ride the legacy `district_window` windowed carrier (measured 21×–563× over the ~30 KB windowed-payload reference across the three canvas sizes — a cell-count gap of two-to-three orders of magnitude no encoding closes), so it rides a **new tagged carrier**: a `StepCanvasRequest` inbound variant carrying a **required marker field** (`step_canvas: bool`, extending the existing `star_map`/`city_names`/`browse` discriminated-shape `ShapeProbe` pattern) plus a **dedicated `StepCanvasResponse`** outbound message (not a field on `AtlasLayerResponse`, per the [D-226](#d-226) §2 windowed-family ceiling, which is re-scoped to govern the legacy carrier only). One flat tagged response carries all fields together: dense classification fields ship **PNG-per-field** (the measured smallest-and-fastest encoding); sparse feature lists (`courses`, `cliffs`) stay MessagePack-native. Both the Global opener (rung 0) and every fixed rung ride this one carrier (rung 0's variable extent is a field value, not a different message shape); the legacy `district_window` survives byte-unchanged for its existing consumer until that consumer is replaced. This discharges D-225's 2026-06-12 tagged-envelope deferral. + + **(d) Cache — three tiers, determinism makes every geometry tier a pure cache.** Cheapest-first: client in-memory LRU → client disk-backed `FileAccess` store (self-cleaning TTL for sim-state entries) → server ([D-203](#d-203)-shaped resident global tier + `TTL(detail, time, distance)` for finer rungs). Determinism ([D-227](#d-227)) makes every geometry tier a pure cache, never a source of truth (evict → recompute, byte-identical, always valid). **Staleness and storage are distinct eviction axes** (D-227 amendment): geometry never goes stale (storage-evicted only); sim-state planes ([D-253](#d-253) frozen/flooded) carry a real short staleness TTL. **The server global (rung-0) tier costs ~8.85 MB PNG-encoded (27.61 MB raw, SI decimal) across the entire real ~267-body population** (measured: 4,825,615 total region-grid cells, avg ~18,073/body; Earth-class reference 195×97 = 18,915 — this supersedes an earlier ~174 MB figure that mis-priced a fixed 4K-class Region-*spacing* canvas per body, ~440× too many cells), and derives in **~16–21 ms per body single-thread** (populated lazily once per body on first Atlas-open via the [D-206](#d-206) background queue; the ~4.0 s all-267-summed figure is a sanity ceiling never paid synchronously). At ~8.85 MB the keep-always tier is trivially process-resident. **Two client-cache hardening requirements:** (i) a **per-body deep-rung retention cap** (max resident chunk/block-spacing tile count or disk quota per body, independent of the rung-0 retention floor) — the structural ceiling closing the [D-226](#d-226)(d) accumulation gap against a systematic exhaustive pan (most plausibly the D-226 item-(4) `AtlasAgentInterface` QA sweep); (ii) every persistent cache entry carries a **schema/version tag**, mismatch = cache miss + re-fetch, never decode (the one place [D-192](#d-192)'s co-ship guarantee does not reach — handled in the [D-227](#d-227) amendment). + + **(e) Determinism boundary — what the client may interpolate.** The client may interpolate **only within a closed, server-supplied input set** — two arrived textures (step-cross morph/tween, cosmetic only), a finite list of wire-carried control points (spline-fit river/road curves through server-given stations), or texture-to-viewport resize (display-ratio scaling). It may **never** invent a sample outside that set (client-side upsampling of terrain detail — the T-1143 error class) nor smooth across a step/rung-truncation/cliff boundary as if continuous. This generalizes the T-1170 course-invention discipline: derivation stays CPU/Rust server-side, GPU is presentation only. + + **(f) Seed-chaining — a cache-accelerated pure function.** A finer step may consume a coarser step's resolved output as an **optimization** (reading a resident coarser canvas) with a **derive-fresh fallback** — staying inside derive-don't-store because the coarser value is itself re-derivable to identical bytes (full ruling in the [D-227](#d-227) amendment). What is consumed is the coarser rung's continuous primitive baseline, never its resolved categorical classification; a byte-identical cache-hit==cache-miss determinism test is the correctness gate; the benched costs are the cache-cold worst-case ceiling. + +- **Rationale:** The shipped model compensated for a zoom-scaled client canvas at every draw call — an "invisible until it's wrong" error class (T-1143's Lendel failure) rooted in the client re-drawing derived geometry inside a `_canvas.scale` node. Relocating content determination to the server, making the client a pure map-art function, and making zoom stepped eliminates that class at the root: sizes are texel-exact by construction, the render is auditable (the data canvas is inspectable server-side, decoupled from draw), and the one hard part feared going in — reconciling derived geometry with a zoom-scaled canvas — stops existing because there is no zoom-scaled canvas. Every pre-workshop cost gate came back GO with no extrapolation (hydrology 0.7–0.8 s all 273 bodies; row-chunked derive flat ~190–220 ns/cell from 330K to 8.3M cells; PNG-per-field the smallest-and-fastest encoding; texture upload 0.03–4.6 ms; deepest chunk canvas ~1.7 s parallel). The tagged-envelope migration is the one genuine chunk of new work — challenging but doable, executing D-225's planned deferral rather than inventing one; at the code level Dudley's read of `bridge/mod.rs` puts it at "doable" (one `Inbound` variant + one `SimBridge` method on a pattern proven five times), with the harder work in the client rebuild. +- **Raised by:** Jeroen (the design outline: server-determines-content, client-draws-art, stepped zoom, lazy/late compute, the tile-drop and Global/Region rung-identity corrections, seed-chaining ruling — body-map-viewer workshop, 2026-07-23/24). Designed by Tyre (governance/architecture lead, record author) + Dudley (server derivation, wire/serving, hydrology, cache costs) + Araminta (named-feature encoding, payload schema, lakes) + Stig (client component, cache store, measurement ⑥) + Troblum (adversarial pass — D-226(d) accumulation, unit note, cache version tag). +- **Implementation:** Phase 4 (epic [T-750](../../.pql)). Implementation chain (measurement-informed): server step-canvas serving via the tagged envelope (the migration), the client two-layer component (RTT terrain + unscaled screen-space annotations) replacing the `_canvas.scale` model, the three-tier cache, the two lake tickets (morphology-sourcing from `HydrologyResult`; basin-outlet→D8 wiring, pre-cleared by T-1170 Ruling 7b). Ticket reconciliations: **T-1176** (this design discussion) closes as delivered; **T-1158** (viewer decomposition) cancelled — the canonical-frame machinery it would extract changes shape under stepped zoom; **T-1175** (nature polish) re-scoped onto the new annotation layer (c1 = CPU-first, its measurement-⑥ blocker discharged); **T-1157** (visual-capture goldens) re-scoped as the stepped mechanism's verification story; **T-1174** (batch-vs-window derive divergence) kept, priority raised (the envelope depends on batch/window derive agreeing); **T-1152/T-1153** stay `done`, their continuous-zoom + coverage-walk + Region-tile-mosaic code and test suites retire with the `_canvas.scale` model. Measurement appendix, per-agent positions, and the full deprecation sweep: `docs/workshops/body-map-viewer/`. +- **Cross-reference:** [D-166](#d-166) (development cascade — the 2026-07-23 corollary repoint; this is the stepped successor to the continuous ladder), [D-226](#d-226) (live-pause harness — the windowed-family ceiling re-scoped, item-(d) per-request + floor-partial-restore, the T-1143 rulings this supersedes; item-(4) `AtlasAgentInterface` the cache cap protects), [D-227](#d-227) (derive-don't-store — the four cache/derive amendments: TTL-split, version-tag, seed-chaining, lakes; the discipline every cache tier obeys), [D-243](#d-243) (spatial scale ladder — the rungs gridunit snaps to, the Global rung-0 elastic-seam view, the `gridunit` vocabulary), [D-225](#d-225) (layer-stream proxy — the tagged-envelope deferral this discharges), [D-192](#d-192) (no version handshake — persistent-cache boundary), [D-253](#d-253) (region transient state — the sim-state planes the map-time split carries), [D-010](#d-010) (determinism — server-owns-derivation, client-art-function; the cache-accelerated-pure-function and byte-identical-paths tests), [D-169](#d-169)/[D-170](#d-170) (implant UI — the map component lives in the implant Atlas app, occludes gameplay via HudGroups), [D-239](#d-239) §6 (the frozen 17-zone `MorphologyZone` vocabulary the lake fix reuses, never widens). Tickets: [T-1176](../../.pql) (design), [T-1177](../../.pql)/[T-1178](../../.pql)/[T-1179](../../.pql)/[T-1180](../../.pql) (measurements ①–⑤), the reconciled T-1152/T-1153/T-1157/T-1158/T-1174/T-1175 above. +- **Dissent:** None. + +--- + +*109 decisions (D-001 through D-255, excluding gaps). Last updated: 2026-07-24 (D-255 — body-map-viewer stepped Atlas render architecture: server-determines-content + client-map-art-function + stepped six-level ladder (Global opener + Region→Chunk fixed rungs), tagged-envelope wire migration, three-tier cache, cache-accelerated-pure-function seed-chaining, lakes from settled hydrology; amends D-166/D-226/D-227/D-243/D-225/D-192).* diff --git a/governance/questions/architecture.md b/governance/questions/architecture.md index 6a7c6e238..a84f46a19 100644 --- a/governance/questions/architecture.md +++ b/governance/questions/architecture.md @@ -322,7 +322,7 @@ Technical foundation questions: engine, protocols, data structures, performance, ### Q-093: Tile-based exploration map in player insert (Google Maps for the implant) - **Status:** Open (high interest) - **Question:** Reference: MapTileProvider (https://github.com/AngryMeenky/MapTileProvider) — lazy-loading tile map provider. Concept: the player's insert has a map that works like Google Maps — pan, zoom, tile-based rendering. Server generates map tiles from ECS exploration data (what the player has seen). Explored areas show room layouts, corridors, points of interest. Unexplored areas are blank/fogged. Zoom levels: room detail → building → district → zone → station overview. Knowledge-graph-driven overlays: NPC last-known positions (if the player tracked them), quest markers, danger zones, faction territories. Map tiles are server-authoritative (can't see what you haven't explored) and cached on the client. The tile pyramid approach means the map scales to any world size without loading everything at once. -- **Cross-reference:** Information boundary (D-011), insert/minimap UI, knowledge graph (D-041), T-732 (minimap ticket) +- **Cross-reference:** Information boundary (D-011), insert/minimap UI, knowledge graph (D-041), T-732 (minimap ticket). See also [D-255](../decisions/architecture.md#d-255) (body-map-viewer stepped Atlas render architecture, 2026-07-24) — a different system (Phase-4 world-generation Atlas, not the player exploration insert), but its server-authoritative per-step data canvases, tagged-envelope carrier, and client cache tiers are shipped prior art for exactly the tile-pyramid/lazy-load/server-authoritative-cache architecture this question sketches. ---