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>
This commit is contained in:
@@ -785,8 +785,9 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
### D-195: Attractor-Matching Compatibility Matrix for Generative City Placement
|
||||
- **Date:** 2026-05-01
|
||||
- **Decision:** City placement on a planetary surface uses an attractor-matching model. A `GeographicAttractor` is a terrain feature that increases city placement score at nearby positions. Seven `AttractorType` variants: `RiverMouth`, `CoastalAccess`, `RiverCrossing`, `ValleyFloor`, `PassEntrance`, `LakeShore`, `PlainCenter`. A `CompatibilityMatrix` is a 10×7 scoring table (10 `economic_role` values × 7 attractor types) whose cells are float weights (0.0–3.0) representing how strongly that economic role favors that terrain feature. Examples: manufacturing → RiverMouth 2.8, ValleyFloor 2.1; financial → CoastalAccess 2.5, PlainCenter 1.8; agricultural → ValleyFloor 3.0, PlainCenter 2.5. The matrix is authored data (not computed at runtime). Attractor extraction from heightmaps is defined in D-209. Matching algorithm is defined in D-211.
|
||||
- **Rationale:** Terrain-naive city placement produces spatially incoherent worlds. The compatibility matrix gives different city types different terrain affinities, so financial hubs appear on coasts and agricultural cities appear in fertile valleys — without hard-coding placement rules per city type. The float weight matrix gives graduated preference, not binary requirement.
|
||||
- **Ticket:** #919, #925
|
||||
- **Rationale:** Terrain-naive city placement produces spatially incoherent worlds. The compatibility matrix gives different city types different terrain affinities, so financial hubs appear on coasts and agricultural cities appear in fertile valleys — without hard-coding placement rules per city type. The weight matrix gives graduated preference, not binary requirement.
|
||||
- **Amended 2026-06-03 (#955):** the matrix weights and the whole placement-scoring path are **integer basis-points, not f32** (D-010 determinism / D-227 save-critical). When #955 wired `match_cities` into the deterministic generation cascade, the original f32 scoring became a live cross-platform divergence risk (a near-tie score comparison or the Hungarian's f32 reductions can round differently per platform → a different world from the same seed). Now: `CompatibilityMatrix.weights` are `i32` bps (10000 = 1.0×; the examples above are 28000 / 30000 / 25000 …), `GeographicAttractor.strength` and `terrain_modification_cost` are bps, and `cell_score` / the Hungarian / `CityPlacement.score` use integer arithmetic. The 0.0–3.0 affinity semantics are unchanged; only the representation is now integer.
|
||||
- **Ticket:** #919, #925, #955
|
||||
- **Raised by:** Generation cascade workshop (#897)
|
||||
- **Cross-reference:** D-191 (atlas pipeline), D-208 (D8 drainage — attractor extraction), D-209 (feature tag extraction), D-211 (attractor-matching pipeline)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user