A2 (river_course.rs): Stage A rung-independent valley-seeking control
path (chord/8 stations, k=5 bilinear-scored candidates + continuity
penalty); Stage B rung-indexed perpendicular warp on GLOBAL arc-length
(window-independence, Ruling 1e), band chord/2 down to min_wavelength_m
hard-truncate, sine taper to zero at anchors, amplitude min(8% chord,
half-cell) slope/class-scaled. SeedDomain::RiverCourse=17, distinct
salt. Wire: RiverCourse{edge_id,class,points,terminus} on
DistrictWindowLayer.courses (serde-default); bbox-culled, window-
cropped +1 station. TerrainAnalysisCache retains Layer1Output (the
gen_queue:626 discard, Ruling 4b). A3: mouth termination walks
stations sampling the window's OWN rung-consistent morphology verdict,
6-iteration bisect; land-at-anchor probes one segment then None;
EdgeDrain never probes. A5: near_perennial_water point-to-segment
predicate (D-239 §8 governed bands 1-3m class-scaled) threaded through
both batch and window paths; Region always false; never touches
moisture_q. Discipline closed: dormant zoom-ladder bench run + numbers
recorded in the design doc (District 3.009/1.785, Quarter 1.823
us/cell, Region window 0.617ms); course-cost bench CAUGHT a real
+12-36% per-cell riparian scan regression -> precomputed bbox O(1)
reject (60ns->2.2ns/call), final delta +3.4-6.9% at budget; three
determinism tests (overlapping-window byte-identity, cross-rung
amplitude bound, warp-stream cross-correlation r<0.3); goldens: window
sweep gained a verified course-bearing position (pure append), new
river_course golden at both rungs, believability verified unchanged.
Revert-verification discovered the pole-row branch is structurally
unreachable (flow_direction bounds-check) — the real edge-drain path
is k<0 flat-plateau; test fixture rewritten to exercise reality.
scale.rs stale comment fixed. Full cargo test green.
Tickets: T-1170, T-1168
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RiverNetwork gains river_downstream: Vec<u8> (serde-default, parallel
to river_cells): values 0-7 index drainage::D8 (row,col deltas, N/S/E/
W/NE/NW/SE/SW order); sentinels MOUTH=8, EDGE_DRAIN=9, TERMINAL=10
(reserved — the future endorheic-basin hook, Ruling 2c/7b). Captured
in extract_river_network's existing pass (fdir already in scope, one
map, no new grid pass). Pole-edge D8 exits reclassify as EDGE_DRAIN
and leave the mouths list (Ruling 3f); flat-peak interior no-outflow
cells get EDGE_DRAIN too. cascade_golden deliberately re-pinned: GJ1c
mouths 19->3 — sixteen were pole-edge artifacts, exactly Jeroen's
'circles with no sea in sight'; river_cells/attractors counts
unchanged (pure reclassification + additive field). 21/21 drainage
tests incl. mouth-sentinel/mouths-list bijection on the real fixture
and a synthetic pole-draining-grid case.
Tickets: T-1170
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The one additive server change of wave 1 per Tyre's carrier ruling:
river_class: Vec<u8> parallel to river_cells, serde-default-safe, no
new wire field. Log-scaled binning between RIVER_THRESHOLD and the
RIVER-RESTRICTED max accumulation (max over cells passing the is_river
elevation filter — NOT the grid-wide max, which peaks past the
coastline on wet large-ocean bodies and would starve the trunk class
exactly where Araminta's District-shows-trunk-only table needs it;
caught in lead review of round 1, fixed round 2). Log not linear
because accumulation grows combinatorially downstream. By construction
every body with rivers has trunk cells — pinned by a non-synthetic
regression on the committed GJ1c heightmap at the golden's exact
downsample. cascade_layer1.json regenerated: every pre-existing field
byte-identical (python-verified), only the new river_class arrays
added — GJ1c distribution stream 72 / tributary 18 / trunk 3.
drainage 16/16 (+10), layer1 3/3, layer_proxy 50/50, build --tests
clean; revert-verified (non-monotonic binning fails the monotonicity
test by name).
Tickets: T-1156
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DISTRICT_MIN_WL_M's doc in window_derivation_golden.rs still carried
the 'NOT 2×DISTRICT_M by coincidence alone' framing the I1 fix
inverted everywhere else — now states the rung authors the floor and
the terrain-octave coincidence is guard-pinned, matching the rewritten
MIN_WL_BANDS_M and coast_invention docs.
Tickets: T-1162
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tyre's I1 (his ruling: the cutoff's job is Nyquist, a property of the
RUNG): MIN_WL_BANDS_M's District band now derives from 2*DISTRICT_M
directly, decoupled from OCTAVE_WAVELENGTHS_M[3] — a detail_scatter
retune can no longer silently redefine the Atlas rung floor. Direction-
agnostic const assert pins the coincidence so drift on either side
breaks the build for a deliberate human decision; golden_cutoffs_match_
the_scale_ladder now pins the District coupling too. I2: D-226
paraphrase replaced with cites to the T-1150 wire-contract note +
T-1162 refinement resolution (2), plus a self-found stale 2048m claim
fixed in the same comments. I4: golden extended with airless/dry
(ceiling_q==0 short-circuit) and volcanic-coast (ridged warp) body
rows via body_sweep_samples() — fixture regen verified 510 insertions,
0 deletions (existing rows byte-identical, purely appended). Q2 nit:
vegetation_invention module doc now states majority-preservation as
the coherence guarantee, not per-cell class stability (a boundary
cell's flip IS the clearing mechanism). Forward-contract for T-1156:
0x5EED_C0DE promoted to named WINDOW_RELIEF_SALT const. 158 focused
tests green; cargo check --tests clean.
Tickets: T-1162, T-1161
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The octave extension changes derived window output by design (coast
crinkle below 16384m, voxel relief at Quarter, vegetation moisture
perturbation) — this regen records the sanctioned new baseline per the
ticket's part (d) discipline. The pre-existing BELIEVABILITY_STRICT=1
'vegetation present' advisory failure predates this change (Q-123-
tracked debt) and is unaffected.
Tickets: T-1162
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Coast: WARP_OCTAVE_WAVELENGTHS_M 5->9 entries (262144m down to 1024m,
Quarter's Nyquist floor) with new min_wavelength_m cutoff plumbing in
coast_warp_px/warp_fbm (the warp previously had no cutoff at all).
Relief: invent_primitives feeds the VOXEL band (1024-128m, salted) into
elev_q/slope_q under the same envelope/cutoff discipline. Vegetation:
new vegetation_invention module — two-tier (BodyParams+latitude envelope
ceiling; single blended fBm field, never-gated 100-400km massif tier at
70% + cutoff-gated district/voxel texture at 30%) perturbing moisture_q
OUTPUT so precipitation/glaciation/vegetation shift as one world-fact;
wire format unchanged. MIN_WL_BANDS_M 5->6 (adds 1024m band).
Mid-implementation correction caught by tests: District's quantized
floor is 4096m (OCTAVE_WAVELENGTHS_M[3]), not 2x DISTRICT_M — the 8192m
and 4096m coast octaves are legitimately District-admitted enrichment;
only 2048/1024m coast + the voxel band are Quarter-exclusive. Docs and
golden cutoffs corrected to match.
New golden: tests/window_derivation_golden.rs + fixture (derivation_
harness pattern, UPDATE_GOLDEN=1 regen) with structural guards pinning
Quarter/District divergence. Bench (zoom_ladder_bench, release): no
regression — all rungs at or below baseline (District c0 3.54->~3.0
us/cell, Quarter 2.10->1.77, orbital 1.48->1.42, n=6400 window
0.86->0.59 ms). Revert-verified voxel_relief cutoff exclusion. Server
lib 1840 passed; new module 11 tests; district_profile 84 (+6, 1
noise-fragile pre-existing test properly fixed via 8-district
latitude-band averaging).
Tickets: T-1162
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dudley's audit of the suspected ConnectionId(0) starvation: exhaustive
trace of the delivery path (bridge/mod.rs insert/lookup/send paths,
atlas plugin serve/drain, gen_queue coalescing) found NO sentinel or
default-value collision — ConnectionId doesn't even derive Default, and
every id path is a plain monotonic allocation with linear lookup. The
real gap was coverage: every existing window test used ConnectionId(1).
New connection_zero_window_delivery module replicates main.rs's actual
bootstrap (blocking first-accept -> BridgeResource -> accept loop) and
pins: the first-ever connection receives a district window; the exact
six-tile entry shape delivers all six to connection 0; connection 1
keeps working. 5/5 repeated runs, single- and parallel-threaded.
The live 'starvation' itself was client-side (pending responses never
re-requested on the tile path — separate fix in flight); these tests
stay as the server half's permanent pin.
R1 measured first: a capped Region tile through the real production path
(build_district_window_layer + T-1151 par_iter) at the 64x64 wire cap
costs 0.40-0.48ms — faster than the shipped district n=64 window, so
Jeroen's progressive capped-density tiling ruling is comfortably
interactive on-demand. Raw orbital derive ~0.9µs/cell (~2.3x faster than
full derive; region_baseline dominates, not invent_primitives).
R5 redesign: WindowGranularity enum (Quarter/District/Region), serde
named-variant per the RoadNodeKind precedent, spacing from D-243 scale::
constants — the single source of truth. Additive serde-default
window_granularity_v2 request field (None = legacy u32 path; v2 wins when
Some); DistrictWindowLayer.granularity_v2 always echoed. Legacy u32 echo
for Region uses reserved WINDOW_GRANULARITY_REGION_KEY = u32::MAX (never
a legal input) so the old slot cannot lie about aliasing. Cache and
coalescing keys carry the enum itself (Ord by declaration order, D-010).
n stays district-extent at every rung; Region's cell grid is a DIVISION
(round(n/100), min 1) with its own per-axis ceiling
DISTRICT_WINDOW_MAX_N_REGION=6400 and a bounded halving-loop clamp (no
closed form under the rounding division — the client mirror must
replicate the loop).
derive_orbital_at_metres: bilinear envelope reads + region_baseline
temperature, NO invent_primitives (proven by test — slope_q pinned 0),
routed through the shared build_district_profile classification tail so
the existing colorizer family renders orbital cells unchanged. R2
stepped-categorical behavior documented at the function, not implied.
Region aliasing + clamp/echo tests mirror the T-1150 discipline. 1813
lib tests green; clippy clean; fixture regenerated (254->278 bytes, new
echoed field).
T-1151: build_district_window_layer dispatches one Rayon task per row
(pure derive_window_cell via derive_at_metres), scattered row-major into
the flat arrays; a cfg(test) serial path backs the bit-identical
parallel-vs-serial golden.
T-1150: serde-default window_granularity (1=district, 4=quarter) +
window_min_wl_m on AtlasLayerRequest — additive, no sixth demux shape,
old frames decode unchanged (tested). Quarter mode = full
reclassification at 512m spacing over the same world rect ((4n)x(4n)
cells); WIRE_CAP_CELLS=4096 enforces n*granularity <= cap (quarter
clamps n to 16, the design doc's worked example). Granularity + min_wl
key ALL five touch points: DistrictWindowLayer echo, server FIFO-256
cache key (now a 5-tuple), per-connection coalescing key, client
request codec (omitted-at-default wire fields), client LRU key.
Mandatory aliasing regressions on both ends: identical (body, center, n)
at granularity 1 vs 4 produce distinct cache entries and correct
per-granularity payload shapes (server, 3-thread queue to avoid the
AnalyzeBody thread contention found while writing it) and distinct
client cache keys (gdUnit). Replay fixture regenerated — the layer
struct grew two echoed fields (231->254 bytes, content verified).
Client requests stay district-granularity by default — quarter requests
arrive with T-1153's rung selection.
Extract derive_district's fractional-metres interior into the metres-
addressable derive_at_metres(seed, body, params, ta, wx, wy, climate,
min_wavelength_m) — the zoom ladder's keystone (design doc §8 step 1).
derive_district is now a thin DistrictPos-quantizing wrapper calling it
with cutoff 0.0; all four golden/believability/derivation harnesses pass
byte-identical.
enveloped_fbm/terrain_detail/voxel_relief gain min_wavelength_m: octaves
below the cutoff are hard-truncated (amp still advances so surviving
octaves keep relative weight; norm==0 guarded). Amplitude-fade-near-
cutoff is documented as a seam, not built — the pop-risk A/B needs the
client ladder (T-1153). Cutoff 0.0 is bit-identical to pre-change
output, asserted by test.
Measured (release, 4096-cell sweeps, zoom_ladder_bench.rs): district
spacing 1.200µs/cell (cutoff 0) / 1.201µs (cutoff 2048m — all octaves
survive, plumbing check); quarter spacing 1.165µs/cell (cutoff 512m).
Matches the design doc's ~1.2-1.4µs/cell release estimate.
AtlasLayerRequest gains window_center/window_n (serde-default, absent
= whole-body, wire back-compat; demux untouched — up_to stays the
discriminator). DistrictWindowLayer echoes center/n + six parallel
arrays (morphology, elev_q, temp_dc i16 with the region sentinel,
moisture_q, vegetation incl Marine=6, glaciation).
Serving per the amendment's binding model: NEVER inline —
GenWorkItem::DeriveWindow rides the Rayon queue, completion drain
caches into DistrictWindowCache (bounded FIFO 256; no staleness by
D-227 purity, capacity bound only), serve_district_window polls the
cache and returns Pending-shaped None until derived. Per-connection
coalescing: submit_window supersedes a still-pending item for the
same (ConnectionId, body) — the surviving item is the newer one,
proven by dedicated tests.
TerrainAnalysis decision (option b, numbers in ticket/PR): re-derive
via run_layer1 in the DeriveWindow branch rather than caching ~1.5MB
x 50 LRU slots (~100MB permanent, the exact D-203 bloat T-1044's own
text guarded against); ~45ms one-time on the Rayon path, invisible to
the tick thread. T-1044 confirmed within-cascade-only (cascade.rs:358
still drops the analysis before BodyWorldState) — the fork was open.
Window derive loop promoted from aliveness_probe::render_window_panels;
determinism promoted from probe-only proof to a real test (two passes
byte-identical). New fixture atlas_response_ready_with_window
exercises all six arrays incl. the airless sentinel and Marine; three
existing fixtures gain district_window: None. Server clamps window_n
to 1..=DISTRICT_WINDOW_MAX_N=64 (never trust the wire).
1774/1774 lib + 19/19 bridge_tcp green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ZoningType gains PartialOrd/Ord (DistrictType T-994 mirror) for the
dominant_zoning declaration-order tie-break. layer_proxy:
QuarterFootprintEntry/QuarterFootprintLayer (BTreeMap keyed by
city_id; density_avg_pct floor-mean, dominant_* mode with
declaration-order tie-break, landmark/corridor counts) +
build_quarter_footprint_layer gated None on empty state.quarters;
sixth AtlasLayerResponse field wired into all construction sites;
growth-ceiling doc updated to say the whole-body budget is consumed
(what carries a seventh is T-1124's design, resolved in the same
branch's amendment). Placements whose derived Layer4Quarter id is not
yet in state.quarters are SKIPPED (skeleton gen is async Low-priority
— entries may be a proper subset; client decodes defensively). Stale
TODO(#957) removed (premise predates T-957 landing).
Tie-break bug caught by its own test: Iterator::max_by_key over a
BTreeMap returns the LAST max on ties — backwards from
declaration-order-first; replaced with an explicit forward walk that
replaces only on strictly-greater count.
gen_fixtures: populated RegionGridLayer (T-1118 replay coverage) +
QuarterFootprintLayer literals in the ready fixture; msgpack fixtures
regenerated via make fixtures. Note: landmark_count is inert on real
data today (BlockSkeleton.landmark hardcoded None until landmark
placement lands); fixtures use non-zero counts to exercise the logic.
5 new unit tests + populated round-trip; atlas:: 549 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
browse_reader.rs: BrowseReader on the CityContextReader::open() pattern
— six index + six detail reads against systems.db (bodies filterable by
containing system; system/corporation/commodity details fold their join
partners). browse_proxy.rs: BrowseRequest{browse, kind, query} /
BrowseResponse{kind, status, index, detail} wire types + dispatcher;
BrowseIndexRow{id, primary, secondary} generic across kinds;
BrowseDetail a per-kind enum of field-exhaustive structs.
Demux: Inbound::BrowseRequest is the FIFTH map shape — deliberately the
last; the doc's four-shape ceiling is re-pinned at five with rationale
(six kinds x two forms folded into ONE envelope whose internal enums
pick sub-behavior, the AtlasLayerRequest.up_to precedent, instead of
twelve top-level shapes) and a hard rule that a sixth shape must
migrate to the D-225 tagged-envelope framing. Served for BOTH roles,
connection-tagged 1:1 in-order like atlas/starmap/citynames.
serve_browse_requests pub so integration tests drive the true
end-to-end pipeline. Wire-only per D-254 (T-949 precedent); v1
exclusions (cascade geometry, event logs) respected.
30 unit tests + 7 bridge_tcp integration tests (six-kind round-trip
over real TCP, reader-can-browse, no crossed responses between two
readers, unknown-id NotFound, empty-table Ready); 2 pre-existing tests
updated for the new receive_bridge_inputs parameter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ConnectionRole (Player|Reader, serde-default Player for wire back-compat;
shaped for a future TradingReader) on StartupMessage. BridgeResource
rewritten as 0-1 Player + 0-N Readers with ConnectionId; per-tick
accept_new_connections loop replaces the single blocking accept (the
listener is cloned non-blocking into ConnectionListener). First
connection installs per startup.role — spawn-mode Readers are often the
only connection a server gets.
Permitted-message matrix: readers may handshake and issue
Atlas/StarMap/CityNames requests (responses connection-tagged, own
requests only); PlayerInput from a reader is dropped with a strike
(disconnect at 3); ObserverSnapshot has no reader-facing path at all.
Shutdown scope: only a PLAYER send failure flips ServerRunning —
reader-only servers idle with the snapshot queued; reader disconnects
never kill the session.
Deliberately out of scope, documented at the call site: character spawn
stays in monolithic world-setup (reader-first servers carry an inert
unpiloted PlayerCharacter); no total-reader-count cap (per-reader frame
cap only, loopback-only scope).
6 new bridge_tcp integration tests; layer3 subprocess test exercises the
accept-loop end to end.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mechanical call-site updates (7) for the invent_primitives signature;
believability golden regenerated via UPDATE_GOLDEN=1. Deltas are
deliberate: slope_q/elev_q spreads widen, vegetation_classes +1
(Marine), water-districts-wet coherence reaches 46/46 and 19/19
(T-1082 class closed — classification and coastline are the same line).
Reductions (ocean_fraction distinct, morphology_zones on GJ244Ad)
are the eager pseudo-grid point-sampling coasts honestly instead of
area-averaging them into synthetic intermediates; believability
coast-sampling resolution is Q-123 calibration territory. All 37
derivation-harness law checks unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CascadeLayer::Region appended per the enum's append-only Ord rule (depends only on seed/body_params/heightmap dims — documented); BodyWorldState.regions BTreeMap per the districts precedent; RegionGridLayer dense row-major all-integer encoding (season/weather repr(u8) discriminants, mean_temp deci-degC i16 with i16::MIN airless sentinel, moisture_q u8); build_region_grid mirrors build_district_grid; protocol.gd region_grid passthrough (visual overlay deliberately out of scope); wire fixtures regenerated via make fixtures. atlas:: suite 507 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
H1 demux: ShapeProbe defensive multi-shape rejection (union frames now Err, not first-match; +2 tests) and doc claim made honest. H2/T1 SystemIndex.reset_test_state() folded into SimBridge.reset_test_state() (load() inline per autoload rule) + has_pending_request() accessor. H3 no-op tests now assert the replay flag both directions. H4 retry test actually ingests a failure and asserts the retry semantic. H5 error fixture uses the normalized status string. H6 bridge_tcp e2e sends all five frame shapes over real TCP (star-map + city-names buffers asserted). H7 positive replay-on-CONNECTED test via the test_local_bridge test-mode-flip precedent (stub bridge captures + decodes the request bytes). H8/T2 stale PLACEHOLDER doc replaced with the confirmed contract. H9 is_capital doc matches the COALESCE reality. T-r1 demux ceiling written down (next shape = tagged envelope). T-r2 AtlasLayerResponse governance ceiling comment. Lead item: the four cargo-fmt-formatted files from the gate round are now committed (layer_proxy/plugin/bridge-mod/main). H10 note for the record: the 13 snapshot_*.msgpack fixtures in commit 845737617 were regenerated because they were stale against their own generator (pre-existing version-key removal) — verified harmless, no client reads that key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
T-960: RoadGraphLayer (L2 edges/junctions, MaintenanceAuthority, rail flag, named routes) + SettlementLayer (name/position/size-class/is_capital/is_port from T-955 placements) as new Option fields on AtlasLayerResponse — T-1046 precedent; layer_proxy doc comment corrected (cascade runs through RoadGraph, client up_to ignored). T-949: new StarMapRequest/StarMapResponse (raw JSON passthrough of the star_map_data.json bake) + CityNamesRequest/CityNamesResponse (atlas_city_names via CityContextReader) with defense-in-depth Sol exclusion (GJ-0 system id OR settlement_wave='origin', D-236). Inbound demux extended with mandatory boolean discriminator fields (serde ignores unknown fields — optional-shape sniffing would be ambiguous); AtlasLayerRequest unchanged. SimBridge trait +2 methods, implemented on TcpBridge + LocalBridge. CityPlacement/CityRecord gained name/population/is_capital (BodyWorldState cache-hit path stays DB-free). ~30 new unit tests incl. demux disambiguation + Sol branches; gen_fixtures extended for road/settlement samples.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hoshe + Tyre review of the micro-habitat-mosaic branch requested changes on two
data-confirmed correctness bugs plus follow-through. All addressed.
Blocking:
- C1 (voxel.rs): the mosaic re-granted vegetation the climate withheld — a frozen
ice world (Edict) read 61% vegetated tundra. The Grassland apply-gate now fires
only where the climate already grants cover (!Barren); a climatically-barren
district (frozen < -50C = surface ice/geology D-239 §2, or hyper-arid moisture<5,
both resolved to VegetationClass::Barren upstream) is skipped. Genuine tundra
(cold + moisture>=15) is Scrub upstream and still reaches the tundra palette via
the same !Barren path. believability.json regenerated: Edict vegetated_districts
39->0, Arbour 20->8 (its real cold-pole/arid districts revert to barren);
intra-class variety preserved (Arbour vegetation_classes 3, micro_habitat_distinct 2).
- H1 (bodies.py): biosphere Gate 0 keyed on `inhabited`, conflating settlement with
biosphere and force-Airless'ing uninhabited-but-alive worlds (D-247: worlds were
alive before humans). Gate 0 is now settlement-independent — keyed on
atmosphere/hydrosphere/planet_class. 24 uninhabited-alive worlds now classified
(was 0); GJ0g-1 (dense atm + rivers) -> NativeMirror. Split 122 Compatible :
121 Mirror; all 9 canon exemplars hold.
Follow-through:
- C2 (bodies.py + D-247): Gate 0 now reads planet_class (barren -> Airless) as
D-247's "+ planet_class" phrasing names. D-247 amended to record the actual
two-gate signal set (atm/hydro/planet_class habitability; economic_base_primary
chirality; settlement-independence).
- C3 (believability.rs): micro_habitat_distinct criterion relaxed to `== 0 || >= 2`
— the min-over-patches estimator makes K=3 unreachable against a dominant-entry
palette; K=2 provisional, Q-123 calibrates.
- C4 + H2 (D-246): amended to record the v1 scope cut — only the Soil-derived
vegetated/wet classes take the mosaic; material-driven families are `_ => false`
so Hardpan/Scree are authored but unreachable in v1; no elevation change in v1;
the mosaic OWNS the §8 climate->vegetation law for the classes it touches.
- H3 (voxel.rs): three dedicated mosaic-pass regression tests — C1 barren-respect,
§8 material-untouched + forest-textured, T-1040 no-water-without-channel.
- H4 (voxel.rs): relief_signal comment corrected to ~[-1,+1].
- H5 (bodies.py): frontmatter override now strips quotes + validates against the
4-value enum (warn-and-skip on typo).
systems.db regenerated + stamped (H1). believability golden regenerated (C1).
Full cargo test green; clippy -D warnings + ruff clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mosaic palette gains a climate-biome dimension for the Grassland surface-class:
savanna / temperate / boreal / tundra now read distinctly rather than collapsing to
one biome-blind palette (the D-246 biome-awareness fix).
- atlas/mosaic.rs: ClimateBiome (from temperature_c + moisture_q) + four biome-keyed
Grassland palettes; palette(class, biome). Savanna carries scattered Forest copses,
tundra carries lichen + sparse bare ground and no forest. §8-safe (veg + water only).
- atlas/voxel.rs: derive the biome; a cold barren district (tundra/boreal) now takes
the sparse mosaic rather than staying blank — worlds that read "0 vegetated / blank"
now read structured-sparse (D-245 climate-appropriateness). vegetated_districts
8→20 and 0→39 on the cold validation bodies.
- data/mosaic_constants.toml: biome-keyed grassland mirror.
- docs/design/alien-ecology-vocabulary.md: Miri's full biome × register naming layer
(the cane/vane/wrack/husk/drum-root native lexicon + Earth-register names) — the
derived-label source for the palette and Phase-5 rendering.
Believability golden regenerated. Full server suite green. Forest biomes and the
introduced-Earth register are the noted continuation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sub-chunk mosaic that modulates the D-228 axes into a spatially-coherent,
relief/moisture-conditioned mosaic of micro-habitats within a terrain class —
the fix for the aliveness probe's uniform "Wetland 100%".
- seed.rs: SeedDomain::VoxelMosaic = 12 (append-only; pinned-discriminant guard)
- voxel.rs: five new D-228 axis values (Vegetation::{Meadow,Deadfall,Lichen},
TerrainMaterial::{Hardpan,Scree}); the per-family-gated mosaic pass
- detail_scatter.rs: voxel_mosaic() selection field (~8-64 m band)
- atlas/mosaic.rs: surface-class palette + integer-basis-point weighted selection
with moisture-affinity conditioning; temperate/native baseline palettes
- data/mosaic_constants.toml: source-canonical palette mirror
- believability.rs: micro_habitat_distinct metric + D-245 intra-class criterion
v1 scope (D-246-faithful): modulates vegetation + water only, in the vegetated
classes (Wetland/Forest/Grassland), so the D-239 §8 lithology law is untouched;
water is gated on the T-1040 has_active_channel hydrology rule. Material variants
(hardpan/scree) and the extreme classes are a §8-aware follow-up; the biome-aware
palette variants (T-1085 vocab) extend the baseline before generation.
Goldens regenerated (believability + derivation). Full server suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The push gate's integration tests (not in cargo test --lib) caught two regressions
from the relief change:
- derivation_harness::cross_district_elevation_blend_reduces_seam_step — the relief's
large per-position term (span 300 → ±200 m, far above the compressed elev_q/2 base)
swamped the T-1042 seam measurement and clamped heavily at sea level. Fixes:
(1) shift the voxel-relief octave band to 0.13–1 km (all sub-district, dropping the
2 km octave that competed with elev_q's district role); (2) reduce VOXEL_RELIEF_SPAN_M
300 → 100 so relief stays mostly below the base (fewer sea-level clamp artifacts,
proportional hills); (3) rewrite the seam test's avg_elev to average over an 8 km
multi-wavelength y-transect so the zero-mean relief cancels, isolating the base seam.
- derivation_harness::golden_seed_determinism_regression — legitimate golden refresh
(determinism still holds; elevation values changed intentionally).
Believability relief now ≈22 m mean (was 5 m flat; 59 m at the over-aggressive span 300)
— navigable hills without clamp artifacts, 7/8 criteria. Both goldens regenerated; full
cargo test (38 binaries) + clippy --all-targets -D warnings green. D-239 amendment +
Q-123 item 4 updated to the final span/octave/numbers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The voxel tier read flat (the D-245 "0–3 m, no hills to navigate by" bug):
family generators set elevation from elev_q at a compressed scale plus only ±4 m
micro-scatter, and detail-scatter's mid-scale relief (D-243 §2) only ever reached
the district tier (elev_q), never per-voxel elevation_m.
- detail_scatter.rs: extract the enveloped-fBm core; add `voxel_relief` at the
0.25–2 km octave band (vs the district 4–40 km band).
- voxel.rs: add the mid-scale relief post-dispatch in derive_voxel_column, body-global
SeedDomain::VoxelRelief seed (position-keyed), f64 truncated to integer metres (D-010).
Envelope = slope_q*3 + elev_q — the coarse heightmap gives slope_q ≈ 0 even on high
ground (Arbour max 13), so elevation must drive ruggedness. Flat families only
(Alluvial/Lava/BraidedDelta/Dune/Meander); the dramatic families already carry strong
internal relief and WaterBody stays at sea level.
- seed.rs: SeedDomain::VoxelRelief (D-224 domain separation).
- believability.rs: new `contrast.voxel_relief_m` metric — mean within-district elevation
range over a 2 km transect (a single 64 m sample chunk is narrower than the relief
band) + a "voxel relief" criterion.
Arbour @ yolo: voxel relief 5 m → 59 m, 6/8 → 7/8 criteria (remaining fail = the
Q-123 vegetation-denominator item). Edict 59 m. Golden regenerated; 1586 lib tests +
believability harness (determinism) pass; clippy -D warnings clean.
D-239 amended (T-1081); Q-123 item 4 updated. Deferred to Q-123/follow-up: the
provisional span + threshold, and a per-body hypsometric absolute-elevation model.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stack review (Hoshe + Tyre) of T-1083+T-1082+T-1080. Every finding fixed or disposed:
- believability.rs sample_indices: replace the rejection-sampling loop (could stall
when n ≈ take) with deterministic partial Fisher-Yates — O(n), guaranteed
termination, distinct, sorted (Hoshe+Tyre).
- believability.rs drainage proxy: exclude water-body districts (all elev 0, no banks)
and require both wet + dry voxels — they were passing vacuously and inflating the
metric (Hoshe).
- believability.rs read_cities: log skipped malformed rows instead of dropping silently
(Hoshe).
- voxel.rs: add the 3 WaterBody zones to all_families_no_panic_at_extreme_positions +
fix the "7 families" comment (Hoshe).
- climate_constants.toml: bold warning that [moisture_gradient] is not loaded until
T-1032 — tune ClimateConstants::default() (Tyre).
- Q-123: record the per-zone "water renders wet" threshold + the land-relative
vegetation-present denominator as calibration items (Tyre + the open 7th criterion).
- T-1083 ticket: note the sampling-fix attribution (landed in the T-1082 commit).
Believability golden regenerated (sampler + drainage fixes moved the metrics).
clippy --all-targets -D warnings clean; 1580 lib tests + harness pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
moisture_q was a single body constant (derive_moisture_q took only &BodyParams →
80 for every one of 2048 ocean-world districts), so vegetation/terrain/ecotones
were uniform — "nothing to fuzz" (the T-1080 believability bug).
- district_profile.rs: derive_moisture_q is now a body ceiling (hydrosphere+atmosphere)
× per-district gradient — latitude (equator wet → pole dry), elevation (orographic/
rain-shadow), continentality (100 − ocean_fraction_q). Integer/D-010, no new plumbing
(elev_q/ocean_fraction_q/latitude_deg already reach build_district_profile). Mirrors
D-240's temperature model. Coefficients in ClimateConstants + climate_constants.toml
[moisture_gradient] (provisional; Q-123 calibrates). Tuned so a wet body stays mostly
green with drier patches and a frozen body clamps mostly barren. + gradient unit test.
- region_profile.rs: region-tier moisture gets the region-latitude gradient (elevation/
continentality are district-scale).
- Propagates to precipitation_class / vegetation_class / morphology_zone (all read
moisture_q). D-239 §2 amended. Believability golden regenerated.
Probe (Arbour @ yolo): moisture_q distinct 1 → 49 (spread 0 → 48); 5/7 → 6/7 criteria.
Edict (frozen): moisture 0..20 ×21 — gradient within its low ceiling. Remaining fail
(vegetation-present) is the metric dividing by all-sampled incl. water on a 2/3-ocean
world — a Q-123 calibration concern, not a climate defect.
clippy --all-targets -D warnings clean; 1580 lib tests + harnesses pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The water zones (OpenOcean/Lake/TidalFlat) fell through to the dry-land AlluvialPlain
fallback, so the sea rendered as dry forested land (the D-245 believability bug).
- voxel.rs: new MorphologyFamily::WaterBody (9th family) + generate_water_body,
dispatched from zone_to_family. Deep open water with a Shallow shoal band on the
district-anchored coast line (wider for shallower bodies); TidalFlat all-Shallow.
Seabed Rock (steep) / Sand (gentle) / Wetland (tidal mud); Vegetation::Barren;
water surface elevation_m = 0; seasonal Ice via derive_cover (frozen seas). Wetland
stays AlluvialPlain (it is land — a marsh, not open water). Integer/D-010. + 4 tests.
- believability.rs: fix a sampling bias in the T-1083 enforcer found while verifying
this — the metrics sampled the first 64 districts in BTreeMap order (a spatial corner,
e.g. all-ocean), which reported a forested world as "0 vegetated". Now a deterministic
seeded spread across the body (matches D-245's "randomly-sampled locations").
- D-239 amended (§5 8→9 families; §6 refined — the 3 water zones now dispatch to
WaterBody). Believability golden regenerated.
Probe (Arbour @ yolo): water-renders-wet 4/64 FAIL → 42/44 PASS; terrain-material
variety 1 → 4 PASS; 3/7 → 5/7 criteria. (Remaining fails — moisture gradient,
vegetation-present — are T-1080.)
clippy --all-targets -D warnings clean; 1579 lib tests + the harness pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Promote the throwaway aliveness probe into a committed, repeatable believability
protocol — the instrument that found T-1080/T-1081/T-1082.
- atlas/believability.rs (new): BelievabilityReport (serde) + analyze() computing
CONTRAST (per-field min/max/distinct for moisture/elev/slope/ocean over all
districts; distinct morphology zones / vegetation classes / terrain materials)
and COHERENCE (water-renders-wet, drainage-monotonic, vegetation-present) — never
marginal per-tile counts (the lesson: that called a broken uniform world ALIVE).
evaluate_criteria() = advisory D-245 checks; cascade_for_body()/seed_to_u64()
loader shared by the bin and the harness. Unit tests prove the metric tells a
uniform world (fails) from a varied one (passes) + determinism.
- bin/aliveness_probe.rs: refactored to a thin CLI over the module — prints the
body-level report + advisory D-245 criteria, drops the naive per-tile verdict.
- tests/believability_harness.rs (+ golden): runs the real cascade for Arbour
(temperate/ocean) + Edict (frozen/ice), asserts determinism, golden-snapshots
the reports (regression — updates when T-1080/T-1082 land), advisory criteria
with BELIEVABILITY_STRICT=1 to fail on unmet D-245 criteria. Skips if committed
data absent. x86_64 golden (cascade has f32 warp paths).
Baseline @ believability-v1: Arbour 3/7, Edict 3/7 criteria pass — moisture
gradient, water-renders-wet, drainage all FAIL (T-1080/T-1082); Edict vegetation
0/64 (blank tundra — the frozen 'reads dead' case D-245 targets).
PNG layer maps (ticket item 3) deferred — explicitly optional; the contrast +
coherence metrics are the core enforcer.
clippy --all-targets -D warnings clean; 1575 lib tests + the harness pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T-1044: run_layer1 now returns TerrainAnalysis (carried transiently on
CascadeSnapshot, dropped after the district + road-graph passes), eliminating the
redundant per-body drainage::analyze + TerrainAnalysis::analyze re-run flagged by
PERF/TODO(T-1044). Not persisted on the LRU-cached state (D-203/T-1048 size concern).
T-1047: basin_direction is now derived from the real D8 thalweg. run_layer1
aggregates a per-district dominant D8 direction from the live fdir grid (carried
transiently on DrainageResult), threaded via Layer1Output.district_basin_dirs ->
derive_all_districts -> DistrictProfile.basin_direction; derive_chunk_context reads
it directly. Removed the false derive_basin_direction (it branched on ocean_fraction_q
then read seed bits despite a doc comment claiming an elev_q/slope_q D8 proxy) +
corrected the module contract. D-239 §8 (D8 thalweg) now actually honoured.
1559 tests pass; golden byte-identical (district_basin_dirs is #[serde(skip)], transient).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses PR #167 review (Tyre T1, T3).
T1 — the region climate tier was built but no live path consulted it: both
derive_district_profile (cascade) and derive_district (on-demand) passed
region_baseline_c: None, so the D-243 §4 edge-fuzz blend was dead in production
and district temperature was still derived from scratch. Now both paths compute
region_baseline_at_district(...) and pass it through build_district_profile so
the two-phase modulation (D-243 §3) actually runs. The cascade path
(derive_all_districts) builds a BTreeMap<RegionPos, RegionProfile> covering all
district positions + their +/-1 neighbours once per body and looks up from it;
the on-demand path derives the four surrounding region baselines on the fly.
No production path passes None except genuinely airless bodies (no planet_class).
T3 — renamed BodyParams.district_latitude_deg -> latitude_deg (it is the region
centre latitude in the region fns, a call-site trap under the old name);
dual-scale doc; propagated to body_params_reader.rs and all literal sites.
Golden fixtures are unaffected (they construct districts directly via make_region,
bypassing build_district_profile) — derivation_harness 37/37 green, no re-pin.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the single-DistrictProfile walking-skeleton restriction so
continuous terrain parameters no longer step at the 2 km district pitch
(D-239 §4/§7/§8). ChunkContext gains secondary: Option<DistrictProfile> +
blend_weight: u8; derive_chunk_context detects a chunk within one chunk of
a district border, looks up the adjacent profile, and integer-blends
meander/channel params on the context. derive_voxel_column integer-blends
elev_q/moisture_q before material selection, reusing the same warp offset so
the seam cannot align with the chunk edge (Cow fast-path keeps interior
chunks bit-identical — golden seed unchanged).
Morphology FAMILY selection is never blended — stays sharp per D-239 §7
(family dispatch reads the primary district only). Climate feathering is the
separate T-1078 path. Adds cross_district_elevation_blend_reduces_seam_step
and cross_district_morphology_family_seams_stay_sharp to the derivation
harness.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server half of wiring the derived tier into production. The D-225 proxy
(AtlasLayerResponse) carried only Layer1Output, so the Atlas could not show the
DistrictProfile tier that the cascade derives. Adds DistrictGridLayer (cols/rows
+ row-major MorphologyZone discriminants + elev_q for relief shading) and
build_district_grid(), populated on a cache hit. Because the response is
rmp_serde msgpack end-to-end, the new field reaches the client automatically.
This is the planetary-scale coarse grid (the Atlas map view); the on-demand 2km
derive_district (T-1077) is for in-world Phase 5, not the map. MorphologyZone
gains Copy (fieldless repr(u8) enum; additive).
Next: client decode (protocol.gd) + the D-226 generation overlay in the Atlas
viewer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2 of the D-243 re-scale. The ~1km carrier is renamed and re-scaled to
the 2km district, and every consumer reads scale from the canonical scale.rs
instead of a local literal.
- region_profile.rs -> district_profile.rs; RegionProfile -> DistrictProfile,
RegionPos -> DistrictPos (re-exported from scale::DistrictPos), derive_*_regions
-> derive_*_districts, across all 10 consumers + the derivation harness.
- chunk_context now references scale:: for the carrier cell: district 1024m -> 2048m,
shift 4 -> 5 (CHUNK_DISTRICT_SHIFT). This caught a real latent bug: the seed-district
index used a literal '>> 4' while the anchor used the constant, so same-district
chunks could derive different anchors — now both use scale::CHUNK_DISTRICT_SHIFT.
- derivation_harness scale literals ('>> 4', '* 1024', '0..16') converted to
scale:: constants (the same drift the canonical source eliminates). Believability
tests pass at 2km; the voxel determinism golden re-pinned (anchors moved with the
re-scale — deterministic, intended).
Full suite + clippy --all-targets -D warnings green. The district DERIVATION still
tiles the heightmap at 8 cells (the old coarse path); replacing it with on-demand
heightmap interpolation + detail-scatter is phase 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
derive_chunk_context now derives channel_anchor_m and coast_anchor_m from
the Region-or-higher seed path (D-239 §10); all five voxel placement sites
measure distance to these anchors in continuous world coordinates:
- T-1040: compute_channel_state / compute_meander_reach_channel /
in_levee_band subtract the region anchor instead of measuring from the
world origin axis — active channels now exist in their chunks anywhere
on the body, continuous across chunk boundaries. has_active_channel is
honest in both directions (band-crossing gate; floor covers the
BraidedDelta belt at short wavelengths).
- T-1041: fjord/cliff/gorge/delta drop their rem_euclid(64) chunk-frame
folds — one valley/coastline/fan per region instead of one per chunk.
D-239 §9 chokepoint widths preserved; two i32 wall-rise overflows on
region-scale distances fixed by clamping before multiply.
Harness: 6 new believability tests (channel presence + gate honesty at
chunk (1000,-750), cross-boundary continuity, one-landform-per-region,
braid-belt confinement); drainage sweeps strengthened with anchor-band
chunks. Voxel golden regenerated deliberately: case A now pins a genuinely
in-channel Shallow voxel on the anchor, case C a Deep trough voxel
matching its label; case B byte-identical and L0/L1 cascade golden
untouched (no upstream leak). Review: approved, three minors addressed
(truncation-convention docs, delta gate floor, case C relocation).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FrameAccumulator state machine in framing.rs holds partial prefix/payload
bytes across non-blocking receive() calls — a frame split across TCP
segments no longer desyncs the stream (read_exact previously discarded
partially-consumed bytes on WouldBlock). receive_bridge_inputs now drains
all ready frames per tick (capped) instead of exactly one, covering input
batches + atlas requests in the same window.
Review hardening: EOF mid-frame escalates to Disconnected like clean EOF
(peer died with a truncated stream) instead of logging an Io error every
tick. Tests: frame split inside prefix / inside payload, multi-frame
drain, mid-frame-EOF disconnect. Corrupt-stream escalation tracked as
T-1072.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hoshe review findings — close silent-pass gaps in the §8 drainage tests:
- assert_drainage_monotonicity now returns whether it actually checked (chunk
had both wet and dry tiles); the three sweep tests (Alluvial/Meander/Braided
Delta) assert at least one chunk exercised the law, so a regression that
zeroes all channels fails loudly instead of passing vacuously.
- The fjord-floor test scans the full 64x64 chunk instead of a single Y=32 row
and asserts the deep-water trough exists, so the sea-level floor law can no
longer be skipped by a probe that missed the trough.
- Remove the unreferenced make_test_terrain_analysis helper (was behind
#[allow(dead_code)] with a 'future use' comment — the maintenance trap the
review flagged); make_dry_terrain_analysis covers the validation bodies.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adapt the believability/budget verification harness to the T-1033 BodyParams
strip: validation-body fixtures (Kallast/Glødberg/Marevna) no longer set the
removed orbit/star fields (orbital_period_days, axial_tilt_deg, spectral_class,
star_type). Temperature is now derived from the planet_class envelope (D-240),
so the Marevna validation note/assertion is rewritten — oceanic band [-12,28]°C
+ maritime 0.6 + standard-atmosphere greenhouse yields ~+26°C at the equator
(was a stale ~-56°C orbit-equilibrium note). Add the seed→tile golden fixture
(tests/golden/derivation_harness.json) that the determinism regression locks.
Also fmt the harness and fix one clippy lint (iter().copied().collect() ->
to_vec()) — the file predated the pre-push gate's cargo-test/clippy coverage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First foundation slice of the Atlas-to-tile derivation model (epic T-974),
building the carrier layer ahead of its T-1027+ consumers.
T-1026 — Anti-squaring domain warp (D-239 §4): stateless pure
fn(seed,body_id,pos)->(f64,f64), ±8m, f64 to the final voxel then as-i32
truncation for IEEE-754 cross-target determinism. New domain_warp.rs,
SeedDomain::DomainWarp; golden-vector + cross-thread tests. Position math
only — D-010 integer discipline preserved downstream. Marked dead_code
until the T-1028 VoxelColumn pipeline consumes it.
T-1023 — RegionProfile carrier (D-239 §1,§10): new RegionProfile +
TectonicClass/GlaciationGrade/PrecipitationClass enums + BodyParams; derived
per-region river_threshold replacing the global 200 for tile consumers.
regions: BTreeMap on BodyWorldState, populated via the cascade's new
RegionProfile layer (runs when body_params is Some, else falls back to
Settlement). D-010 integer discipline, BTree ordering.
T-1024 — District climate primitives (D-239 §2): nullable temperature_c +
moisture on RegionProfile, mean-annual scalar (no clock dep; dynamic branch
deferred to Q-105). Hybrid inputs — new bodies.axial_tilt_deg column imported
from planet-gen body-defs (populate_axial_tilt_deg, 2611 bodies), luminosity
and orbital distance derived at runtime; greenhouse + diurnal-swing tables in
source-canonical climate_constants.toml. D-239 implementation note added.
cargo test: 1498 passed, 0 failed. clippy clean (pre-existing
large_enum_variant only). make check-systems-db: stamp fresh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close the last D-234 piece — terrain water-direction extraction wired through
to founding orientation and the quarter waterfront rule:
- Layer 1: TerrainAnalysis::water_bearing — 8-octant integer bearing toward the
nearest water from the water_dist gradient (D-010, no atan2). Stored on
GeographicAttractor.water_bearing (360 = none).
- #956 founding orientation: coastal/river settlements now get a real
water-facing bearing (the anchoring attractor's), replacing the 0 stub.
- #957 waterfront rule (D-234b): the water-facing quarter edge (from the
settlement's Coastal founding orientation) drops its block setback to 0 so
buildings present flush to the quay (dock-orthogonal). Typed Edge + coastal_edge
+ per-block gating.
Golden + atlas_response fixture rebaked (additive water_bearing field only).
8 new tests. All integer-deterministic (D-010).
Pending: the waterfront rule reads context.founding_orientation, which
city_context_reader still stubs to Cardinal — real per-settlement orientation
reaches quarter generation once the Layer-3 placement -> Layer-4 GenerateSkeleton
dispatch is wired (the remaining cross-layer integration).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DrainageBasin gained a territorial_status field, so the Layer-1 golden and the
atlas_response msgpack fixture pick it up. Both changes are additive/
representational — basin/attractor/river selection is unchanged.
- cascade_golden.rs: pass None dominant_faction; golden rebaked (only
"territorial_status": "FrontierUnclaimed" added per basin).
- gen_fixtures.rs: set the new field in the atlas_response generator; fixture
regenerated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
all_fixtures_deserialize panicked with "unknown fixture naming
convention: atlas_response_ready" — the test reads every .msgpack in
client/tests/fixtures/msgpack but had no branch for the atlas_response
prefix (the AtlasLayerResponse fixtures gen_fixtures emits, #969). A
pre-existing gap (present on main; the suite isn't gated by the pre-push
hook, which runs cargo-deny/ruff/JSON/stamp, not cargo test).
Add an atlas_response branch deserializing AtlasLayerResponse, and
regenerate atlas_response_ready.msgpack so its bytes match the now-integer
GeographicAttractor types (#955).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the existing attractor-matching engine (#919/#925) into the
generation cascade as Layer 3, and make the whole placement-scoring path
integer-deterministic.
Layer 3 (D-211):
- CascadeLayer::Settlement + Layer3Output (placements) on the snapshot;
BodyWorldState gains a `placements` field (the D-203 hot cache).
- run_layer3 runs the five-phase match_cities against Layer-1 attractors
via the authored D-195 compatibility matrix; pure function of
(attractors, cities) — no RNG. cities are passed in by the caller so the
cascade stays DB-free and testable. A `// cache seam` marks where a
persistent cache wraps it later (#1021).
- gen_queue passes &[] for now (Topography needs no cities); the runtime
settlement read (gen_queue/layer_proxy) is the #955 follow-on.
Integer determinism (D-010 / D-227 — D-195 amended):
- Wiring match_cities into the deterministic cascade made its f32 scoring
a live cross-platform divergence risk (a near-tie comparison or the
Hungarian's f32 reductions can round differently per platform → a
different world from the same seed). Converted the entire path to
integers: CompatibilityMatrix is a 0-100 affinity table; attractor
strength is 0-100 and terrain cost is a percent (100 = baseline),
quantized once at the Layer-1 feature boundary; cell_score, the
Hungarian, and CityPlacement.score are i64. No f32 in any placement or
ranking decision.
- Layer-1 golden fixture rebaked: confirmed selection/positions are
unchanged (same 256 attractors, 93 river cells) — only the strength/cost
representation changed.
Tests: lib green (1292); new settlement_layer_places_cities_deterministically
covers placement + determinism + propagation into BodyWorldState.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two Layer-1 generation fixes surfaced by the per-layer atlas viewer:
- Layer1Output now carries grid_w/grid_h (the downsampled working-grid the
positions live in). The client maps overlays from these, so the scale is
correct for any source heightmap resolution rather than assuming the texture
size — fixes overlays projecting at half scale into a corner.
- Drainage basin boundaries are traced as ordered, non-self-crossing contours
via Moore-neighbour tracing instead of an angle-from-centroid sort. The sort
produced star-shaped, self-crossing polygons for concave basins that rendered
as straight chords across the map.
Golden (cascade_layer1.json) and the cross-language atlas_response_ready
fixture regenerated. 100 atlas lib tests + the new tracer test pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Client half of the layer-stream protocol (codec only; transport wiring next).
- protocol.gd: encode_atlas_layer_request (bare {body_id, up_to} map so the
server demux routes it to the proxy, not the PlayerInput array) and
decode_atlas_layer_response (-> {body_id, status, error, layer1}; returns null
for non-atlas frames, e.g. a snapshot, so receive_bytes can disambiguate).
- gen_fixtures.rs: cross-language fixtures (atlas_response_ready/pending/
not_found) from real rmp_serde output, matching the test_protocol.gd pattern.
- test_protocol.gd: 5 tests decode the fixtures + verify a snapshot is not
mistaken for a response + the request encodes to the right shape. 68/68 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the fixed-type SimBridge::receive_inputs() with a tagged
receive() -> Option<Inbound>, where Inbound is Inputs(Vec<PlayerInput>) or
AtlasRequest(AtlasLayerRequest). A shared decode_inbound() demuxes a frame by
shape (msgpack array = inputs, map = atlas request) — additive, no wire change
to existing input/snapshot frames. Adds send_atlas_response() to the trait
(both TcpBridge + LocalBridge impls). receive_bridge_inputs routes inputs to
the InputQueue as before; atlas requests to a new AtlasRequestBuffer (drained
by the serve system next). Integration tests (bridge_tcp/bridge_ipc) updated to
the tagged receive(); a demux unit test covers all three branches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>