feat(simulation): thread political_archetype/morphology_zone/road_entry_directions through L3->L4 dispatch (T-1039, T-1043)

Completes the GenerateSkeleton dispatch T-1022 wired (which only carried
founding_orientation). build_skeleton_work_item now also threads:

T-1039:
- political_archetype — real value from CityPlacement, replacing the Commission stub.
- morphology_zone — looked up from the covering DistrictProfile via new
  scale::heightmap_pixel_to_district (exported HEIGHTMAP_CELLS_PER_DISTRICT;
  cascade.rs drops its local CELLS_PER_REGION literal for the shared const).
- arrangement_pattern — RE-DERIVED at L4 (option b) via the single pure fn
  attractor_matching::arrangement_pattern(&archetype, &economic_role); both
  inputs already at L4. No CityGenerationContext field, no stored derived state
  (DB-as-cache). Parity test asserts L4 re-derivation == L3-stored value.

T-1043:
- road_entry_directions — derived from T-1038's BodyWorldState.road_graph:
  octant = bearing of each incident road edge, deduped per octant, ordered by
  MaintenanceAuthority rank (the AdminFacing prestige-edge consumer). Feeds
  derive_access_points -> AccessKind::QuarterEdge; BlockJunction fallback now
  fires only for genuinely isolated settlements.

Acceptance covered: Corporate+Fjord -> Ribbon topology (not mesh+Commission);
settlement with a road -> QuarterEdge on the correct octant. 11 new tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-16 11:34:22 +02:00
co-authored by Claude Opus 4.8
parent f98297d136
commit 9c8ad83071
3 changed files with 904 additions and 23 deletions
+33
View File
@@ -92,6 +92,39 @@ pub fn chunk_to_region(c: ChunkPos) -> RegionPos {
district_to_region(chunk_to_district(c))
}
// ---------------------------------------------------------------------------
// Working-heightmap pixel ↔ district (cascade.rs working grid)
// ---------------------------------------------------------------------------
/// Number of working-heightmap-grid pixels per district side on the standard
/// cascade working resolution (~128×64 working grid; D-203, D-239 §1, T-1023).
///
/// This is NOT a metre-scale constant — it is the `grid_cells_per_district`
/// parameter passed to [`crate::atlas::district_profile::derive_all_districts`].
/// Centralised here so plugin.rs and cascade.rs share one definition and neither
/// hard-codes `8` independently.
///
/// `DistrictPos = (col / HEIGHTMAP_CELLS_PER_DISTRICT, row / HEIGHTMAP_CELLS_PER_DISTRICT)`
/// for a working-grid pixel `(row, col)` — see [`heightmap_pixel_to_district`].
pub const HEIGHTMAP_CELLS_PER_DISTRICT: usize = 8;
/// Convert a working-heightmap-grid pixel coordinate `(row, col)` to the
/// [`DistrictPos`] that covers it.
///
/// `CityPlacement.position` and `RoadNode.position` are both stored in
/// working-heightmap-grid coordinates (row-major, `(row, col)` order), and the
/// district grid is built with the same pixel grid by
/// [`crate::atlas::district_profile::derive_all_districts`]. Integer division
/// floors toward zero, which matches the `BTreeMap` keys inserted by `derive_all_districts`.
#[inline]
pub fn heightmap_pixel_to_district(pixel: (u16, u16)) -> DistrictPos {
let cpd = HEIGHTMAP_CELLS_PER_DISTRICT as i32;
// pixel = (row, col); DistrictPos convention is (dx=col_district, dy=row_district).
let dx = pixel.1 as i32 / cpd;
let dy = pixel.0 as i32 / cpd;
(dx, dy)
}
// ---------------------------------------------------------------------------
// The elastic seam — region ↔ planet (the only per-body-floating quantity)
// ---------------------------------------------------------------------------