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>
This commit is contained in:
2026-06-06 10:22:14 +02:00
co-authored by Claude Opus 4.8
parent 4c6fe596d1
commit b73b4e6d92
8 changed files with 704 additions and 268 deletions
+16 -3
View File
@@ -17,6 +17,7 @@
use tracing::{error, warn};
use crate::atlas::features::NO_WATER_BEARING;
use crate::seed::{splitmix64, SeedChain};
use crate::simulation::generator::{
AttractorType, CompatibilityMatrix, GeographicAttractor, SettlementClass, SubBiomeVariant,
@@ -265,6 +266,7 @@ fn synthetic_attractor(placed: &[CityPlacement], grid_w: u32, grid_h: u32) -> Ge
strength: 50,
sub_biome: SubBiomeVariant::TemperateGrassland,
terrain_modification_cost: 100,
water_bearing: NO_WATER_BEARING, // inland synthetic — no water direction
}
}
@@ -282,6 +284,7 @@ fn synthetic_attractor(placed: &[CityPlacement], grid_w: u32, grid_h: u32) -> Ge
/// grids vary per seed (D-213) while staying deterministic (D-010).
fn city_character(
attractor_type: AttractorType,
water_bearing: u16,
economic_role: &str,
territorial_status: &TerritorialStatus,
city_id: u64,
@@ -290,9 +293,15 @@ fn city_character(
let archetype = political_archetype(territorial_status, economic_role);
let pattern = arrangement_pattern(&archetype, economic_role);
let free_bearing = (splitmix64(seed.seed() ^ city_id) % 360) as u16;
// river_bearing / coastal_facing are 0 until Layer 1 exposes terrain bearings
// (D-209 follow-on); free_bearing seeds the pioneer/open-terrain case.
let orientation = founding_orientation(&attractor_type, territorial_status, 0, 0, free_bearing);
// Layer-1 water bearing (#957) drives both the coastal facing and the river
// bearing (the anchoring attractor's direction-to-water); 360 = none → 0.
let wb = if water_bearing >= 360 {
0
} else {
water_bearing
};
let orientation =
founding_orientation(&attractor_type, territorial_status, wb, wb, free_bearing);
(archetype, pattern, orientation)
}
@@ -354,6 +363,7 @@ pub fn match_cities(
flag_mismatch(&cities[ci].name, score);
let (archetype, pattern, orientation) = city_character(
attractors[ai].attractor_type,
attractors[ai].water_bearing,
&cities[ci].economic_role,
territorial_status,
cities[ci].city_id,
@@ -420,6 +430,7 @@ pub fn match_cities(
flag_mismatch(&cities[ci].name, score);
let (archetype, pattern, orientation) = city_character(
attractors[ai].attractor_type,
attractors[ai].water_bearing,
&cities[ci].economic_role,
territorial_status,
cities[ci].city_id,
@@ -453,6 +464,7 @@ pub fn match_cities(
flag_mismatch(&city.name, score);
let (archetype, pattern, orientation) = city_character(
AttractorType::PlainCenter,
synthetic.water_bearing,
&city.economic_role,
territorial_status,
city.city_id,
@@ -659,6 +671,7 @@ mod tests {
strength,
sub_biome: SubBiomeVariant::TemperateGrassland,
terrain_modification_cost: 100,
water_bearing: NO_WATER_BEARING,
}
}