Files
settled-reach/project.yaml
T
jpmschweitzerandClaude Opus 5 9b146f9e1f fix(simulation): derive at the octaves a rung can actually reconstruct (T-1240)
Region rendered as fine uniform stucco while District and Quarter, on identical
code, read as terrain. The cause was sampling: `min_wl_m` arrives as an LOD
request and defaults to 0, so every invented octave contributed at every rung.
MIN_WL_BANDS_M was meant to be the floor but is built from the rung's CELL SIZE
(2 x DISTRICT_M), which stopped being the sample spacing at the D-255 extent
inversion — a rung fixes EXTENT now and spacing falls out of the canvas size.
The bands were off by roughly the cell count, and the served path never consulted
them anyway.

The cutoff is now derived from the resolved spacing, which is what this ticket
asked for. Two things had to be measured rather than reasoned to get it right,
and both corrected me.

FIRST: the field was the culprit, not the renderer. I attributed the stucco to
the client stipple painting noise onto a smooth field. Surfacing the terrain
layer's own mean |relief_q gradient| in the capture readout settled it in one
shot: Region 18.24 steps per cell — 144 m of relief between NEIGHBOURING cells —
against District's 0.30 and Quarter's 0.07. The server was sending noise. That
diagnostic ships here for the same reason `plane_variety` did in T-1213: a noisy
field and a renderer inventing noise look identical, and one number separates
them.

SECOND: Nyquist is the wrong threshold. The first version floored at 2 x spacing,
the aliasing limit, and Region barely moved (56.16 -> 59.73 lum spread, gradient
still 18.24) because 2 samples per cycle is unaliased but renders jagged. The
rungs that already worked say what the real bar is: District reconstructs its
finest surviving octave at 34 samples per cycle, Quarter at 135. At 8x, Region
goes to 1.08 gradient and 70.01 spread, and shows ridges and valleys.

THE TRADE, taken deliberately and recorded in the tests: an 8x floor also
truncates the coast warp's 2,048 and 1,024 m octaves at Region, the band T-1160
added for "one coastline at every rung". An earlier test here asserted that band
must survive; it now asserts the opposite. Same reasoning as the relief: a
1,024 m coastline wiggle at 379.3 m per cell is 2.7 samples per cycle, so drawing
it draws noise rather than coastline character — a rung cannot show shape finer
than its own cell. The warp is amplitude-capped sub-pixel on the working grid, so
what is lost is small. If a future pass wants the warp exempt, the fix is a
relief-only floor threaded through derive_at_metres, NOT a lower multiple, which
takes the stucco back.

Global is exempt: its floor would be ~70 km and would truncate the whole warp
band, and it needs none — the orbital derive leaves relief_q flat at 50. District
(3.79 m spacing) and Quarter (0.948 m) floor below every octave in play and
derive byte-identically, which their own test pins.

Cache-safe by construction: the floor is a pure function of (rung, extent,
body_radius), all three already in the step-canvas cache key. 0.4.12 is required
anyway — this changes derived BYTES at Region, so a 0.4.11 entry holds a field
this build would never produce.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 12:43:46 +02:00

112 lines
6.5 KiB
YAML

name: The Settled Reach
# Version scheme: 0.{phase}.{n} — phase = active Development Cascade phase (D-166).
# Phase 4 (deterministic world generation) is active.
# 0.4.1-tagged step canvases carried lake_margin_q's flat absolute-ceiling
# semantics (pre-per-basin-normalization) and were written to real disk
# caches during T-1188 eyeball runs — 0.4.2 forces those entries to miss.
# 0.4.2-tagged Global/Region-rung step canvases sampled the coastline
# position raw (no coast_warp_px) — T-1160 applies the warp at orbital
# sampling, changing orbital-rung canvas bytes (elev_q/temperature_dc/
# moisture_q shift near coasts) — 0.4.3 forces those cached entries to miss.
# 0.4.3-tagged canvases predate the D-255 extent inversion (a rung now fixes
# the canvas EXTENT and the gridunit spacing falls out, rather than the
# reverse). District/Quarter/Block/Chunk kept the same 960x540 cell counts
# through that change, 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. 0.4.4 forces those entries to miss.
# 0.4.4-tagged GLOBAL canvases are all 2x1 — `serve_step_canvas_request` was
# still zeroing Global's wire extent (a pre-extent-inversion sentinel), so
# every Global request resolved to a two-cell canvas regardless of viewport.
# Those entries were also cached under a key that ignored extent, so they
# answered every later request forever. 0.4.5 forces them to miss.
# 0.4.5-tagged canvases carry pre-T-1237 river courses: one course per D8 HOP
# rather than one per river (build_paths walks whole headwater->mouth rivers
# now). The hop-shaped entries survived that fix because it changed only server
# and client code, never this version — so Ferrath Global replayed 375 fragments
# with a 106 km longest run, all of them below D-261's read-as-a-line floor, and
# drew NOTHING, while a cold cache on the same build produced 73 whole rivers
# with a 1,644 km trunk and drew 18. Diagnosed as T-1239. 0.4.6 forces them to
# miss.
#
# THE PATTERN, now four bumps deep: a change to how a canvas is GENERATED is
# only half the change — the other half is this line. Nothing enforces the
# pairing, so a canvas-generation commit that leaves this version alone ships a
# silent regression that reproduces only on machines with a warm cache. The
# capture harness no longer hides it (tests/run-visual isolates user:// per run,
# T-1239), but a real player's cache is still governed by this number alone.
# 0.4.7 carries NO canvas-generation change — it is the gate that enforces the
# pairing described above, arriving (T-1242). tooling/canvas_sources.py lists the
# paths, tooling/check-canvas-version rejects a push that touches them without
# moving this line, and the registry includes ITSELF so that narrowing it and
# changing a canvas file in one push still demands a bump. That self-inclusion is
# why this bump exists: adding the registry trips its own rule. The version is
# spent rather than special-cased, because "this change is obviously harmless" is
# the reasoning that produced all five regressions above.
# 0.4.8 also carries NO canvas-generation change: a test-only edit to
# composition.rs (its conservation assertion was a tautology) tripped the T-1242
# gate, because that gate is path-based and cannot tell test code from generator
# code. Second no-op bump in two days — the false-positive rate is real, and is
# the price of the ruling that a false negative costs a week. Revisit if it
# becomes noise; do NOT add an override to dodge it.
# 0.4.9 is a REQUIRED bump, not a gate formality (T-1213). The disk cache stores
# the DECODED canvas, and the decode was silently dropping the `relief_q` plane —
# so every 0.4.8-and-earlier entry physically lacks the field, and would keep
# rendering the deep rungs as a flat wash even against a build that reads it.
# This is the first bump in the series where a warm cache is wrong about CONTENT
# rather than merely stale.
# 0.4.10 changes how a canvas is PAINTED, not how it is derived, so cached
# entries are still valid content — but the client step_canvas cluster is in the
# T-1242 registry and the rule is bump-when-touched, so it moves. Cheap: one
# round of misses against a map that now reads as terrain.
# 0.4.11 — per-class cover marks (copse, thicket, rocky outcrop). Paint again,
# not derivation; bumped because the client step_canvas cluster is in the T-1242
# registry.
# 0.4.12 changes DERIVED BYTES, not paint (T-1240): Region canvases now truncate
# every octave the rung cannot reconstruct, so a 0.4.11 Region entry holds a
# relief field this build would never produce. Warm caches are genuinely wrong
# here, not merely stale.
version: 0.4.12
repository: settled-reach
description: >
Top-down life-sim set in an original science fiction universe. Asymmetric
information and occlusion-based perception from a single-character
perspective, with a Rimworld-style storyteller. Systems interactions like
The Sims, combat and visuals like single-character Rimworld, world
generation inheriting from Dwarf Fortress and NMS, economy inspired by
X4/EVE. Every system is interactable but ignorable — the world is alive
for any given run.
setting: >
The Settled Reach — a network of star systems connected by Founder Gates.
Neural lattice technology enables soft immortality, forking, and
re-embodiment.
development: >
Built outside-in along the six-phase Development Cascade (D-166): wiki
content and star map, economics layer, planetary maps and atlas,
deterministic world generation (active), player control and in-world
rendering, room-level detail.
architecture:
client: Godot 4 (GDScript)
server: Rust with bevy_ecs (simulation server)
transport: subprocess/IPC via TCP with MessagePack serialization
protocol: length-prefixed MessagePack frames (4-byte big-endian)
determinism: ChaCha20 seeded RNG, deterministic tick processing
simulation:
tick_rate: 10 ticks per game-minute
time_model: 4 day phases (Morning, Afternoon, Evening, Night)
grid: dual-scale (0.5m simulation tiles, 1m visual tiles)
visibility: symmetric shadowcasting + forward/peripheral/blind vision cone
knowledge: per-entity knowledge graphs with confidence decay
content:
format: YAML with JSON Schema validation
structure: campaigns/systems/stations/districts hierarchy
npcs: 10-axis model (7 essential + 3 supporting) with CombatCapability
dialogue: tagged line pools with 4-layer relational filtering
population: 30% flat / 50% mundane triangles / 20% intrigue-entangled