Phase 3 of the D-243 re-scale — the corrected 2km carrier and its 'stretch
magic'.
- detail_scatter.rs: the mid-scale terrain (2-40km) invented between coarse
heightmap samples. Locked character (T-1077): ADAPTIVE (ruggedness follows
local slope/morphology -> gentle flats, rugged highs) + ENVELOPE+MODULATION
(heightmap relief caps amplitude; morphology flavors within). Deterministic
seed+position value-noise (D-227/D-010); C1-continuous, no lattice creases.
- derive_district(): the on-demand 2km carrier. Maps the district to a
fractional heightmap pixel via body_radius_km (the elastic seam, D-204),
bilinearly interpolates the L1 envelope, composes the scatter, derives the
profile. body_radius None falls back to direct indexing (tiny test bodies).
- Factored build_district_profile() (climate+morphology tail) shared by the
cell-aggregate and interpolation paths.
5 scatter tests + 5 derive_district tests (determinism, latitude->climate via
the seam, the envelope rule, radius fallback). Full suite + clippy -D warnings
green; existing goldens untouched (additive).
Scope: T-1077 delivers the corrected on-demand 2km derivation + scatter. The
eager-grid -> on-demand region/district production + Atlas surfacing (decision
A) is T-1046's chartered job (derive_all_districts stays the coarse Atlas grid
for now, documented).
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>
First phase of the D-243 re-scale. Adds server/src/atlas/scale.rs as the
single source of truth for the metric containment ladder (voxel 1m -> chunk
64m -> block 128m -> quarter 512m -> district 2048m -> region 204.8km), the
fixed integer addressing (ChunkPos -> DistrictPos -> RegionPos), and the
elastic seam regions_per_equator(R). Compile-time const asserts enforce the
nesting so the rungs cannot silently drift again — the structural fix for the
Q-110 failure mode (three files, three different region sizes).
Wires body_radius_km (D-204) through BodyParams + the reader: the single
body-specific input to the elastic seam.
No behavior change — foundation only. The rename/re-scale of the carrier
(RegionProfile -> DistrictProfile) and on-demand district derivation follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New deterministic cascade layer (CascadeLayer::RoadGraph) building the
transport topology between Layer-3 settlement placements, stored on
BodyWorldState.road_graph (session-cached, never serialized).
- MST force-connect (strict Kruskal over squared distance) + secondary
links across a 30-60% body-scale band where the MST detours >1.5x.
- A* over the Layer-1 terrain-cost field on a coarse ~64-wide routing grid:
ocean/lake impassable, river corridors 1/2 cost, slope >=35deg (D-210
roughness ~0.7) 10x, else the D-210 terrain_modification_cost. Columns
wrap (equirectangular); integer costs + index tie-break keep A*
deterministic.
- MaintenanceAuthority per edge (new generator.rs enum) from endpoint
political archetypes + haul length; waypoint nodes at long-edge midpoints;
named-route identity join (designed-for, pool empty post-D-223); junction
detection (high_connectivity_junctions, degree>=3).
Wired into the cascade (gen_queue requests CascadeLayer::RoadGraph). Adds
Copy to PoliticalArchetype. 10 road_graph unit tests + a cascade e2e test;
full suite + clippy --all-targets -D warnings green.
Deferred to T-1076 (gated on the D-242 settlement model): the RailHeadFacing
junction pass (needs a D-213 FoundingOrientation amendment + Layer-4 handling)
and the hub-selection refinement (needs baked population/specialization).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Content byte-identical (proven by the empty table-dump diff); the regen
records the new source-set SHA (registry + economy_import modules).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generator output is byte-identical (verified on the full generated TOMLs);
the regen refreshes the meta stamp after generate_brands sources changed
(shared surname_corpus.rs) and import_economics.py registry/lint edits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- make test-tooling: planet-gen determinism guard + import_economics
--dry-run, wired into pre-push on TOOLING_CHANGED; ruff widened to
E4/E7/E9/F/W (90 safe auto-fixes applied; E402/E702/F841 ignored with
documented counts)
- one-generator reality fixed in DEVOPS.md, asset-pipeline rule, CLAUDE.md
(import_economics sole generator since #951/D-223); dead check-protocol
target deleted; DEVOPS hook/config sections rewritten from the actual
hook sources; team-patterns gate description updated (client+tooling)
- project.yaml: 0.2.0 → 0.4.0 per the 0.{phase}.{n} scheme, description
refreshed from the v0.1 Sova narration to cascade reality
- stale comment sweep: voxel.rs stub claims (all 8 families implemented),
cascade.rs TODO recited to T-1044, main.rs D-192 handshake claim,
relationships.rs/chunk_streaming.rs version targets → phase language
- gitignore: client/settings.db* e2e-run artifacts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- workers/pool.rs: 5 new tests; catch_unwind keeps worker threads alive on
handler panic (in-flight request loss unchanged, pinned by test + #843
docs); stubs.rs no longer falsely claims the pool is tested
- save/load: execute_save_load pinned .after(Storyteller) so the scheduler
cannot legally save pre-Input state; exclusive-system exception recorded
in tick_phases.rs rules
- surname corpus extracted to bin/shared/surname_corpus.rs (both economy
generators import it; byte-identical output verified on 23.6MB+1.45MB
TOMLs); all three stamp/watch registries updated
- generator_spike gated behind non-default 'generator-spike' feature
- economy.rs: 11 new D-181 signal-derivation tests on the new
econ_sim Simulation::from_economy in-memory constructor
- perception exemption comments now state the consumer sort contract;
unused bytemuck removed; rayon comment corrected; the 22 allow(dead_code)
documented as serde schema enforcement
Co-Authored-By: Claude Fable 5 <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>
The four voice::lookup tests shared one on-disk cache directory (base_dir
temp/sr-voice-lookup-test + world_seed 99). VoiceCacheStore persists on Drop
(save_all) and reloads on first zone access (load_zone), and cache_hit stores
'Voiced line.' under the identical (zone_id=100, CacheKey) that cache_miss looks
up — so under parallel execution the hit test's Drop/save could leak into the
miss test's lookup, returning 'Voiced line.' instead of 'Base line.'.
Give each test a directory keyed by test label + process id, so neither parallel
tests nor concurrent cargo test runs collide. Verified clean across repeated runs.
Closes T-1035.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
derive_moisture_q matched hydrosphere on 'ocean'/'rivers'/'subsurface', but the
real bodies.hydrosphere vocab in systems.db is dominated by 'liquid_water' (175
bodies) — so most surface-water worlds fell to the '_ => 30' default, propagating
wrong precipitation_class / glaciation_grade / vegetation. Same root cause as
D-240 (code keyed on a vocab the data doesn't use).
Align the match to the canonical vocab, grouped by available surface moisture
(mirrors the [hydrosphere_maritime] table from T-1033): surface liquid
(liquid_water/ocean/ocean-coastal/extensive) -> 80; rivers/rivers-lakes/moderate
-> 55; ice -> 20; subsurface_liquid -> 15; subsurface/subsurface_ice -> 10;
minimal/trace -> 5; none -> 0. Add a liquid_water regression test and broaden the
clamp sweep to the full vocabulary.
Closes T-1034 — last open ticket under epic T-974 (Atlas-to-tile derivation),
which is now complete.
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
Re-review of the maritime moderation addition (Hoshe REQUEST_CHANGES, Tyre APPROVE):
- every_planet_class_derives_within_its_band now sweeps hydrosphere
(none/ocean/liquid_water/rivers/ice) as well, so the maritime-compressed
gradient path is covered by the band invariant — not just the None/1.0 case.
(Hoshe #1, Tyre #1)
- Update the BodyParams.hydrosphere doc comment to the real systems.db vocabulary
(liquid_water/ocean-coastal/extensive/etc.) the maritime table keys on. (Hoshe #2)
cargo test passes (the band sweep now ~59k in-band assertions), clippy -D warnings
clean, fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the D-240 hydrosphere modulator: water-rich worlds compress the equator->pole
temperature gradient toward the band midpoint (milder at both ends); dry worlds
swing the full class band.
- [hydrosphere_maritime] table in climate_constants.toml + ClimateConstants
field/method (maritime_factor). Keyed on the ACTUAL bodies.hydrosphere vocab
(liquid_water/ocean/extensive=0.6, rivers/moderate=0.8, ice=0.85, subsurface*
=0.9-0.95; minimal/trace/none/NULL -> 1.0).
- derive_temperature_c step 2 now lerps across a maritime-compressed sub-band
(mid +/- band_half*factor) instead of the full band. Clamp still guarantees the
class-band invariant.
- New maritime_hydrosphere_compresses_the_gradient test (ocean delta < dry delta).
Verified on real worlds: liquid_water/ocean temperate worlds now delta ~24 (milder)
vs dry ~40, all in-band. cargo test passes, clippy -D warnings clean, fmt clean.
Note: discovered derive_moisture_q has the SAME vocab mismatch (expects 'ocean',
DB uses 'liquid_water' for 175 bodies -> falls to default moisture). Pre-existing,
out of scope here — filing a follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Band-invariant test now sweeps high altitude (6 km) as well as sea level, so
the lapse term + cold-end clamp are exercised jointly, not lat alone. (Hoshe #1, Tyre #3)
- Clarify the temp() helper comment: seed 0 is deterministic but still applies a
constant non-zero nudge. (Hoshe #2)
- (Hoshe #3: confirmed tectonic_activity is genuinely absent from systems-schema.sql;
the reader's 'not in schema' comment is accurate — no change.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the sun-driven Stefan-Boltzmann temperature with the D-240 class-envelope
model. Temperature derives only from authored fields — no orbit/star physics.
- [planet_class_temperature] envelope table in climate_constants.toml (approved
bands: frozen [-90,-25] .. hot_arid [20,58] .. volcanic [30,90]); cold_/hot_/
warm_ prefix parsing; temperate fallback for unknowns.
- derive_temperature_c(params, constants, body_seed): band = envelope(planet_class);
latitude lerps across it (equator=warm, pole=cold); atmosphere greenhouse +
elevation lapse modulate within; small seed nudge for per-body variety; CLAMP to
band — a body can never escape its class. Airless -> None.
- Delete the dead astrophysics: derive_distance_au, ClimateConstants::luminosity(),
the [star_luminosity] table. Strip orbital_period_days/spectral_class/star_type/
axial_tilt_deg from BodyParams + BodyParamsReader (read 3 cols from bodies, no
star_systems join). DB columns left in place (no regen).
- New every_planet_class_derives_within_its_band test (13 classes x 5 atmo x 19 lat
x 4 seeds, all in band) — the D-240 consistency guard.
Verified on 25 diverse real bodies: the worlds that derived to +356C now sit inside
their class bands (temperate capped at 28C). cargo test passes, clippy -D warnings
clean, fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hoshe found a CRITICAL coherence defect (Tyre APPROVE):
- CRITICAL: cluster_scatter received the per-voxel sub_chunk_seed as its cluster
seed, so voxels in the same 6m cell got different scatter -> salt-and-pepper,
not the §3-mandated clustered patches. Re-key on (world, body, cluster-cell):
add SeedDomain::Cover=10 (pinned); cluster_scatter(world_seed, body_id, x, y)
derives for_body(world_seed,body_id).derive(Cover, cluster_pair_id). derive_cover
now takes (world_seed, body_id, ...). (Hoshe #1)
- The coherence test was tautological (constant seed bypassed the broken path).
Rewritten to the production derive_cover signature in a cold-lake scatter band;
it FAILED at 20% on the old code, passes 100% now. (Hoshe #2)
- Zigzag-encode cluster (cx,cy) before Cantor pairing (negatives were cast as u64
directly); fix the misleading 'no collision' comment. (Hoshe #3, Tyre)
- Sea-ice band was inverted (~85% ice at the -2 onset). Now monotonic: ice grows
~8% (onset) -> ~92% (cold edge); leads (open gaps) layered only in the cold half
via a 3x super-cell, passing the super-cell index directly. Dead depth==0 guard
removed. (Hoshe #4, Tyre #1/#3)
cargo test passes, clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D-239 §3 — the static mean-state freeze/snow cover. New SeasonalCover
{None,Snow,Ice} overlay axis on VoxelColumn (D-228 cover, NOT TerrainMaterial),
derived after the family generator in derive_voxel_column.
- Spatially-COHERENT cluster scatter (never per-tile dice): coarse 6m cluster
cells (div_euclid) -> Cantor pair -> splitmix64 ^ sub_chunk_seed, so voxels in
a cell share the freeze decision -> ragged patches. All integer (D-010).
- Bands by surface (classify_surface from morphology_zone + water): freshwater
+5..-10C; salt water (OpenOcean) -2C onset / -14C, 3x-coarser sheets+leads;
land snow +2..-10C, moisture-gated (moisture_q<30 -> None). Linear frozen
fraction across the band; permanent below the band (poles/glaciers).
- Airless (temperature_c None) -> cover None.
- Transient/clock-bound layer (dawn frost melting by noon) doc'd as a Q-105
forward contract; only the static model is built.
16 new cover tests incl real spatial-coherence (>=90% intra-cluster agreement),
band edges per surface, moisture gating, determinism. cargo test passes, clippy
-D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hoshe REQUEST_CHANGES + Tyre APPROVE:
- Cache key omitted world_seed → a cache reused across worlds could return
another seed's column. CacheKey is now (world_seed, body_id_hash, voxel_pos).
(Hoshe #1)
- Sub-chunk voxel seed reused SeedDomain::ChunkContext (tier-collision risk vs
the region meander seed). Add SeedDomain::Voxel = 9 (append-only, pin test
updated) and use it. (Hoshe #2, Tyre #1)
- get_or_derive forced callers to pre-compute body_id_hash while fnv1a_64 is
pub(crate). Drop the param; compute the hash internally. (Tyre #2)
- Fix two inaccurate comments (micro-relief range [-4,+3] not ±2; scatter mask
is 4-bit [0,15] not bits [4:6]). (Hoshe #3/#4)
- Strengthen the LRU eviction test: add a test-only contains_voxel() helper and
assert WHICH entry was evicted (refreshed pos survives, LRU pos evicted). (Hoshe #5)
- LRU O(capacity)-scan perf flagged on T-1031 (Tyre #3, deferred to the budget harness).
cargo test passes, clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D-239 §1/§7/§10 — the lower derivation chain end-to-end with the AlluvialPlain
fallback only (specialized families are T-1029).
- chunk_context.rs: ChunkContext (64m) — basin_direction + meander phase/wavelength
+ active-channel params. NO per-tile flow_direction[64x64] (D8 ~152m coarser than
a chunk, D-239 §10); features >64m seed from Region-or-higher. SeedDomain::ChunkContext=8.
- voxel.rs: VoxelColumn (1m) composing the D-228 tile axes (TerrainMaterial /
FloorMaterial=None / Vegetation / Water / elevation_m). Derive-on-demand + VoxelCache
(LRU BTreeMap, never persisted per D-227). Domain warp (T-1026) wired through:
f64 warp -> as-i32 truncation to the voxel address; all material/morphology gates
integer (D-010 — the meander wavelength f64 is truncated before the Water decision).
- AlluvialPlain generator: Soil/Wetland terrain, climate vegetation, integer-meander
channel (Shallow/Deep) from ChunkContext. The other 7 families are dispatch stubs
(canonical terrain material, fall back to AlluvialPlain geometry) — no panic.
29 new tests incl end-to-end determinism (same seed/pos -> identical column),
cache hit/miss/eviction/re-derivation, warp-applied. cargo test 1563 pass,
clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hoshe REQUEST_CHANGES + Tyre APPROVE:
- Riparian vegetation logic was doubly-wrong: it granted the micro-oasis only
in the impossible cold case (temp < -50, no liquid water) and DENIED it in the
intended hyper-arid case. Split: extreme cold -> Barren always; hyper-arid ->
RiparianScrub iff near perennial water. (Hoshe #2)
- morphology_zone_has_exactly_17_variants was misleading (asserted >=16). Rename
to morphology_zone_region_scale_emits_16_of_17, tighten to ==16, and document
that BraidedPlain is the lone region-unreachable zone (needs lithology, deferred
to ChunkContext) and that Lake IS reachable. Tyre's 'Lake also deferred' was a
false positive (ocean 60-79 -> Lake). (Hoshe #1, Tyre #1)
- derive_morphology_zone docstring now lists the real multi-path emission sites
for TidalFlat/Wetland, ValleyFloor/RiverBank as their own gates, and BraidedPlain's
deferral — the §6 parent-family names are descriptive, not exhaustive. (Hoshe #3, Tyre #2/#3/#4)
- D-239 record: added a T-1025/T-1027 implementation note (enum freeze location,
16/17 region reachability + BraidedPlain deferral, §7 gate-ordering interpretation).
No vocabulary change. cargo test passes, clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Hoshe: body_without_system_row test inserted a NULL system_id, impossible
under production schema (system_id NOT NULL REFERENCES star_systems). Rewrite
as body_with_orphan_system_id_* — non-NULL system_id with no matching
star_systems row (the real case the LEFT JOIN guards) + NOT NULL in the test
schema + corrected comment.
- Tyre: cascade.rs PERF/TODO comment said the region path 'defers to T-1032';
T-1032 IS this PR, so production dispatch is now live. Update to track T-1028
only and note the cost is live in production.
No production logic change. cargo test body_params_reader 7 pass, clippy clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The D-239 carrier layer (T-1023/1024/1026) only ran in tests because every
production AnalyzeBody enqueue passed body_params: None. Wire the real path:
- New BodyParamsReader (server/src/atlas/body_params_reader.rs): read-only
systems.db reader, joins bodies -> star_systems (LEFT JOIN) for the climate/
tectonic inputs. SQL verified against systems-schema.sql. All fields Option,
NULLs handled; tectonic_activity absent from schema -> None (derives from
planet_class). 5 unit tests.
- layer_proxy.rs: on cache miss, read the body's params and pass
Some(Box::new(..)). On read error, warn + fall back to None (cascade stops at
Settlement, no panic) — graceful degradation.
- plugin.rs / main.rs: register BodyParamsReaderResource (CityContextReader
pattern) and thread it through serve_atlas_requests.
BodyWorldState.regions now populates for real bodies in the D-206 background
pass. End-to-end tests cover wired (regions populated) + unwired (empty) paths.
cargo test 1504 pass, clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-review (Hoshe + Tyre, both APPROVE) caught one stale comment: after the
per-region elevation fix, derive_region_profile owns elevation_km (from elev_q),
not the caller. Update the comment so maintainers don't read it as caller-supplied.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hoshe + Tyre review (CHANGES REQUESTED):
- domain_warp golden tests were hollow (discarded values, only asserted
dx!=dy). Consolidate into one real golden_vector test with pinned f64
literals as the regression anchor. (Hoshe #1, Tyre #2)
- Per-region temperature used body-level elevation_km for every region, so
alpine and sea-level regions on a body got identical lapse — defeats the
D-239 §2 per-district temperature. Derive elevation_km per region from the
region's own elev_q (× MAX_REGION_ELEVATION_KM). (Tyre #1)
- Region seed was derived under SeedDomain::DomainWarp (collision risk with
the tile warp) and discarded unused. Remove it + the orphaned region_pos_id
and SeedDomain import; keep a reserved _seed param for T-1027/T-1028. (Hoshe #2)
- Fix inverted tilt_factor comment. (Hoshe #3)
- ClimateConstants doc referenced a load() that doesn't exist; correct it —
embedded default() is authoritative today, climate_constants.toml is the
canonical mirror, runtime load lands with T-1032. (Hoshe + Tyre)
- cascade.rs: drainage re-run was mislabeled 'cheap'/'no drainage needed';
document the real cost + PERF/TODO(T-1028/T-1032) against the D-239 §10
budget. (Tyre #3)
cargo test 1497 pass, clippy -D warnings clean, fmt clean.
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>
The Layer-4 quarter-skeleton geometry (D-234: morphology-correct streets +
the D-234b waterfront rule) was fully implemented but dormant — no
production call site dispatched GenerateSkeleton from a settlement
placement, so founding_orientation stayed pinned to the
context_from_read_set Cardinal stub.
Expand the BodyAnalyzed arm of drain_generation_completions to, before
caching the body, submit one GenerateSkeleton per CityPlacement: build the
D-199 context from the read-set, override founding_orientation with the
attractor-matched value carried on the placement (D-213), and derive a
canonical namespace-isolated quarter_id from (world_seed, body, city)
(D-194/D-230) instead of the city_id*10 placeholder. Dispatch logic is
extracted into the pure build_skeleton_work_item helper for DB-free testing.
Reader + world seed are optional system params (mirrors serve_atlas_requests);
absent either, dispatch is skipped and the body is still cached. Empty
placements and per-placement read_set errors are handled (warn + skip).
Station/multi-source dispatch (Q-109) is out of scope — BodyAnalyzed only
fires on the planetary AnalyzeBody path.
Tests: orientation-override + canonical-quarter_id unit test and a
determinism/city-scoping test on the helper. Full lib suite green (1327).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D-229 makes BuildingPropertyTag the typed step-3 output that replaces the era/
society_profile/zone_palette String stubs on the skeleton. The tag + its derivation
(zone_type_id, flavor_ref, era/ConstructionEra, FloorExtent) all landed under T-957/
T-1006; this removes the now-dead stub fields they superseded:
- BlockSkeleton.era_modifications: Vec<EraModification> (era is per-footprint on
BuildingPropertyTag.era now; era_cause retained as the block-level cause)
- QuarterSkeleton.society_profile: SocietyProfileRef (culture → tag.flavor_ref)
- QuarterSkeleton.zone_palette: Vec<ZoneDefinition> (zoning → tag.zone_type_id;
distinct from FloorZone.zone_palette: ZonePalette, the real D-101 palette, kept)
- the now-orphaned `EraModification` and `SocietyProfileRef` String aliases
Kept: chunk_layout (real ChunkLayout / D-234 street network), z_band_zones (still
uses the ZoneDefinition alias), boundaries (separate stub, not D-229 scope).
cargo check + clippy -D warnings --all-targets clean; full server lib suite
1325 passed / 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #153 review (Hoshe H1): zone_type/era/floors/flavor all shared one fp_chain
and each took splitmix64(seed) of the same value, correlating the fields (era
tracking floor height across a city). Derive a distinct sub-chain per field so
they draw independent entropy. Block_tags aren't serialized, so no fixture
change; behavior stays deterministic.
Also (review): n=1 corridor test (empty, no panic), Frontier-density waterfront
test, and a comment on the reserved `_morphology` param (Tyre).
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>
Complete the Layer-4 quarter geometry with the street layer (D-234), replacing
the ChunkLayout/CorridorSpine/AccessPoint String stubs with real types:
- AccessPoint{position,kind} + AccessKind (QuarterEdge/ReservationGate/
BlockJunction): the quarter's road nodes (from road_entry_directions octants +
reservation gates), the node set the D-097 audit reads.
- CorridorSpine{from,to,path}: arterial trunk edges, ±45°-snapped polylines
(D-096 cap). Topology gated by morphology (D-234a): Ribbon (fjord/canyon/
mountain-pass), HubSpoke (delta/island/enclosed water), Mesh/Prim-MST
(plains/meander/coastal). A spanning tree over the access nodes.
- ChunkLayout{spacing,offset,rotation_steps}: per-block local ±45° lattice,
spacing from density, offset/rotation modulated by the D-096 Grid/Organic mode.
Wired into generate_quarter_skeleton (access_points + corridors + per-block
chunk_layout). All integer-deterministic (D-010). 7 new tests.
Remaining D-234 piece: the per-edge waterfront pier/quay rule needs Layer-1
terrain water-adjacency threaded to the skeleton (cross-layer plumbing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build the per-quarter building geometry + tags (absorbs #976's footprint
subdivision into #957 — one coherent walkable-quarter deliverable):
- subdivide_block_footprints: deterministic BSP of each 128x128-tile block into
variable axis-aligned building plots (D-229 fast-path). Lot size + setback
scale with D-220 density (Frontier -> few big lots/wide gaps; Compressed ->
many small lots/shared walls); the D-233 BulkClass roofed-coverage fraction
decides building-vs-interstitial. morphology is wired in for the D-234
waterfront/street layer (needs Layer-1 terrain water-adjacency threaded up).
- assign_all_block_tags: subdivides + tags every non-reserved block's footprints
via the #957 derivation helpers, populating QuarterWorldState.block_tags in the
GenerateSkeleton plan-phase (D-230). Reserved blocks get no standard fill.
- Remove the write-only BlockSkeleton.era String stub - construction era now
lives per-footprint on BuildingPropertyTag.era (typed ConstructionEra).
All integer-deterministic (D-010). 5 new tests. Doors stay Vec::new() (#979).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pure per-footprint tag derivations for Layer-4 quarter generation (D-229),
ahead of the footprint-subdivision + assembly step:
- zone_type_for: the authored (ZoningType × economic_role × setting) → ZoneTypeId
selection (D-229 amendment) — planetary variants only, setting as a tweaker,
station-only ids never selected (Q-109); deterministic seed-pick, no empty slice.
- building_entry_class: zone × layout_mode × prosperity, with the D-217 Broken-band
U-curve degrade (Commercial→BreachOnly) and the Grid/Organic credential fork.
- construction_era: founding_age + prosperity + seed (D-229); old+Broken → Derelict.
- floor_extent: D-220 density-class floor ranges from density_pct + seed-jitter.
- initial_condition: D-217 frozen-amber condition from prosperity + era cause.
All integer-deterministic (D-010). 9 unit tests incl. the no-station-ids invariant.
Footprint subdivision + assign_building_tags assembly + block_tags wiring follow
(needs the footprint-packing model decided).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #152 review (Hoshe): the faction→TerritorialStatus mapping test covered 7 of
the 8 D-237 values; add the missing "mixed" assertion (grouped with "disputed",
behavior already correct).
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>
Derive and store each placed settlement's spatial character on top of the
#955 placement (D-212/213/214/215). Derivation is pure + integer-deterministic
(D-010); the cascade stays DB-free (D-225) — the enqueuer pre-resolves the
body's system faction onto the work item, like #955's settlements.
- TerritorialStatus (D-212): mapped from the authored dominant_faction
(territorial_status_from_faction). Adds an AutonomistHeld variant for the
Compact of Westphalia (self-governing bloc that rejects Assembly authority —
neither Commission, Corp, Contested, nor truly Frontier). Grounded in
wiki/factions/. Stored per-province on DrainageBasin.territorial_status
(uniform per body for now; forward-compatible for per-province faction data).
- PoliticalArchetype (D-214): political_archetype(status, role), status takes
precedence over economic_role.
- ArrangementPattern (D-215): new 5-variant enum + arrangement_pattern(); the
block-adjacency *enforcement* stays deferred to the Quarter-skeleton gen (#957)
— this only derives + stores which pattern applies.
- FoundingOrientation (D-213): existing fn extended with a seed-derived Free
bearing so pioneer/open-terrain grids vary per seed.
Wiring: dominant_faction threaded through CityContextReader
(read_body_dominant_faction) → atlas proxy (cache-miss read) → AnalyzeBody work
item → run_cascade → run_layer3/match_cities. Enrichment stored on CityPlacement
(political_archetype, arrangement_pattern, founding_orientation).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #151 review (Hoshe/Tyre): four doc comments in gen_queue.rs still said
"district" next to the renamed quarter_id field / generate_quarter_skeleton.
Pure comment update, no code change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D-222 renamed the 512m generation cell from District to Quarter (District
is now a new 2048m tier above it). Align the generation skeleton code to
the canonical vocabulary. Pure naming — no behavior change; all 1296 lib
tests + integration tests pass unchanged.
Renamed (spatial-cell identifiers):
- DistrictSkeleton → QuarterSkeleton, DistrictWorldState → QuarterWorldState
- DistrictId → QuarterId, DistrictContext/DistrictBoundaries → Quarter*
- field district_id → quarter_id, district_type → quarter_type
- BodyWorldState.districts map → .quarters
- generate_skeleton → generate_quarter_skeleton
Deliberately left as-is (these name functional ZONING, not the spatial
tier — orthogonal to D-222): DistrictType, DistrictLayoutMode, the
district_mix module (DistrictMix/compute_district_mix), and the
GenWorkItem::GenerateSkeleton / GenCompletion::SkeletonGenerated variants.
Also aligned the perception "sim tile" → "subtile" vocabulary (D-222:
Subtile = 0.5m) in decisions/perception.md and the generation-cascade code
comments. Historical D-066/D-094/D-201/D-220 decision bodies keep their
existing D-222 amendment notes (not rewritten in place); the public
max_offset_sim_tiles fn name is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The doctest imported `server::bps::log10_floor`, but the library crate is
`settled_reach_server` — so `cargo test --doc` failed to compile the
example (unresolved module `server`). Broken since the helper landed in
#145; it only surfaces under `--doc`, not `--lib`, so the pre-push gate
(which runs `--lib`) never caught it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the doc/test-correctness items from PR #149 review:
- cascade.rs: Layer 3 is RNG-free (pure fn of attractors+cities); it does
not consume the carried SeedChain. Corrected the "first RNG-using layer"
claims on the module doc and the Settlement variant (Hoshe H3).
- cascade.rs: layers_are_ordered now also asserts Topography < Settlement,
pinning the invariant the up_to >= Settlement guards rely on (Tyre T2).
- features.rs: strength is an integer 0-100 (quantized at extraction), not
f32, and consumers that rank by it use the integer value (Hoshe H2).
- layer_proxy.rs: AtlasLayerRequest.up_to is not yet honored — run_work_item
runs through Settlement unconditionally; per-request depth deferred to
#1021 (Tyre T1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>