feat(simulation): D-232 three-phase trait draw + deviation/swerve system (T-994, T-1003)
T-994 — three-phase draw replacing the flat splitmix64 flavor pick: - Phase 1 body K-draw (trait_draw.rs): hard gates -> soft weights (two-part geographic_sector join, PR #148) -> SeedChain::for_body-seeded weighted draw of K (5/3/1/0 by ComplexityTier), pins count toward K, coverage-aware over a new body-level district-type-mix aggregate threaded into CityGenerationContext at L3->L4 dispatch. - Phase 2 district-dominant pick keyed by the D-243 2048m District cell — settlements sharing a cell independently derive the identical template. Resolved at dispatch, never in FillChunk (T-987 stays pure). - trait_catalog_reader.rs: systems.db reader for trait_templates + atlas_body_trait_bias (D-225 resource pattern), registered in main.rs with graceful degradation. - BlockSkeleton carries district_type; assign_block_tags is a pure lookup. T-1003 — deviation/swerve system (trait_swerve.rs): - ArchitectureFlavorRef is now InVocabulary(u8) | Swerve(tag). - Rare per-building wildcard: foreign-import pool (other corridors + cross_corridor) driven by Epicenter/Passage tier, mixed faction, road-graph degree; heritage-callback pool (own-corridor heritage) driven by isolation, remote tier, founding_age_years. Integer bps, base 100, cap 300 per driver — placeholder constants pending calibration. - Sparsity escape hatch = same mechanism, necessity-triggered at the phase-2 pick (deterministic max-weight over the hard-gate-eligible pool). - New SeedDomains: TraitVocabulary(13) / TraitDistrict(14) / TraitSwerve(15). Governance: D-232 amended (district pinned to the D-243 2048m tier — the D-222/D-243 redefinition was never captured; swerve driver mappings + placeholder rates recorded), D-229 flavor_ref field updated to the enum shape, D-235 amended (T-995 vocabulary ratification note; rides here due to single-file entanglement). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1605,7 +1605,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
- `footprint: TileRect` — integer tile-space rect within the 128-tile block (D-010 integer-only).
|
||||
- `extent: FloorExtent` — floor/basement extent; **resolves Q-104** (below).
|
||||
- `entry_class: BuildingEntryClass` — physical access character: `Public | Commercial | Restricted | BreachOnly`. **Named `BuildingEntryClass`, not "access tier"**, to avoid colliding with D-028's relational dialogue layers. Derived from `zone_type × layout_mode (D-096) × prosperity (D-197)`: Commission-Grid → formal/logged/corporate-or-authority credentials; Organic → social/reputation/unlogged credentials. U-curve degrade: low prosperity on a normally-Commercial zone → `BreachOnly` (derelict).
|
||||
- `flavor_ref: ArchitectureFlavorRef { flavor_index: u8 }` — index into the body's flavor profile (D-232); deterministic `(seed + zone_type) → index`; no rolling-economy read. (The selection mechanism is D-232's weighted `allow`/`block` filter; the tag records the chosen index for Phase-6 to read cold.)
|
||||
- `flavor_ref: ArchitectureFlavorRef` — which trait template characterizes this building (D-232); no rolling-economy read. *(Amended 2026-07-08, T-994/T-1003: now an enum, not a bare index — `InVocabulary(u8)` (index into the body's closed `trait_selection`, resolved via the phase-2 district-dominant pick) `| Swerve(tag)` (the rare out-of-vocabulary deviation draw, or the sparsity escape hatch). The original `(seed + zone_type) → index` mechanism was the pre-three-phase-draw stopgap.)*
|
||||
- `era: ConstructionEra` (`Founding | Established | Modern | Derelict`) + `era_cause: EraCause` — feeds ZonePalette modifier axis C (D-101) and sets the D-217 condition floor. Derived from `founding_age_years + prosperity_baseline + seed`; a body carries mixed-era buildings (founding period anchors the distribution; seed scatters outliers).
|
||||
- `initial_condition: TileCondition` — frozen-amber snapshot from `prosperity_baseline` (D-197/D-217). The rolling condition overlay (D-198) paints *over* this; it never mutates the tag.
|
||||
- **`FloorExtent { base_floor: i8, floor_count: u8, heights: FloorHeightProfile }`** where `FloorHeightProfile = Uniform(u8) | Variable(Vec<u8>)`. **Q-104 resolution (the D-110 ↔ D-227 bridge):** two pure functions — `floor_at_voxel_z(z) -> Option<i8>` and `voxel_range_for_floor(f) -> Option<(i32,i32)>` — map D-110 floor-index addressing onto D-227 physical voxel-z. Default `Uniform(3)` (3 voxels ≈ 3 m/floor, per Jeroen); a cathedral/hangar is `Uniform(10)`; a mixed-use stack is `Variable([5,3,3,3,3])`. The `Variable` branch carries per-floor memory only when floors actually differ.
|
||||
@@ -1660,6 +1660,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
- **Catalog population:** ~25–35 templates at floor, 40–45 target. Core hand-curated (Miri authors cultural meaning + eligibility; Araminta authors the matching `visual_bundle`), then a *bounded* Gemma corpus-distillation pass (one read of all wiki, propose new templates, human-gated) — not per-body generation.
|
||||
- **Channel separation from D-233 (held):** D-233 decides **what** a building is (vocabulary, coverage); D-232 decides **how** it is characterized. Same inputs (e.g. `bulk_class`) used non-conflictingly — D-233 as hard function gate, D-232 as soft cultural weight. They compose at fill.
|
||||
- **Amended 2026-05-31 ([D-237](#d-237) — authored specialization layer):** the template draw now reads an authored `cultural_specialization` (new column on `system_economy`) that **selects/biases the template pool** when a system's cultural character diverges from its corridor baseline. The field carries two value sub-types in one column — *activity/character* values (`agrarian`, `industrial_heritage`, `institutional`, `scholarly`, etc.) and *heritage* values (`scottish`, `vietnamese`, `zulu`, `french_provencal`, etc.); heritage values take precedence where present. This is a Phase-4 correctness fix, not just flavor: without it, a system whose founding heritage diverges from the corridor (e.g. Vietnamese-founded Dài Lộ in the east_reach Korean/Japanese corridor) draws the wrong cultural templates. `NULL` = use the existing corridor-pool algorithm unchanged. Consistent with the held channel separation — `cultural_specialization` is a D-232 cultural-weight input, never a D-233 function gate. Singular landmarks (e.g. Groombridge's GSH within a `financial_hub` district) are expressed via D-222 multi-block reservation + an `atlas_body_trait_bias` hero pin, not the system-level field.
|
||||
- **Amended 2026-07-08 (T-1003 — deviation/swerve system implemented, driver inputs pinned):** the two opposed drivers now map to real fields (`server/src/atlas/trait_swerve.rs`): *foreign-import* scaled up by `WorldTier::Epicenter`/`Passage` (transit), `dominant_faction = "mixed"` (cosmopolitanism), and road/rail-graph node degree (centrality); *heritage-callback* scaled up by road-graph isolation (degree ≤ 1), remote tier (Waypoint/Backwater), and `founding_age_years` bands (conservatism). Rates are integer bps: base **100 bps/building** per driver, hard cap **300 bps** — placeholder constants pending Nigel/Burnelli calibration; a `dist_ly`-percentile remoteness input is deferred until the read-set carries it. The wildcard result is recorded as `ArchitectureFlavorRef::Swerve(tag)` (out-of-vocabulary by construction); pools are hard-gate-eligible only (cultural-only rule held). The sparsity escape hatch is implemented as the same mechanism, necessity-triggered at the phase-2 district-dominant pick (deterministic max-weight, no dice). The passive past-vogue holdover stays on the D-217 condition layer, as decided.
|
||||
- **Amended 2026-07-08 (T-994 — phase-2 "district" pinned to the D-243 tier):** the word "district" in the phase-2 *district-dominant* draw means the **D-243 2 048 m District cell** (4 quarters), **not** the 512 m Quarter — this record was written after D-222 renamed the 512 m unit to Quarter, but the pin was never made explicit and the shipped code had no District-tier representation at all (caught in the 2026-07-07 /whats-next refinement; the "one template per district, applied whole" composition rule reads at 2 048 m). Implementation (T-994): the dominant template per `(DistrictType)` is pre-resolved at L3→L4 dispatch time, seeded by `(SeedChain::for_body, district-cell position, district type)` — so any settlements whose quarters share a District cell independently derive the identical dominant template with no cross-settlement coordination, and `assign_block_tags` is a pure lookup. Phase 1's body K-draw is likewise seeded from `SeedChain::for_body` (never the per-settlement chain), preserving the closed-vocabulary invariant.
|
||||
- **Supersedes (architecture/generator domain only):** **D-104** (`HeritageGrammarOverlay` + per-root data → the catalog + `allow`/`block`), **D-105** (heritage-root→informal-zone lookup → flavor-filtered selection; the three zone *types* survive), **D-101 modifier axis A** (`HeritageRoot` → catalog draw; axes B/C + faction/climate/condition/season unchanged), **D-107** (per-root trauma decay → per-template/condition; the "trauma intensifies culture" principle survives). Also retires the round-2 `atlas_body_culture` / `atlas_body_culture_era` tables.
|
||||
- **Storage:** `trait_templates` (the catalog) + `atlas_body_trait_bias` (sparse, hero bodies). The per-body draw result lives as `trait_selection: Vec<String>` on the skeleton — **re-derivable** from catalog + economics + bias + `SeedChain`, no Gemma in the hot path. `CityGenerationContext` carries `trait_selection` + `morphology_zone` (replacing the round-2 `flavor_profile`; D-199 amend).
|
||||
- **Source-location deferred:** the human-authored *source* home (catalog file + `bias.json`) rides on **Q-107** (wiki → Atlas content-set consolidation). The generator-facing tables are invariant to it, so the fill seam is unblocked regardless.
|
||||
@@ -1713,6 +1715,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
- **Exterior vocabulary** (extends D-228's FloorMaterial axis): `WallMaterial` (stone_cut/stone_rough/fired_brick/clay_render/heavy_timber/pile_timber/timber_frame/reinforced_concrete/corrugated_metal/steel_panel/composite_panel/precision_glass/smart_facade), `RoofForm` (pitched_steep/pitched_shallow/flat/composite_curved/corrugate_shed/dome), `FacadeRhythm` (bay_window/grid_panel/solid_punched/arcade/open_front/blind_wall), `StreetSurface` (cobble/packed_earth/poured_slab/elevated_boardwalk/dock_plank/rail_embedded — derives from district + density + template, the same filter as buildings, for a consistent world). **Color** is template-bounded (cultural palette cue), seed-selected within range — always within the template's register.
|
||||
- **Fallback hierarchy (the incremental-content mechanism, D-232).** Every specific texture/material token declares a **generic parent it degrades to**: `temple_wall_wood` → generic `wood_wall` placeholder until the specific asset ships, then it **upgrades in place**. The *logical* token a building uses is fixed at generation (deterministic, frozen); only its rendered *fidelity* sharpens as themes/textures are patched in. This is where incremental content lives — an asset-resolution concern, not a generation one — so no catalog versioning is needed. (`era_fallback` from the round-2 draft survives only in this generalized form — a fallback chain, not a tech ladder.)
|
||||
- Worked example: a fjord port (template `fjord_maritime` → stone base + steep roof + zero-lot + solid-punched + cobble) and a delta port (template `river_delta` → pile-timber + shallow roof + arcade + boardwalk) share density and zone types yet read as completely different cities.
|
||||
- **Amended 2026-07-07 (T-995 — ObjectTag vocabulary ratified, resolves Q-049):** the `WallMaterial`/`RoofForm`/`FacadeRhythm`/`StreetSurface` example token lists above never shipped. The canonical ObjectTag vocabulary is the shipped 28-template `architecture_trait_catalog.toml` (T-1005) material palette, now formalized as a machine-readable registry at `wiki/economics/object_tag_vocabulary.toml`, importer-validated by `economy_import/traits.py` (V-TT-03 existence, V-TT-04 fallback-graph). The ratified tags, by axis: **wall** (10) — `concrete_wall stone_wall brick_wall rendered_wall stucco_wall timber_wall rammed_earth_wall steel_frame glass_curtain_wall composite_panel`; **roof** (7) — `flat_roof pitched_roof corrugated_roof clay_tile_roof terraced_roof vaulted_roof green_roof`; **facade** (8) — `regular_facade ornamental_facade industrial_glazing arcade_facade shuttered_facade screen_facade colonnade lattice_screen`; **street** (7) — `paved cobble packed_earth canal_way elevated_walkway heavy_haul boardwalk`; plus the four fallback-terminal **generic** placeholders — `generic_wall generic_roof generic_facade generic_street`. Every specific tag's registry entry declares its generic fallback parent directly (the per-template `fallback` maps in the catalog remain illustrative/non-exhaustive documentation, not the validated source).
|
||||
- **Formally retires** Araminta's generator-architecture Round-4 `D-READY-9` ten-root heritage-modifier TOML system — superseded by the D-232 trait-template catalog (body-specific draw, allow/block-bounded, no root taxonomy).
|
||||
- **Rationale:** The template is the differentiator, but it needs a concrete visual vocabulary to act on, filtered consistently across walls, roofs, facades, *and* streets or the world reads incoherent. The "old quarter vs new development" texture comes from **wear + occasional past-vogue holdover** (D-232), not from a material-technology ladder — because in a post-space-travel setting there is no such ladder. The fallback hierarchy lets the logical world be complete and frozen at launch while the art catches up over patches.
|
||||
- **Implementation:** Phase 4+ (the token logic + Atlas-level data); textured render + the bulk of the theme library are Phase 5+ and post-launch, behind the fallback chain. `ObjectTag`/material vocabulary is Miri + Araminta co-maintained.
|
||||
|
||||
Reference in New Issue
Block a user