Commit Graph
19 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 7c09f23b9a test(simulation): cover the Some(basin_dirs) threading + clarify on-demand default (T-1047)
Addresses PR #170 review (Hoshe H1/H2).

H1: the Some(basin_dirs) path through derive_all_districts was untested (tests
only used None). Added derive_all_districts_threads_supplied_basin_directions
(synthetic per-district directions propagate to DistrictProfile.basin_direction;
unmapped districts fall back to North), and added basin_direction to the cascade
determinism loop so the full run_layer1->district chain is guarded.

H2: clarified the derive_district on-demand comment — the North default is an
accepted limitation (a fallback, not a computed value); the batch path threads
the true D8 direction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:11:55 +02:00
jpmschweitzerandClaude Opus 4.8 0263b68df9 feat(simulation): cache TerrainAnalysis + derive basin_direction from the D8 thalweg (T-1044, T-1047)
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>
2026-06-17 09:46:42 +02:00
jpmschweitzerandClaude Opus 4.8 9c8ad83071 feat(simulation): thread political_archetype/morphology_zone/road_entry_directions through L3->L4 dispatch (T-1039, T-1043)
Completes the GenerateSkeleton dispatch T-1022 wired (which only carried
founding_orientation). build_skeleton_work_item now also threads:

T-1039:
- political_archetype — real value from CityPlacement, replacing the Commission stub.
- morphology_zone — looked up from the covering DistrictProfile via new
  scale::heightmap_pixel_to_district (exported HEIGHTMAP_CELLS_PER_DISTRICT;
  cascade.rs drops its local CELLS_PER_REGION literal for the shared const).
- arrangement_pattern — RE-DERIVED at L4 (option b) via the single pure fn
  attractor_matching::arrangement_pattern(&archetype, &economic_role); both
  inputs already at L4. No CityGenerationContext field, no stored derived state
  (DB-as-cache). Parity test asserts L4 re-derivation == L3-stored value.

T-1043:
- road_entry_directions — derived from T-1038's BodyWorldState.road_graph:
  octant = bearing of each incident road edge, deduped per octant, ordered by
  MaintenanceAuthority rank (the AdminFacing prestige-edge consumer). Feeds
  derive_access_points -> AccessKind::QuarterEdge; BlockJunction fallback now
  fires only for genuinely isolated settlements.

Acceptance covered: Corporate+Fjord -> Ribbon topology (not mesh+Commission);
settlement with a road -> QuarterEdge on the correct octant. 11 new tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 11:34:22 +02:00
jpmschweitzerandClaude Opus 4.8 d974cf42b0 fix(simulation): wire region baseline into production district temp + rename latitude field (T-1078)
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>
2026-06-15 17:30:33 +02:00
jpmschweitzerandClaude Opus 4.8 b3b5bfc2e3 refactor(simulation): rename carrier RegionProfile->DistrictProfile + re-scale to the D-243 ladder (T-1077)
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>
2026-06-14 22:53:18 +02:00
jpmschweitzerandClaude Opus 4.8 c0d4ac89d1 feat(simulation): add Layer-2 inter-settlement road/rail graph (T-1038)
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>
2026-06-14 20:13:26 +02:00
jpmschweitzerandClaude Fable 5 346d87df7a chore(meta): docs/build sweep + tooling test gate (T-1069, T-1066)
- 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>
2026-06-12 16:22:55 +02:00
jpmschweitzerandClaude Opus 4.8 0a8933fbcc test+docs(simulation): address PR #159 review (T-1032)
- 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>
2026-06-08 12:50:35 +02:00
jpmschweitzerandClaude Opus 4.8 95f26ae3e9 fix(simulation): address PR #158 review (carrier foundation)
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>
2026-06-08 11:10:32 +02:00
jpmschweitzerandClaude Opus 4.8 def70eaf37 feat(simulation): D-239 three-carrier foundation (T-1023/1024/1026)
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>
2026-06-08 09:28:31 +02:00
jpmschweitzerandClaude Opus 4.8 af60d85d94 feat(simulation): Layer-3 settlement spatial-character enrichment (#956)
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>
2026-06-05 14:57:09 +02:00
jpmschweitzerandClaude Opus 4.8 c71c26a1a6 refactor(simulation): rename DistrictSkeleton→QuarterSkeleton per D-222 (#950)
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>
2026-06-05 13:06:44 +02:00
jpmschweitzerandClaude Opus 4.8 2d8367d3bc docs(simulation): correct stale cascade docs and pin layer order (#955)
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>
2026-06-03 17:33:36 +02:00
jpmschweitzerandClaude Opus 4.8 ab084269f3 feat(simulation): integer-deterministic Layer-3 settlement placement (#955)
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>
2026-06-03 16:30:57 +02:00
jpmschweitzerandClaude Opus 4.8 46b8688fa3 feat(simulation): foundation shared types + struct plumbing (#1006)
Define the type-definition layer the Phase-4 fill-seam tickets depend on
(D-229/D-230/D-231/D-232/D-233), compiling with stubs/defaults; behavior
logic lands in #982-985/#998.

- New types in generator.rs: BuildingPropertyTag, FloorExtent +
  FloorHeightProfile (floor_at_voxel_z/voxel_range_for_floor, resolves
  Q-104), BuildingEntryClass, ConstructionEra, ZoneTypeId, MorphologyZone,
  BulkClass(5), ProductionUbiquity, DoorSpec, InteriorDescriptor,
  DistrictWorldState.
- Rename spatial AccessTier -> ZoneAccessTier to free the name for the new
  per-building BuildingEntryClass.
- CityGenerationContext: +morphology_zone, +trait_selection,
  +dominant_bulk_class, +dominant_production_ubiquity.
- BodyWorldState: +districts (DistrictWorldState w/ block_tags).
- GenCompletion::SkeletonGenerated carries body_id + DistrictWorldState;
  plugin handler inserts into BodyWorldState.districts.
- Add smallvec as a direct dep (DoorSpec list stays Vec for now, TODO).

cargo check --all-targets / clippy clean; 1259 lib tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 16:07:33 +02:00
jpmschweitzerandClaude Opus 4.7 edbc0f4ff1 feat(simulation): atlas layer-proxy handler — cache hit/miss -> queue (#969, D-225)
handle_atlas_request: cache hit -> serialize Layer1Output, reply Ready; miss ->
resolve the source heightmap (BodySourceResolver) + enqueue an Immediate
AnalyzeBody on the #968 background queue, reply Pending (client re-requests; the
drain system populates the cache so a later request hits); unknown/no-terrain ->
NotFound. Adds the AtlasLayerRequest/Response/Status wire types (+ Serialize on
CascadeLayer). Pure handler; the bridge routing is the proxy's other half.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 11:41:28 +02:00
jpmschweitzerandClaude Opus 4.7 8cedf670f0 feat(simulation): activate background generation tier end-to-end (#968, D-206/D-225)
The D-206 queue (#924) and D-203 cache (#917) were built but never connected to
the running app — the whole background tier was inert. This closes the loop:
submit -> Rayon -> cascade -> completion -> drain -> cache.

- gen_queue.rs: GenWorkItem::AnalyzeBody now carries enqueuer-resolved inputs
  { body_id, heightmap_path, sea_level, body_seed: SeedChain } (D-225 boundary —
  run_work_item stays pure compute, no path/DB resolution). run_work_item runs
  the real cascade: load heightmap.png -> downsample to GRID_W×GRID_H working
  grid (D-202) -> run_layer1 -> BodyWorldState; load failure -> Failed.
  GenCompletion::BodyAnalyzed carries the computed BodyWorldState.
- cascade.rs: CascadeSnapshot::into_body_world_state() conversion.
- plugin.rs (new): GenerationPlugin registers GenerationQueue +
  BodyWorldStateCache and adds a PreInput drain system that inserts BodyAnalyzed
  states into the cache (off the Rayon workers — a cheap channel drain, never
  the ~45ms cascade). Wired into both the production and test app setups.

Tests run the real cascade on a tiny temp heightmap PNG (no committed fixture);
the plugin test proves the full submit->...->cache loop. The proxy (#969) is the
production submitter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 10:47:59 +02:00
jpmschweitzerandClaude Opus 4.7 a61d030b47 refactor(simulation): harden #952 cascade per QA + architecture review
Addresses the Hoshe (QA) + Tyre (architecture) review of the SeedChain/cascade
work:

- Golden was pinning an empty river network (128x64 produced 0 river cells).
  Bumped to 256x128, where GJ1c yields a real network (93 river cells, 19
  mouths) — Layer 1's rivers are now actually guarded, not just attractors.
- SeedChain::for_body(world_seed, body_id) + fnv1a_64: the single canonical
  body_id(String) -> u64 path (FNV-1a, the repo convention), so callers can't
  derive divergent worlds from the same seed via different ad-hoc hashes. The
  golden now uses it.
- Stability guards: seed_domain_discriminants_are_pinned test (CI fails if a
  SeedDomain tag is renumbered); AttractorType gains #[repr(u8)] + explicit
  discriminants (it's cast as a sort key in features.rs).
- Tests: SeedChain::root(0) non-degenerate; run_cascade error path (missing
  file -> Err, not panic).
- Comments: clarified the id=0 derivations (sibling separation is caller-side
  via the per-district/quarter chain; #957 threads the index), tightened the
  all_district_types reachability comment (it pins the seed-0 sequence, not a
  probabilistic claim), and noted the golden's WORLD_SEED is cosmetic at
  Layers 0-1 + the x86_64 f32 capture caveat.

Deferred with reason: the run_cascade -> CascadeInputs struct refactor (Tyre)
is left for #954 — designing Layer-2's context shape now would be later-phase
detail, and there's a single caller to migrate then. EntityRng keeps its
domainless combine (migrating is stream-changing) — noted in D-224.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 13:24:03 +02:00
jpmschweitzerandClaude Opus 4.7 9bc2ed28f2 feat(simulation): extensible generation cascade harness (#952, D-200)
server/src/atlas/cascade.rs — run_cascade(body_seed, body_id, path,
default_sea_level, up_to) loads Layer 0 (heightmap.png) and runs every layer up
to the requested CascadeLayer, returning a CascadeSnapshot. The pure core
run_cascade_from_heightmap orchestrates the layers without file I/O (testable);
run_cascade is the thin path-loading wrapper.

CascadeSnapshot is extensible — each layer's artifact is an Option that becomes
Some once it runs (Layer 0 heightmap always present, Layer 1 topography next;
#954+ append their fields). The carried SeedChain is unused by the RNG-free
Layers 0-1 and feeds the RNG-using layers later (D-224). CascadeLayer is an
append-only ordered enum.

Tests: layer-gating, same-heightmap determinism, layer ordering. The golden-seed
regression fixture (the #952 deliverable) builds on this next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 12:09:49 +02:00