Commit Graph
452 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 bafed6ad9a test+docs(simulation): address PR #164 re-review (maritime)
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>
2026-06-08 19:09:39 +02:00
jpmschweitzerandClaude Opus 4.8 b894a721ad feat(simulation): hydrosphere maritime moderation of temperature (T-1033, D-240)
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>
2026-06-08 18:53:26 +02:00
jpmschweitzerandClaude Opus 4.8 54609eab1a test+docs(simulation): address PR #164 review (both APPROVE)
- 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>
2026-06-08 18:36:41 +02:00
jpmschweitzerandClaude Opus 4.8 b6be9f5758 feat(simulation): planet_class temperature envelope, drop orbit/star inputs (T-1033, D-240)
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>
2026-06-08 18:28:24 +02:00
jpmschweitzerandClaude Opus 4.8 88b4323585 fix(simulation): address PR #163 review (freeze/snow model)
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>
2026-06-08 17:30:48 +02:00
jpmschweitzerandClaude Opus 4.8 ed7380245d feat(simulation): static scattered freeze/snow model (T-1030)
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>
2026-06-08 17:15:07 +02:00
jpmschweitzerandClaude Opus 4.8 fb47f144f8 fix(simulation): address PR #162 review (morphology geometry)
Hoshe + Tyre CHANGES REQUESTED (§8 laws + D-010 confirmed clean):

- FjordWall floor 4+grade -> ~12m violated D-239 §9 (<=8m). Cap to
  glacier_grade.clamp(2,4) = 4-8m (still widens with glaciation, in-spec);
  add fjord_wall_floor_width_in_spec test. (Hoshe #1)
- BraidedDelta circular-distance wrap was one-sided (edge threads invisible).
  Fix to true modular distance: d.rem_euclid(64).min(64-d). (Tyre #1)
- BraidedDelta thread centres used correlated bit-slices of one u8 -> threads
  could merge. Derive 3 centres via independent splitmix64 passes (distinct
  salts) so separation holds across all phase values. Remove dead *64/64. (Tyre #2, Hoshe #4)
- Add N/S basin-direction guard asserts to the gorge + 2 fjord cross-section
  tests (were passing by seed luck). (Hoshe #2)
- meander_reach_stronger_sinuosity test was a tautology (|| elev_q!=elev_q
  always true). Rewrite: same elev_q, only morphology_zone differs, assert
  wet-tile sets differ -> fails if MeanderReach regresses to AlluvialPlain. (Hoshe #3)
- Fix FjordWall moraine + DuneStrand comments. (Tyre #3/#4)
- Lead: fix manual RangeInclusive::contains in the new fjord test.

cargo test passes (1457 lib), clippy -D warnings clean, fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:40:38 +02:00
jpmschweitzerandClaude Opus 4.8 311eabc51d feat(simulation): 7 specialized morphology geometry generators (T-1029)
D-239 §5/§8/§9 — replace the AlluvialPlain-fallback stubs with real per-family
geometry on the VoxelColumn pipeline. All integer (D-010); warp is the only f64.

- LavaField: Lava shield slopes + tube depressions, immature drainage (no deep water).
- FjordWall: Rock glacial U-valley — Deep-water floor, moraine band (grade>=1),
  near-vertical walls, cirques (grade>=2); floor half-width 4..8m (§9).
- CliffCoast: Rock vertical face at the water edge; plateau -> cliff -> splash -> ocean.
- BraidedDelta: Gravel, 3 braided threads (not single-thread), capped near sea level.
- DuneStrand: Sand, wind-aligned dunes at <=32deg angle of repose (integer ratio).
- IncisedGorge: Rock gorge, floor 2..8m wide (§9), near-vertical walls, single channel.
- MeanderReach: Soil single-thread meander (higher sinuosity than AlluvialPlain),
  thalweg below / levees above the waterline (§9 ElevationDelta); Wetland sub-zone.

§8 laws honoured: drainage monotonicity (mouths at sea level), lithology->landform,
glaciation->form (FjordWall U not V; grade-0 gated upstream at classification).
New helpers: compute_meander_reach_channel, in_levee_band.

53 family tests incl real geometry assertions (gorge floor width in [2,8]m, fjord
walls > floor, braided multi-thread, dune Sand). cargo test 1595 pass, clippy
-D warnings clean, fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:13:40 +02:00
jpmschweitzerandClaude Opus 4.8 841e222192 fix(simulation): address PR #161 review (VoxelColumn skeleton)
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>
2026-06-08 14:48:07 +02:00
jpmschweitzerandClaude Opus 4.8 cd8bfec64b feat(simulation): VoxelColumn walking skeleton (T-1028)
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>
2026-06-08 13:53:47 +02:00
jpmschweitzerandClaude Opus 4.8 868da28192 fix(simulation): address PR #160 review (climate + morphology)
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>
2026-06-08 13:24:13 +02:00
jpmschweitzerandClaude Opus 4.8 961855f276 feat(simulation): climate-derived fields + frozen 17-zone morphology (T-1025, T-1027)
T-1025 (D-239 §2) — climate-derived fields, all from the temperature(+moisture)
primitive:
- precipitation_class + glaciation_grade re-keyed to (temperature_c, moisture_q)
  inputs (were keyed off raw body params); river_threshold signature follows.
- New VegetationClass (Absent/Barren/Scrub/Forest/RiparianScrub/RiparianThicket)
  + derive_vegetation: treeline bands x elevation, structural Forest->Scrub->Barren
  no-skip, riparian 1-3 tile band; airless (temp None) -> Absent.
- 21 tests.

T-1027 (D-239 §5/§6/§7) — morphology classifier:
- FROZEN 17-zone MorphologyZone enum exactly per D-239 §6 (OpenOcean..Wetland,
  repr(u8) pinned). Removed legacy Sea/CoastalLowland/Island/Canyon/Unknown;
  all consumers (skeleton_gen street_topology) remapped to nearest D-239 zone.
- 8-family gated decision tree (LavaField..AlluvialPlain fallback) over integer
  inputs; hard gates: Fjord>=GlaciationGrade2, LavaField=Volcanic tectonic,
  lithology slope/form laws (§8). 4 sub-classification zones (TidalFlat,
  Estuarine, Alpine, Wetland) derived from family + elevation/water.
- Build-time compatibility-matrix invariant tests (§7): MeanderReach<->Volcanic
  and Volcanic<->Fjord forbidden adjacencies structurally prevented by gate order.
- 13 tests incl 17-variant completeness + discriminant pinning.

D-010 integer discipline throughout. cargo test 1534 pass, clippy -D warnings
clean, fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 13:12:34 +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 1887c886ef feat(simulation): dispatch RegionProfile layer in production (T-1032)
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>
2026-06-08 12:28:01 +02:00
jpmschweitzerandClaude Opus 4.8 7084a41013 docs(simulation): fix stale elevation comment in derive_all_regions
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>
2026-06-08 11:21: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 a69f4cf3ec feat(simulation): wire L3->L4 GenerateSkeleton dispatch (T-1022)
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>
2026-06-07 13:30:51 +02:00
jpmschweitzerandClaude Opus 4.8 6e24cc98df refactor(simulation): remove superseded skeleton String stubs (T-984)
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>
2026-06-06 23:40:21 +02:00
jpmschweitzerandClaude Opus 4.8 f880635b24 fix(simulation): decorrelate per-footprint tag fields + test gaps (#957)
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>
2026-06-06 10:55:07 +02:00
jpmschweitzerandClaude Opus 4.8 b73b4e6d92 feat(simulation): Layer-1 water bearing + D-234 waterfront rule (#957)
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>
2026-06-06 10:22:14 +02:00
jpmschweitzerandClaude Opus 4.8 4c6fe596d1 feat(simulation): quarter street network — typed corridors/access/lattice (#957)
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>
2026-06-05 23:29:21 +02:00
jpmschweitzerandClaude Opus 4.8 728a27e1b4 feat(simulation): quarter footprint subdivision + block-tag assembly (#957)
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>
2026-06-05 23:08:19 +02:00
jpmschweitzerandClaude Opus 4.8 1909e15edb feat(simulation): building-property-tag derivation helpers (#957)
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>
2026-06-05 22:15:46 +02:00
jpmschweitzerandClaude Opus 4.8 21544cbb47 test(simulation): assert "mixed" faction → ContestedZone (#956)
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>
2026-06-05 15:05:40 +02:00
jpmschweitzerandClaude Opus 4.8 31d7870823 test(simulation): rebake golden + atlas fixture for Layer-3 enrichment (#956)
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>
2026-06-05 14:57:18 +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 8b1005221d docs(simulation): finish district→quarter doc residue in gen_queue (#950)
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>
2026-06-05 13:15:36 +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 f1d52944af fix(simulation): correct crate name in log10_floor doctest
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>
2026-06-03 17:44:06 +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 9d8ff8b341 fix(simulation): fall back on unrecognized settlement_class (#955)
read_body_settlements propagated a parse error for any non-NULL but
unrecognized settlement_class, which the atlas proxy swallowed and then
enqueued the body with zero cities — one malformed row silently dropped
placement for the entire body. Treat an unknown variant like NULL: fall
back to PopulationBudget with a warning. parse_settlement_class stays
strict for the D-199 read_set path, which must abort on bad fields.

Found in PR #149 review (Hoshe H1). Adds a test covering the fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:33:24 +02:00
jpmschweitzerandClaude Opus 4.8 e2787fbfb1 feat(simulation): run Layer-3 settlement placement in the runtime cascade (#955)
Wire the existing D-211 attractor-matching engine into the live
generation cascade so settlements are placed in-game, not just in tests.

- CityContextReader::read_body_settlements reads a body's settlements
  from atlas_city_names (ordered by id for determinism). NULL
  settlement_class defaults to PopulationBudget, not NameLocked: the
  class is NULL until placement runs, and NameLocked would force every
  settlement Tier-A in match_cities and collapse population tiering
  (D-211). NULL economic_role falls back to residential.
- The AnalyzeBody work item carries the body's Vec<CityRecord>, and
  run_work_item now runs up_to Settlement (was Topography). A body with
  no settlements yields empty placements at negligible cost.
- The atlas layer proxy reads settlements on a cache miss and pins them
  onto the work item, keeping the Rayon task DB-free (D-225). A read
  failure is non-fatal: log and place no cities (Layer 1 still runs).
  Threaded through a new CityContextReaderResource Bevy resource opened
  in main.rs, mirroring BodySourceResolverResource.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:14:18 +02:00
jpmschweitzerandClaude Opus 4.8 adce12035a fix(simulation): route atlas_response fixtures in the serialize test
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>
2026-06-03 16:31:19 +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 a4727bf73e fix(economics): address PR #148 review — dry-run guard + pin validation
Review fixes (Hoshe/Tyre, both APPROVE):

- populate_trait_templates / populate_atlas_body_trait_bias: the table
  DELETEs ran unconditionally (safe only via transaction rollback on
  dry-run, and divergent from every other populate_* function). Restructured
  so validation + guardrails always run (dry-run now actually surfaces the
  would-bake counts and catches errors) but mutations happen only under
  `if not dry_run:`. Verified: --dry-run reports 28 templates, writes nothing.
- atlas_body_trait_bias: reject `pin` entries that carry a
  weight_multiplier_bps (pin is mandatory, no multiplier) — closes a silent-
  accept gap before #1017 authors ~30-40 real pins.

Deferred (noted on tickets): visual_bundle fallback-map completeness
(Phase-5/Araminta), pin-count-vs-K bake check (#1017 acceptance),
geographic_sector pool-narrowing semantics (#977).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:36:37 +02:00
jpmschweitzerandClaude Opus 4.8 375d04a29d data(content): core architecture-flavor trait-template catalog (#1005)
The D-232 core hand-curated catalog (28 templates) the architecture-flavor
draw reads from, baked into trait_templates (#993):

- Cross-corridor pool (10): economic-function templates gated by BulkClass
  (extraction/industrial/cold-chain/precision/information/civic) + universal
  baselines + the foreign-import swerve. Guarantees the CI floor.
- Per-corridor baseline (8): each corridor's default cohesive look — core
  cosmopolitan, north anglo-frontier, south lusophone, west germanic +
  compact-cooperative, east dense-utilitarian, deep-frontier surname +
  hardscrabble.
- Heritage sub-pools (10): deep-history callbacks tied to the D-237 heritage
  taxonomy (scottish highland, west-african compound, iberian hacienda,
  atlantic creole, nordic timber, central-european blok, east-asian temple,
  vietnamese water village, afrikaans kraal, arab oasis).

Each template = holistic bundle: two-tier eligibility (hard gates
bulk_class/ubiquity/prosperity_bps + soft weight mods), zone_affinity over
the real DistrictType enum, allow/block tags from a shared ObjectTag
palette, and a D-235 visual_bundle with generic fallback parents. All
numerics integer basis-points (D-010).

Adds the D-232 CI guardrails to the baker (V-TT-01: >=5 templates eligible
per BulkClass; V-TT-02: no template >60% of pool weight) — both pass with
margin (23-24 eligible/class, 5% max share). Catalog can grow via the
bounded Gemma pass (#992); hero pins are #1017.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:04:43 +02:00
jpmschweitzerandClaude Opus 4.8 a133b6416e feat(schema): trait_templates + atlas_body_trait_bias tables + bake (#993)
D-232 architecture-flavor storage mechanism (content is #1005/#1017):

- trait_templates: shared catalog of holistic template bundles — two-tier
  eligibility (hard gates bulk_class/prosperity_bps/ubiquity + soft weight
  mods), zone_affinity, allow/block tags, visual_bundle. List/map fields
  are JSON; all numerics integer basis-points (D-010).
- atlas_body_trait_bias: sparse per-body hero pins — pin/boost/suppress
  with basis-point multiplier ranges (boost ≤3×, suppress ≥0.33× never 0).
- Baked at import (steps 14/15) from wiki/economics/architecture_trait_
  catalog.toml + architecture_trait_bias.toml. FK-validated (body_id →
  bodies, template_tag → trait_templates), enum + multiplier-range checks,
  graceful on absent source. Source location provisional pending Q-107;
  the baked tables are invariant per D-232. Retires the round-2
  atlas_body_culture tables.
- Catalog ships a 3-template bootstrap seed to prove the bake end-to-end;
  the full ~35-template catalog is #1005, hero pins #1017.
- Stamp sources updated (IMPORT_ECONOMICS_SOURCES + GENERATOR_SOURCES).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:47:35 +02:00
jpmschweitzerandClaude Opus 4.8 00c8574e11 data(economics): address PR #147 review — fused/activity pin fixes
Review fixes for the #1016 cultural pass (Hoshe/Paula/Miri):

- Kruger 60 (GJ 860A) xhosa -> cosmopolitan (Miri): GTTR describes an
  Afrikaner/Xhosa/Zulu founding "without mapping cleanly onto any of
  them" — the textbook genuinely-fused case, which taxonomy rule 4
  resolves to cosmopolitan rather than one of the three. Commented.
- Altmark (GJ 357) financial_technocratic -> cosmopolitan (Paula):
  reverts to the original shipped value; the GTTR reads as commercial
  non-alignment, not credential-based technocracy.
- Kampala Gate (GJ 667B) + Moyale (GJ 1292) keep swahili but add inline
  comments documenting that these are East-African-INTERIOR founders
  (Baganda; Kenyan/Ethiopian Borana-Oromo) for which the taxonomy has no
  dedicated token — swahili is the nearest available, NULL would fall to
  the wrong corridor default. Gap logged for a follow-up taxonomy value.
  Mwangaza (693) swahili is literally correct (coastal) — untouched.
- heritage-taxonomy-draft.md: stale "(38)" subheadings -> "(47)" to match
  the actual canonical count (Hoshe/Miri doc typo).

Retracted: Pedra Seca (GJ 421B) xhosa — reviewer thought it lacked
authored backing, but its gttr.md explicitly states "Afrikaans and Xhosa
heritage"; pin kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:23:42 +02:00
jpmschweitzerandClaude Opus 4.8 21793c1a94 data(economics): #1016 cultural-write pass + canonical 47-value heritage migration
Completes the deferred cultural half of #1016 and migrates the heritage
vocabulary to the canonical 47-value taxonomy (D-237).

Migration (mechanical):
- _CULTURAL_HERITAGE replaced with the canonical 47-value set
  (docs/.../heritage-taxonomy-draft.md). Renamed the 4 shipped hero
  values to canonical: afrikaans_cape->afrikaans, french_provencal->
  french, italian_northern->italian, norse_compact->nordic.

Content pass (authored from GTTR/name heritage triage, 6-sector fan-out):
- +139 new cultural_specialization pins and 1 change (Altmark
  cosmopolitan->financial_technocratic), taking coverage 32 -> 171
  across 54 distinct values. Each pin is grounded in an explicit GTTR
  founding-community statement or system/feature name etymology, applied
  only where founding heritage DIVERGES from the corridor baseline
  (D-167/D-232); corridor-typical systems left NULL.

Distribution is now balanced and diverse — portuguese 15, german 12,
vietnamese 10, afrikaans 10 (no longer dominant post-#1019), then a long
tail filling the previously-missing slots (welsh, herero, czech, akan,
hungarian, konkan, afro_brazilian, cape_verdean, shona, arab/persian/
turkic, etc.).

Judgment calls (flagged for review):
- Kruger 60 + Pedra Seca -> xhosa (mixed SA founding; adds diversity).
  Pedra Seca has a Portuguese name but Afrikaans/Xhosa GTTR founding —
  name/heritage mismatch noted for a future content fix.
- Kampala Gate / Moyale / Mwangaza -> swahili as the nearest token for
  East-African-interior heritage; taxonomy may want a dedicated value.
- Kept hero pins Kensho=scholarly, Keid=scholarly, Nyrheim=nordic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:06:23 +02:00
jpmschweitzerandClaude Opus 4.8 8dc6c0ed42 data(content): address PR #146 review — Ghawr rename, faction pins, catalog
Review fixes for the #1019 heritage rebalance:

- Marib's farm world Ghouta -> Ghawr (Miri): "Ghouta" carries strong
  modern associations (2013 Damascus attack); Ghawr (the Jordan Rift
  Valley, an irrigated lowland farming region) keeps the water-fed-
  drylands sense without the resonance.
- Add dominant_faction = "independent" to GJ 914A / 508A / 1245B (Hoshe)
  — all five rebalanced systems are self-governing independent
  communities; uniform with siblings GJ 722 / 680 and clears V-FAC-01.
- catalog.md: fill GJ 914A -> Marib and GJ 722 -> Sığınak (Hoshe) — the
  two anchor systems still showed em-dash placeholders.
- Marib gttr: name the second aperture neighbour Sığınak instead of the
  bare "GJ 722" (Miri).

Pre-existing Golestan aperture/spur wording (reviewers split on whether
it is even wrong) is left for a separate content pass — not introduced
by this rename.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:17:07 +02:00
jpmschweitzerandClaude Opus 4.8 8844bef139 data(content): rebalance 5 thin afrikaans systems to MENA heritage (#1019)
The corridor over-represented southern-African heritage (afrikaans on 15
systems; arab=2, persian/turkic=0) as an artifact of the earlier
name-balancing overcorrecting. Convert five genuinely-thin afrikaans
systems — whose distinctive threads are heritage-agnostic — into
Arab/Persian/Turkic founders so the cultural cascade is built on a
balanced set. Load-bearing mystery systems (Eerste Wacht / Helderoog /
Brandpunt etc.) are left untouched.

  Droëland  (GJ 914A)  -> Marib    (arab)
  Stilwater (GJ 508A)  -> Sawad    (arab)
  Koeberg   (GJ 1245B) -> Akhgar   (persian)
  Skuilplek (GJ 722)   -> Siginak  (turkic)
  Carnarvon (GJ 680)   -> Golestan (persian; Mostert dynasty -> Farahani)

Result: afrikaans 15->10; arab 0->2, persian 0->2, turkic 0->1.

Full narrative retouch (not just names): gttr.md + index.md prose,
body/station proper_names, cultural_specialization, gttr_hook,
atlas_city_names, and the four brand-corps named after the converted
systems (corp IDs kept stable; only display names + products renamed).
Cross-references in neighbour systems, the catalog, the aggregate
drifter guides, and the atlas proposals are updated to match.

arab/persian/turkic added to _CULTURAL_HERITAGE so the values validate;
the full canonical 47-value migration remains #1016.

Also fixes a pre-existing canon bug: Sawad (GJ 508A) no longer claims
GJ 914A is "unsettled" — it is Marib, a three-century settlement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 07:11:10 +02:00
jpmschweitzerandClaude Opus 4.8 8addf04835 data(economics): #1016 econ+faction content pass (143 systems, cultural deferred)
The 6-sector content pass (core pilot + deep_frontier/north/east/south/west,
~260 systems read from both index.md and gttr.md) authored economic_
specialization + dominant_faction for the systems whose lore makes those
identities distinct. Conservative pin bar: corridor-typical systems left NULL
for the heuristic/derivation fallback.

Totals now: economic 170 (27 hero + 143), faction 76 (27 hero + 49),
cultural still 27 (DEFERRED — the culture pass needs a canonical heritage
vocabulary first; the agents surfaced ~30 real heritages from the names plus a
genuine Middle-East/Arabic gap, to be reconciled into one taxonomy before
writing cultural_specialization).

regen-db green, stamp fresh, all CI guardrails pass. Soft warnings are the
expected D-177 MonopolySource review list, the known Arbour estate/breadbasket
question, and sparse-corp_presence W-FAC-03 advisories — none blocking.

Notable pins: Haodu financial_hub/syndic_dominant, Matamba legal_archive,
Inhambane/Sonnwend/etc breadbasket, the terroir cluster, the west_reach
Compact zone, Aurelius governance_center/veil_institute. FLAGs left NULL
pending human calls (ISPI=veil_institute?, Gate-Corp-as-faction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:27:24 +02:00
jpmschweitzerandClaude Opus 4.8 c6abff0f44 chore(db): regen systems.db — D-237 specialization layer populated
make regen-db with the D-237 authored layer live: 27-row
specialization_vocabulary, economic_specialization + cultural_specialization
on 27 hero systems, dominant_faction on the authored set. Meta stamp fresh
(check-systems-db OK). Unauthored systems remain NULL pending the #1014
fallback and #1016 content pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 13:09:56 +02:00
jpmschweitzerandClaude Opus 4.8 4f53793755 feat(schema): D-237 specialization columns + specialization_vocabulary table
Ticket #1011. system_economy gains economic_specialization +
cultural_specialization (TEXT, NULL = heuristic fallback); new
specialization_vocabulary table (specialization_id PK, commodity_id
FK->commodities, production_ubiquity_override, projected BulkClass +
ProductionUbiquity for D-233, description) + commodity index. Columns
mirrored into COLUMN_MIGRATIONS for idempotent migration; table rides
systems-schema.sql. dominant_faction already exists on system_factions
(populate-only). Validated: importer parses; schema applies to scratch DB;
idempotent migration ran 2x on a copy of live systems.db, 301 rows preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 21:04:00 +02:00
jpmschweitzerandClaude Opus 4.8 d6e37c0c0e fix(simulation): prosperity in basis points + shared log10_floor helper (#954)
Address PR #145 review. Convert prosperity from f32 to integer basis
points (matching the existing prosperity_bps, D-010 integer-only),
removing both float-determinism bugs by construction:

- New server/src/bps.rs: log10_floor (integer order-of-magnitude) +
  bps_to_f32 edge helper, with boundary tests.
- city_context_reader: prosperity_baseline_bps (u32, 0-10000). role base
  + pop bonus (400*log10_floor, cap 1200) + symmetric noise via integer
  modulo (fixes the always-negative bug) + clamp [1000,9500]. No floats.
- CityGenerationContext.prosperity_baseline -> prosperity_baseline_bps;
  updated the two test context builders. Not serialized — no wire break.
- district_mix population_tier now uses log10_floor (same determinism
  bug class as the comment claimed to avoid).
- Tests in bps + assert positive noise is achievable (the case the old
  test hid).

cargo check/clippy --all-targets -D warnings clean; 1291 lib tests pass;
fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:31:27 +02:00
jpmschweitzerandClaude Opus 4.8 a854b89aa0 feat(simulation): wire D-199 economic read-set into generation context (#954)
Layer 2 clean half. GenerateSkeleton now builds a CityGenerationContext
from a body's real economic read-set instead of stub defaults.

- New CityContextReader (server/src/atlas/city_context_reader.rs): reads
  the 6-field D-199 set (economic_role, prosperity_baseline, population,
  dominant_faction, founding_age_years, settlement_class) via one JOIN;
  prosperity_baseline derived per D-197.
- GenWorkItem::GenerateSkeleton carries the resolved context; run_work_item
  builds the real context instead of DistrictWorldState::default().
- Left at defaults (deferred, documented): dominant_bulk_class /
  dominant_production_ubiquity (#982, design-blocked), trait_selection
  (#1005), morphology_zone + Layer-3 fields (attractor placement / D-204 /
  D-214).

16 new tests; cargo check/clippy --all-targets -D warnings clean; 1279
lib tests pass; fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 13:46:24 +02:00
jpmschweitzer 273db192b9 Merge PR #144: backfill founding_age_years for inhabited bodies (#1000)
Reviewed-by: Hoshe (data QA), Tyre (pipeline) — both APPROVE.
2026-05-30 22:50:17 +02:00
jpmschweitzerandClaude Opus 4.8 9f4b1bf02c fix(db): address PR #144 review (#1000)
- Update D-216 amendment text to match the implemented decision:
  events-first uses event_type='colonial_charter' (9 systems, 11 bodies,
  5 diverging from wave) — not the stale founding/first_settlement/
  colonization scaffolding vocabulary.
- Add ORDER BY sort_order ASC to the colonial_charter subquery for a
  deterministic pick if a system ever has two charter rows.
- Add explicit ELSE NULL to the wave-fallback CASE so an unexpected
  settlement_wave leaves a trace instead of silently nulling.

Data unchanged: 269/273 set, 4 Sol NULL; stamp fresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 22:07:06 +02:00
jpmschweitzerandClaude Opus 4.8 99da796cde fix(simulation): address PR #143 review (#1006)
- Add D-230 skeleton: DistrictSkeleton field to DistrictWorldState;
  cascade Default to DistrictSkeleton + contained enums/structs. Box the
  GenCompletion::SkeletonGenerated state to avoid large_enum_variant.
- Derive PartialEq on FloorExtent/FloorHeightProfile/DistrictWorldState/
  CityGenerationContext (+ minimal cascade) for downstream assert_eq tests.
- Add 4 unit tests for floor_at_voxel_z / voxel_range_for_floor (uniform,
  basement, variable heights, boundary) — the Q-104 deliverable.
- Drop unused smallvec direct dep (stays transitive via bevy_ecs).
- Key districts insert by skeleton.district_id, sharpen TODO(#957).

clippy --all-targets -D warnings clean; 1263 lib tests pass; fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 22:06:57 +02:00