feat(simulation): BuildingExteriorTag grammar + Layer-5 material fill + ops-surface (T-988/T-959/T-1097)
T-988 — BuildingExteriorTag {wall_material, roof_form, facade_rhythm,
setback_tier, color:HsvColor, street_surface}. New trait_exterior.rs:
3-step era-free derivation (visual_bundle filter -> zone-bias weighted
pick -> density->setback), color seed-sampled within the register band.
Resolved at GenerateSkeleton plan time (T-994 precedent), frozen on
BuildingPropertyTag; FillChunk only reads it. Four append-only
integer-discriminant enums (WallMaterial/RoofForm/FacadeRhythm/
StreetSurface), unknown token -> axis Generic + warn. trait_catalog_reader
now parses visual_bundle + the two new tables (OnceLock-cached).
New SeedDomain::TraitExterior (per-axis sub-chains — no cross-field
correlation).
T-959 — FillChunk reads the frozen exterior tag: wall_material/roof_form
onto Wall/Roof shell voxels (FilledChunk.surface_material). Additive,
ShellVoxel untouched, all shell tests pass. (Interstitial-fill-from-setback
split to T-1098 — needs a FillChunk block-metadata + geometry design pass;
nothing consumes FillChunk until Phase 5.)
T-1097 — BlockSkeleton.interstitial_character (OpenSpace|OperationsSurface),
D-233 bulk-driven: bulk-industry blocks' non-roofed remainder tags as built
economic infrastructure, not generic open space.
cargo check --all-targets clean; 1666 lib tests pass; no golden impact
(harnesses top out at CascadeLayer::RoadGraph, unreachable by Layer 4/5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@ use crate::atlas::district_profile::BodyParams;
|
||||
use crate::atlas::heightmap::{load_heightmap_png, GRID_H, GRID_W};
|
||||
use crate::atlas::shell::{fill_chunk, FilledChunk};
|
||||
use crate::atlas::skeleton_gen::{assign_all_block_tags, generate_quarter_skeleton};
|
||||
use crate::atlas::trait_catalog_reader::ExteriorCatalog;
|
||||
use crate::seed::SeedChain;
|
||||
use crate::simulation::generator::{BuildingPropertyTag, CityGenerationContext, QuarterWorldState};
|
||||
|
||||
@@ -113,6 +114,15 @@ pub enum GenWorkItem {
|
||||
economic_role: String,
|
||||
population: i64,
|
||||
founding_age_years: u32,
|
||||
/// D-235 exterior-grammar content (T-988): the trait-template catalog's
|
||||
/// `visual_bundle`s plus the two sibling content tables
|
||||
/// (`architecture_zone_bias`, `color_register_bands`), pre-resolved at
|
||||
/// dispatch time — mirrors `context`'s own pre-resolution rationale, so
|
||||
/// `assign_block_tags` never touches `systems.db` (T-987/D-230 purity).
|
||||
/// A `Vec`/`BTreeMap`-backed struct is a handful of words on the stack
|
||||
/// regardless of its contents' size (same reasoning already documented
|
||||
/// for `FillChunk.block_tags` below), so this needs no `Box`.
|
||||
exterior_catalog: ExteriorCatalog,
|
||||
},
|
||||
/// Derive the building shell for one 64 m chunk of an existing quarter
|
||||
/// (D-230 derive phase, T-987).
|
||||
@@ -431,6 +441,7 @@ fn run_work_item(item: &GenWorkItem) -> GenCompletion {
|
||||
economic_role,
|
||||
population,
|
||||
founding_age_years,
|
||||
exterior_catalog,
|
||||
} => {
|
||||
// Build the Phase 1 skeleton from the pre-resolved D-199 context.
|
||||
// `economic_role`, `population`, and `founding_age_years` are the
|
||||
@@ -445,13 +456,16 @@ fn run_work_item(item: &GenWorkItem) -> GenCompletion {
|
||||
*chain,
|
||||
);
|
||||
// Step-3 building-property tags per footprint (D-229, #957): subdivide
|
||||
// each block into building plots and tag them.
|
||||
// each block into building plots and tag them. `exterior_catalog`
|
||||
// (D-235, T-988) resolves each tag's BuildingExteriorTag in the
|
||||
// same pass.
|
||||
let block_tags = assign_all_block_tags(
|
||||
&skeleton,
|
||||
context,
|
||||
economic_role,
|
||||
*founding_age_years,
|
||||
*chain,
|
||||
exterior_catalog,
|
||||
);
|
||||
GenCompletion::SkeletonGenerated {
|
||||
city_id: *city_id,
|
||||
@@ -591,6 +605,7 @@ mod tests {
|
||||
economic_role: "service_mixed".to_string(),
|
||||
population: 500_000,
|
||||
founding_age_years: 200,
|
||||
exterior_catalog: ExteriorCatalog::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,8 +715,10 @@ mod tests {
|
||||
fn quarter_with_one_building() -> QuarterWorldState {
|
||||
use crate::atlas::tile_condition::TileCondition;
|
||||
use crate::simulation::generator::{
|
||||
ArchitectureFlavorRef, BuildingEntryClass, BuildingPropertyTag, ConstructionEra,
|
||||
EraCause, FloorExtent, FloorHeightProfile, QuarterSkeleton, TileRect, ZoneTypeId,
|
||||
ArchitectureFlavorRef, BuildingEntryClass, BuildingExteriorTag, BuildingPropertyTag,
|
||||
ConstructionEra, EraCause, FacadeRhythm, FloorExtent, FloorHeightProfile, HsvColor,
|
||||
QuarterSkeleton, RoofForm, SetbackTier, StreetSurface, TileRect, WallMaterial,
|
||||
ZoneTypeId,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -721,6 +738,18 @@ mod tests {
|
||||
era: ConstructionEra::Founding,
|
||||
era_cause: EraCause::Original,
|
||||
initial_condition: TileCondition::Intact,
|
||||
exterior: BuildingExteriorTag {
|
||||
wall_material: WallMaterial::Generic,
|
||||
roof_form: RoofForm::Generic,
|
||||
facade_rhythm: FacadeRhythm::Generic,
|
||||
setback_tier: SetbackTier::Standard,
|
||||
color: HsvColor {
|
||||
hue: 0,
|
||||
sat: 0,
|
||||
val: 5_000,
|
||||
},
|
||||
street_surface: StreetSurface::Generic,
|
||||
},
|
||||
doors: Vec::new(),
|
||||
}],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user