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>
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>
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>
Two Layer-1 generation fixes surfaced by the per-layer atlas viewer:
- Layer1Output now carries grid_w/grid_h (the downsampled working-grid the
positions live in). The client maps overlays from these, so the scale is
correct for any source heightmap resolution rather than assuming the texture
size — fixes overlays projecting at half scale into a corner.
- Drainage basin boundaries are traced as ordered, non-self-crossing contours
via Moore-neighbour tracing instead of an angle-from-centroid sort. The sort
produced star-shaped, self-crossing polygons for concave basins that rendered
as straight chords across the map.
Golden (cascade_layer1.json) and the cross-language atlas_response_ready
fixture regenerated. 100 atlas lib tests + the new tracer test pass.
Co-Authored-By: Claude Opus 4.7 (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>
End-to-end determinism guard. cascade_golden.rs pins two artifacts for a real
committed body heightmap (GJ1c) in one diffable JSON golden
(server/tests/golden/cascade_layer1.json):
- Layer 0: SHA-256 of the source heightmap.png bytes (flips if the Python
heightmap generator or the file changes)
- Layer 1: the serialized Layer1Output of run_cascade on a 128x64 downsample
(flips if the Rust drainage/feature/sub-biome code changes)
JSON (not the msgpack discussed in refinement) to match the existing
golden_suite.rs convention and stay diffable — a failure shows what drifted.
UPDATE_GOLDEN=1 regenerates; wired into `make golden-update`.
Adds Serialize/Deserialize to RiverNetwork/DrainageBasin/Layer1Output and a
sha2 dev-dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>