feat(simulation): D-232 three-phase trait draw + deviation/swerve system (T-994, T-1003)

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>
This commit is contained in:
2026-07-08 09:16:47 +02:00
co-authored by Claude Fable 5
parent 766ceb436c
commit 90655382eb
13 changed files with 2392 additions and 33 deletions
+81 -7
View File
@@ -198,7 +198,13 @@ pub enum SettingType {
}
/// Classification of a district (high-level function).
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)]
///
/// `Ord`/`PartialOrd` (T-994): lets callers dedup a body-wide coverage set into a
/// `BTreeSet`/`BTreeMap` key (D-010 determinism) — the D-232 trait-template draw's
/// `zone_affinity` lookup and the body-level district-type-mix coverage aggregate.
/// Declaration order is not a stability-pinned wire format (unlike `MorphologyZone`
/// or `SeedDomain`), so this is a safe additive derive.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum DistrictType {
LogisticsHub,
Residential,
@@ -817,15 +823,31 @@ impl TileRect {
}
}
/// Architecture flavor index into the body's trait-template draw (D-229, D-232).
/// Architecture flavor reference into the body's trait-template draw (D-229, D-232).
///
/// Records which template the generator selected at skeleton time for Phase-6 to
/// read cold. The selection mechanism is D-232's weighted `allow`/`block` filter;
/// the index is frozen-amber once written.
/// read cold. The selection mechanism is D-232's three-phase draw (T-994): a body
/// vocabulary K-draw, then a per-district dominant-template pick by `zone_affinity`,
/// then within-template seed picks (not represented here — that's the D-235 visual
/// bundle resolution). The reference is frozen-amber once written.
///
/// Shaped as an enum (rather than a bare index) so the closed-vocabulary case and
/// the future out-of-vocabulary swerve (T-1003, D-232's deviation system) share one
/// wire type without a breaking change when the swerve lands — `T-994` introduces
/// the shape only; swerve *logic* (foreign-import / heritage-callback draws) is not
/// implemented here.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct ArchitectureFlavorRef {
/// Index into `CityGenerationContext::trait_selection` (0-based).
pub flavor_index: u8,
pub enum ArchitectureFlavorRef {
/// Index into `CityGenerationContext::trait_selection` (0-based) — the body's
/// closed K-template vocabulary (D-232 phase 1).
InVocabulary(u8),
/// A template tag drawn from *outside* the body's closed vocabulary (T-1003,
/// D-232 deviation system): the rare per-building foreign-import /
/// heritage-callback wildcard (`trait_swerve::roll_building_swerve`), or the
/// sparsity escape hatch at district-dominant time
/// (`trait_swerve::necessity_swerve`). The passive past-vogue holdover is NOT
/// a swerve — it rides the D-217 wear/era condition layer (`era_cause`).
Swerve(String),
}
/// A single building footprint tag — the frozen step-3 output placed on every
@@ -1116,11 +1138,57 @@ pub struct CityGenerationContext {
/// Body vocabulary draw result — K trait-template tags selected at skeleton
/// time (D-232). K is locked to `complexity_tier`: Full=5, Moderate=3,
/// Minimal=1, Empty=0. NOT named `flavor_profile` (that was the round-2 name).
/// Populated by the T-994 phase-1 K-draw; empty when the trait catalog reader
/// is unavailable or `complexity_tier == Empty` (K=0).
pub trait_selection: Vec<String>,
/// Dominant `BulkClass` for this settlement's primary commodity (D-233).
pub dominant_bulk_class: BulkClass,
/// Spatial concentration of dominant production (D-233).
pub dominant_production_ubiquity: ProductionUbiquity,
// ── T-994 additions (D-232 three-phase draw) ───────────────────────────
/// This system's corridor (`star_systems.geographic_sector` —
/// core/north_reach/south_reach/west_reach/east_reach/deep_frontier; `None`
/// if unset). A SOFT weight on the phase-1 body-vocabulary draw only — composes
/// with a template's own `geographic_sector` pool-narrowing column AND its
/// `weight_mods.geographic_sector` map (PR #148 review note: a two-part join,
/// never a hard gate — D-232 "corridors are tendencies, not borders").
pub geographic_sector: Option<String>,
/// Body-level district-type coverage: every `DistrictType` present anywhere
/// among this body's settlements, deduped (T-994). Threaded from the L3→L4
/// dispatch aggregation so the phase-1 K-draw is coverage-aware up front
/// (≥1 eligible template per district type actually present on the body).
pub body_district_type_mix: Vec<DistrictType>,
/// The D-243 2 048 m District cell (`atlas::scale::DistrictPos`) this
/// settlement's quarter falls in (T-994). Two settlements sharing a
/// `settlement_district_pos` independently derive the identical phase-2
/// dominant template for a given `DistrictType` — same body seed + same key,
/// so a coherent 2 048 m area reads as one style with no cross-quarter
/// coordination required.
pub settlement_district_pos: (i32, i32),
/// Phase-2 dominant-template pick (D-232), pre-resolved at L3→L4 dispatch time
/// (T-994) — **not** at `FillChunk` (T-987 keeps fill pure/cache-free) and not
/// even inside the `GenerateSkeleton` Rayon task itself, since the inputs
/// (`trait_selection` + the catalog's `zone_affinity`) are already known at
/// dispatch. One entry per `DistrictType` (all 9, so `assign_block_tags` is a
/// cheap infallible lookup). Usually `InVocabulary` (an index into
/// `trait_selection`); `Swerve` when the sparsity escape hatch fired (T-1003).
/// `BTreeMap` for D-010 determinism.
pub district_dominant_by_type: BTreeMap<DistrictType, ArchitectureFlavorRef>,
// ── T-1003 additions (D-232 deviation/swerve system) ────────────────────
/// Per-building wildcard chances (bps of 10 000): `(foreign_bps,
/// heritage_bps)`, resolved once per settlement at dispatch time from the
/// driver inputs (`trait_swerve::compute_swerve_rates`). Both zero when no
/// catalog reader is wired.
pub swerve_rates_bps: (u32, u32),
/// Foreign-import swerve candidates — hard-gate-eligible templates outside
/// the body vocabulary, from another corridor's grammar or the shared
/// `cross_corridor` pool: `(tag, weight_bps)` (`trait_swerve` module).
pub swerve_foreign_pool: Vec<(String, u32)>,
/// Heritage-callback swerve candidates — the body's own corridor heritage
/// sub-pool: `(tag, weight_bps)`.
pub swerve_heritage_pool: Vec<(String, u32)>,
}
// ---------------------------------------------------------------------------
@@ -1146,6 +1214,12 @@ pub struct BlockSkeleton {
/// Grid position (03, 03).
pub position: (u8, u8),
pub zoning: ZoningType,
/// The D-194 district-mix `DistrictType` this block was assigned (T-994).
/// `zoning` is the *function* derived from it (`zoning_for_district`); this
/// field keeps the source classification around because the D-232 phase-2
/// dominant-template pick keys `zone_affinity` by `DistrictType`, not
/// `ZoningType` — dropping it here would make the pick unrecoverable.
pub district_type: DistrictType,
/// Which multi-block reservation this block belongs to (if any).
pub reservation: Option<ReservationId>,
pub chunk_layout: ChunkLayout,