//! 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 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)] 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. Waypoint, } /// Generator content budget for a district. /// Derived from WorldTier + SettingType at Phase 1. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] 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. Empty, } /// Physical setting type for a district. /// Merged from Gestalt's SettingGeometry and Tyre's TerrainType. #[derive(Serialize, Deserialize, Clone, Debug)] pub enum SettingType { Station, 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)] pub enum DistrictType { LogisticsHub, Residential, Commercial, Industrial, Administrative, Entertainment, MixedUse, Transit, Specialized, } /// How blocks are placed within the district's 512×512 footprint. #[derive(Serialize, Deserialize, Clone, Debug)] pub enum DistrictLayoutMode { /// Standard Cartesian grid — perpendicular streets. 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)] pub enum ZoningType { Commercial, Residential, Industrial, Administrative, Transit, Recreational, Restricted, Mixed, } /// Reservation function — what purpose a multi-block reservation serves. #[derive(Serialize, Deserialize, Clone, Debug)] pub enum ReservationFunction { Skyscraper, Park, Terminal, Plaza, Monument, IndustrialComplex, MilitaryBase, ResearchFacility, UndergroundComplex, } /// Access tier — who is allowed into a zone under normal circumstances. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum AccessTier { /// 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)] 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)] 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)] 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], } /// 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 #[derive(Serialize, Deserialize, Clone, Debug)] 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, } // --------------------------------------------------------------------------- // Supporting structs // --------------------------------------------------------------------------- /// Organic layout placement for a single block. #[derive(Serialize, Deserialize, Clone, Debug)] 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)] 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)] 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: AccessTier, } /// Vertical connection spec within a multi-level reservation. #[derive(Serialize, Deserialize, Clone, Debug)] 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: AccessTier, pub corridor_type: VerticalCorridorType, } /// Visual palette for a zone — base material + contextual modifiers. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ZonePalette { pub base: BasePalette, pub modifiers: Vec, } /// Contextual modifier applied on top of a base palette. #[derive(Serialize, Deserialize, Clone, Debug)] pub enum PaletteModifier { EconomicFunction(EconomicModifier), Era(Era), FactionPresence(FactionModifier), Condition(ConditionModifier), Season(Season), } /// Social site placement within a district. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct SocialSitePlacement { pub site_id: SocialSiteId, /// Which blocks this site spans. pub blocks: Vec<(u8, u8)>, pub template_tag: String, pub access_tier: AccessTier, pub triangles: Vec, pub role_slots: Vec, pub active_phases: Vec, } /// Triangle assignment within a social site. #[derive(Serialize, Deserialize, Clone, Debug)] 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)] 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)] 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::*; /// 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: AccessTier::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"); } }