//! World generator data model — quarter skeleton and mobile chunk types. //! //! Phase 1 generator output: the `QuarterSkeleton` produced by the world generation //! pipeline. Consumed by Phase 2 (chunk fill) and by the simulation startup path. //! //! **D-110 (signed z-levels):** All z-level *position* fields use `i8` (negative values //! represent basements/sub-levels). Z-level *count* fields use `u8` (always ≥ 1). //! The distinction: //! - `base_z: i8` — where the bottom floor starts (can be negative) //! - `z_levels: u8` — how many floors total (always positive) //! //! **D-108 (MobileChunk):** Vessel/vehicle interior chunks attached to mobile entities. //! Uses the same chunk primitives as static districts in a simpler flat structure //! (no Phase 1/Phase 2 split, template-stamped interiors). //! //! Sources: workshop-outcomes.md, tyre-round4.md use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; // --------------------------------------------------------------------------- // Concept ID type aliases // --------------------------------------------------------------------------- /// Unique district identifier (content-addressable via hash of world position + seed). pub type QuarterId = u64; /// Unique multi-block reservation identifier within a district. pub type ReservationId = u64; /// Unique social site identifier within a district. pub type SocialSiteId = u64; /// Unique role slot identifier within a social site. pub type RoleSlotId = u64; // --------------------------------------------------------------------------- // Stub types for concepts not yet implemented (defined here to allow // the data model to compile; will be replaced with proper types when // the respective systems are implemented) // --------------------------------------------------------------------------- /// Biome classification for wilderness districts. Stub — full taxonomy deferred. pub type Biome = String; /// Water body sub-classification. Stub — full taxonomy deferred. pub type WaterType = String; /// Specialized district function (e.g. military, medical, penal). Stub. pub type SpecializedFunction = String; /// Cultural / architectural era tag. Stub. pub type Era = String; /// Landmark slot description within a block. Stub. pub type LandmarkSlot = String; // `ChunkLayout`, `CorridorSpine`, `AccessPoint` are typed street-network structs // (D-234, #957) — see the "Street-network types" section below. /// District context — neighboring districts, world position, system. Stub. pub type QuarterContext = String; /// Zone definition — base palette + modifiers + zone name. Stub. pub type ZoneDefinition = String; /// District boundary system — edge descriptors with neighbors. Stub. pub type QuarterBoundaries = String; /// Guarantee audit result — tier-appropriate spatial invariant checks. Stub. pub type GuaranteeAuditResult = String; // --------------------------------------------------------------------------- // Street-network types (D-234, #957) // --------------------------------------------------------------------------- /// Where a quarter's street network connects to the outside, to a reservation, /// or to an interior junction (D-234). These are the nodes the arterial graph /// spans and the guarantee audit (D-097) reads. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct AccessPoint { /// Tile-space position within the 512-tile quarter (0–511). pub position: (u16, u16), pub kind: AccessKind, } /// Kind of [`AccessPoint`] (D-234). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum AccessKind { /// Road enters the quarter from outside at a compass octant (0=N…7=NW), /// from `CityGenerationContext.road_entry_directions`. QuarterEdge { octant: u8 }, /// Gate into a multi-block reservation. ReservationGate { reservation: ReservationId }, /// Internal junction between blocks. BlockJunction, } /// One arterial spine — a ±45°-snapped edge of the quarter's trunk-road graph /// (D-234 refined: arterials = least-cost graph over the access nodes). `from`/`to` /// index into the quarter's `access_points`; `path` is the tile-space polyline /// (segments axis- or 45°-aligned, D-096 ±45° cap). This node/edge graph is what /// the guarantee audit (D-097) reads as the trunk topology. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct CorridorSpine { pub from: u16, pub to: u16, pub path: Vec<(u16, u16)>, } /// Local street lattice descriptor for one block (D-234 refined: local streets = /// a ±45° grid lattice within each block, modulated by the D-096 layout mode). /// A compact parametric form the fill layer rasterizes — not the raster itself. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] pub struct ChunkLayout { /// Street spacing in tiles (the block is divided into spacing-sized cells). pub spacing: u8, /// Lattice origin offset in tiles (organic jitter; `(0,0)` for grid). pub offset: (u8, u8), /// Rotation in 15° steps (0–3 ⇒ 0–45°, the D-096 cap; 0 for grid). pub rotation_steps: u8, } /// Base visual palette for a zone. Stub. pub type BasePalette = String; /// Economic modifier on a zone palette. Stub. pub type EconomicModifier = String; /// Faction presence modifier on a zone palette. Stub. pub type FactionModifier = String; /// Condition modifier on a zone palette (worn, pristine, damaged). Stub. pub type ConditionModifier = String; /// Season modifier (affects palette and ambient conditions). Stub. pub type Season = String; /// Role slot within a social site template. Stub. pub type RoleSlot = String; /// Day phase (morning, afternoon, evening, night, late-night). Stub. pub type DayPhase = String; /// Triangle template reference (links to server/content/templates/). Stub. pub type TriangleTemplate = String; /// Raw chunk tile data for generator use (64×64 bool grid, true = walkable). Stub. pub type GeneratorChunkData = Vec; /// Tile type identifier (references visual tile set). Stub. pub type TileId = String; /// Entity identifier for mutation causality tracking. Stub. pub type EntityId = u64; /// Action identifier for player-caused mutations. Stub. pub type ActionId = String; /// Object instance identifier within a chunk. Stub. pub type ObjectId = u64; /// Placed object (item, furniture, fixture) within a chunk. Stub. pub type PlacedObject = String; // --------------------------------------------------------------------------- // World and complexity tier enums // --------------------------------------------------------------------------- /// Network importance of a world in the galaxy. /// Determines simulation fidelity budget and NPC complexity ceiling. /// /// Source: D-218, workshop-outcomes.md #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum WorldTier { /// Hub system. Full simulation, high faction pressure. Epicenter, /// Regional system. 1–4 districts, partial full-budget simulation. Regional, /// Small community. 1 district. Network-insignificant, NOT budget-capped. Backwater, /// Transit stop. Pass-through node. Moderate complexity ceiling. Passage, /// Not simulated until player approaches. Minimal complexity ceiling. #[default] Waypoint, } /// Generator content budget for a district. /// Derived from WorldTier + SettingType at Phase 1. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] pub enum ComplexityTier { /// Full social architecture. All Tier 1+2 guarantees. 20-80+ NPCs. Full, /// Moderate social architecture. Tier 1 + partial Tier 2. 8-20 NPCs. Moderate, /// Minimal social architecture. Tier 1 only. 1-8 NPCs. Minimal, /// No social architecture. Pure terrain. 0 NPCs. No guarantees. #[default] Empty, } /// Physical setting type for a district. /// Merged from Gestalt's SettingGeometry and Tyre's TerrainType. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] pub enum SettingType { Station, #[default] Urban, Agricultural, Maritime, Wilderness { biome: Biome, }, Water { water_type: WaterType, }, Transitional, Orbital, Specialized { function: SpecializedFunction, }, } /// Classification of a district (high-level function). /// /// `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, Commercial, Industrial, Administrative, Entertainment, #[default] MixedUse, Transit, Specialized, } /// How blocks are placed within the district's 512×512 footprint. #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)] pub enum DistrictLayoutMode { /// Standard Cartesian grid — perpendicular streets. #[default] Grid, /// Organic placement with per-block offsets and rotations. Organic { placements: [[BlockPlacement; 4]; 4], }, } /// Zoning classification for a block or floor zone. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] pub enum ZoningType { Commercial, Residential, Industrial, Administrative, Transit, Recreational, Restricted, #[default] Mixed, } /// Reservation function — what purpose a multi-block reservation serves. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum ReservationFunction { Skyscraper, Park, Terminal, Plaza, Monument, IndustrialComplex, MilitaryBase, ResearchFacility, UndergroundComplex, } /// Zone access tier — who is allowed into a spatial zone under normal circumstances. /// /// Renamed from `AccessTier` to `ZoneAccessTier` to avoid collision with the /// dialogue-layer `AccessTier` (D-028) in `simulation::line_pool`. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum ZoneAccessTier { /// Anyone can enter. Public, /// Requires employment or residence credential. Credentialed, /// Requires explicit invitation or escort. Restricted, /// Law enforcement / military only. Secured, /// Breach access only — normally sealed. BreachOnly, } /// Vertical corridor type (how floors connect in a multi-level reservation). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum VerticalCorridorType { Stairwell, Elevator, FreightLift, AccessHatch, EmergencyLadder, } /// Load state for a z-level within a reservation. /// Controls lazy loading of floor data during play. #[derive(Serialize, Deserialize, Clone, Debug)] pub enum ZLevelLoadState { /// Floor is fully generated and in memory. Loaded(GeneratorChunkData), /// Floor skeleton only — full data not yet generated. Skeleton(FloorZone), /// Floor not yet generated (deferred to Phase 2 on first entry). Ungenerated, } /// Cause of a chunk mutation (for save/load audit trail). #[derive(Serialize, Deserialize, Clone, Debug)] pub enum MutationCause { Explosion { radius: u8, source: EntityId }, Fire { spread_from: Option<(u16, u16)> }, Construction { builder: EntityId }, Decay { time_since_maintenance: u32 }, PlayerAction { action: ActionId }, } /// Type of structural change to a region of chunk tiles. #[derive(Serialize, Deserialize, Clone, Debug)] pub enum StructuralChangeType { WallDestroyed, FloorCollapsed, CeilingBreached, AreaSealed, WallConstructed, } /// Purpose classification for a triangle within a social site. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum TrianglePurpose { Investigation, Economic, Social, Political, Tactical, Mundane, } /// What exists on the far side of a wall tile (for destructible geometry). /// Every wall tile in a generated chunk is tagged with one of these. #[derive(Serialize, Deserialize, Clone, Debug)] pub enum WallBackside { /// Another room/corridor exists (tiles already generated). AdjacentSpace, /// Solid structural material — 1-2 tiles of fill, then another wall. StructuralFill, /// Narrow utility gap (1-3 tiles): pipes, conduits, non-navigable. ServiceVoid, /// Edge of chunk — adjacent chunk's boundary tiles on the far side. ChunkBoundary, /// Faces outside (hull, exterior wall) — breach has catastrophic consequences. Exterior, } /// Era cause — why a block has the era tag it has. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum EraCause { Original, CorporateMerger, EmergencyExtension, OrganicGrowth, InstitutionalIncursion, EconomicDisruption, CulturalShift, } // --------------------------------------------------------------------------- // Settlement classification enums (D-196, D-212, D-213, D-214, D-215) // --------------------------------------------------------------------------- /// How a settlement enters and exits active simulation. /// Controls whether generation runs, and at what complexity level. /// Source: D-196 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum SettlementClass { /// Named in wiki; always active regardless of population threshold. NameLocked, /// Active if pop ≥ 50_000; ghost stub if pop < 5_000. PopulationBudget, /// Active only while the triggering economic condition holds. EconomicTriggered, /// Emergent settlement not in atlas at generation time; written during simulation. OrganicGrowth, } /// Dominant power structure of a settlement and its physical spatial expression. /// Derived from TerritorialStatus + economic_role at generation time. /// Source: D-214 #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)] pub enum PoliticalArchetype { /// Top-down Commission planning; rectilinear, institutional core, radial-core arrangement. Commission, /// Corp-dominated; commercial density, restricted campus blocks, restricted-perimeter adjacent. Corporate, /// Self-organized; organic growth, mixed use, ribbon arrangement. Pioneer, /// Garrison or fortification origin; defensible geometry, fortified-perimeter arrangement. Military, /// University or research origin; campus-quad structure, green space, radial-core arrangement. Academic, /// Factory-first; large-footprint industrial blocks, worker residential rings, ribbon arrangement. Industrial, } /// Primary spatial axis of a city's original street grid. /// Derived from the matched attractor type (D-211). Controls district grid rotation. /// Source: D-213 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum FoundingOrientation { /// Street grid perpendicular to coastline. `facing_degrees`: compass bearing toward water (0–359). Coastal { facing_degrees: u16 }, /// Street grid parallel to founding river. `bearing_degrees`: river flow direction (0–359). RiverAligned { bearing_degrees: u16 }, /// Grid rotated to follow local contours (valley floor settlements). TerrainFollowing, /// Grid aligned to cardinal N/S/E/W (Commission-planned settlements on flat terrain). Cardinal, /// Arbitrary bearing (pioneer settlements on open terrain). `bearing_degrees`: 0–359. Free { bearing_degrees: u16 }, } /// Who maintains an inter-settlement road/rail edge — readable in the road's /// condition (Ozzie: "you can tell who controls territory by the state of the /// roads"). Derived at Layer-2 road generation from the endpoint settlements' /// political archetypes + the edge's haul length (no separate authoring). /// Five-value vocabulary frozen by the cascade workshop (workshop-outcomes.md /// "OQ-R3-4"); see [`crate::atlas::road_graph::maintenance_authority`]. /// Source: D-211 (Layer-2 transport), D-212 (territorial status input). #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum MaintenanceAuthority { /// Government-built/maintained — a Commission (or strategic Military) endpoint. Administrative, /// Corporation-maintained — a Corporate endpoint (e.g. road to a corp mine). Corporate, /// Locally maintained short links between neighbouring small settlements. Communal, /// Long-haul inter-regional routes between distant non-state settlements. Trade, /// Roads that exist but are no longer maintained (a Derelict-province /// endpoint, or runtime abandonment). Not reachable from the current /// authored data alone (D-212 Derelict is deferred) — wired for forward use. Abandoned, } /// Territory control status for a province (drainage basin). Priority-ordered derivation. /// Source: D-212 (amended 2026-06-05 — see `AutonomistHeld` + the dominant_faction /// mapping note on the variant docs and in `attractor_matching::territorial_status_from_faction`). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum TerritorialStatus { /// Established central governance — the Concord Assembly or its aligned /// institutions (e.g. the Veil Institute). (Original D-212: Commission /// faction_influence ≥ 0.6.) CommissionControlled, /// Commercial control — Syndic combines. (Original D-212: single corporation /// faction_influence ≥ 0.5.) CorpTerritory, /// Two or more factions each ≥ 0.3; no dominant faction. ContestedZone, /// No faction with influence ≥ 0.2. FrontierUnclaimed, /// Self-governing autonomist bloc that rejects central (Assembly) authority — /// the Compact of Westphalia and compact-sympathetic systems. Locally /// governed, but outside the Assembly's reach (D-212 amendment 2026-06-05). AutonomistHeld, /// Cultural corridor has indigenous autonomy flag. IndigenousHeld, /// Population density < 0.01 AND no faction ≥ 0.1. Derelict, } /// Spatial arrangement pattern governing district adjacency and landmark /// placement within a settlement (D-215). Derived from `PoliticalArchetype` /// (+ transit_hub economic role → `HubAndSpoke`). #956 derives and stores which /// pattern applies; the block-adjacency *enforcement* is the Quarter-skeleton /// generator's job (#957). /// Source: D-215 #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)] pub enum ArrangementPattern { /// Central landmark surrounded by mixed-use rings, transit spokes radiating /// outward (Commission, Academic). RadialCore, /// Restricted campus block in the interior, commercial ring, peripheral /// worker residential (Corporate). CampusGrid, /// Districts string along a linear feature; no dominant center (Pioneer, /// Industrial). RibbonDevelopment, /// Restricted/secured districts at the footprint edge, open interior core, /// single controlled access per edge (Military). FortifiedPerimeter, /// Transit district at center, all others reachable via direct corridors /// (transit_hub economic role, any archetype). HubAndSpoke, } // --------------------------------------------------------------------------- // Attractor types for settlement placement (D-195, D-209, D-211) // --------------------------------------------------------------------------- /// The type of terrain feature that attracts settlement placement. /// Source: D-195, D-209 // `#[repr(u8)]` with explicit discriminants: `AttractorType` is cast `as u8` as // the primary sort key for the attractor list (`features.rs`), so the layout is // load-bearing — reordering would change attractor ordering and the cascade // golden. Append new variants; never renumber or reorder existing ones. #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)] #[repr(u8)] pub enum AttractorType { /// Where a river meets sea level or coastline. Historically high-value. RiverMouth = 0, /// Proximity to coast without a river mouth. Port access. CoastalAccess = 1, /// Where a river crosses a topographic saddle or confluence point. RiverCrossing = 2, /// Local elevation minimum; flat, arable, sheltered. ValleyFloor = 3, /// Saddle point between adjacent drainage basins; controls a mountain pass. PassEntrance = 4, /// Adjacent to a lake polygon. LakeShore = 5, /// Flat terrain away from all other attractors; fallback for plains settlements. PlainCenter = 6, } /// Fine-grained terrain classification carried by each `GeographicAttractor`. /// Classifies the local terrain more finely than the top-level `SettingType`; /// drives ZonePalette modifier selection (D-101) and `terrain_modification_cost`. /// Source: D-210 #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)] pub enum SubBiomeVariant { TropicalWet, TemperateForest, TemperateGrassland, BorealForest, Tundra, Desert, Savanna, Alpine, Wetland, CoastalLowland, /// No heightmap-derivable signal in the Layer-1 inputs (elevation, slope, /// moisture, latitude); reserved for a future volcanic data source (D-210). Volcanic, } /// A terrain feature at a specific map position that influences city placement scoring. /// Source: D-195, D-209, D-210 #[derive(Serialize, Deserialize, Clone, Debug)] pub struct GeographicAttractor { /// Pixel position in heightmap space [row, col]. pub position: (u16, u16), pub attractor_type: AttractorType, /// Strength on a 0–100 integer scale (100 = strongest). Quantized from flow /// accumulation / habitability at Layer-1 output. Integer for D-010 /// determinism — every ranking/scoring decision is exact, no f32 (#955). pub strength: i32, /// Fine-grained terrain classification at this position (D-210). pub sub_biome: SubBiomeVariant, /// Infrastructure-build cost as a percent of baseline (100 = baseline /// grassland; >100 = more expensive, e.g. 200 = 2× cost). Derived from /// `sub_biome` + local slope; penalizes marginal cities in the matching /// pipeline (D-211). Integer for D-010 determinism (#955). (D-210) pub terrain_modification_cost: i32, /// Compass bearing (degrees, quantized to 8 octants: 0=N,45=NE…315=NW) /// toward the nearest water from this position, or 360 = "no water nearby". /// Layer-1 terrain extraction feeding D-213 founding orientation (coastal /// facing / river bearing) and the D-234 waterfront rule. Integer 8-octant /// keeps it D-010-deterministic (no `atan2`). pub water_bearing: u16, } /// Compatibility weights between economic roles and attractor types. /// A 10×7 matrix (10 economic_role values × 7 AttractorType variants). /// Each cell is an integer affinity 0–100 (100 = ideal terrain for that role, /// ~50 = neutral, low = poor) used during attractor-matching scoring. /// Integer for D-010 determinism (#955). Source: D-195 #[derive(Serialize, Deserialize, Clone, Debug)] pub struct CompatibilityMatrix { /// Row order: manufacturing, financial, agricultural, extraction, service_mixed, /// institutional, transit_hub, research, military, residential. /// Column order: RiverMouth, CoastalAccess, RiverCrossing, ValleyFloor, /// PassEntrance, LakeShore, PlainCenter. pub weights: [[i32; 7]; 10], } impl CompatibilityMatrix { /// The canonical D-195 authored compatibility table (#955). /// /// Integer affinity 0–100: 100 = ideal terrain for the role, ~50 = neutral, /// low (10–30) = poor fit (graduated, never a hard 0 — D-195). Re-authored /// on the 0–100 scale from D-195's relative examples (the agricultural→ /// ValleyFloor 3.0 peak maps to the global max 100). /// /// Rows: manufacturing, financial, agricultural, extraction, service_mixed, /// institutional, transit_hub, research, military, residential. /// Cols: RiverMouth, CoastalAccess, RiverCrossing, ValleyFloor, /// PassEntrance, LakeShore, PlainCenter. pub fn d195() -> Self { CompatibilityMatrix { weights: [ // RMth Coast RXing Vall Pass Lake Plain [95, 80, 75, 60, 30, 35, 55], // manufacturing [70, 90, 65, 25, 40, 45, 70], // financial [85, 30, 55, 100, 10, 70, 90], // agricultural [30, 35, 40, 65, 95, 30, 25], // extraction [60, 65, 70, 50, 35, 50, 70], // service_mixed [65, 65, 60, 45, 20, 55, 90], // institutional [65, 75, 100, 35, 95, 25, 60], // transit_hub [40, 70, 35, 75, 40, 95, 60], // research [60, 75, 80, 55, 100, 25, 40], // military [70, 85, 50, 65, 20, 85, 70], // residential ], } } } // --------------------------------------------------------------------------- // D-229/D-230/D-231/D-232/D-233 shared types (economic-built-world workshop) // --------------------------------------------------------------------------- /// Physical access character of a building entrance. /// /// D-229: Named `BuildingEntryClass`, NOT `AccessTier`, to avoid colliding with /// the dialogue-layer `AccessTier` (D-028) in `simulation::line_pool`. Both /// types share the `Public` and `BreachOnly` labels but have orthogonal semantics. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum BuildingEntryClass { /// Openly accessible — no credential required. Public, /// Requires a commercial transaction or active business purpose. Commercial, /// Requires explicit invitation, resident status, or employment credential. Restricted, /// Normally sealed — only accessible via breach mechanics. BreachOnly, } /// Construction era of a building block (D-229). /// /// Derived from `founding_age_years + prosperity_baseline_bps + seed`. /// Reads primarily as **age/wear** via the condition layer (D-217/D-198); /// not a material-technology ladder (era = maintenance signal, not style signal). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum ConstructionEra { /// Built during the settlement's founding period. Founding, /// Built during the established growth phase. Established, /// Recent construction. Modern, /// Original structure; now degraded beyond economic viability. Derelict, } /// Zone-type identifier — references a RON zone-type definition (D-229, D-142). /// /// A `Box` newtype matching the `id` field of one of the 31 D-142 zone-type /// RON files. Using `Box` (heap-intern) rather than `String` to discourage /// mutation and signal that the value is a stable content identifier. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct ZoneTypeId(pub Box); impl ZoneTypeId { pub fn new(s: impl Into>) -> Self { Self(s.into()) } pub fn as_str(&self) -> &str { &self.0 } } impl std::fmt::Display for ZoneTypeId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } /// Per-floor height specification for a building (D-229, resolves Q-104). /// /// `Uniform(voxels_per_floor)` covers the common case (3 voxels ≈ 3 m/floor). /// `Variable` carries per-floor memory only when floors actually differ /// (e.g. ground-floor retail at 5 voxels, offices at 3 voxels each). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum FloorHeightProfile { /// All floors have the same height in voxels. Default: `Uniform(3)`. Uniform(u8), /// Per-floor heights in voxels, index 0 = `base_floor`. Variable(Vec), } impl Default for FloorHeightProfile { fn default() -> Self { // 3 voxels ≈ 3 m/floor (Jeroen confirmed default). FloorHeightProfile::Uniform(3) } } /// Floor/basement extent for a building, bridging D-110 floor-index addressing /// and D-227 physical voxel-z (D-229, resolves Q-104). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct FloorExtent { /// Index of the bottom floor (negative = basement, per D-110). pub base_floor: i8, /// Total number of floors, always ≥ 1. pub floor_count: u8, /// Per-floor height specification. pub heights: FloorHeightProfile, } impl FloorExtent { /// Number of floors above ground (base_floor ≥ 0 inclusive). pub fn above_ground(&self) -> u8 { if self.base_floor >= 0 { self.floor_count } else { let basement_floors = (-self.base_floor) as u8; self.floor_count.saturating_sub(basement_floors) } } /// Map a building-relative voxel-z coordinate to the D-110 floor index that contains it. /// /// Returns the floor index whose voxel span covers `voxel_z`, or `None` if `voxel_z` /// falls outside the building's extent (below base floor or above the topmost floor). /// /// `voxel_z` is building-relative: the base floor maps to z = 0. Callers working in /// world space must subtract the building's world-base z offset before calling this method. pub fn floor_at_voxel_z(&self, voxel_z: i32) -> Option { let voxel_z_for_base = self.voxel_z_for_floor(self.base_floor)?; if voxel_z < voxel_z_for_base { return None; } let mut accum: i32 = voxel_z_for_base; let top_floor = self.base_floor as i16 + self.floor_count as i16 - 1; for f_offset in 0..self.floor_count { let floor_idx = self.base_floor as i16 + f_offset as i16; let h = self.floor_height_voxels(floor_idx as i8); let next_accum = accum + h as i32; if voxel_z >= accum && voxel_z < next_accum { return Some(floor_idx as i8); } accum = next_accum; if floor_idx >= top_floor { break; } } None } /// Map a D-110 floor index to the inclusive voxel-z range `(min_z, max_z)`. /// /// Returns `None` if `floor_index` is outside the building's extent. /// /// The returned range is building-relative: the base floor maps to z = 0. Callers working /// in world space must add the building's world-base z offset to the returned values. pub fn voxel_range_for_floor(&self, floor_index: i8) -> Option<(i32, i32)> { let base_z = self.voxel_z_for_floor(self.base_floor)?; let relative = floor_index as i16 - self.base_floor as i16; if relative < 0 || relative >= self.floor_count as i16 { return None; } let mut start: i32 = base_z; for f_offset in 0..relative { let fi = self.base_floor as i16 + f_offset; start += self.floor_height_voxels(fi as i8) as i32; } let h = self.floor_height_voxels(floor_idx_for_offset(self.base_floor, relative)) as i32; Some((start, start + h - 1)) } // ── Private helpers ──────────────────────────────────────────────────── /// Convert a floor index to its cumulative voxel-z offset from the ground-floor /// voxel origin (ground floor base = 0). fn voxel_z_for_floor(&self, floor: i8) -> Option { let relative = floor as i16 - self.base_floor as i16; if relative < 0 || relative >= self.floor_count as i16 { return None; } let mut z: i32 = 0; for f_offset in 0..relative { let fi = self.base_floor as i16 + f_offset; z += self.floor_height_voxels(fi as i8) as i32; } Some(z) } fn floor_height_voxels(&self, floor_index: i8) -> u8 { match &self.heights { FloorHeightProfile::Uniform(h) => *h, FloorHeightProfile::Variable(v) => { let offset = (floor_index as i16 - self.base_floor as i16) as usize; v.get(offset).copied().unwrap_or(3) } } } } fn floor_idx_for_offset(base: i8, offset: i16) -> i8 { (base as i16 + offset) as i8 } /// Axis-aligned rectangle in integer tile space (D-229, D-010 integer-only). /// /// Represents a building footprint within a 128-tile block. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct TileRect { /// Origin corner (tile-space x, y within the block, 0-based). pub origin: (u8, u8), /// Width and height in tiles (always ≥ 1). pub size: (u8, u8), } impl TileRect { pub fn new(origin_x: u8, origin_y: u8, width: u8, height: u8) -> Self { Self { origin: (origin_x, origin_y), size: (width, height), } } /// Returns `true` if the point `(x, y)` is inside this rect (inclusive). pub fn contains(&self, x: u8, y: u8) -> bool { x >= self.origin.0 && x < self.origin.0.saturating_add(self.size.0) && y >= self.origin.1 && y < self.origin.1.saturating_add(self.size.1) } } /// 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 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 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 /// building footprint at plan time (D-229). /// /// Written once inside `GenerateSkeleton`; read-only thereafter by FillChunk /// (D-230), the guarantee audit (D-097), and the Phase-6 interior generator (D-231). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct BuildingPropertyTag { /// What the building is — matches a D-142 RON zone-type `id`. pub zone_type_id: ZoneTypeId, /// Axis-aligned footprint within the 128-tile block. pub footprint: TileRect, /// Floor/basement extent (D-110 ↔ D-227 bridge, resolves Q-104). pub extent: FloorExtent, /// Physical access character (D-229). pub entry_class: BuildingEntryClass, /// Architecture flavor index into the body's trait-template draw (D-232). pub flavor_ref: ArchitectureFlavorRef, /// Construction era tag (D-229). pub era: ConstructionEra, /// Cause of this block's era classification. pub era_cause: EraCause, /// Frozen-amber condition snapshot from `prosperity_baseline_bps` (D-197/D-217). /// The rolling condition overlay (D-198) paints over this; never mutates the tag. pub initial_condition: crate::atlas::tile_condition::TileCondition, /// Doors into / out of this building (D-231). At least one `Main` door. /// /// Using `Vec` rather than `SmallVec<[DoorSpec; 4]>` for now; /// the vast majority of buildings have 1–4 doors and the heap allocation /// matches the D-231 intent. SmallVec upgrade tracked as a future /// micro-optimisation once the fill layer is profiled. // TODO: SmallVec<[DoorSpec; 4]> per D-231 once the door subsystem is hot. pub doors: Vec, } /// Cardinal compass direction (used by DoorSpec.facing, D-231). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum CardinalDirection { North, East, South, West, } /// Door class within a building (D-231). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum DoorClass { /// Primary public / commercial entrance — at least one per building. Main, /// Service or logistics entrance. Service, /// Emergency egress (required when `above_ground ≥ 2`, D-231). Emergency, /// Hidden entrance (D-106 rooftop/hidden discovery layer). Hidden, } /// Initial state of a door (D-231). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum DoorInitialState { Open, Closed, Locked, /// Permanently sealed — breach mechanics only. Sealed, } /// Door credential requirement (D-231). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum DoorCredential { None, /// Door is only accessible during specified hours (0–23, inclusive range). TemporalWindow { open_hour: u8, close_hour: u8, }, /// Requires an employment credential from this corporation. Corporate { corp_id: String, }, /// Requires a residence credential in this block. Resident { block_id: String, }, /// Law-enforcement / government authority only. Authority, /// Social trust score threshold (basis points; D-010 integer, 10 000 = 1.0). Social { trust_threshold_bps: u32, }, } /// What a door connects to on the other side (D-231). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum DoorConnectsTo { /// Opens onto a named street. Street { street_id: String }, /// Opens into an adjacent building in the same block. AdjacentBuilding { block_pos: (u8, u8) }, /// Opens into interstitial space (courtyard, alley, gap). Interstitial, } /// The complete Phase-6 seed for an interior (D-231). /// /// A future interior generator produces a deterministic floor plan from this /// descriptor + `SeedChain` with **no other system queried**. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct InteriorDescriptor { pub zone_type_id: ZoneTypeId, pub entry_class: BuildingEntryClass, pub floor_extent: FloorExtent, pub era: ConstructionEra, pub flavor_ref: ArchitectureFlavorRef, /// Prosperity baseline (0–10 000 basis points; D-010 integer-only). pub prosperity_bps: u32, pub layout_mode: LayoutMode, } /// Layout mode for interior generation (D-231, D-096). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum LayoutMode { /// Grid-planned interior — rectilinear rooms. Grid, /// Organic interior — irregular room shapes with offsets. Organic, } /// A single door on a building — the step3→step4 boundary (D-231). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct DoorSpec { /// Which face of the building footprint this door is on. pub facing: CardinalDirection, pub door_class: DoorClass, pub entry_class: BuildingEntryClass, pub initial_state: DoorInitialState, pub credential: DoorCredential, pub connects_to: DoorConnectsTo, /// The complete Phase-6 interior seed (D-231). pub interior_descriptor: InteriorDescriptor, } /// Region-level morphology zone — D-239 §6 frozen 17-zone vocabulary (T-1027). /// /// **FREEZE POINT**: this enum is the canonical freeze per D-239 §6. /// Adding, renaming, or removing a zone **requires a D-record amendment** /// (reviewer-enforced). Classifier tuning that merely re-classifies a region /// is NOT a vocabulary change and stays free. /// /// Zones ≠ families. Four zones — TidalFlat, Estuarine, Alpine, Wetland — /// are derived sub-classifications (family + elevation/water-height), not /// distinct generator families. /// /// Shared by all tiles in a region; constrains street geometry (D-234) /// and acts as a soft weight on cultural-template eligibility (D-232). /// Carried on `CityGenerationContext` (D-233 amend to D-199). /// /// Integer-discriminant, D-010 compliant. `repr(u8)` pins values for /// serialisation stability; append-only. #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)] #[repr(u8)] pub enum MorphologyZone { /// Deep-water ocean — hub-and-spoke; perimeter access priority. OpenOcean = 0, /// Lake body — hub-and-spoke; perimeter access toward water. Lake = 1, /// Tidal flat (sub-classification): low coastal plain exposed at low tide. /// Derived from CliffCoast/AlluvialPlain family + tidal signal + low elevation. TidalFlat = 2, /// Dune strand — ribbon/hub-and-spoke; wind-aligned lot orientation. DuneStrand = 3, /// Cliff coast — ribbon only; vertical face at water edge. CliffCoast = 4, /// Fjord — ribbon/hub-and-spoke; pier geometry; requires GlaciationGrade ≥ 2. Fjord = 5, /// River delta / braided channel — hub-and-spoke following channels; bridges as forced nodes. Delta = 6, /// Estuarine (sub-classification): brackish tidal zone at river mouth. /// Derived from Delta/AlluvialPlain family + tidal signal + low elevation. Estuarine = 7, /// Flat alluvial plain — any pattern; primary default for plains settlements. #[default] AlluvialPlain = 8, /// River bank — gentle approach; pier geometry on water-facing edges. RiverBank = 9, /// Meandering river reach — any pattern. MeanderReach = 10, /// Braided channel plain — hub-and-spoke; gravel/sand substrate. BraidedPlain = 11, /// Valley floor — ribbon or mesh; enclosed by flanking slopes. ValleyFloor = 12, /// Mountain pass terrain — ribbon only; elevation steps as block boundaries. MountainPass = 13, /// Alpine (sub-classification): above treeline high-altitude zone. /// Derived from IncisedGorge/ValleyFloor family + high elevation. Alpine = 14, /// Volcanic — lava field / shield slope; requires TectonicClass::Volcanic. Volcanic = 15, /// Wetland (sub-classification): low-gradient, saturated or seasonally flooded. /// Derived from AlluvialPlain/Delta family + low slope + high moisture. Wetland = 16, } /// Built-form archetype derived from a settlement's dominant commodity (D-233). /// /// 5 archetypes projected from the 8 cargo-type classifications in `commodities.toml`; /// drives building-vocabulary pool selection and roofed-coverage fraction. /// The 8 cargo types stay on the economics model — no fidelity lost. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum BulkClass { /// Solid bulk commodity (ore, aggregate, grain). Coverage: 0.25–0.40. /// Vocabulary: mine_head / conveyor_run / tailings_area. BulkSolid, /// Liquid bulk commodity (fuel, chemical, water). Coverage: 0.20–0.35. /// Vocabulary: tank_farm / flare_stack / pump_station. BulkLiquid, /// Precision-dense, high-value goods (electronics, manufactured parts). /// Coverage: 0.75–0.90. Vocabulary: cleanroom_facility / qc_lab / assembly_bay. PrecisionDense, /// Perishable goods (food, pharma, biologics). Coverage: 0.50–0.65. /// Vocabulary: field_shed / cold_store / processing_plant. Perishable, /// Non-physical services / information economy. Coverage: 0.85–0.95. /// Vocabulary: office_tower / civic_hall / data_centre. NonPhysical, } /// Spatial concentration of a settlement's dominant production (D-233). /// /// Controls how zone-type blocks are spread across the district grid — /// spread vs cluster — not per-block weighting. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum ProductionUbiquity { /// Production scattered through mixed-use (e.g. ubiquitous water infrastructure). Ubiquitous, /// Present in most districts but not dominant (e.g. common agriculture). Common, /// Concentrated in a few specialist districts. Specialist, /// Contiguous block groups dominate the settlement (the mine IS the city). MonopolySource, } /// Quarter-level world state produced by the plan phase (D-230). /// /// Output of `GenerateSkeleton` extended to include building-property tags. /// Stored in `BodyWorldState.quarters: BTreeMap`. /// `BTreeMap` for D-010 determinism. /// /// D-230: `{ skeleton: QuarterSkeleton, block_tags: BTreeMap<(u8,u8), Vec> }` #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)] pub struct QuarterWorldState { /// Phase 1 skeleton produced by `GenerateSkeleton` (D-230). pub skeleton: QuarterSkeleton, /// Building property tags keyed by block position (row, col) within the 4×4 grid. /// Each entry is a Vec of one tag per building footprint placed in that block. /// `BTreeMap` for D-010 determinism (no HashMap non-determinism). pub block_tags: BTreeMap<(u8, u8), Vec>, } /// Data contract between build-time (systems.db) and the runtime-background /// generation tier. Populated from atlas_city_names + bodies at generation /// dispatch time. All 8 fields are required before a generation task may run. /// Source: D-200, D-199 /// /// Amended by D-229/D-232/D-233: adds `morphology_zone`, `trait_selection`, /// `dominant_bulk_class`, `dominant_production_ubiquity`. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct CityGenerationContext { /// Foreign key into atlas_city_names.id pub city_id: u64, pub political_archetype: PoliticalArchetype, /// Starting economic health seed in basis points (0–10_000; 10_000 = 1.0). /// Derived per D-197. Integer to avoid f32 non-determinism (D-010). pub prosperity_baseline_bps: u32, pub surrounding_biome: SettingType, /// Compass octants (0=N, 1=NE … 7=NW) where roads enter the city footprint. pub road_entry_directions: Vec, /// City footprint radius in km. Derived from body_radius_km (D-204) + population. pub footprint_radius_km: f32, pub founding_orientation: FoundingOrientation, pub world_tier: WorldTier, // ── D-233/D-232 additions ───────────────────────────────────────────── /// Region morphology zone (D-228). Constrains street geometry (D-234) and /// acts as a soft weight on cultural-template eligibility (D-232). pub morphology_zone: MorphologyZone, /// 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, /// 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, /// 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, /// 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, // ── 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)>, } // --------------------------------------------------------------------------- // Supporting structs // --------------------------------------------------------------------------- /// Organic layout placement for a single block. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] pub struct BlockPlacement { /// Offset from grid-aligned position (±16 subtiles per axis max). pub offset: (i16, i16), /// Rotation in 15° increments (0–3, max 45°). pub rotation_steps: u8, /// Street width in basis points (10000 = 1.0×, range 7500–20000 for 0.75–2.0×). /// Default 10000 = 4 visual tiles. Uses integer to avoid f32 non-determinism (D-010). pub street_width_bps: u16, } /// Single block within a quarter's 4×4 block grid. /// Each block = 128×128 subtiles = 2×2 chunks. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] pub struct BlockSkeleton { /// Grid position (0–3, 0–3). 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, pub chunk_layout: ChunkLayout, pub hosted_sites: Vec, // Per-block `era: Era` / `era_modifications` String stubs removed (#957, T-984) — // construction era now lives per-footprint on `BuildingPropertyTag.era` (typed // `ConstructionEra`); `era_cause` is retained as the block-level cause. pub era_cause: Option, /// Build density percentage (0 = open/empty, 100 = fully built-up). /// Integer to avoid f32 non-determinism (D-010). pub density_pct: u8, pub landmark: Option, } /// Floor zone within a multi-level reservation. /// /// **D-110:** `z_level` is `i8` — negative values represent basements /// (e.g. `z_level = -1` for a sub-basement). This differs from `z_levels: u8` /// on the parent reservation which counts total floors (always ≥ 1). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct FloorZone { /// Absolute z-level of this floor. Negative for sub-ground levels (D-110). pub z_level: i8, pub zone_type: ZoningType, pub zone_palette: ZonePalette, pub access_tier: ZoneAccessTier, } /// Vertical connection spec within a multi-level reservation. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct VerticalCorridorSpec { /// Which blocks this vertical corridor passes through. pub block_coords: Vec<(u8, u8)>, /// Which z-bands (0-indexed band indices, not absolute z-levels) this /// corridor connects. See D-110 for signed z-level coordinate system. pub z_bands_connected: Vec, pub access_tier: ZoneAccessTier, pub corridor_type: VerticalCorridorType, } /// Visual palette for a zone — base material + contextual modifiers. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] pub struct ZonePalette { pub base: BasePalette, pub modifiers: Vec, } /// Contextual modifier applied on top of a base palette. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum PaletteModifier { EconomicFunction(EconomicModifier), Era(Era), FactionPresence(FactionModifier), Condition(ConditionModifier), Season(Season), } /// Social site placement within a district. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct SocialSitePlacement { pub site_id: SocialSiteId, /// Which blocks this site spans. pub blocks: Vec<(u8, u8)>, pub template_tag: String, pub access_tier: ZoneAccessTier, pub triangles: Vec, pub role_slots: Vec, pub active_phases: Vec, } /// Triangle assignment within a social site. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct TriangleAssignment { pub template: TriangleTemplate, pub purposes: Vec, pub participants: Vec, /// Which block provides the primary staging area (optional for multi-block sites). pub staging_block: Option<(u8, u8)>, } /// Mutations applied to an already-generated chunk. /// Stored alongside the chunk in the save file. #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct ChunkMutations { pub tile_overrides: Vec, pub structural_changes: Vec, pub placed_objects: Vec, pub removed_objects: Vec, } /// Single tile override within a chunk mutation. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TileOverride { /// (x, y, z) within the chunk (z is relative to chunk base, always ≥ 0). pub position: (u16, u16, u8), pub new_tile: TileId, pub cause: MutationCause, } /// Rectangular structural change within a chunk. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct StructuralChange { pub min: (u16, u16, u8), pub max: (u16, u16, u8), pub change_type: StructuralChangeType, } // --------------------------------------------------------------------------- // Multi-block reservation // --------------------------------------------------------------------------- /// A reservation spanning multiple blocks within a district. /// /// Used for skyscrapers, parks, transit terminals, plazas, and any structure /// that requires more than one 128×128 block. /// /// **D-110:** `base_z: i8` — the lowest floor of this reservation. /// Negative values represent sub-ground levels (basements, underground complexes). /// `z_levels: u8` remains unsigned — it counts total floors (always ≥ 1). /// /// Example: a building with a 3-level basement at ground + 20 floors above: /// `base_z: -3, z_levels: 24` /// A deep mine shaft descending 30 floors below ground: /// `base_z: -30, z_levels: 30` #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct MultiBlockReservation { /// Which blocks (grid positions) are part of this reservation. pub blocks: Vec<(u8, u8)>, pub template_tag: String, pub function: ReservationFunction, /// Total number of floors (always ≥ 1, unsigned). D-110. pub z_levels: u8, /// Lowest floor's z-level (can be negative for sub-basement). D-110. pub base_z: i8, /// Per-floor zone specifications. pub floor_zones: Vec, /// Number of z-bands (logical groupings of floors by zoning). pub z_band_count: u8, /// Zone definitions for each z-band. pub z_band_zones: Vec, /// Vertical connection specs (stairs, elevators, ladders). pub vertical_corridors: Vec, pub hosted_sites: Vec, } // --------------------------------------------------------------------------- // Quarter skeleton // --------------------------------------------------------------------------- /// Phase 1 generator output for one quarter. /// /// Produced by the async background generation pipeline. /// Consumed by Phase 2 (chunk-by-chunk fill, on demand) and by /// the production simulation startup path. /// /// ## Z-level naming convention (D-110) /// /// - `z_levels: u8` — how many floor levels exist in this quarter (count, always ≥ 1) /// - `base_z` does not appear on `QuarterSkeleton` itself — the quarter's /// ground level is always 0. Sub-ground structures use [`MultiBlockReservation`] /// with a negative `base_z` field. /// /// ## Determinism (D-010) /// /// All `Vec<_>` fields are stable-ordered at generation time (sorted by a /// deterministic key). Generation reproduces identical output for the same seed. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] pub struct QuarterSkeleton { // ── Identity ────────────────────────────────────────── pub quarter_id: QuarterId, /// Deterministic seed (derived from master seed via SeedChain). pub seed: u64, /// The quarter's primary functional zone (still a `DistrictType` — the /// zoning vocabulary is orthogonal to the spatial tier, D-222). pub quarter_type: DistrictType, /// World context (system, world, neighboring districts). pub context: QuarterContext, // ── Classification ──────────────────────────────────── pub world_tier: WorldTier, pub complexity: ComplexityTier, pub setting: SettingType, pub layout_mode: DistrictLayoutMode, // ── Spatial Structure ───────────────────────────────── /// The 4×4 block grid (each block = 128×128 subtiles = 2×2 chunks). pub blocks: [[BlockSkeleton; 4]; 4], /// Multi-block reservations (skyscrapers, parks, terminals, plazas). pub reservations: Vec, /// Corridor spines connecting key access points. pub corridors: Vec, /// Number of floor levels in this district (count, unsigned — D-110). pub z_levels: u8, // ── Social Structure ────────────────────────────────── pub social_sites: Vec, pub access_points: Vec, // Cultural / zone String stubs (`society_profile`, `zone_palette`) removed // (T-984, D-229) — per-building culture and zoning now live on // `BuildingPropertyTag` (`flavor_ref`, `zone_type_id`) in the district's `block_tags`. // ── Boundary System ─────────────────────────────────── pub boundaries: QuarterBoundaries, // ── Validation ──────────────────────────────────────── /// Guarantee audit result. `None` for Empty districts. pub guarantee_audit: Option, } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; // ── FloorExtent: floor_at_voxel_z and voxel_range_for_floor ───────────── /// (a) Uniform building at ground level — round-trip voxel↔floor. /// /// Uniform(3), base_floor=0, floor_count=2 → floors 0,1; heights 3 each. /// Floor 0 occupies voxels 0–2; floor 1 occupies voxels 3–5. #[test] fn floor_extent_uniform_ground_round_trip() { let extent = FloorExtent { base_floor: 0, floor_count: 2, heights: FloorHeightProfile::Uniform(3), }; // Range for each floor. assert_eq!(extent.voxel_range_for_floor(0), Some((0, 2))); assert_eq!(extent.voxel_range_for_floor(1), Some((3, 5))); // Round-trip: voxel → floor → range contains voxel. assert_eq!(extent.floor_at_voxel_z(0), Some(0)); assert_eq!(extent.floor_at_voxel_z(2), Some(0)); assert_eq!(extent.floor_at_voxel_z(3), Some(1)); assert_eq!(extent.floor_at_voxel_z(5), Some(1)); } /// (b) Basement building — base_floor=-1, floor_count=3 (floors -1, 0, 1). /// /// Uniform(3): floor -1 → voxels 0–2, floor 0 → 3–5, floor 1 → 6–8. #[test] fn floor_extent_basement_building() { let extent = FloorExtent { base_floor: -1, floor_count: 3, heights: FloorHeightProfile::Uniform(3), }; assert_eq!(extent.voxel_range_for_floor(-1), Some((0, 2))); assert_eq!(extent.voxel_range_for_floor(0), Some((3, 5))); assert_eq!(extent.voxel_range_for_floor(1), Some((6, 8))); assert_eq!(extent.floor_at_voxel_z(0), Some(-1)); assert_eq!(extent.floor_at_voxel_z(3), Some(0)); assert_eq!(extent.floor_at_voxel_z(6), Some(1)); assert_eq!(extent.floor_at_voxel_z(8), Some(1)); } /// (c) Variable([5,3,3]) floor heights — confirm each floor's voxel range. /// /// base_floor=0, floor_count=3: floor 0 → 5 voxels (0–4), floor 1 → 3 voxels (5–7), /// floor 2 → 3 voxels (8–10). #[test] fn floor_extent_variable_heights() { let extent = FloorExtent { base_floor: 0, floor_count: 3, heights: FloorHeightProfile::Variable(vec![5, 3, 3]), }; assert_eq!(extent.voxel_range_for_floor(0), Some((0, 4))); assert_eq!(extent.voxel_range_for_floor(1), Some((5, 7))); assert_eq!(extent.voxel_range_for_floor(2), Some((8, 10))); assert_eq!(extent.floor_at_voxel_z(0), Some(0)); assert_eq!(extent.floor_at_voxel_z(4), Some(0)); assert_eq!(extent.floor_at_voxel_z(5), Some(1)); assert_eq!(extent.floor_at_voxel_z(7), Some(1)); assert_eq!(extent.floor_at_voxel_z(8), Some(2)); assert_eq!(extent.floor_at_voxel_z(10), Some(2)); } /// (d) Boundary — one voxel above the top floor returns None; one below the base returns None. /// /// Uniform(3), base_floor=0, floor_count=2: valid range [0, 5]. /// voxel_z=6 is one above the top; voxel_z=-1 is one below the base. #[test] fn floor_extent_boundary_returns_none() { let extent = FloorExtent { base_floor: 0, floor_count: 2, heights: FloorHeightProfile::Uniform(3), }; // One voxel above the top floor (top floor ends at voxel 5). assert_eq!( extent.floor_at_voxel_z(6), None, "one voxel above top floor must return None" ); // One voxel below the base floor (base maps to voxel 0). assert_eq!( extent.floor_at_voxel_z(-1), None, "one voxel below base floor must return None" ); // Floor index outside extent also returns None from voxel_range_for_floor. assert_eq!( extent.voxel_range_for_floor(2), None, "floor index beyond floor_count must return None" ); assert_eq!( extent.voxel_range_for_floor(-1), None, "floor index below base_floor must return None" ); } /// D-110: FloorZone.z_level must be i8 (signed) to support negative sub-levels. #[test] fn floor_zone_z_level_is_signed() { let fz = FloorZone { z_level: -2, zone_type: ZoningType::Restricted, zone_palette: ZonePalette { base: String::new(), modifiers: vec![], }, access_tier: ZoneAccessTier::BreachOnly, }; assert_eq!(fz.z_level, -2, "sub-basement z_level must round-trip as i8"); } /// D-110: MultiBlockReservation.base_z must be i8 (signed) for underground buildings. #[test] fn reservation_base_z_is_signed() { let r = MultiBlockReservation { blocks: vec![(0, 0)], template_tag: "deep-mine".into(), function: ReservationFunction::UndergroundComplex, z_levels: 30, base_z: -30, floor_zones: vec![], z_band_count: 5, z_band_zones: vec![], vertical_corridors: vec![], hosted_sites: vec![], }; assert_eq!( r.base_z, -30, "deep mine base_z must be representable as i8" ); assert_eq!(r.z_levels, 30u8, "z_levels count must remain u8"); } /// D-110: z_levels (count) remains u8 on QuarterSkeleton. #[test] fn quarter_skeleton_z_levels_is_unsigned() { // Verify z_levels is u8 (cannot hold negative value). Static assertion: // if this compiled with z_levels = 255u8, the type is correct. fn assert_u8(_: u8) {} let z_levels: u8 = 4; assert_u8(z_levels); assert!(z_levels > 0, "z_levels is always at least 1"); } }