Files
settled-reach/server/src/simulation/generator.rs
T
jpmschweitzerandClaude Opus 4.6 cae3d3ab85 refactor(simulation): strip archetype trace + HeritageRoot per cascade (#877, #878)
Sprint 37 dead-code sweep closing out two stale supersession chains:

#877 (D-167, 2026-03-24): Removes HeritageRoot type alias and
ZonePaletteModifier::Heritage variant from server/src/simulation/
generator.rs. The 7 abstract heritage roots were retired in favour of
the corridor cultural system; these two stubs were the only remaining
references.

#878 (D-032 + cascade rule): Strips the entire CharacterArchetype
(Smuggler/Detective) trace from the server. Per lead direction
2026-04-21 and the development cascade (CLAUDE.md), character/NPC/
verb-differentiation/monologue code is Phase 6 detail that should
not exist in code yet. The running archetype trace was pre-cascade
filler, not production — production is only the client's character-
creation UI and insert screens (client follow-up in #882).

Deleted:
- CharacterArchetype enum + StartupMessage.character_archetype field
- archetype_verb_label() + archetype branch of apply_phase2_verb_filter
  (D-057 character-verb differentiation — marked superseded)
- MonologueState.character partitioning
- Gauntlet archetype plumbing (setup_gauntlet no longer takes an archetype)
- server/content/schemas/drama_module.schema.yaml (zero Rust consumers)
- server/content/modules/tier1/smuggling_ring_v0_1.yaml
- server/tests/archetype_monologue.rs (regression guard for the removed system)
- server/tests/v01_integration_playthrough.rs (archetype-dependent)

Decision updates:
- decisions/content.md D-032 supersession rewritten to cite the cascade
  (v0.2 drop invalidated the prior D-117 framing).
- decisions/content.md D-035 tag taxonomy: `character` enum footnote
  updated; field noted as unused, do not reintroduce without a
  confirmed Phase 6 design.
- decisions/perception.md D-057: archetype-verb differentiation marked
  superseded.

Also bundles the types.rs version-field removal from #874 since the
file was already touched here.

Full trace audit in docs/architecture/sprint-37-878-audit.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 08:55:48 +02:00

577 lines
21 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! World generator data model — district skeleton and mobile chunk types.
//!
//! Phase 1 generator output: the `DistrictSkeleton` 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
//! represent basements/sub-levels). Z-level *count* fields use `u8` (always ≥ 1).
//! The distinction:
//! - `base_z: i8` — where the bottom floor starts (can be negative)
//! - `z_levels: u8` — how many floors total (always positive)
//!
//! **D-108 (MobileChunk):** Vessel/vehicle interior chunks attached to mobile entities.
//! Uses the same chunk primitives as static districts in a simpler flat structure
//! (no Phase 1/Phase 2 split, template-stamped interiors).
//!
//! Sources: workshop-outcomes.md, tyre-round4.md
use serde::{Deserialize, Serialize};
// ---------------------------------------------------------------------------
// Concept ID type aliases
// ---------------------------------------------------------------------------
/// Unique district identifier (content-addressable via hash of world position + seed).
pub type DistrictId = u64;
/// Unique multi-block reservation identifier within a district.
pub type ReservationId = u64;
/// Unique social site identifier within a district.
pub type SocialSiteId = u64;
/// Unique role slot identifier within a social site.
pub type RoleSlotId = u64;
// ---------------------------------------------------------------------------
// Stub types for concepts not yet implemented (defined here to allow
// the data model to compile; will be replaced with proper types when
// the respective systems are implemented)
// ---------------------------------------------------------------------------
/// Biome classification for wilderness districts. Stub — full taxonomy deferred.
pub type Biome = String;
/// Water body sub-classification. Stub — full taxonomy deferred.
pub type WaterType = String;
/// Specialized district function (e.g. military, medical, penal). Stub.
pub type SpecializedFunction = String;
/// Cultural / architectural era tag. Stub.
pub type Era = String;
/// Single era modification applied to a block (conversion, overlay, hybrid). Stub.
pub type EraModification = String;
/// Landmark slot description within a block. Stub.
pub type LandmarkSlot = String;
/// Chunk layout specification within a block (how the 2×2 chunks are arranged). Stub.
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;
/// 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;
/// Guarantee audit result — tier-appropriate spatial invariant checks. Stub.
pub type GuaranteeAuditResult = String;
/// District access point (entry/exit to neighboring district). Stub.
pub type AccessPoint = String;
/// Base visual palette for a zone. Stub.
pub type BasePalette = String;
/// Economic modifier on a zone palette. Stub.
pub type EconomicModifier = String;
/// Faction presence modifier on a zone palette. Stub.
pub type FactionModifier = String;
/// Condition modifier on a zone palette (worn, pristine, damaged). Stub.
pub type ConditionModifier = String;
/// Season modifier (affects palette and ambient conditions). Stub.
pub type Season = String;
/// Role slot within a social site template. Stub.
pub type RoleSlot = String;
/// Day phase (morning, afternoon, evening, night, late-night). Stub.
pub type DayPhase = String;
/// Triangle template reference (links to server/content/templates/). Stub.
pub type TriangleTemplate = String;
/// Raw chunk tile data for generator use (64×64 bool grid, true = walkable). Stub.
pub type GeneratorChunkData = Vec<bool>;
/// Tile type identifier (references visual tile set). Stub.
pub type TileId = String;
/// Entity identifier for mutation causality tracking. Stub.
pub type EntityId = u64;
/// Action identifier for player-caused mutations. Stub.
pub type ActionId = String;
/// Object instance identifier within a chunk. Stub.
pub type ObjectId = u64;
/// Placed object (item, furniture, fixture) within a chunk. Stub.
pub type PlacedObject = String;
// ---------------------------------------------------------------------------
// World and complexity tier enums
// ---------------------------------------------------------------------------
/// Network importance of a world in the galaxy.
/// Determines simulation fidelity budget and NPC complexity ceiling.
///
/// Source: tyre-round4.md §2.1, workshop-outcomes.md
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum WorldTier {
/// Background system — minimal simulation, sparse NPCs. Pure environmental.
Peripheral,
/// Standard Settled Reach system — full simulation, complex social sites.
Connected,
/// Major hub — maximum fidelity, multi-faction politics, all triangle types.
Core,
}
/// Generator content budget for a district.
/// Derived from WorldTier + SettingType at Phase 1.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum ComplexityTier {
/// Full social architecture. All Tier 1+2 guarantees. 20-80+ NPCs.
Full,
/// Moderate social architecture. Tier 1 + partial Tier 2. 8-20 NPCs.
Moderate,
/// Minimal social architecture. Tier 1 only. 1-8 NPCs.
Minimal,
/// No social architecture. Pure terrain. 0 NPCs. No guarantees.
Empty,
}
/// Physical setting type for a district.
/// Merged from Gestalt's SettingGeometry and Tyre's TerrainType.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum SettingType {
Station,
Urban,
Agricultural,
Maritime,
Wilderness { biome: Biome },
Water { water_type: WaterType },
Transitional,
Orbital,
Specialized { function: SpecializedFunction },
}
/// Classification of a district (high-level function).
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum DistrictType {
LogisticsHub,
Residential,
Commercial,
Industrial,
Administrative,
Entertainment,
MixedUse,
Transit,
Specialized,
}
/// How blocks are placed within the district's 512×512 footprint.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum DistrictLayoutMode {
/// Standard Cartesian grid — perpendicular streets.
Grid,
/// Organic placement with per-block offsets and rotations.
Organic {
placements: [[BlockPlacement; 4]; 4],
},
}
/// Zoning classification for a block or floor zone.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum ZoningType {
Commercial,
Residential,
Industrial,
Administrative,
Transit,
Recreational,
Restricted,
Mixed,
}
/// Reservation function — what purpose a multi-block reservation serves.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum ReservationFunction {
Skyscraper,
Park,
Terminal,
Plaza,
Monument,
IndustrialComplex,
MilitaryBase,
ResearchFacility,
UndergroundComplex,
}
/// Access tier — who is allowed into a zone under normal circumstances.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum AccessTier {
/// Anyone can enter.
Public,
/// Requires employment or residence credential.
Credentialed,
/// Requires explicit invitation or escort.
Restricted,
/// Law enforcement / military only.
Secured,
/// Breach access only — normally sealed.
BreachOnly,
}
/// Vertical corridor type (how floors connect in a multi-level reservation).
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum VerticalCorridorType {
Stairwell,
Elevator,
FreightLift,
AccessHatch,
EmergencyLadder,
}
/// Load state for a z-level within a reservation.
/// Controls lazy loading of floor data during play.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum ZLevelLoadState {
/// Floor is fully generated and in memory.
Loaded(GeneratorChunkData),
/// Floor skeleton only — full data not yet generated.
Skeleton(FloorZone),
/// Floor not yet generated (deferred to Phase 2 on first entry).
Ungenerated,
}
/// Cause of a chunk mutation (for save/load audit trail).
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum MutationCause {
Explosion { radius: u8, source: EntityId },
Fire { spread_from: Option<(u16, u16)> },
Construction { builder: EntityId },
Decay { time_since_maintenance: u32 },
PlayerAction { action: ActionId },
}
/// Type of structural change to a region of chunk tiles.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum StructuralChangeType {
WallDestroyed,
FloorCollapsed,
CeilingBreached,
AreaSealed,
WallConstructed,
}
/// Purpose classification for a triangle within a social site.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum TrianglePurpose {
Investigation,
Economic,
Social,
Political,
Tactical,
Mundane,
}
/// What exists on the far side of a wall tile (for destructible geometry).
/// Every wall tile in a generated chunk is tagged with one of these.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum WallBackside {
/// Another room/corridor exists (tiles already generated).
AdjacentSpace,
/// Solid structural material — 1-2 tiles of fill, then another wall.
StructuralFill,
/// Narrow utility gap (1-3 tiles): pipes, conduits, non-navigable.
ServiceVoid,
/// Edge of chunk — adjacent chunk's boundary tiles on the far side.
ChunkBoundary,
/// Faces outside (hull, exterior wall) — breach has catastrophic consequences.
Exterior,
}
/// Era cause — why a block has the era tag it has.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum EraCause {
Original,
CorporateMerger,
EmergencyExtension,
OrganicGrowth,
InstitutionalIncursion,
EconomicDisruption,
CulturalShift,
}
// ---------------------------------------------------------------------------
// Supporting structs
// ---------------------------------------------------------------------------
/// Organic layout placement for a single block.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BlockPlacement {
/// Offset from grid-aligned position (±16 sim tiles per axis max).
pub offset: (i16, i16),
/// Rotation in 15° increments (03, max 45°).
pub rotation_steps: u8,
/// Street width in basis points (10000 = 1.0×, range 750020000 for 0.752.0×).
/// Default 10000 = 4 visual tiles. Uses integer to avoid f32 non-determinism (D-010).
pub street_width_bps: u16,
}
/// Single block within a district's 4×4 block grid.
/// Each block = 128×128 sim tiles = 2×2 chunks.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BlockSkeleton {
/// Grid position (03, 03).
pub position: (u8, u8),
pub zoning: ZoningType,
/// Which multi-block reservation this block belongs to (if any).
pub reservation: Option<ReservationId>,
pub chunk_layout: ChunkLayout,
pub hosted_sites: Vec<SocialSiteId>,
pub era: Era,
pub era_modifications: Vec<EraModification>,
pub era_cause: Option<EraCause>,
/// Build density percentage (0 = open/empty, 100 = fully built-up).
/// Integer to avoid f32 non-determinism (D-010).
pub density_pct: u8,
pub landmark: Option<LandmarkSlot>,
}
/// Floor zone within a multi-level reservation.
///
/// **D-110:** `z_level` is `i8` — negative values represent basements
/// (e.g. `z_level = -1` for a sub-basement). This differs from `z_levels: u8`
/// on the parent reservation which counts total floors (always ≥ 1).
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FloorZone {
/// Absolute z-level of this floor. Negative for sub-ground levels (D-110).
pub z_level: i8,
pub zone_type: ZoningType,
pub zone_palette: ZonePalette,
pub access_tier: AccessTier,
}
/// Vertical connection spec within a multi-level reservation.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct VerticalCorridorSpec {
/// Which blocks this vertical corridor passes through.
pub block_coords: Vec<(u8, u8)>,
/// Which z-bands (0-indexed band indices, not absolute z-levels) this
/// corridor connects. See D-110 for signed z-level coordinate system.
pub z_bands_connected: Vec<u8>,
pub access_tier: AccessTier,
pub corridor_type: VerticalCorridorType,
}
/// Visual palette for a zone — base material + contextual modifiers.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ZonePalette {
pub base: BasePalette,
pub modifiers: Vec<PaletteModifier>,
}
/// Contextual modifier applied on top of a base palette.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum PaletteModifier {
EconomicFunction(EconomicModifier),
Era(Era),
FactionPresence(FactionModifier),
Condition(ConditionModifier),
Season(Season),
}
/// Social site placement within a district.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SocialSitePlacement {
pub site_id: SocialSiteId,
/// Which blocks this site spans.
pub blocks: Vec<(u8, u8)>,
pub template_tag: String,
pub access_tier: AccessTier,
pub triangles: Vec<TriangleAssignment>,
pub role_slots: Vec<RoleSlot>,
pub active_phases: Vec<DayPhase>,
}
/// Triangle assignment within a social site.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TriangleAssignment {
pub template: TriangleTemplate,
pub purposes: Vec<TrianglePurpose>,
pub participants: Vec<RoleSlotId>,
/// Which block provides the primary staging area (optional for multi-block sites).
pub staging_block: Option<(u8, u8)>,
}
/// Mutations applied to an already-generated chunk.
/// Stored alongside the chunk in the save file.
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct ChunkMutations {
pub tile_overrides: Vec<TileOverride>,
pub structural_changes: Vec<StructuralChange>,
pub placed_objects: Vec<PlacedObject>,
pub removed_objects: Vec<ObjectId>,
}
/// Single tile override within a chunk mutation.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TileOverride {
/// (x, y, z) within the chunk (z is relative to chunk base, always ≥ 0).
pub position: (u16, u16, u8),
pub new_tile: TileId,
pub cause: MutationCause,
}
/// Rectangular structural change within a chunk.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct StructuralChange {
pub min: (u16, u16, u8),
pub max: (u16, u16, u8),
pub change_type: StructuralChangeType,
}
// ---------------------------------------------------------------------------
// Multi-block reservation
// ---------------------------------------------------------------------------
/// A reservation spanning multiple blocks within a district.
///
/// Used for skyscrapers, parks, transit terminals, plazas, and any structure
/// that requires more than one 128×128 block.
///
/// **D-110:** `base_z: i8` — the lowest floor of this reservation.
/// Negative values represent sub-ground levels (basements, underground complexes).
/// `z_levels: u8` remains unsigned — it counts total floors (always ≥ 1).
///
/// Example: a building with a 3-level basement at ground + 20 floors above:
/// `base_z: -3, z_levels: 24`
/// A deep mine shaft descending 30 floors below ground:
/// `base_z: -30, z_levels: 30`
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MultiBlockReservation {
/// Which blocks (grid positions) are part of this reservation.
pub blocks: Vec<(u8, u8)>,
pub template_tag: String,
pub function: ReservationFunction,
/// Total number of floors (always ≥ 1, unsigned). D-110.
pub z_levels: u8,
/// Lowest floor's z-level (can be negative for sub-basement). D-110.
pub base_z: i8,
/// Per-floor zone specifications.
pub floor_zones: Vec<FloorZone>,
/// Number of z-bands (logical groupings of floors by zoning).
pub z_band_count: u8,
/// Zone definitions for each z-band.
pub z_band_zones: Vec<ZoneDefinition>,
/// Vertical connection specs (stairs, elevators, ladders).
pub vertical_corridors: Vec<VerticalCorridorSpec>,
pub hosted_sites: Vec<SocialSiteId>,
}
// ---------------------------------------------------------------------------
// District skeleton
// ---------------------------------------------------------------------------
/// Phase 1 generator output for one district.
///
/// Produced by the async background generation pipeline.
/// Consumed by Phase 2 (chunk-by-chunk fill, on demand) and by
/// the production simulation startup path.
///
/// ## 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
/// ground level is always 0. Sub-ground structures use [`MultiBlockReservation`]
/// with a negative `base_z` field.
///
/// ## Determinism (D-010)
///
/// 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)]
pub struct DistrictSkeleton {
// ── Identity ──────────────────────────────────────────
pub district_id: DistrictId,
/// Deterministic seed (derived from master seed via SeedChain).
pub seed: u64,
pub district_type: DistrictType,
/// World context (system, world, neighboring districts).
pub context: DistrictContext,
// ── Classification ────────────────────────────────────
pub world_tier: WorldTier,
pub complexity: ComplexityTier,
pub setting: SettingType,
pub layout_mode: DistrictLayoutMode,
// ── Spatial Structure ─────────────────────────────────
/// The 4×4 block grid (each block = 128×128 sim tiles = 2×2 chunks).
pub blocks: [[BlockSkeleton; 4]; 4],
/// Multi-block reservations (skyscrapers, parks, terminals, plazas).
pub reservations: Vec<MultiBlockReservation>,
/// Corridor spines connecting key access points.
pub corridors: Vec<CorridorSpine>,
/// Number of floor levels in this district (count, unsigned — D-110).
pub z_levels: u8,
// ── Social Structure ──────────────────────────────────
pub social_sites: Vec<SocialSitePlacement>,
pub access_points: Vec<AccessPoint>,
// ── Cultural / World Context ──────────────────────────
pub society_profile: SocietyProfileRef,
pub zone_palette: Vec<ZoneDefinition>,
// ── Boundary System ───────────────────────────────────
pub boundaries: DistrictBoundaries,
// ── Validation ────────────────────────────────────────
/// Guarantee audit result. `None` for Empty districts.
pub guarantee_audit: Option<GuaranteeAuditResult>,
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
/// D-110: FloorZone.z_level must be i8 (signed) to support negative sub-levels.
#[test]
fn floor_zone_z_level_is_signed() {
let fz = FloorZone {
z_level: -2,
zone_type: ZoningType::Restricted,
zone_palette: ZonePalette {
base: String::new(),
modifiers: vec![],
},
access_tier: AccessTier::BreachOnly,
};
assert_eq!(fz.z_level, -2, "sub-basement z_level must round-trip as i8");
}
/// D-110: MultiBlockReservation.base_z must be i8 (signed) for underground buildings.
#[test]
fn reservation_base_z_is_signed() {
let r = MultiBlockReservation {
blocks: vec![(0, 0)],
template_tag: "deep-mine".into(),
function: ReservationFunction::UndergroundComplex,
z_levels: 30,
base_z: -30,
floor_zones: vec![],
z_band_count: 5,
z_band_zones: vec![],
vertical_corridors: vec![],
hosted_sites: vec![],
};
assert_eq!(
r.base_z, -30,
"deep mine base_z must be representable as i8"
);
assert_eq!(r.z_levels, 30u8, "z_levels count must remain u8");
}
/// D-110: z_levels (count) remains u8 on DistrictSkeleton.
#[test]
fn district_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) {}
let z_levels: u8 = 4;
assert_u8(z_levels);
assert!(z_levels > 0, "z_levels is always at least 1");
}
}