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:
@@ -850,6 +850,172 @@ pub enum ArchitectureFlavorRef {
|
||||
Swerve(String),
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// D-235 building exterior visual grammar (T-988)
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// The vocabulary below is the RATIFIED ObjectTag palette (T-995, resolving
|
||||
// Q-049) — `wiki/economics/object_tag_vocabulary.toml` — not the illustrative
|
||||
// example lists in D-235's original body text (superseded by the 2026-07-07
|
||||
// amendment). Each axis carries its specific tokens plus one `Generic`
|
||||
// fallback-terminal placeholder (the D-235 asset-resolution degrade target).
|
||||
//
|
||||
// Integer-discriminant, append-only (D-010) — these values round-trip through
|
||||
// `systems.db`-backed derivation today and are candidates for savegame
|
||||
// persistence in Phase 5+ (D-227), so historical discriminants must never be
|
||||
// renumbered, only appended to. Unknown registry strings parse to the axis's
|
||||
// `Generic` variant with a `tracing::warn!` (patch tolerance) — see
|
||||
// `atlas::trait_catalog_reader`.
|
||||
|
||||
/// Wall material token (D-235 `wall` axis, 10 specific + `Generic`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
|
||||
#[repr(u8)]
|
||||
pub enum WallMaterial {
|
||||
ConcreteWall = 0,
|
||||
SteelFrame = 1,
|
||||
BrickWall = 2,
|
||||
RenderedWall = 3,
|
||||
StoneWall = 4,
|
||||
TimberWall = 5,
|
||||
StuccoWall = 6,
|
||||
GlassCurtainWall = 7,
|
||||
CompositePanel = 8,
|
||||
RammedEarthWall = 9,
|
||||
/// Fallback-terminal placeholder (`generic_wall`) — renders until the
|
||||
/// specific asset ships (D-235), and the "unrecognized token" parse target.
|
||||
#[default]
|
||||
Generic = 10,
|
||||
}
|
||||
|
||||
/// Roof form token (D-235 `roof` axis, 7 specific + `Generic`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
|
||||
#[repr(u8)]
|
||||
pub enum RoofForm {
|
||||
FlatRoof = 0,
|
||||
PitchedRoof = 1,
|
||||
CorrugatedRoof = 2,
|
||||
ClayTileRoof = 3,
|
||||
TerracedRoof = 4,
|
||||
VaultedRoof = 5,
|
||||
GreenRoof = 6,
|
||||
/// Fallback-terminal placeholder (`generic_roof`) — see [`WallMaterial::Generic`].
|
||||
#[default]
|
||||
Generic = 7,
|
||||
}
|
||||
|
||||
/// Facade rhythm token (D-235 `facade` axis, 8 specific + `Generic`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
|
||||
#[repr(u8)]
|
||||
pub enum FacadeRhythm {
|
||||
RegularFacade = 0,
|
||||
OrnamentalFacade = 1,
|
||||
IndustrialGlazing = 2,
|
||||
ArcadeFacade = 3,
|
||||
ShutteredFacade = 4,
|
||||
ScreenFacade = 5,
|
||||
Colonnade = 6,
|
||||
LatticeScreen = 7,
|
||||
/// Fallback-terminal placeholder (`generic_facade`) — see [`WallMaterial::Generic`].
|
||||
#[default]
|
||||
Generic = 8,
|
||||
}
|
||||
|
||||
/// Street surface token (D-235 `street` axis, 7 specific + `Generic`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
|
||||
#[repr(u8)]
|
||||
pub enum StreetSurface {
|
||||
Paved = 0,
|
||||
Cobble = 1,
|
||||
PackedEarth = 2,
|
||||
CanalWay = 3,
|
||||
ElevatedWalkway = 4,
|
||||
HeavyHaul = 5,
|
||||
Boardwalk = 6,
|
||||
/// Fallback-terminal placeholder (`generic_street`) — see [`WallMaterial::Generic`].
|
||||
#[default]
|
||||
Generic = 7,
|
||||
}
|
||||
|
||||
/// Building setback tier (D-235 step 3): derived from `BlockSkeleton.density_pct`
|
||||
/// bands — Dense blocks read `ZeroLot`, Frontier blocks read `Campus`. Drives
|
||||
/// the interstitial-space character (`void`/`court`/`garden`/`plaza`/
|
||||
/// `dock_slip`/`market_pad`/`open_lawn`, D-235) a future fill pass reads.
|
||||
///
|
||||
/// The five-tier vocabulary is fixed by D-235 (`zero_lot | tight | standard |
|
||||
/// generous | campus`); the density-band **thresholds** that map onto it are
|
||||
/// **calibration placeholders** (T-988) pending Nigel/Araminta tuning, same
|
||||
/// status as the swerve-rate constants (`trait_swerve`). Declaration order is
|
||||
/// the tier's natural density ordering (tightest → loosest), so `Ord`
|
||||
/// comparisons read correctly; not currently used as a save-critical
|
||||
/// discriminant, so it is not `#[repr(u8)]`-pinned like the axis tokens above.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum SetbackTier {
|
||||
/// Buildings meet the lot line — no yard, shared walls common.
|
||||
ZeroLot,
|
||||
Tight,
|
||||
Standard,
|
||||
Generous,
|
||||
/// Buildings sit well back within generous open grounds.
|
||||
Campus,
|
||||
}
|
||||
|
||||
/// A single sampled point within a D-235 `color_register` HSV band (T-988).
|
||||
///
|
||||
/// Integer-only (D-010, save-critical under D-227): `hue` is centidegrees
|
||||
/// (degrees × 100, 0..36000); `sat`/`val` are basis points (0..10000). Sampled
|
||||
/// once per building at plan time and frozen — never re-derived, no drift.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct HsvColor {
|
||||
pub hue: u32,
|
||||
pub sat: u32,
|
||||
pub val: u32,
|
||||
}
|
||||
|
||||
/// The D-235 visible-form layer over a building's tags (T-988) — the frozen
|
||||
/// output of the 3-step derivation: **(1)** the resolved trait template's
|
||||
/// `visual_bundle` filters each axis's available token set; **(2)** the
|
||||
/// building's `zone_type_id` biases the pick within that filtered set
|
||||
/// (`architecture_zone_bias.toml`, uniform where unauthored); **(3)** the
|
||||
/// block's `density_pct` sets `setback_tier`. `color` is seed-picked uniformly
|
||||
/// within the template's `color_register` band.
|
||||
///
|
||||
/// **Era is deliberately absent** (D-232 reframe, D-235 amendment): the Reach
|
||||
/// has no material-technology ladder, so era is never a material gate — it
|
||||
/// reads as maintenance/wear via the D-217 condition layer instead.
|
||||
///
|
||||
/// Resolved once inside `GenerateSkeleton` (the same plan-time phase as
|
||||
/// `flavor_ref`/`zone_type_id`) and stored frozen on `BuildingPropertyTag`;
|
||||
/// `FillChunk` only reads it (T-987 purity) — see `atlas::trait_exterior`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BuildingExteriorTag {
|
||||
pub wall_material: WallMaterial,
|
||||
pub roof_form: RoofForm,
|
||||
pub facade_rhythm: FacadeRhythm,
|
||||
pub setback_tier: SetbackTier,
|
||||
pub color: HsvColor,
|
||||
pub street_surface: StreetSurface,
|
||||
}
|
||||
|
||||
/// Character of a block's non-roofed remainder — the space between building
|
||||
/// footprints (D-233, T-1097).
|
||||
///
|
||||
/// Bulk-industry blocks (`BulkClass::BulkSolid`/`BulkLiquid`) read this space
|
||||
/// as **built economic infrastructure** (haul roads, ore pads, conveyor runs,
|
||||
/// tank berms) — functional ground the settlement's economy depends on, never
|
||||
/// generic open space. Every other `BulkClass` keeps the pre-T-1097 default:
|
||||
/// informal interstitial space (yards, parks, lots) whose specific character
|
||||
/// (`void`/`court`/`garden`/`plaza`/…) is the separate `SetbackTier`-driven
|
||||
/// axis (D-235, T-988) — the two systems are distinct derivation axes sharing
|
||||
/// the same physical space, not alternatives.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum InterstitialCharacter {
|
||||
/// Generic open space — yards, parks, informal lots.
|
||||
#[default]
|
||||
OpenSpace,
|
||||
/// Built economic infrastructure (D-233) — never open space.
|
||||
OperationsSurface,
|
||||
}
|
||||
|
||||
/// A single building footprint tag — the frozen step-3 output placed on every
|
||||
/// building footprint at plan time (D-229).
|
||||
///
|
||||
@@ -874,6 +1040,9 @@ pub struct BuildingPropertyTag {
|
||||
/// Frozen-amber condition snapshot from `prosperity_baseline_bps` (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,
|
||||
/// D-235 visible-form layer (T-988): wall/roof/facade/street materials,
|
||||
/// setback tier, and color — resolved once here, frozen thereafter.
|
||||
pub exterior: BuildingExteriorTag,
|
||||
/// Doors into / out of this building (D-231). At least one `Main` door.
|
||||
///
|
||||
/// Using `Vec<DoorSpec>` rather than `SmallVec<[DoorSpec; 4]>` for now;
|
||||
@@ -1232,6 +1401,11 @@ pub struct BlockSkeleton {
|
||||
/// Integer to avoid f32 non-determinism (D-010).
|
||||
pub density_pct: u8,
|
||||
pub landmark: Option<LandmarkSlot>,
|
||||
/// Character of this block's non-roofed remainder (D-233, T-1097):
|
||||
/// `OperationsSurface` for bulk-industry blocks, `OpenSpace` otherwise.
|
||||
/// Derived once from `CityGenerationContext.dominant_bulk_class` at the
|
||||
/// same plan-time pass as the rest of this skeleton.
|
||||
pub interstitial_character: InterstitialCharacter,
|
||||
}
|
||||
|
||||
/// Floor zone within a multi-level reservation.
|
||||
|
||||
Reference in New Issue
Block a user