feat(simulation): foundation shared types + struct plumbing (#1006)
Define the type-definition layer the Phase-4 fill-seam tickets depend on (D-229/D-230/D-231/D-232/D-233), compiling with stubs/defaults; behavior logic lands in #982-985/#998. - New types in generator.rs: BuildingPropertyTag, FloorExtent + FloorHeightProfile (floor_at_voxel_z/voxel_range_for_floor, resolves Q-104), BuildingEntryClass, ConstructionEra, ZoneTypeId, MorphologyZone, BulkClass(5), ProductionUbiquity, DoorSpec, InteriorDescriptor, DistrictWorldState. - Rename spatial AccessTier -> ZoneAccessTier to free the name for the new per-building BuildingEntryClass. - CityGenerationContext: +morphology_zone, +trait_selection, +dominant_bulk_class, +dominant_production_ubiquity. - BodyWorldState: +districts (DistrictWorldState w/ block_tags). - GenCompletion::SkeletonGenerated carries body_id + DistrictWorldState; plugin handler inserts into BodyWorldState.districts. - Add smallvec as a direct dep (DoorSpec list stays Vec for now, TODO). cargo check --all-targets / clippy clean; 1259 lib tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use bevy_ecs::prelude::Resource;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::simulation::generator::GeographicAttractor;
|
||||
use crate::simulation::generator::{DistrictId, DistrictWorldState, GeographicAttractor};
|
||||
|
||||
/// Simulation tick counter — monotonically increasing u64.
|
||||
pub type SimTick = u64;
|
||||
@@ -71,6 +71,11 @@ pub struct BodyWorldState {
|
||||
pub drainage_basins: Vec<DrainageBasin>,
|
||||
/// Geographic attractors (D-195, D-209). Empty until attractor task completes.
|
||||
pub attractors: Vec<GeographicAttractor>,
|
||||
/// District-level world state, keyed by `DistrictId` (D-230).
|
||||
///
|
||||
/// Populated by `GenCompletion::SkeletonGenerated` after the plan phase
|
||||
/// completes for each city. `BTreeMap` for D-010 determinism.
|
||||
pub districts: BTreeMap<DistrictId, DistrictWorldState>,
|
||||
/// Last sim tick this entry was read. Used for LRU eviction.
|
||||
pub last_accessed: SimTick,
|
||||
}
|
||||
@@ -129,6 +134,14 @@ impl BodyWorldStateCache {
|
||||
self.entries.get(body_id)
|
||||
}
|
||||
|
||||
/// Get a mutable reference without bumping `last_accessed`.
|
||||
///
|
||||
/// Used by the district-state insertion path (D-230) which writes into
|
||||
/// the cached state without constituting a "read" for LRU purposes.
|
||||
pub fn peek_mut(&mut self, body_id: &str) -> Option<&mut BodyWorldState> {
|
||||
self.entries.get_mut(body_id)
|
||||
}
|
||||
|
||||
/// Returns `true` if the cache has an entry for `body_id`.
|
||||
pub fn contains(&self, body_id: &str) -> bool {
|
||||
self.entries.contains_key(body_id)
|
||||
@@ -183,6 +196,7 @@ mod tests {
|
||||
river_network: RiverNetwork::default(),
|
||||
drainage_basins: vec![],
|
||||
attractors: vec![],
|
||||
districts: BTreeMap::new(),
|
||||
last_accessed: tick,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ impl CascadeSnapshot {
|
||||
river_network,
|
||||
drainage_basins,
|
||||
attractors,
|
||||
districts: std::collections::BTreeMap::new(),
|
||||
last_accessed: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ 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::seed::SeedChain;
|
||||
use crate::simulation::generator::DistrictWorldState;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Priority
|
||||
@@ -99,6 +100,11 @@ pub enum GenCompletion {
|
||||
},
|
||||
SkeletonGenerated {
|
||||
city_id: u64,
|
||||
/// The body this skeleton belongs to — used to route state into
|
||||
/// `BodyWorldState.districts` (D-230).
|
||||
body_id: String,
|
||||
/// District-level world state (block tags) produced by the plan phase (D-230).
|
||||
state: DistrictWorldState,
|
||||
},
|
||||
ChunkFilled {
|
||||
district_id: u64,
|
||||
@@ -336,7 +342,13 @@ fn run_work_item(item: &GenWorkItem) -> GenCompletion {
|
||||
},
|
||||
},
|
||||
GenWorkItem::GenerateSkeleton { city_id } => {
|
||||
GenCompletion::SkeletonGenerated { city_id: *city_id }
|
||||
// Stub: real skeleton generation (#957) will populate `body_id` from
|
||||
// the CityGenerationContext and `state` from the plan phase (D-230).
|
||||
GenCompletion::SkeletonGenerated {
|
||||
city_id: *city_id,
|
||||
body_id: String::new(),
|
||||
state: DistrictWorldState::default(),
|
||||
}
|
||||
}
|
||||
GenWorkItem::FillChunk {
|
||||
district_id,
|
||||
|
||||
@@ -200,6 +200,7 @@ mod tests {
|
||||
river_network: RiverNetwork::default(),
|
||||
drainage_basins: vec![],
|
||||
attractors: vec![],
|
||||
districts: std::collections::BTreeMap::new(),
|
||||
last_accessed: 0,
|
||||
});
|
||||
let (_db, resolver) = empty_resolver();
|
||||
|
||||
@@ -80,8 +80,26 @@ fn drain_generation_completions(
|
||||
GenCompletion::Failed { item, reason } => {
|
||||
tracing::warn!(?item, %reason, "background generation work item failed");
|
||||
}
|
||||
// Produced once the later layers land (#957 / #959); no consumer yet.
|
||||
GenCompletion::SkeletonGenerated { .. } | GenCompletion::ChunkFilled { .. } => {}
|
||||
// Insert district world state into the matching body's cache entry (D-230).
|
||||
GenCompletion::SkeletonGenerated {
|
||||
city_id,
|
||||
body_id,
|
||||
state,
|
||||
} => {
|
||||
if !body_id.is_empty() {
|
||||
if let Some(body_state) = cache.peek_mut(&body_id) {
|
||||
body_state.districts.insert(city_id, state);
|
||||
} else {
|
||||
tracing::warn!(
|
||||
city_id,
|
||||
body_id,
|
||||
"SkeletonGenerated: body not in cache — district state dropped"
|
||||
);
|
||||
}
|
||||
}
|
||||
// body_id empty = stub result from GenerateSkeleton stub; silently ignore.
|
||||
}
|
||||
GenCompletion::ChunkFilled { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +411,8 @@ fn derive_reservations(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::simulation::generator::{
|
||||
CityGenerationContext, FoundingOrientation, PoliticalArchetype, SettingType, WorldTier,
|
||||
BulkClass, CityGenerationContext, FoundingOrientation, MorphologyZone, PoliticalArchetype,
|
||||
ProductionUbiquity, SettingType, WorldTier,
|
||||
};
|
||||
|
||||
fn make_context(archetype: PoliticalArchetype, world_tier: WorldTier) -> CityGenerationContext {
|
||||
@@ -424,6 +425,11 @@ mod tests {
|
||||
footprint_radius_km: 10.0,
|
||||
founding_orientation: FoundingOrientation::Cardinal,
|
||||
world_tier,
|
||||
// D-229/D-232/D-233 additions — sensible stubs for existing tests.
|
||||
morphology_zone: MorphologyZone::AlluvialPlain,
|
||||
trait_selection: Vec::new(),
|
||||
dominant_bulk_class: BulkClass::NonPhysical,
|
||||
dominant_production_ubiquity: ProductionUbiquity::Common,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
//!
|
||||
//! Threshold values are authored constants (D-217): 0.63, 0.43, 0.23.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::simulation::generator::EraCause;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -30,7 +32,7 @@ use crate::simulation::generator::EraCause;
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Visual condition band for a tile, derived from prosperity_score (D-217).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum TileCondition {
|
||||
/// prosperity_score > 0.63. Clean, undamaged, well-maintained.
|
||||
Intact,
|
||||
|
||||
Reference in New Issue
Block a user