From c71c26a1a6edcdd89694265bd14284cd5e029fc7 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Fri, 5 Jun 2026 13:06:44 +0200 Subject: [PATCH] =?UTF-8?q?refactor(simulation):=20rename=20DistrictSkelet?= =?UTF-8?q?on=E2=86=92QuarterSkeleton=20per=20D-222=20(#950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D-222 renamed the 512m generation cell from District to Quarter (District is now a new 2048m tier above it). Align the generation skeleton code to the canonical vocabulary. Pure naming — no behavior change; all 1296 lib tests + integration tests pass unchanged. Renamed (spatial-cell identifiers): - DistrictSkeleton → QuarterSkeleton, DistrictWorldState → QuarterWorldState - DistrictId → QuarterId, DistrictContext/DistrictBoundaries → Quarter* - field district_id → quarter_id, district_type → quarter_type - BodyWorldState.districts map → .quarters - generate_skeleton → generate_quarter_skeleton Deliberately left as-is (these name functional ZONING, not the spatial tier — orthogonal to D-222): DistrictType, DistrictLayoutMode, the district_mix module (DistrictMix/compute_district_mix), and the GenWorkItem::GenerateSkeleton / GenCompletion::SkeletonGenerated variants. Also aligned the perception "sim tile" → "subtile" vocabulary (D-222: Subtile = 0.5m) in decisions/perception.md and the generation-cascade code comments. Historical D-066/D-094/D-201/D-220 decision bodies keep their existing D-222 amendment notes (not rewritten in place); the public max_offset_sim_tiles fn name is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- decisions/perception.md | 6 +-- server/src/atlas/block_irregularity.rs | 2 +- server/src/atlas/body_world_state.rs | 8 +-- server/src/atlas/cascade.rs | 2 +- server/src/atlas/gen_queue.rs | 42 +++++++-------- server/src/atlas/layer_proxy.rs | 2 +- server/src/atlas/plugin.rs | 14 ++--- server/src/atlas/skeleton_gen.rs | 73 +++++++++++++++----------- server/src/npc/blueprint.rs | 4 +- server/src/simulation/generator.rs | 52 +++++++++--------- 10 files changed, 108 insertions(+), 97 deletions(-) diff --git a/decisions/perception.md b/decisions/perception.md index fc2ad0bec..9c3b810d8 100644 --- a/decisions/perception.md +++ b/decisions/perception.md @@ -149,7 +149,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight ### D-044: Art direction — visual hierarchy (entity > object > structure) - **Date:** 2026-02-12 - **Decision:** Three-layer visual hierarchy: entities (2px outline, D-033 relationship color, highest saturation) > objects (1px outline, era-appropriate palette, moderate saturation) > structure (minimal/no outline, muted zone palette, lowest saturation). Hard rendering rule: entity always wins visual ties — if an entity and object overlap, the entity's D-033 color must remain visible. This is a readability guarantee, not an aesthetic preference. -- **Entity spec:** 24x32 pixel footprint within 64x64px visual tiles. Entities occupy 1x1 sim tiles (0.5m) but render across a 2x2 sim tile sprite footprint per [D-066](architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor). D-033 color as primary information signal, silhouette as primary identity signal. One identifying silhouette feature per named NPC (Kael's vest, Lera's apron, Sera's uniform). 3-4 template silhouettes for generic NPCs. +- **Entity spec:** 24x32 pixel footprint within 64x64px visual tiles. Entities occupy 1x1 subtiles (0.5m) but render across a 2x2 subtile sprite footprint per [D-066](architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor). D-033 color as primary information signal, silhouette as primary identity signal. One identifying silhouette feature per named NPC (Kael's vest, Lera's apron, Sera's uniform). 3-4 template silhouettes for generic NPCs. - **Canonical reference:** `docs/workshops/art-direction-mood-board/workshop-outcomes.md` §1.4, §1.5 - **Raised by:** Araminta (outline spec), Ozzie (readability priority). Unanimously endorsed. - **Dissent:** None @@ -212,7 +212,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Object hover:** Ticks rotate 45° to X-shape. Muted grey #8b8ba0 (amber #e8c547 if flagged). Simpler frame than entity. - **Weapon aim:** Hard transition. Ticks extend, center gap widens, lines thicken 1→2px. Warm white #f0e8d8. NO bloom. Entity in sights tints to D-033 color — aiming at a friend (green tint) should feel wrong. - All transitions 150ms linear. z-layer 7. Never changes by zone or narrative state ([D-045](#d-045-art-direction--environmental-neutrality-strict-zero-shift)). -- Cursor changes on LOS, not just proximity. Click interaction range: ~2 sim tiles (= 1m per [D-066](architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor)). Weapon-selected mode suppresses interaction prompts unless Shift held ("combat intent trumps social intent"). +- Cursor changes on LOS, not just proximity. Click interaction range: ~2 subtiles (= 1m per [D-066](architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor)). Weapon-selected mode suppresses interaction prompts unless Shift held ("combat intent trumps social intent"). - **Diegetic test (Stig):** Interaction labels render on z-layer 6 (insert overlay). If the insert is off, labels disappear. Passes the "is this information from the character's implant?" test. - **Rationale:** Diegetic — cursor is the insert's own interface element. Consistent with [D-048](#d-048-neural-insert-overlay--visual-design). Entity in weapon sights tinting to D-033 creates moral friction. - **Cross-reference:** Insert overlay ([D-048](#d-048-neural-insert-overlay--visual-design)), entity color ([D-033](#d-033-entity-color--relationship-to-player)), z-stack ([D-049](#d-049-z-level-rendering-stack-8-layers)) @@ -254,7 +254,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight ### D-059: Fog — shader-based, five layers, knowledge-graph-driven - **Date:** 2026-02-13 - **Decision:** Fog is a shader-driven system (not particles) with five distinct layers: - 1. **Clear (vision cone):** Soft gradient edge over 6-8 sim tiles (= 3-4 visual tiles per [D-066](architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor)), no hard line. Darkwood approach. + 1. **Clear (vision cone):** Soft gradient edge over 6-8 subtiles (= 3-4 visual tiles per [D-066](architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor)), no hard line. Darkwood approach. 2. **Light fog (peripheral):** Desaturated 40-50%, brightness -30%. Animated Perlin noise overlay (8-10s cycle). Entity [D-033](#d-033-entity-color--relationship-to-player) colors visible but reduced. 3. **Deep fog (previously explored):** Near-monochrome with ~10% "zone temperature" tint (bar=warm dark, hub=cool dark, corridor=neutral dark). More pronounced noise (15-20s cycle). Fog breathes. 4. **Unexplored + maps app:** Geometric wireframe outlines #333340. Insert data aesthetic. diff --git a/server/src/atlas/block_irregularity.rs b/server/src/atlas/block_irregularity.rs index cac168af6..06f213624 100644 --- a/server/src/atlas/block_irregularity.rs +++ b/server/src/atlas/block_irregularity.rs @@ -45,7 +45,7 @@ fn archetype_step_bp(archetype: &PoliticalArchetype) -> i32 { } } -/// Derive the maximum block offset in sim tiles from `block_irregularity`. +/// Derive the maximum block offset in subtiles from `block_irregularity`. /// /// Used by `DistrictLayoutMode::Organic`: `max_offset = (irregularity × 16.0) as i16`. pub fn max_offset_sim_tiles(irregularity: f32) -> i16 { diff --git a/server/src/atlas/body_world_state.rs b/server/src/atlas/body_world_state.rs index 4a4ec87c7..30c0f2dbb 100644 --- a/server/src/atlas/body_world_state.rs +++ b/server/src/atlas/body_world_state.rs @@ -14,7 +14,7 @@ use bevy_ecs::prelude::Resource; use serde::{Deserialize, Serialize}; use crate::atlas::attractor_matching::CityPlacement; -use crate::simulation::generator::{DistrictId, DistrictWorldState, GeographicAttractor}; +use crate::simulation::generator::{GeographicAttractor, QuarterId, QuarterWorldState}; /// Simulation tick counter — monotonically increasing u64. pub type SimTick = u64; @@ -75,11 +75,11 @@ pub struct BodyWorldState { /// Settlement placements (D-211, #955). Attractor-matched city positions. /// Empty until the Layer-3 placement task completes. pub placements: Vec, - /// District-level world state, keyed by `DistrictId` (D-230). + /// Quarter-level world state, keyed by `QuarterId` (D-230). /// /// Populated by `GenCompletion::SkeletonGenerated` after the plan phase /// completes for each city. `BTreeMap` for D-010 determinism. - pub districts: BTreeMap, + pub quarters: BTreeMap, /// Last sim tick this entry was read. Used for LRU eviction. pub last_accessed: SimTick, } @@ -201,7 +201,7 @@ mod tests { drainage_basins: vec![], attractors: vec![], placements: vec![], - districts: BTreeMap::new(), + quarters: BTreeMap::new(), last_accessed: tick, } } diff --git a/server/src/atlas/cascade.rs b/server/src/atlas/cascade.rs index 21825f178..aff125657 100644 --- a/server/src/atlas/cascade.rs +++ b/server/src/atlas/cascade.rs @@ -88,7 +88,7 @@ impl CascadeSnapshot { drainage_basins, attractors, placements, - districts: std::collections::BTreeMap::new(), + quarters: std::collections::BTreeMap::new(), last_accessed: 0, } } diff --git a/server/src/atlas/gen_queue.rs b/server/src/atlas/gen_queue.rs index a3a4c20df..e9b995560 100644 --- a/server/src/atlas/gen_queue.rs +++ b/server/src/atlas/gen_queue.rs @@ -12,7 +12,7 @@ //! //! **Work item types (D-206):** //! - `AnalyzeBody`: D8 drainage + attractor extraction for a body. -//! - `GenerateSkeleton`: Phase 1 DistrictSkeleton for a city. +//! - `GenerateSkeleton`: Phase 1 QuarterSkeleton for a city. //! - `FillChunk`: Phase 2 chunk fill for a pre-loaded district. //! //! Completion events are delivered to the main thread via @@ -31,9 +31,9 @@ use crate::atlas::attractor_matching::CityRecord; use crate::atlas::body_world_state::BodyWorldState; use crate::atlas::cascade::{run_cascade_from_heightmap, CascadeLayer}; use crate::atlas::heightmap::{load_heightmap_png, GRID_H, GRID_W}; -use crate::atlas::skeleton_gen::generate_skeleton; +use crate::atlas::skeleton_gen::generate_quarter_skeleton; use crate::seed::SeedChain; -use crate::simulation::generator::{CityGenerationContext, DistrictWorldState}; +use crate::simulation::generator::{CityGenerationContext, QuarterWorldState}; // --------------------------------------------------------------------------- // Priority @@ -73,7 +73,7 @@ pub enum GenWorkItem { /// (#955); empty if the body has no settlements (cascade stops at Layer 1). cities: Vec, }, - /// Generate a Phase 1 DistrictSkeleton for this city. + /// Generate a Phase 1 QuarterSkeleton for this city. /// /// `context` is the D-199 economic read-set pre-resolved at dispatch time. /// All 6 required fields must be populated before this item is submitted @@ -83,11 +83,11 @@ pub enum GenWorkItem { /// `body_id` routes the resulting `SkeletonGenerated` completion into the /// correct `BodyWorldState` cache entry (D-230). /// - /// `district_id` is the stable content-addressable id for the generated + /// `quarter_id` is the stable content-addressable id for the generated /// district (keyed by city position + world seed). /// /// `economic_role`, `population`, and `founding_age_years` are D-199 fields - /// carried alongside the context because `generate_skeleton` accepts them as + /// carried alongside the context because `generate_quarter_skeleton` accepts them as /// separate parameters (its signature is not changed by this ticket). GenerateSkeleton { city_id: u64, @@ -95,17 +95,17 @@ pub enum GenWorkItem { /// D-199 economic read-set + all other context fields. context: Box, /// Stable content-addressable district id (D-194/D-230). - district_id: u64, + quarter_id: u64, /// District-level seed chain (D-224). chain: SeedChain, - // D-199 raw fields passed to generate_skeleton separately. + // D-199 raw fields passed to generate_quarter_skeleton separately. economic_role: String, population: i64, founding_age_years: u32, }, /// Pre-fill a chunk in an existing district. FillChunk { - district_id: u64, + quarter_id: u64, block_pos: (u32, u32), }, } @@ -135,14 +135,14 @@ pub enum GenCompletion { SkeletonGenerated { city_id: u64, /// The body this skeleton belongs to — used to route state into - /// `BodyWorldState.districts` (D-230). + /// `BodyWorldState.quarters` (D-230). body_id: String, /// District-level world state (skeleton + block tags) produced by the plan phase (D-230). /// Boxed to keep `GenCompletion` variant sizes balanced (D-230 skeleton is ~2.7 KB). - state: Box, + state: Box, }, ChunkFilled { - district_id: u64, + quarter_id: u64, block_pos: (u32, u32), }, /// Work item failed — body_id or city_id for logging. @@ -385,7 +385,7 @@ fn run_work_item(item: &GenWorkItem) -> GenCompletion { city_id, body_id, context, - district_id, + quarter_id, chain, economic_role, population, @@ -394,29 +394,29 @@ fn run_work_item(item: &GenWorkItem) -> GenCompletion { // Build the Phase 1 skeleton from the pre-resolved D-199 context. // `economic_role`, `population`, and `founding_age_years` are the // D-199 raw fields carried alongside the context because - // `generate_skeleton` accepts them as separate parameters. - let skeleton = generate_skeleton( + // `generate_quarter_skeleton` accepts them as separate parameters. + let skeleton = generate_quarter_skeleton( context, *population, economic_role, - *district_id, + *quarter_id, *founding_age_years, *chain, ); GenCompletion::SkeletonGenerated { city_id: *city_id, body_id: body_id.clone(), - state: Box::new(DistrictWorldState { + state: Box::new(QuarterWorldState { skeleton, block_tags: std::collections::BTreeMap::new(), }), } } GenWorkItem::FillChunk { - district_id, + quarter_id, block_pos, } => GenCompletion::ChunkFilled { - district_id: *district_id, + quarter_id: *quarter_id, block_pos: *block_pos, }, } @@ -493,7 +493,7 @@ mod tests { dominant_bulk_class: BulkClass::NonPhysical, dominant_production_ubiquity: ProductionUbiquity::Common, }), - district_id: city_id * 10, + quarter_id: city_id * 10, chain: SeedChain::root(42 + city_id), economic_role: "service_mixed".to_string(), population: 500_000, @@ -590,7 +590,7 @@ mod tests { let q = make_queue(); q.submit( GenWorkItem::FillChunk { - district_id: 99, + quarter_id: 99, block_pos: (0, 0), }, GenPriority::High, diff --git a/server/src/atlas/layer_proxy.rs b/server/src/atlas/layer_proxy.rs index a2db34b3d..95529b3f9 100644 --- a/server/src/atlas/layer_proxy.rs +++ b/server/src/atlas/layer_proxy.rs @@ -228,7 +228,7 @@ mod tests { drainage_basins: vec![], attractors: vec![], placements: vec![], - districts: std::collections::BTreeMap::new(), + quarters: std::collections::BTreeMap::new(), last_accessed: 0, }); let (_db, resolver) = empty_resolver(); diff --git a/server/src/atlas/plugin.rs b/server/src/atlas/plugin.rs index fabef8554..2911b9478 100644 --- a/server/src/atlas/plugin.rs +++ b/server/src/atlas/plugin.rs @@ -93,16 +93,16 @@ fn drain_generation_completions( } => { if !body_id.is_empty() { if let Some(body_state) = cache.peek_mut(&body_id) { - // Key by state.skeleton.district_id (D-194/D-230): a city has many - // districts, each with its own DistrictId. `city_id` is only the + // Key by state.skeleton.quarter_id (D-194/D-230): a city has many + // quarters, each with its own QuarterId. `city_id` is only the // dispatch key used in the work item — the canonical insert key is - // the district's own stable id. TODO(#957): the stub GenerateSkeleton - // returns a default skeleton with district_id=0; real gen (#957) will + // the quarter's own stable id. TODO(#957): the stub GenerateSkeleton + // returns a default skeleton with quarter_id=0; real gen (#957) will // populate it from CityGenerationContext. - let _ = city_id; // used as dispatch key only; district_id is the map key + let _ = city_id; // used as dispatch key only; quarter_id is the map key body_state - .districts - .insert(state.skeleton.district_id, *state); + .quarters + .insert(state.skeleton.quarter_id, *state); } else { tracing::warn!( city_id, diff --git a/server/src/atlas/skeleton_gen.rs b/server/src/atlas/skeleton_gen.rs index bd703f457..7e34f728a 100644 --- a/server/src/atlas/skeleton_gen.rs +++ b/server/src/atlas/skeleton_gen.rs @@ -1,8 +1,8 @@ -//! Phase 1 district skeleton generator (D-194, D-196, D-211, D-213, D-214). +//! Phase 1 quarter skeleton generator (D-194, D-196, D-211, D-213, D-214). //! -//! Entry point: [`generate_skeleton`]. Consumes a [`CityGenerationContext`] +//! Entry point: [`generate_quarter_skeleton`]. Consumes a [`CityGenerationContext`] //! together with the city's raw population and economic role, and produces a -//! fully classified [`DistrictSkeleton`] with: +//! fully classified [`QuarterSkeleton`] with: //! //! - [`SettingType`] derived from the surrounding biome context. //! - [`ComplexityTier`] derived from population tier × [`WorldTier`]. @@ -22,8 +22,8 @@ use crate::atlas::block_irregularity::block_irregularity; use crate::atlas::district_mix::{compute_district_mix, population_tier}; use crate::seed::{SeedChain, SeedDomain}; use crate::simulation::generator::{ - BlockPlacement, BlockSkeleton, CityGenerationContext, ComplexityTier, DistrictId, - DistrictLayoutMode, DistrictSkeleton, DistrictType, MultiBlockReservation, PoliticalArchetype, + BlockPlacement, BlockSkeleton, CityGenerationContext, ComplexityTier, DistrictLayoutMode, + DistrictType, MultiBlockReservation, PoliticalArchetype, QuarterId, QuarterSkeleton, ReservationFunction, ReservationId, SettingType, WorldTier, ZoningType, }; @@ -31,23 +31,23 @@ use crate::simulation::generator::{ // Public API // --------------------------------------------------------------------------- -/// Generate a Phase 1 [`DistrictSkeleton`] from a city's generation context. +/// Generate a Phase 1 [`QuarterSkeleton`] from a city's generation context. /// /// # Parameters /// - `context`: Build-time city context (archetype, world tier, orientation…). /// - `population`: Raw population count from atlas_city_names. /// - `economic_role`: Economic role string (one of the 10 canonical values). -/// - `district_id`: Content-addressable identifier for this district. +/// - `quarter_id`: Content-addressable identifier for this district. /// - `founding_age_years`: Years since founding — controls block irregularity. /// - `chain`: This district's position in the deterministic seed tree (D-224 SeedChain). -pub fn generate_skeleton( +pub fn generate_quarter_skeleton( context: &CityGenerationContext, population: i64, economic_role: &str, - district_id: DistrictId, + quarter_id: QuarterId, founding_age_years: u32, chain: SeedChain, -) -> DistrictSkeleton { +) -> QuarterSkeleton { // ── 1. SettingType ──────────────────────────────────────────────────── // Pass through the surrounding_biome from context — it already encodes // the planet/station/wilderness classification established at atlas time. @@ -103,11 +103,11 @@ pub fn generate_skeleton( // Reserved blocks carry their own z_levels count. let z_levels: u8 = 1; - DistrictSkeleton { - district_id, + QuarterSkeleton { + quarter_id, seed: chain.seed(), - district_type: district_type_from_mix(&primary_district_type), - context: String::new(), // stub — DistrictContext = String + quarter_type: district_type_from_mix(&primary_district_type), + context: String::new(), // stub — QuarterContext = String world_tier: context.world_tier.clone(), complexity, setting, @@ -242,7 +242,7 @@ fn derive_layout_mode( /// Generate 4×4 organic block placements seeded deterministically (D-010). /// /// Uses a seeded LCG; offset range controlled by `irregularity` (0.05–1.0) -/// scaled to the ±16 sim tile maximum from `block_irregularity::max_offset_sim_tiles`. +/// scaled to the ±16 subtile maximum from `block_irregularity::max_offset_sim_tiles`. fn organic_placements(irregularity: f32, chain: SeedChain) -> [[BlockPlacement; 4]; 4] { let max_offset = (irregularity * 16.0) as i16; // id = 0: one placement pass per district. Sibling separation comes from the @@ -292,7 +292,7 @@ fn zoning_for_district(dt: &DistrictType) -> ZoningType { } } -/// Map the primary district type to the DistrictType field on DistrictSkeleton. +/// Map the primary district type to the DistrictType field on QuarterSkeleton. fn district_type_from_mix(primary: &DistrictType) -> DistrictType { primary.clone() } @@ -437,14 +437,15 @@ mod tests { fn setting_station_passthrough() { let mut ctx = make_context(PoliticalArchetype::Commission, WorldTier::Regional); ctx.surrounding_biome = SettingType::Station; - let sk = generate_skeleton(&ctx, 500_000, "institutional", 1, 200, SeedChain::root(42)); + let sk = + generate_quarter_skeleton(&ctx, 500_000, "institutional", 1, 200, SeedChain::root(42)); assert!(matches!(sk.setting, SettingType::Station)); } #[test] fn setting_urban_for_city_on_planet() { let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Regional); - let sk = generate_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42)); + let sk = generate_quarter_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42)); assert!(matches!(sk.setting, SettingType::Urban)); } @@ -452,14 +453,15 @@ mod tests { fn complexity_epicenter_high_pop_is_full() { let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Epicenter); // 10M pop → pop_tier = 1 → Full on Epicenter - let sk = generate_skeleton(&ctx, 10_000_000, "financial", 1, 200, SeedChain::root(42)); + let sk = + generate_quarter_skeleton(&ctx, 10_000_000, "financial", 1, 200, SeedChain::root(42)); assert_eq!(sk.complexity, ComplexityTier::Full); } #[test] fn complexity_waypoint_tiny_pop_is_empty() { let ctx = make_context(PoliticalArchetype::Pioneer, WorldTier::Waypoint); - let sk = generate_skeleton(&ctx, 1_000, "residential", 1, 50, SeedChain::root(42)); + let sk = generate_quarter_skeleton(&ctx, 1_000, "residential", 1, 50, SeedChain::root(42)); assert_eq!(sk.complexity, ComplexityTier::Empty); } @@ -467,7 +469,7 @@ mod tests { fn complexity_backwater_low_pop_is_moderate() { let ctx = make_context(PoliticalArchetype::Pioneer, WorldTier::Backwater); // 50_000 pop → pop_tier = 0 → Moderate on Backwater (D-218: not budget-capped) - let sk = generate_skeleton(&ctx, 50_000, "residential", 1, 50, SeedChain::root(42)); + let sk = generate_quarter_skeleton(&ctx, 50_000, "residential", 1, 50, SeedChain::root(42)); assert_eq!(sk.complexity, ComplexityTier::Moderate); } @@ -475,7 +477,8 @@ mod tests { fn complexity_backwater_high_pop_is_full() { let ctx = make_context(PoliticalArchetype::Pioneer, WorldTier::Backwater); // 10M pop → pop_tier = 1 → Full on Backwater (D-218: not budget-capped) - let sk = generate_skeleton(&ctx, 10_000_000, "residential", 1, 50, SeedChain::root(42)); + let sk = + generate_quarter_skeleton(&ctx, 10_000_000, "residential", 1, 50, SeedChain::root(42)); assert_eq!(sk.complexity, ComplexityTier::Full); } @@ -483,28 +486,31 @@ mod tests { fn complexity_passage_low_pop_is_minimal() { let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Passage); // 50_000 pop → pop_tier = 0 → Minimal on Passage (transit stop, budget-capped) - let sk = generate_skeleton(&ctx, 50_000, "transit_hub", 1, 100, SeedChain::root(42)); + let sk = + generate_quarter_skeleton(&ctx, 50_000, "transit_hub", 1, 100, SeedChain::root(42)); assert_eq!(sk.complexity, ComplexityTier::Minimal); } #[test] fn layout_commission_is_grid() { let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Regional); - let sk = generate_skeleton(&ctx, 500_000, "institutional", 1, 100, SeedChain::root(99)); + let sk = + generate_quarter_skeleton(&ctx, 500_000, "institutional", 1, 100, SeedChain::root(99)); assert!(matches!(sk.layout_mode, DistrictLayoutMode::Grid)); } #[test] fn layout_pioneer_is_organic() { let ctx = make_context(PoliticalArchetype::Pioneer, WorldTier::Regional); - let sk = generate_skeleton(&ctx, 500_000, "residential", 1, 400, SeedChain::root(99)); + let sk = + generate_quarter_skeleton(&ctx, 500_000, "residential", 1, 400, SeedChain::root(99)); assert!(matches!(sk.layout_mode, DistrictLayoutMode::Organic { .. })); } #[test] fn block_grid_is_fully_populated() { let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Regional); - let sk = generate_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42)); + let sk = generate_quarter_skeleton(&ctx, 500_000, "financial", 1, 200, SeedChain::root(42)); // All 16 blocks must have valid positions. for row in 0..4 { for col in 0..4 { @@ -519,7 +525,7 @@ mod tests { fn no_reservations_for_small_city() { let ctx = make_context(PoliticalArchetype::Pioneer, WorldTier::Backwater); // pop_tier 0, not transit_hub → no reservations. - let sk = generate_skeleton(&ctx, 80_000, "residential", 1, 50, SeedChain::root(42)); + let sk = generate_quarter_skeleton(&ctx, 80_000, "residential", 1, 50, SeedChain::root(42)); assert!(sk.reservations.is_empty()); } @@ -527,7 +533,8 @@ mod tests { fn park_reservation_for_large_city() { let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Epicenter); // 100M pop → pop_tier 2 → park reservation. - let sk = generate_skeleton(&ctx, 100_000_000, "financial", 1, 300, SeedChain::root(42)); + let sk = + generate_quarter_skeleton(&ctx, 100_000_000, "financial", 1, 300, SeedChain::root(42)); let has_park = sk .reservations .iter() @@ -539,7 +546,8 @@ mod tests { fn transit_terminal_for_transit_hub_role() { let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Regional); // pop_tier 0 but transit_hub → terminal reservation. - let sk = generate_skeleton(&ctx, 80_000, "transit_hub", 1, 200, SeedChain::root(42)); + let sk = + generate_quarter_skeleton(&ctx, 80_000, "transit_hub", 1, 200, SeedChain::root(42)); let has_terminal = sk .reservations .iter() @@ -551,7 +559,8 @@ mod tests { fn reserved_blocks_linked_in_grid() { let ctx = make_context(PoliticalArchetype::Commission, WorldTier::Epicenter); // 100M pop → park at (1,2),(1,3),(2,2),(2,3) with reservation id 1. - let sk = generate_skeleton(&ctx, 100_000_000, "financial", 1, 300, SeedChain::root(42)); + let sk = + generate_quarter_skeleton(&ctx, 100_000_000, "financial", 1, 300, SeedChain::root(42)); // All park blocks must reference the park reservation (id=1). for &(row, col) in &[(1u8, 2u8), (1, 3), (2, 2), (2, 3)] { let b = &sk.blocks[row as usize][col as usize]; @@ -565,7 +574,7 @@ mod tests { #[test] fn determinism_same_seed_same_output() { let ctx = make_context(PoliticalArchetype::Industrial, WorldTier::Regional); - let sk1 = generate_skeleton( + let sk1 = generate_quarter_skeleton( &ctx, 2_000_000, "manufacturing", @@ -573,7 +582,7 @@ mod tests { 250, SeedChain::root(12345), ); - let sk2 = generate_skeleton( + let sk2 = generate_quarter_skeleton( &ctx, 2_000_000, "manufacturing", diff --git a/server/src/npc/blueprint.rs b/server/src/npc/blueprint.rs index f1494ae5d..42c80d564 100644 --- a/server/src/npc/blueprint.rs +++ b/server/src/npc/blueprint.rs @@ -9,7 +9,7 @@ //! - `NpcBlueprint` — generator output for a single NPC //! //! Plus `SpikeOutput` as the top-level binary output container. -//! These are spike-specific and intentionally decoupled from `DistrictSkeleton`. +//! These are spike-specific and intentionally decoupled from `QuarterSkeleton`. //! //! ## Content contract //! @@ -547,7 +547,7 @@ pub enum RelationshipValence { /// Top-level output for the generator spike binary. /// -/// Intentionally decoupled from `DistrictSkeleton` — this is a proof-of-life +/// Intentionally decoupled from `QuarterSkeleton` — this is a proof-of-life /// container, not production architecture. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SpikeOutput { diff --git a/server/src/simulation/generator.rs b/server/src/simulation/generator.rs index 9c9e95a8f..76f9b5b94 100644 --- a/server/src/simulation/generator.rs +++ b/server/src/simulation/generator.rs @@ -1,6 +1,6 @@ -//! World generator data model — district skeleton and mobile chunk types. +//! World generator data model — quarter skeleton and mobile chunk types. //! -//! Phase 1 generator output: the `DistrictSkeleton` produced by the world generation +//! 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 @@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize}; // --------------------------------------------------------------------------- /// Unique district identifier (content-addressable via hash of world position + seed). -pub type DistrictId = u64; +pub type QuarterId = u64; /// Unique multi-block reservation identifier within a district. pub type ReservationId = u64; /// Unique social site identifier within a district. @@ -55,13 +55,13 @@ 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; +pub type QuarterContext = 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; +pub type QuarterBoundaries = String; /// Guarantee audit result — tier-appropriate spatial invariant checks. Stub. pub type GuaranteeAuditResult = String; /// District access point (entry/exit to neighboring district). Stub. @@ -939,17 +939,17 @@ pub enum ProductionUbiquity { MonopolySource, } -/// District-level world state produced by the plan phase (D-230). +/// Quarter-level world state produced by the plan phase (D-230). /// /// Output of `GenerateSkeleton` extended to include building-property tags. -/// Stored in `BodyWorldState.districts: BTreeMap`. +/// Stored in `BodyWorldState.quarters: BTreeMap`. /// `BTreeMap` for D-010 determinism. /// -/// D-230: `{ skeleton: DistrictSkeleton, block_tags: BTreeMap<(u8,u8), Vec> }` +/// D-230: `{ skeleton: QuarterSkeleton, block_tags: BTreeMap<(u8,u8), Vec> }` #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)] -pub struct DistrictWorldState { +pub struct QuarterWorldState { /// Phase 1 skeleton produced by `GenerateSkeleton` (D-230). - pub skeleton: DistrictSkeleton, + 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). @@ -999,7 +999,7 @@ pub struct CityGenerationContext { /// 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). + /// 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, @@ -1008,8 +1008,8 @@ pub struct BlockPlacement { pub street_width_bps: u16, } -/// Single block within a district's 4×4 block grid. -/// Each block = 128×128 sim tiles = 2×2 chunks. +/// 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). @@ -1160,10 +1160,10 @@ pub struct MultiBlockReservation { } // --------------------------------------------------------------------------- -// District skeleton +// Quarter skeleton // --------------------------------------------------------------------------- -/// Phase 1 generator output for one district. +/// 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 @@ -1171,8 +1171,8 @@ pub struct MultiBlockReservation { /// /// ## 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 +/// - `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. /// @@ -1181,14 +1181,16 @@ pub struct MultiBlockReservation { /// 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 { +pub struct QuarterSkeleton { // ── Identity ────────────────────────────────────────── - pub district_id: DistrictId, + pub quarter_id: QuarterId, /// Deterministic seed (derived from master seed via SeedChain). pub seed: u64, - pub district_type: DistrictType, + /// 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: DistrictContext, + pub context: QuarterContext, // ── Classification ──────────────────────────────────── pub world_tier: WorldTier, @@ -1197,7 +1199,7 @@ pub struct DistrictSkeleton { pub layout_mode: DistrictLayoutMode, // ── Spatial Structure ───────────────────────────────── - /// The 4×4 block grid (each block = 128×128 sim tiles = 2×2 chunks). + /// 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, @@ -1215,7 +1217,7 @@ pub struct DistrictSkeleton { pub zone_palette: Vec, // ── Boundary System ─────────────────────────────────── - pub boundaries: DistrictBoundaries, + pub boundaries: QuarterBoundaries, // ── Validation ──────────────────────────────────────── /// Guarantee audit result. `None` for Empty districts. @@ -1367,9 +1369,9 @@ mod tests { assert_eq!(r.z_levels, 30u8, "z_levels count must remain u8"); } - /// D-110: z_levels (count) remains u8 on DistrictSkeleton. + /// D-110: z_levels (count) remains u8 on QuarterSkeleton. #[test] - fn district_skeleton_z_levels_is_unsigned() { + 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) {}