ZoningType gains PartialOrd/Ord (DistrictType T-994 mirror) for the
dominant_zoning declaration-order tie-break. layer_proxy:
QuarterFootprintEntry/QuarterFootprintLayer (BTreeMap keyed by
city_id; density_avg_pct floor-mean, dominant_* mode with
declaration-order tie-break, landmark/corridor counts) +
build_quarter_footprint_layer gated None on empty state.quarters;
sixth AtlasLayerResponse field wired into all construction sites;
growth-ceiling doc updated to say the whole-body budget is consumed
(what carries a seventh is T-1124's design, resolved in the same
branch's amendment). Placements whose derived Layer4Quarter id is not
yet in state.quarters are SKIPPED (skeleton gen is async Low-priority
— entries may be a proper subset; client decodes defensively). Stale
TODO(#957) removed (premise predates T-957 landing).
Tie-break bug caught by its own test: Iterator::max_by_key over a
BTreeMap returns the LAST max on ties — backwards from
declaration-order-first; replaced with an explicit forward walk that
replaces only on strictly-greater count.
gen_fixtures: populated RegionGridLayer (T-1118 replay coverage) +
QuarterFootprintLayer literals in the ready fixture; msgpack fixtures
regenerated via make fixtures. Note: landmark_count is inert on real
data today (BlockSkeleton.landmark hardcoded None until landmark
placement lands); fixtures use non-zero counts to exercise the logic.
5 new unit tests + populated round-trip; atlas:: 549 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
is_standalone_hq threaded through CityRecord/CityPlacement via corporations LEFT JOIN (both readers); hubs = top-cap by population among non-HQ settlements (HUB_SPACING_DIAG_PX=64, HUB_CAP_MIN=6); exact-name collapse keeping lowest city_id with loud warn; nearest-point-on-polyline snap (SNAP_MAX_PX=8) with Junction edge-split preserving from<to via norm_edge, else A*-spur to nearest hub; FoundingOrientation::RailHeadFacing{bearing_degrees} assigned at degree>=3 junctions (octant bearing toward dominant incident edge), consumed by skeleton_gen railhead_edge() through the D-234b flush-frontage machinery; believability reader now honours baked settlement_class (stale hardcode since T-1075). 11 new targeted tests; existing suites green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
D-226 layer 1. World-advancing phases gated on pause: Movement/Storyteller/Knowledge/TickAdvance set-gated via sim_not_paused; Simulation + Economy gated per-system at their registration sites — collect_sound_events and serve_econ_state_query stay unconditioned (transient-buffer clear + paused-allowed query; set-gating Simulation leaked a stale tick-7 footstep into frozen snapshots — caught by golden_suite, fixed without touching the fixture; regression test encodes the bug shape). New PlayerAction::AutoPause/AutoResume + AutoPauseState resource implement Option A reconciliation: auto-resume only fires if auto-pause caused the pause; manual pause and Half rate survive implant open/close. PauseParams SystemParam bundle keeps process_player_input under the 16-param ceiling (BookmarkInputParams precedent). Client: HudGroups.gameplay_occluded now sends AutoPause/AutoResume via send_named_action; 5 gdUnit tests + 7 Rust tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
T-988 — BuildingExteriorTag {wall_material, roof_form, facade_rhythm,
setback_tier, color:HsvColor, street_surface}. New trait_exterior.rs:
3-step era-free derivation (visual_bundle filter -> zone-bias weighted
pick -> density->setback), color seed-sampled within the register band.
Resolved at GenerateSkeleton plan time (T-994 precedent), frozen on
BuildingPropertyTag; FillChunk only reads it. Four append-only
integer-discriminant enums (WallMaterial/RoofForm/FacadeRhythm/
StreetSurface), unknown token -> axis Generic + warn. trait_catalog_reader
now parses visual_bundle + the two new tables (OnceLock-cached).
New SeedDomain::TraitExterior (per-axis sub-chains — no cross-field
correlation).
T-959 — FillChunk reads the frozen exterior tag: wall_material/roof_form
onto Wall/Roof shell voxels (FilledChunk.surface_material). Additive,
ShellVoxel untouched, all shell tests pass. (Interstitial-fill-from-setback
split to T-1098 — needs a FillChunk block-metadata + geometry design pass;
nothing consumes FillChunk until Phase 5.)
T-1097 — BlockSkeleton.interstitial_character (OpenSpace|OperationsSurface),
D-233 bulk-driven: bulk-industry blocks' non-roofed remainder tags as built
economic infrastructure, not generic open space.
cargo check --all-targets clean; 1666 lib tests pass; no golden impact
(harnesses top out at CascadeLayer::RoadGraph, unreachable by Layer 4/5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
T-994 — three-phase draw replacing the flat splitmix64 flavor pick:
- Phase 1 body K-draw (trait_draw.rs): hard gates -> soft weights (two-part
geographic_sector join, PR #148) -> SeedChain::for_body-seeded weighted
draw of K (5/3/1/0 by ComplexityTier), pins count toward K, coverage-aware
over a new body-level district-type-mix aggregate threaded into
CityGenerationContext at L3->L4 dispatch.
- Phase 2 district-dominant pick keyed by the D-243 2048m District cell —
settlements sharing a cell independently derive the identical template.
Resolved at dispatch, never in FillChunk (T-987 stays pure).
- trait_catalog_reader.rs: systems.db reader for trait_templates +
atlas_body_trait_bias (D-225 resource pattern), registered in main.rs
with graceful degradation.
- BlockSkeleton carries district_type; assign_block_tags is a pure lookup.
T-1003 — deviation/swerve system (trait_swerve.rs):
- ArchitectureFlavorRef is now InVocabulary(u8) | Swerve(tag).
- Rare per-building wildcard: foreign-import pool (other corridors +
cross_corridor) driven by Epicenter/Passage tier, mixed faction,
road-graph degree; heritage-callback pool (own-corridor heritage) driven
by isolation, remote tier, founding_age_years. Integer bps, base 100,
cap 300 per driver — placeholder constants pending calibration.
- Sparsity escape hatch = same mechanism, necessity-triggered at the
phase-2 pick (deterministic max-weight over the hard-gate-eligible pool).
- New SeedDomains: TraitVocabulary(13) / TraitDistrict(14) / TraitSwerve(15).
Governance: D-232 amended (district pinned to the D-243 2048m tier — the
D-222/D-243 redefinition was never captured; swerve driver mappings +
placeholder rates recorded), D-229 flavor_ref field updated to the enum
shape, D-235 amended (T-995 vocabulary ratification note; rides here due
to single-file entanglement).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
apply_move no longer writes Facing (was the only movement-facing coupling,
player-only); the player's view changes solely via explicit SetFacing.
NPC path-follow now sets Facing to the step direction — a strict improvement
recorded as a D-252 correction: NPCs previously never received Facing from
movement, their cones sat at spawn direction while walking. Bump-to-turn
retired (blocked moves change nothing) with a regression test for each
semantic. Wire schema unchanged; player_facing docs now say view/aim.
Gauntlet fixtures regenerated (facing octants now reflect view-only
semantics); client replay + live-roundtrip suites green against the new
server.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Server half of wiring the derived tier into production. The D-225 proxy
(AtlasLayerResponse) carried only Layer1Output, so the Atlas could not show the
DistrictProfile tier that the cascade derives. Adds DistrictGridLayer (cols/rows
+ row-major MorphologyZone discriminants + elev_q for relief shading) and
build_district_grid(), populated on a cache hit. Because the response is
rmp_serde msgpack end-to-end, the new field reaches the client automatically.
This is the planetary-scale coarse grid (the Atlas map view); the on-demand 2km
derive_district (T-1077) is for in-world Phase 5, not the map. MorphologyZone
gains Copy (fieldless repr(u8) enum; additive).
Next: client decode (protocol.gd) + the D-226 generation overlay in the Atlas
viewer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
- 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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
New top-level seed module that owns every deterministic-RNG primitive:
- splitmix64 — the one canonical mixer (was duplicated as a private fn in
simulation/rng.rs; EntityRng now imports the shared one, no behavior change)
- AtlasRng — moved here from atlas/rng.rs (it is a generation-RNG primitive,
not atlas-specific); atlas now depends on seed, not the reverse
- SeedDomain — append-only domain tags (Body/Layer1Topography/Layer3Settlement/
Layer4Quarter/Block/Npc) for collision-proof per-domain seed separation
- SeedChain — root(world_seed) → derive(domain, id) → atlas_rng()/seed(), per
the D-224 formula splitmix64(self ^ splitmix64(domain)) ^ splitmix64(id)
Structural move only — SeedChain is not yet threaded through the cascade
callers (skeleton_gen still uses ad-hoc wrapping_add pre-mixing); that is the
next step. Unit tests cover the splitmix64 known-vector, avalanche,
determinism, domain/id separation, and chain composition.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Manual clippy-1.93 fixes that the prior machine-applicable sweep couldn't auto-
apply, all in cfg(test) modules and tests/ targets (invisible to the lib-only
pre-push clippy, hence accumulated unflagged):
- disallowed_types HashSet/HashMap → BTreeSet/BTreeMap (determinism rule):
shadowcast_bench.rs (×8, (i32,i32) keys), mood.rs, sound.rs. SoundEventKind
gains a PartialOrd/Ord derive (fieldless Copy enum) so it is BTree-usable.
- field_reassign_with_default → struct-init: disclosure.rs, monologue.rs (×2),
save_io.rs (keeps `mut` for the deliberate last-write-wins overwrite).
- assertions_on_constants on the EAVESDROP_THRESHOLD invariant → compile-time
`const _: () = assert!(...)`: listening.rs, cross_room_transitions.rs. This is
stronger than the runtime assert and needs no #[allow].
- approx_constant: settings/types.rs round-trip literal 3.14 → 2.5 (the value is
arbitrary test data, never meant to be PI — change avoids both the lint and a
suppression).
- drop_non_drop: vision.rs early Mut<WalkabilityMap> release → scoped block.
- unnecessary_get_then_check → contains_key: information_boundaries.rs (×3).
- cloned_ref_to_slice_refs → std::slice::from_ref: triangle_validation.rs.
- unused_must_use: input.rs dropped the unused .id() on a spawn.
cargo clippy --all-targets -- -D warnings is clean; cargo test green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Focused Rust dependency-maintenance pass from the 2026-05-23 security/freshness
review. No CVEs; one advisory cleared and one deprecated crate replaced.
- rand 0.9.2 → 0.9.4 (lockfile): clears RUSTSEC-2026-0097 (unsound with a
custom logger using rand::rng()). Semver-compatible; rand 0.10 is a separate
major.
- Compatible-update sweep: ~90 lockfile-only patch/minor bumps (bevy 0.18.0→
0.18.1, clap 4.5→4.6, rayon 1.11→1.12, pathfinding 4.14→4.15, uuid 1.20→1.23,
zerocopy, serde_json, tracing-subscriber, etc.). cargo test green.
- serde_yaml 0.9 (deprecated/archived upstream) → serde_norway 0.9, an actively
maintained drop-in fork. In the server it is test-only (poi.rs round-trip,
trait_modifiers.rs fixture, tests/news_ticker.rs) so it moves to
dev-dependencies; line-previewer parses dialogue/monologue pool YAML at
runtime, so it keeps it as a normal dependency. API is identical (from_str/
to_string).
news_ticker.rs also picks up its share of the #967 clippy sweep (HashSet/HashMap
→ BTree, doc-list indent) since it is the same file as the serde rename.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cargo clippy --fix on files the new clippy (1.93) flags: unused imports
(name_index, storyteller), manual_range_contains (block_irregularity),
length-comparison/is_empty (layer3, serialization). All behavior-preserving.
Surfaced because a warm target/ makes the pre-push hook actually run clippy
(it skips on cold worktrees). Remaining non-auto-fixable test-code lints
tracked in #967.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire the empty-world topography cascade (D-208/209/210):
- generator.rs: add SubBiomeVariant (11 variants, D-210) + sub_biome and
terrain_modification_cost fields on GeographicAttractor; AttractorType
is now Copy.
- features.rs (new, D-209): extract the 7 attractor tags from heightmap +
drainage. Coast/lake derived from the heightmap (D-209/D-223
reconciliation — markers are names-only now, no polygons). Deterministic
(sorted seeds, integer keys, bucket-grid thinning); strength-capped at
MAX_ATTRACTORS preserving type diversity. Shared TerrainAnalysis
(masks/slope/moisture/percentile) feeds both extraction and sub-biome.
- subbiome.rs (new, D-210): classify sub-biome + terrain_modification_cost
from elevation/slope/moisture/latitude. Volcanic stays in the enum but
is not emitted (no heightmap signal).
- layer1.rs (new): run_layer1 orchestrator + attach_feature_names (D-223
pool names to largest rivers / Alpine peaks).
- attractor_matching constructors updated for the new fields.
76 atlas tests pass; run_layer1 determinism verified.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WorldTier enum fixed to Epicenter/Regional/Backwater/Passage/Waypoint
(D-218). Full enum implementations for ComplexityTier, SettingType,
SettlementClass, DistrictType, PoliticalArchetype, FoundingOrientation,
TerritorialStatus, GeographicAttractor, AttractorType, and
CompatibilityMatrix. SystemNameIndex with Aho-Corasick text scanning
for background pre-generation queue integration (D-206).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three root causes: SnapshotBuffer hard-dependency in economy.rs
(Option-wrapped), TickPhase::configure missing from SimulationPlugin
(added idempotent call), and stale golden file after D-192 dropped
the version field (regenerated).
All 6 previously-failing tests now pass with zero regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses Tyre, Hoshe, and lead review comments on PR #137:
- **Audit doc amendment** (Tyre E1 / Hoshe H1 / Lead): add "Lead override
(2026-04-21)" section at top of docs/architecture/sprint-37-878-audit.md.
Rewrites the conclusion to "DECISION: STRIP" with the cascade-based
rationale. Preserves the original audit body as the pre-override record.
- **Regression tests** (Lead 2a-2b / Hoshe H2 / H3): add POSITIVE
assertions of the new uniform behavior so silent reintroduction fails.
- `phase2_container_verb_labels_uniform_regardless_of_player_state` —
two trials (empty KG, POI-bearing KG) assert container verb labels
equal Phase-1 defaults.
- `monologue_pool_selection_uniform_no_archetype_key` — two observers
with divergent MonologueState both draw from OBSERVE_NPC_LINES.
- **Decision record amendments** (Lead 3 / Tyre S2): D-032, D-035, and
D-057 amended with Phase 6 deferral wording. "Retired pending Phase 6,
not deferred with scaffolding." Reintroduction gate: a confirmed
Phase 6 character-model design.
- **types.rs doc fixes** (Tyre S1 / Hoshe H5): StartupMessage protocol-
flow comment updated to reflect no-version handshake (D-192).
ObserverSnapshot version-history block grows a "Sprint 37 wire-format
shifts" section documenting D-192 + #878 schema drops.
- **observer/tests.rs:944 comment** (Hoshe H6): rewritten to cite
cascade rationale instead of the stale D-032-SUPERSEDED premise.
- **tests/run-atlas-determinism exit** (Hoshe H7): exit 0 when EXIT_CODE=2
(venv/DB missing = skip, not fail). Preserves skip semantics for
tests/run-all on machines without the Python venv.
Follow-up tickets filed:
- #895 (server, low): expand check-systems-db-stamp GENERATOR_SOURCES
to cover gemma_naming.py + naming_core.py (Tyre S3).
- #896 (planning, low): add CLAUDE.md carveout for server wiki writes
closing coverage gates (Tyre S4 / Hoshe H8).
H4 investigation: v01_integration_playthrough.rs was not the only E2E
handshake→tick→snapshot test; coverage preserved by bridge_ipc.rs,
bridge_tcp.rs, and game_loop.rs (the latter is pre-existing-broken
per #885). No replacement test needed.
1142/1142 lib tests pass. cargo clippy -- -D warnings clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After #878 removed the `character` field, the manual Default impl is
derivable. Clippy flags this under `derivable_impls` with -D warnings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sprint 37 dead-code sweep closing out two stale supersession chains:
#877 (D-167, 2026-03-24): Removes HeritageRoot type alias and
ZonePaletteModifier::Heritage variant from server/src/simulation/
generator.rs. The 7 abstract heritage roots were retired in favour of
the corridor cultural system; these two stubs were the only remaining
references.
#878 (D-032 + cascade rule): Strips the entire CharacterArchetype
(Smuggler/Detective) trace from the server. Per lead direction
2026-04-21 and the development cascade (CLAUDE.md), character/NPC/
verb-differentiation/monologue code is Phase 6 detail that should
not exist in code yet. The running archetype trace was pre-cascade
filler, not production — production is only the client's character-
creation UI and insert screens (client follow-up in #882).
Deleted:
- CharacterArchetype enum + StartupMessage.character_archetype field
- archetype_verb_label() + archetype branch of apply_phase2_verb_filter
(D-057 character-verb differentiation — marked superseded)
- MonologueState.character partitioning
- Gauntlet archetype plumbing (setup_gauntlet no longer takes an archetype)
- server/content/schemas/drama_module.schema.yaml (zero Rust consumers)
- server/content/modules/tier1/smuggling_ring_v0_1.yaml
- server/tests/archetype_monologue.rs (regression guard for the removed system)
- server/tests/v01_integration_playthrough.rs (archetype-dependent)
Decision updates:
- decisions/content.md D-032 supersession rewritten to cite the cascade
(v0.2 drop invalidated the prior D-117 framing).
- decisions/content.md D-035 tag taxonomy: `character` enum footnote
updated; field noted as unused, do not reintroduce without a
confirmed Phase 6 design.
- decisions/perception.md D-057: archetype-verb differentiation marked
superseded.
Also bundles the types.rs version-field removal from #874 since the
file was already touched here.
Full trace audit in docs/architecture/sprint-37-878-audit.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds Serialize/Deserialize to SelectedBookmark and wires it into
SaveStateV1 so a loaded game remembers which bookmark and starting
location the player picked. Replaces the TODO at bookmark/mod.rs:95
(originally deferred to Sprint 37 alongside #614).
Also refactors BookmarkPlugin to accept an injected BookmarkRegistry
via BookmarkPlugin::new(registry) (#862). The Default constructor
still wires the canonical tycoon registry — injection is for tests
and future TOML loading. Flagged in PR #132 review as a follow-up.
Updates bookmark spec §4.4 to remove the v0.2-deferred scope note.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Per R-012: delete conversation.rs, both overheard content files, and
remove all 6 wire-up points (social_plugin, bridge/types, monologue,
voice/integration). Protocol version 22 → 23. Scope confirmed by
#842 audit — npc/ and content/global/ untouched. Surviving NPC
components (NpcName, NpcColorIndex, NpcConversation) migrated to
simulation/npc_components.rs for use by D-080 knowledge propagation.
Also applies pre-existing cargo fmt debt (names.rs and 4 others).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the hardcoded seed=0 with the seed received in StartupMessage,
threading it through SimulationPlugin -> EconomyPlugin / SimRng. Integration
test fixtures updated for the new SimulationPlugin { seed } signature.
Review fixes from Hoshe + Tyre:
- EntityRng seeding: splitmix64(seed) ^ splitmix64(id) instead of
splitmix64(seed + id) — eliminates collision class where adjacent
seeds produce identical streams
- AtomicBool ordering: Relaxed → SeqCst for shutdown flag (correct
on weakly-ordered architectures)
- Worker Drop: join handles instead of detaching threads
- Normalize stub API: remove ChunkGenWorker convenience wrappers,
use .pool consistently across all 3 workers
- trigger_monologue: downgrade &mut to shared refs (no-op anchor
was blocking parallel systems)
- Remove dead SimRng inserts from migrated monologue tests
- Document determinism gap on poll_worker_results
- Document bevy_tasks/rayon dep rationale in Cargo.toml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generic BackgroundWorkerPool<Req, Resp> with crossbeam channels, closure
handlers, and 3 delivery strategies (Fallback, GracefulDegrade, ModalLock).
Stub workers registered as Bevy resources:
- ChunkGenWorker (2 threads) — terrain/props/navmesh generation
- NpcPrepWorker (1 thread) — pre-compute NPC state for incoming areas
- OffscreenTickWorker (1 thread) — advance NPCs outside active tier
Tick loop integration:
- PreInput: poll_worker_results drains completed work
- PostSnapshot: push_worker_requests queues new work (no-op until Phase 5)
Handlers are stubs — real computation plugs in when the phases that need
them arrive. The infrastructure (channels, threads, push/poll, shutdown)
is real and tested.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enable Bevy multi-threaded executor via bevy_tasks multi_threaded
feature. Systems within the same TickPhase that don't share mutable
resources now run in parallel automatically.
Add EntityRng component — per-entity ChaCha20Rng seeded from
world_seed + StableId via splitmix64 mixing. More deterministic than
shared SimRng (order-independent). Migrate all monologue systems
(4 of 13 SimRng consumers) to EntityRng, removing contention that
serialized them against conversation/dialogue systems.
Add rayon dependency (infrastructure only, no par_iter calls yet).
SimRng retained for world-level randomness: conversation pairing,
knowledge transfer, dialogue, ticker, storyteller.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
10-phase linear pipeline: PreInput → Input → Movement → Simulation →
Economy → Storyteller → Snapshot → PostSnapshot → Knowledge → TickAdvance.
Each system assigned to exactly one phase via .in_set(TickPhase::X).
Cross-phase .after()/.before() eliminated — only intra-phase ordering
remains. Prevents schedule cycles by construction.
SimulationPlugin refactored into sub-plugins by domain:
- InputPlugin (player actions, interactions, dialogue dispatch)
- MovementPlugin (pathfinding, movement validation, spatial indexing)
- SocialPlugin (conversations, sound, voice enrichment, follow state)
- EconomyPlugin (tâtonnement tick, IPC query serving)
- TimePlugin (chunk streaming, news ticker, tick advancement)
All other plugins (NPC, Knowledge, Perception, Storyteller, Settings,
Bridge) updated to use TickPhase assignments instead of cross-plugin
ordering constraints. BridgePlugin trimmed to bridge I/O concerns only.
Part A of #843. Parts B (multi-threaded executor) and C (background
workers) follow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tick_economy_simulation was ordered .after(advance_tick) which created a
cycle: observer_snapshot → send_snapshot → advance_tick → tick_economy →
observer_snapshot. Moved to .after(process_player_input) instead — the
economy checks time.tick which works regardless of advance order.
Also removed the .after(tick_economy_simulation) from handle_debug_commands
that was added during Sprint 34 review — same cycle root cause.
This is a symptom of #843 (ad-hoc ordering is fragile). Pair session
scheduled to replace with system set phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>