//! World generator data model — district skeleton and mobile chunk types. //! //! Phase 1 generator output: the `DistrictSkeleton` 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 DistrictId = 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; /// Single era modification applied to a block (conversion, overlay, hybrid). Stub. pub type EraModification = String; /// Landmark slot description within a block. Stub. pub type LandmarkSlot = String; /// Chunk layout specification within a block (how the 2×2 chunks are arranged). Stub. pub type ChunkLayout = String; /// Corridor spine connecting district access points. Stub. pub type CorridorSpine = String; /// District context — neighboring districts, world position, system. Stub. pub type DistrictContext = String; /// Society profile reference (Miri's cultural ingredients). Stub. pub type SocietyProfileRef = String; /// Zone definition — base palette + modifiers + zone name. Stub. pub type ZoneDefinition = String; /// District boundary system — edge descriptors with neighbors. Stub. pub type DistrictBoundaries = String; /// Guarantee audit result — tier-appropriate spatial invariant checks. Stub. pub type GuaranteeAuditResult = String; /// District access point (entry/exit to neighboring district). Stub. pub type AccessPoint = String; /// 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). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, 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, 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 }, } /// Territory control status for a province (drainage basin). Priority-ordered derivation. /// Source: D-212 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum TerritorialStatus { /// Commission faction_influence ≥ 0.6 in this province. CommissionControlled, /// 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, /// Cultural corridor has indigenous autonomy flag. IndigenousHeld, /// Population density < 0.01 AND no faction ≥ 0.1. Derelict, } // --------------------------------------------------------------------------- // 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, /// Normalized strength 0.0–1.0. Derived from flow accumulation or habitability score. pub strength: f32, /// Fine-grained terrain classification at this position (D-210). pub sub_biome: SubBiomeVariant, /// Infrastructure-build cost multiplier (1.0 = baseline grassland; higher = /// more expensive). Derived from `sub_biome` + local slope. Consumed by the /// attractor-matching pipeline (D-211) to penalize marginal cities. (D-210) pub terrain_modification_cost: f32, } /// Compatibility weights between economic roles and attractor types. /// A 10×7 matrix (10 economic_role values × 7 AttractorType variants). /// Each cell is a weight multiplier 0.0–3.0 applied during attractor-matching scoring. /// 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: [[f32; 7]; 10], } // --------------------------------------------------------------------------- // 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 + 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 index 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. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct ArchitectureFlavorRef { /// Index into `CityGenerationContext::trait_selection` (0-based). pub flavor_index: u8, } /// 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` (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-228, D-232, D-234). /// /// 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). #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] pub enum MorphologyZone { /// Steep-sided inlet — ribbon/hub-and-spoke streets only; pier geometry on water edges. Fjord, /// River delta / braided channel — hub-and-spoke following channels; bridges as forced nodes. Delta, /// Meandering river reach — any pattern. MeanderReach, /// Flat alluvial plain — any pattern; primary default for plains settlements. #[default] AlluvialPlain, /// Open ocean surface (deep-water context) — hub-and-spoke; perimeter access priority. OpenOcean, /// Lake shore — hub-and-spoke; perimeter access toward water. Lake, /// Interior sea body. Sea, /// Mountain pass terrain — ribbon only; elevation steps as block boundaries. MountainPass, /// Coastal lowland — any pattern; pier geometry on water-facing edges. CoastalLowland, /// Island context — hub-and-spoke; perimeter access priority. Island, /// Canyon floor — ribbon or hub-and-spoke only. Canyon, /// Unknown / unclassified — fallback to AlluvialPlain behaviour. Unknown, } /// 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, } /// District-level world state produced by the plan phase (D-230). /// /// Output of `GenerateSkeleton` extended to include building-property tags. /// Stored in `BodyWorldState.districts: BTreeMap`. /// `BTreeMap` for D-010 determinism. /// /// D-230: `{ skeleton: DistrictSkeleton, block_tags: BTreeMap<(u8,u8), Vec> }` #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)] pub struct DistrictWorldState { /// Phase 1 skeleton produced by `GenerateSkeleton` (D-230). pub skeleton: DistrictSkeleton, /// 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 (0.0–1.0). Derived per D-197. pub prosperity_baseline: f32, 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). 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, } // --------------------------------------------------------------------------- // 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 sim tiles 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 district's 4×4 block grid. /// Each block = 128×128 sim tiles = 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, /// Which multi-block reservation this block belongs to (if any). pub reservation: Option, pub chunk_layout: ChunkLayout, pub hosted_sites: Vec, pub era: Era, pub era_modifications: Vec, 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, } // --------------------------------------------------------------------------- // District skeleton // --------------------------------------------------------------------------- /// Phase 1 generator output for one district. /// /// 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 district (count, always ≥ 1) /// - `base_z` does not appear on `DistrictSkeleton` itself — the district'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 DistrictSkeleton { // ── Identity ────────────────────────────────────────── pub district_id: DistrictId, /// Deterministic seed (derived from master seed via SeedChain). pub seed: u64, pub district_type: DistrictType, /// World context (system, world, neighboring districts). pub context: DistrictContext, // ── 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 sim tiles = 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 / World Context ────────────────────────── pub society_profile: SocietyProfileRef, pub zone_palette: Vec, // ── Boundary System ─────────────────────────────────── pub boundaries: DistrictBoundaries, // ── 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 DistrictSkeleton. #[test] fn district_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"); } }