Files
settled-reach/decisions/architecture.md
T
jpmschweitzerandClaude Opus 4.6 63b7d69535 docs(decisions): add character pipeline decisions from Quaternius spike
D-158: Frontal camera angle for editor/mugshot UI
D-159: Character body type enum (4 adult × 2 genders + 1 child)
D-160: Body meshes segmented into 15 bone-group regions
D-161: Head always separate mesh on Head bone
D-162: Clothing pre-baked per body type via Blender Surface Deform
D-163: Trellis generates unique heads via BoneAttachment3D
D-164: Fork Quaternius skeleton, replace all body meshes

Q-060: Surface Deform quality at extreme body types
Q-061: Base clothing mesh count for v0.2
Q-062: Quaternius Source tier body type coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 13:28:35 +01:00

87 KiB
Raw Blame History

Architecture Decisions

Technical foundation decisions that constrain implementation: engine, client-server, ECS, simulation, testability, performance budgets, audio architecture.


D-008: Action pillar design principles

  • Date: 2026-02-08
  • Decision: The action/combat system follows these principles:
    • Rimworld/XCOM hybrid - simple mechanics, stats-driven. Simple gets complicated fast on its own.
    • Z-levels - floors, verticality. Not 3D rendering, but 3D space. Fights across floors of a building.
    • Perception-bounded - you see/hear what your character can. Rest is abstracted/occluded. Same principle as information asymmetry applied to physical space.
    • LOD/occlusion - simulation reduces outside player view. Both a design principle and performance optimization.
    • Large, varied maps - locations should feel big. Achieved through procedural generation (templates + procedural flesh), trickery, or both. Procedural layouts also feed replayability.
    • Multiple maps - different locations are separate maps. Step through a wormhole, load a new place.
    • Wildly asymmetric encounters - balanced fights are the exception. Power mismatches in both directions are the norm and the source of tension.
    • Hubris wall as design principle - let the player feel powerful, then recontextualize their power level. Not cheap difficulty spikes - genuine "you were playing a smaller game than you thought" moments. The books are not always balanced; the fun comes when a character hits their hubris wall.
    • Death = information loss, not game over - memory cell backup system means death costs you everything since your last backup. Lost knowledge, lost relationships, lost deals. The storyteller knows what you've lost and can exploit it.
    • Scales with ascension - baseline human fights with weapons, Higher fights with biononics, ANA-connected fights with something else entirely. The pillar transforms as the character evolves.
  • Design references: Rimworld (real-time, simple systems, personal stakes), XCOM (tactical asymmetry, pod activation moments), Dwarf Fortress (z-levels, simulation depth)
  • Raised by: Team Leader (Jeroen), with input from full team.

D-009: Multiplayer - design for it, build single-player first

  • Date: 2026-02-08
  • Decision: Single-player is the build target. Multiplayer is designed into the architecture from day one so it can be added without rewriting the game.
  • Rationale: Two players experiencing the same conspiracy from different keyholes (Senate insider + Guardian operative) is a killer feature. But building multiplayer too early kills projects. The compromise: architectural decisions now that make multiplayer a "add networking" problem later, not a "rewrite everything" problem. Team Leader flagged that bolting multiplayer on after the fact is one of the hardest things to do - so the architecture must be honest about this from the start.
  • Cost: ~15-20% slower single-player development due to required abstractions. Accepted as cheap insurance.
  • Raised by: Full team discussion. Tyre led technical framing, Team Leader insisted on architectural honesty.

D-010: Multiplayer-ready architectural baseline

  • Date: 2026-02-08
  • Decision: Four non-negotiable architectural principles that must be present from the first line of code:
    1. Client-server separation - even in single-player. The game simulation runs as a "server," the player view is a "client." Single-player = local client + local server. This is the single decision that makes or breaks retrofitting multiplayer.
    2. Information boundaries as a first-class system - every piece of game state is tagged with who knows it. Not fog-of-war bolted on - the engine fundamentally thinks in terms of "what does this observer have access to." Required for single-player asymmetric information anyway. Multiplayer just means multiple observers.
    3. No baking player identity into the game loop - the simulation doesn't know there's "the player." It knows there are characters, some of which are player-controlled. Adding a second player-controlled character should be a configuration change, not a rewrite.
    4. Deterministic simulation with input events - game state advances based on timestamped actions, not "whatever the local machine calculated." Enables synchronization later without rewriting the simulation.
  • Side benefits (Nigel's observation): Every one of these makes single-player better too. Information boundaries make NPC AI smarter about what they know. Client-server makes save/load cleaner. Deterministic simulation makes debugging easier. No sacrifice.
  • Engine implication: Client-server friendliness is now a hard requirement on the engine shortlist (see Q-001).
  • Raised by: Tyre (Technical Architect), endorsed by Team Leader as "sound architectural baseline."

D-012: Chunk-based map architecture for future borderless generation

  • Date: 2026-02-09
  • Decision: Maps use chunk-based generation and loading from day one. Chunks load/unload around the player. A bounded map is "only generate chunks within this boundary." Removing the boundary later to enable Minecraft-style borderless generation is a configuration change, not a rewrite.
  • v0.1: Bounded ~150x150 per world, 2-3 z-levels, chunk-based internally.
  • Future: Borderless generation. The world generates as you explore. The map can never be "solved" by walking to every corner. New areas develop, existing areas change.
  • Rationale: Same principle as D-010 (multiplayer architecture) - design for the future, build the simpler version now.
  • Raised by: Tyre (Technical Architect), endorsed by Team Leader.

D-020: Engine and architecture selection — Godot client + Rust simulation via subprocess/IPC

  • Date: 2026-02-09
  • Decision: The game uses a split architecture: Godot 4 (GDScript) as the rendering client, Rust with bevy_ecs standalone as the simulation server. The two communicate via subprocess/IPC (local socket for single-player, TCP for multiplayer). NOT via GDExtension.
  • Architecture:
    • The Rust simulation is a standalone binary with zero Godot dependencies. It runs the ECS world, perception queries, AI, storyteller, combat — all game logic.
    • The Godot client is a pure renderer: receives ObserverSnapshot data, draws tiles/sprites/fog, plays audio, shows UI, captures input. No game logic in GDScript.
    • Single-player: Godot launches the Rust binary as a child process. Local Unix socket or localhost TCP.
    • Multiplayer: Godot connects to a remote Rust server. Same protocol. The simulation binary doesn't know the difference.
    • This IS the D-010 client-server architecture — literally, not simulated.
  • Serialization:
    • MessagePack for all client-facing communication (Rust↔Godot). Dynamic structure supports variable HUD composition driven by perception modes (D-017). Cross-language, debuggable.
    • bincode reserved for future Rust↔Rust server-to-server sync (same binary, hot path, zero overhead).
    • protobuf rejected — solves deployment/versioning problems we don't have, poor GDScript support.
  • Why subprocess over GDExtension:
    • Eliminates entire risk categories: gdext pre-1.0 API churn, Godot version ABI breakage, FFI thread safety (Gd<T> is !Send), cross-boundary memory management.
    • Decouples learning: build and test Rust simulation standalone, build Godot renderer standalone, connect when both work.
    • Maps directly to D-010 client-server with no simulation — it IS client-server from day one.
    • Either side can be upgraded, replaced, or scaled independently.
    • Cost: ~1-5ms serialization latency per tick. Acceptable for a detective/strategy game, not a twitch shooter.
  • Key patterns:
    • ObserverSnapshot: the only data structure crossing the boundary. Contains visible entities, fog state, sound events, monologue triggers, HUD widget data. Variable shape per character build.
    • PlayerInput: semantic actions (MoveNorth, Interact, UsePerceptionMode), not raw key events. Timestamped for deterministic processing.
    • SimBridge trait: abstracts transport. LocalBridge (subprocess, channels) and NetworkBridge (TCP, MessagePack) implement the same interface.
  • Protocol versioning policy: PROTOCOL_VERSION gates wire format compatibility — field names, types, message structure. Bump when deserialization would fail (added/removed/renamed fields, new enum variants, changed types). Do NOT bump for gameplay parameter changes that affect what data flows through the same format (vision cone angles, NPC behavior, map layout, balance tuning). Per-character variation (D-015 cone config, D-017 perception modes) means these parameters differ between entities on the same server simultaneously — they are game state, not protocol. The client renders whatever ObserverSnapshot the server sends; it has no awareness of cone angles or perception mode configuration.
  • Kill switch: If no working prototype (character + fog + one NPC) exists by week 8 of development, pivot to pure Godot. If bridge/sync code exceeds game logic for 3 consecutive sprints, the architecture tax is too high.
  • Development sequence:
    1. Build Rust simulation as standalone binary (testable via terminal/logs)
    2. Build Godot renderer as standalone project (hardcoded test data)
    3. Connect via MessagePack protocol
  • Evaluation reports: docs/architecture/eval-godot-rust-bridge.md (Tyre), docs/architecture/risk-godot-rust-bridge.md (Troblum)
  • Raised by: Team Leader (Jeroen) proposed Godot client + Rust backend. Tyre designed architecture. Troblum's risk assessment shifted integration from GDExtension to subprocess/IPC. Full team endorsed.
  • Dissent: None. Troblum's CRITICAL risk flags on GDExtension were accepted; subprocess approach addresses them.

D-026: Simulation tiers with timestamp-based eviction

  • Date: 2026-02-10
  • Decision: Four simulation tiers: Active (30-80 NPCs, full sim at 10-20 ticks/sec), Background (500-2,000 NPCs, state machine ticks 1/game-minute with 4 machines: schedule, mood, relationships, job), State-saved (10,000+, frozen serialized structs ~1-2KB each), Ungenerated (doesn't exist yet). Eviction uses interaction-timestamp LRU against available sim-space. NPCs with active scope tags (neighborhood, active-quest, colleague, known-contact) stay fully simulated. State-saved NPCs reactivate on player return (~2-5ms). Density follows the player — content generated ahead of arrival, home system fully instantiated at game start.
  • Rationale: Timestamp-based eviction replaces categorical persistence rules with one priority queue. State-save makes disposal reversible. bevy_ecs dynamic component add/remove makes tier transitions seamless. Tyre confirmed all proposals fit within performance budgets.
  • Cross-reference: Content density implications in D-029.
  • Raised by: Team Leader (timestamp model), Tyre (technical validation), Gestalt (scope tags)
  • Dissent: None

D-030: Testability architecture — 8 decisions for ticket #214

  • Date: 2026-02-11
  • Decision: The v0.1 testability architecture is confirmed with 8 sub-decisions from the Gap Analysis Workshop (Round 18):
    1. Rust test organization = Hybrid. #[cfg(test)] for unit tests inside modules + tests/ directory for integration tests. Both via cargo nextest run.
    2. Godot test framework = gdUnit4 (changed from GUT). Native JSON output, stable headless via GdUnitCmdTool, GdUnitSceneRunner for scene lifecycle tests, organizational maintenance.
    3. IPC testing = Three-layer architecture. Layer 1: fixture-based serialization roundtrip (fast, every edit). Layer 2: mock subprocess protocol state machine (medium, every PR). Layer 3: real subprocess integration (slow, daily/pre-merge).
    4. Production code constraints + CauseChain. No #[cfg(test)] in production. Public API is the test surface. ECS World setup replaces mock injection. CauseChain is a production component (monologue provenance, journal, debugging) that tests also leverage.
    5. Test runner tooling. cargo-nextest (Rust) + gdUnit4 (Godot) + bash wrapper scripts in test/ directory, whitelistable for agent use.
    6. Test output format = JSON summary. Consistent schema across all runners (suite, total, passed, failed, failures array). JUnit XML as secondary CI format.
    7. #201 (Deterministic replay) promoted to CRITICAL. Simulation must consume time, randomness, and input exclusively through injectable resources (SimulationTime, SimRng, InputQueue). Required by D-010 principle 4.
    8. Test priority aligned with hard blockers. Phase 1 (sprint 1-2): test infra + collision/pathfinding/time. Phase 2 (sprint 3-4): monologue pipeline integration test + information boundary negative tests. Phase 3 (sprint 5+): CauseChain verification + divergent snapshots.
  • Rationale: Two rounds of analysis by Tyre (Technical Architect) and Hoshe (QA Engineer) with cross-validation from all design agents. Key change: gdUnit4 over GUT driven by agent-driven development requirements (JSON output, headless stability, bus factor). CauseChain endorsed unanimously after all design agents independently identified the need for information provenance tracking.
  • Raised by: Tyre (architecture), Hoshe (testability analysis). Full workshop endorsed.
  • Dissent: GUT vs gdUnit4 resolved in Hoshe's favor — Tyre explicitly changed position. No remaining dissent.

D-031: Time system — game clock and day phases

  • Date: 2026-02-11
  • Decision: The v0.1 time system uses the following model:
    • Tick-to-time mapping: 10 simulation ticks = 1 game-minute (at 10 tps, 1 real second = 1 game-minute). A 30-minute real-time play session covers ~12-18 game-hours — enough for a full NPC daily cycle.
    • Day phases: Four phases drive routine transitions: Morning, Afternoon, Evening, Night. NPCs transition between routine activities at phase boundaries (e.g., go to work in Morning, to the bar in Evening).
    • Time display: Diegetic — shown on the player's neural insert HUD. The character checks their insert to see the time, consistent with D-013.
    • Pause: Available in single-player. Simulation freezes, UI stays responsive. Compatible with future multiplayer (D-009) where pause would be disabled or vote-based.
    • Time-skip: Deferred for v0.1. The "wait/stake out" mechanic (if implemented) would advance time while the player observes from a fixed position.
  • Not in scope for v0.1: Deep time (years/decades), day/night lighting, seasonal cycles, time zones between locations.
  • Resolves: Q-009
  • Raised by: Tyre (technical proposal), Gestalt (day-phase design). Confirmed in Round 18 Gap Analysis Workshop with full team consensus.
  • Dissent: None.

D-041: Knowledge Graph Data Model

  • Date: 2026-02-11
  • Decision: The knowledge graph is a per-entity bevy_ecs Component with BTreeMap storage for deterministic iteration. Each entity that has knowledge (player character, Active-tier NPCs, Background-tier NPCs) gets a KnowledgeGraph component containing: (1) entity knowledge map: BTreeMap<StableId, EntityKnowledge>, (2) fact knowledge map: BTreeMap<FactId, FactKnowledge>. Knowledge confidence uses a 4-level hierarchy: Suspects < KnowsOf < KnowsDetails < Direct. Knowledge state tracks temporal/logical status: Active (believed true), Contradicted (conflicting information exists), Stale (aged beyond threshold). Knowledge source provides provenance per entry: DirectObservation, Heard, ToldBy, Inferred, Background. Stable entity IDs (StableId(u64)) replace bevy_ecs Entity handles in knowledge references, mapped via EntityRegistry resource for bidirectional StableId <-> Entity lookup. Knowledge updates flow through event-driven architecture: perception systems emit KnowledgeEvent to KnowledgeEventQueue resource, knowledge update system drains queue and writes to KnowledgeGraph components. Basic decay runs once per game-minute (every 10 ticks per D-031), downgrading confidence levels based on last_observed_tick age against configurable DecayThresholds.
  • Sprint 2 scope: Full data structures + direct observation flow + basic decay + observer snapshot integration (#112). Deferred to Sprint 3+: NPC-to-NPC gossip, ToldBy/Inferred source generation, Contradicted state detection, Stale state logic, knowledge-driven dialogue filtering, monologue triggering, misinformation.
  • Canonical reference: Full Rust struct definitions at docs/workshops/knowledge-graph-information-boundaries/round2-synthesis.md Part 3 (lines 320-752). All implementation must conform to those types.
  • Key design choices:
    • BTreeMap over HashMap: D-010 principle 4 (deterministic simulation) requires stable iteration order. BTreeMap provides O(log N) lookups (~6-8 comparisons at N=50-200), deterministic serialization, and predictable replay behavior. HashMap iteration is non-deterministic and incompatible with deterministic replay (D-030 sub-decision #7).
    • Per-entity Component, not centralized Resource: Enables Changed<KnowledgeGraph> dirty tracking, per-entity serialization for D-026 tier transitions, no shared mutable state, natural ECS query patterns.
    • 4-level confidence hierarchy: Resolves Q-016. Suspects = "something's off", gates initial investigation. KnowsOf = "X is involved in Y", gates topic-specific dialogue and peer-tier access (D-028). KnowsDetails = actionable detail, gates confrontation and secret-tier dialogue. Direct = currently in LOS, provides live position data and maximum rendering fidelity. Maps to D-028 access tiers: surface available at any level, real at KnowsOf+, secret at KnowsDetails+.
    • KnowledgeState for contradiction detection: THE FRIEND arc (D-034, D-039 wow moment #3) requires detecting when a ToldBy entry conflicts with a DirectObservation entry. Both entries receive Contradicted state, triggering monologue event and relationship state shift (PersonOfInterest). Sprint 2 only uses Active state; contradiction detection ships Sprint 3.
    • StableId for knowledge references: Partially resolves Q-019 for server-side and knowledge graph purposes. Knowledge graphs reference StableId(u64) that persists across save/load cycles, not bevy_ecs Entity (generational index). EntityRegistry maintains bidirectional mapping. Assigned once at entity creation, never changes. Client-side mapping (Godot StableId -> scene node) remains open.
    • Event-driven updates: Phase 2 (perception) emits events. Phase 3 (knowledge) consumes events and writes graphs. Phase 4 (snapshot) reads graphs. Prevents mutable borrow conflicts in bevy_ecs.
  • Performance budget: ~14 KB per NPC knowledge graph (50 entities + 20 facts). Active tier (80 NPCs) = ~1.1 MB. Background tier (2,000 NPCs, 10 entries each) = ~5 MB. Total live memory: ~6 MB. Knowledge lookups are O(log N) at N=50 (~100ns per query). Not on critical path (shadowcasting/spatial queries consume 10-20ms per tick, knowledge operations <3ms).
  • Resolves: Q-016 (knowledge hierarchy). Partially resolves Q-019 (entity ID stability, server-side).
  • Blocks: #352 (Observer Snapshot Pipeline Workshop)
  • Raised by: Tyre (architecture synthesis), Dudley (implementation analysis), Gestalt (mechanics validation), Paula (narrative requirements). Workshop participants: Tyre, Dudley, Gestalt, Paula, SI. Source: Knowledge Graph & Information Boundaries Workshop (Epic #351), 2026-02-11.
  • Dissent: None. Gestalt initially proposed dual-axis model (confidence + understanding) but validated that single-axis confidence with future content-driven understanding progression is architecturally sufficient. Dudley proposed HashMap; synthesis chose BTreeMap per D-010 principle 4 with Dudley's explicit acknowledgment ("iteration order is a determinism time bomb").

D-042: UI microcopy format — YAML via GDScript autoload

  • Date: 2026-02-13
  • Decision: UI strings (interaction prompt labels, knowledge panel labels, relationship state descriptors, HUD labels, tutorial text) are stored in YAML format at client/data/ui-strings.yaml and loaded via a dedicated GDScript autoload singleton (UIStrings). UI strings are NOT hardcoded as GDScript constants in client/scripts/constants/ui_strings.gd.
  • Rationale: YAML format enables editing UI text without rebuilding the client and supports future localization infrastructure (all player-facing text in one format). UI microcopy is client-side rendering data per D-020 (Godot is the renderer) — distinct from server-side game content (dialogue/monologue lines). UI labels are presentation metadata that never cross the protocol boundary, so they live in the client repository and load via a client-side autoload rather than the content loader system. Hardcoded constants would require client recompilation for copy edits.
  • Related ticket: #409 (UI microcopy)
  • Raised by: Team decision in Sprint 5 planning
  • Dissent: None

D-054: Tile-based movement with same-tile occupancy

  • Date: 2026-02-13
  • Decision: All movement is tile-based (server-authoritative, discrete positions). Client-side Tween interpolation (100-150ms) hides the grid visually. Same-tile occupancy via TilePresence component (Standing/Prone/Seated/Fixture layers) allows multiple entities on one tile in different postures. Mouse facing is a client-side float; the server receives the facing octant only. Tile occupancy provides trivial collision detection.
  • Rationale: Determinism (D-010 principle 4). Tile-based enables shadowcasting (D-035), pathfinding, chunk-based maps (D-012), and trivial collision. Occupancy system adds positioning depth (doorway blocking, eavesdrop positioning, sitting at furniture) within tile-based constraints. ~150 lines server-side.
  • Implementation: TilePresence enum: Standing, Prone, Seated, Fixture. Multiple entities can share a tile if they occupy different posture layers.
  • Cross-reference: Stance system (D-053), shadowcasting (D-035)
  • Source: Control & Interaction Workshop (2026-02-13)
  • Raised by: Tyre (tile-based, non-negotiable), Dudley (tiles-per-tick model), Nigel (converted in Round 2: "tiles are BETTER for replayability — discrete positions = finite meaningful choices")
  • Dissent: Nigel initially proposed free movement with tile-based collision (Round 1). Converted in Round 2 after demonstrating that tile-based spatial puzzles (doorway decisions, corner peeks, eavesdrop corridors) create replayability.

D-055: Sprint explicitly suppresses interaction buffer

  • Date: 2026-02-13
  • Decision: When in Sprint stance (D-053), the server explicitly clears the interaction buffer. No interaction verbs are computed or sent to the client during sprint. Anomaly monologue survives sprint — the "sprint double-take" (if the character passes something anomalous while sprinting, a delayed monologue fires retroactively: "Wait — was that Kael? At this hour?").
  • Rationale: Mouse gymnastics to click during sprint = bad UX. Explicit suppression is cleaner and deterministic. The sprint double-take preserves the feel that the character is still aware even when the player can't interact — sprint suppresses interpretation (monologue at 40%), not sensory data (overlays still render).
  • Cross-reference: Stance system (D-053), monologue (D-016)
  • Source: Control & Interaction Workshop (2026-02-13)
  • Raised by: Dudley (explicit suppression), Ozzie (anomaly survival / double-take), Gestalt (interpretation vs data framing)
  • Dissent: Gestalt argued physics handles it naturally (player passes through interaction radius too fast to click). Lead ruled explicit suppression for clarity and determinism.

D-066: Dual-scale grid — 0.5m simulation, 1m visual (2x retina factor)

  • Date: 2026-02-14
  • Decision: The game uses two coordinate scales with a fixed 2x retina factor:
    • Simulation grid: 0.5m tiles. All movement, LOS/shadowcasting (D-035), pathfinding, occupancy (D-054), and interaction range operate at 0.5m per sim tile. The server knows only sim tiles.
    • Visual grid: 1m tiles. The Godot client renders floor art, wall art, and structural tiles as 2x2 blocks of sim tiles. Art is authored at 1m conceptual scale.
    • World geometry: 2x2 sim tile minimum. All walls, furniture, crates, doors, and environmental objects occupy a minimum of 2x2 sim tiles (= 1 visual tile). This ensures visual truth and sim truth agree on where solid things are — cover, LOS occlusion, and collision map 1:1 with what the player sees.
    • Entities: 1x1 sim tiles. Characters and small items occupy individual 0.5m sim tiles, giving sub-visual-tile positioning precision. Entities naturally take corners/edges within a visual tile's 1m space.
    • Sprites: 2x2 sim tile footprint. Entity sprites render across 2x2 sim tiles so they feel proportional to the 1m visual grid. Tween interpolation (D-054) hides half-visual-tile movement increments.
  • Mental model: "Objects are where they look. I can position myself precisely within open space." The player reads cover and walls at visual scale (always correct). Fine movement granularity is felt, not counted.
  • What the simulation does NOT know: Visual tiles. The retina factor is purely a client rendering convention. The server operates exclusively on 0.5m sim tiles.
  • Fog shader (D-059): Unaffected — fog is screen-space, driven by PointLight2D vision cone and LOS mask from sim-resolution shadowcasting. Gradient edge "3-4 tiles" is retuned to 6-8 sim tiles (= 3-4 visual tiles) to preserve the intended softness.
  • Cursor/interaction: No change — cursor already resolves to sim tile from pixel position. Interaction range of ~2 sim tiles = 1m (arm's length).
  • Map authoring: Author at 1m visual scale. Subdivision tool expands each visual tile to 4 sim tiles (2x2). Validation enforces 2x2 minimum on all world geometry layers.
  • Amends: OQ-01 resolution (ticket #444). Sim tile size remains 0.5m; visual presentation changes from 1:1 to 2:1 retina factor.
  • Cross-reference: Tile-based movement (D-054), shadowcasting (D-035), fog (D-059), art direction (D-043), z-stack (D-049), stance system (D-053)
  • Rationale: 0.5m sim tiles give stealth-grade granularity for movement stances, cover peeking, and interaction range. 1m visual tiles make spaces feel proportional, sprites look right, and world geometry readable. The 2x2 minimum on geometry eliminates visual/sim mismatch for cover and LOS — the only sub-visual-tile positioning is entity movement, which is communicated through fog feedback, not tile counting. Analogous to macOS Retina: logical resolution (visual) differs from physical resolution (sim), but the system is coherent because both agree on where solid objects are.
  • Raised by: Team Leader (Jeroen) — proposed retina scaling analogy and 2x2 geometry constraint. Tyre (feasibility: trivial, half-day integration). Gestalt (approved with 2x2 constraint resolving LOS readability concern). Ozzie (approved: solves sprite scale without uncanny mismatch).
  • Dissent: Gestalt initially objected to dual-scale (mental model mismatch for cover/LOS). Resolved by the 2x2 geometry minimum constraint — all cover maps 1:1 at visual scale.

D-068: 5-bus audio architecture

  • Date: 2026-02-16
  • Decision: Audio uses a 5-bus architecture with player-facing volume sliders:
    1. Music — future/empty in v0.1. Reserved for diegetic Meridian music in social spaces.
    2. Ambient — station hum (D-038 asset 1) + zone overlays (assets 2-4). Continuous soundscape.
    3. World SFX — NPC footsteps, doors, environmental events. Diegetic world sounds not caused by player.
    4. Player Actions — player footsteps (assets 5-6), future: combat sounds, item interactions. Sounds player directly causes.
    5. UI Sounds — cursor hover, implant open, monologue chimes (assets 7-8), fog recognition. Interface feedback.
  • Client implementation:
    • AudioManager GDScript autoload singleton on client branch (lives with rendering, per D-020).
    • Audio assets committed to audio branch (content, not code).
    • Directory-scan registry pattern: AudioManager scans res://audio/ on startup, maps filenames to AudioStream resources. No hardcoded asset list.
    • If directory empty or asset missing, all play methods no-op with visual fallback (per D-038 architecture).
    • 5 player-facing volume sliders, one per bus. Accessible via settings.
  • Bus routing dropped:
    • Dialogue bus removed — no voice acting in v0.1. NPC conversation murmur goes on World SFX (event-driven, per D-072).
  • Rationale: 5 buses provide player control granularity (disable UI sounds, boost World SFX for eavesdropping, mute Ambient for focus) without over-segmentation. Directory-scan registry eliminates hardcoded asset paths — audio branch can add files without touching client code. No-op fallback means client works identically with or without audio.
  • Cross-reference: Audio assets (D-038), audio dip (D-069), client-server architecture (D-020)
  • Raised by: Team Leader (channel split directive), Tyre (architecture), Inigo and Gestalt (bus refinement)
  • Dissent: None

D-073: Zone crossfade approach — hard boundary, soft audio transition

  • Date: 2026-02-16
  • Decision: Zone audio transitions use hard tile boundary triggers with 1.5-2s audio crossfade tweens. Server sends zone_id per tile in ObserverSnapshot (server-authoritative zone assignment). AudioManager receives zone changes and tweens between ambient layers. No blended overlap zones — the transition smoothness comes from audio fade duration, not spatial blending.
  • Implementation: AudioManager stub in Sprint 7 (5-bus setup, directory registry). Full zone crossfade implementation deferred to Sprint 8+.
  • Rationale: Hard boundaries with soft audio = predictable for simulation, pleasant for player. Avoids complex overlap zone geometry. Crossfade duration (1.5-2s) is long enough to feel smooth, short enough that walking back-and-forth across boundary doesn't create audio chaos.
  • Cross-reference: Audio architecture (D-068), client-server (D-020), ambient assets (D-038)
  • Raised by: Tyre
  • Dissent: None

D-085: Per-game save directory structure

  • Date: 2026-02-25
  • Decision: Every new game creates a dedicated directory under the user save path. All saves for that game (manual, quicksave, autosave) live inside the game's directory. Directory name includes a human-readable game identifier and creation timestamp.
  • Rationale: Natively groups saves by game without requiring a database or index file. Players can browse, back up, or delete game saves at the filesystem level. Avoids a flat save folder where 50+ files from different games are interleaved.
  • Structure: user://saves/<game-id>/ where <game-id> is <timestamp>-<seed> (e.g., 20260225-143022-a7b3f1/). Inside: quicksave.sav, autosave.sav, manual_001.sav, etc.
  • Constraints:
    • Game directory created on "New Game" — even before the first save, so the path exists for quicksave/autosave.
    • F5 = quicksave (overwrites quicksave.sav in the active game dir).
    • F6 = quickload (loads quicksave.sav from the active game dir).
    • Loading screen lists game directories sorted by last-modified, shows most recent save per game.
  • Raised by: Team Leader (Jeroen)
  • Dissent: None

D-088: 3-state pause system — Normal/Overlay/Paused, server-authoritative

  • Date: 2026-02-12
  • Decision: Simulation runs at three speed states: Normal (100% tick rate), Overlay (50% — active during knowledge panel, dialogue, map view), Paused (0% — full pause via Esc). Server is authoritative: client sends pause requests, server sets sim_speed field in ObserverSnapshot. Client reads sim_speed and adjusts presentation. No client-side tick manipulation.
  • Rationale: Server-authoritative speed states preserve D-010 principle 4 (deterministic simulation). Client cannot modify simulation state directly. Overlay mode at 50% ensures UI interactions do not require a hard pause while still giving the player time to read and decide.
  • Raised by: Tyre, Dudley
  • Dissent: None
  • Source: v0.1 Content Scoping Workshop, closing round resolution
  • Cross-reference: D-031 (time system), D-020 (client-server architecture)

D-094: District Spatial Hierarchy — Chunk, Block, District Naming and Sizes

  • Date: 2026-02-25
  • Decision: The spatial hierarchy for map generation and streaming is defined as follows. Chunk = 64×64 sim tiles (32×32 visual tiles, 32m) — the streaming and serialization unit. Block = 128×128 sim tiles (64×64 visual tiles, 64m) — the generator planning unit, composed of 4 chunks arranged in a 2×2 grid. Each block contains 4 chunks; chunks within a block can merge into one large edifice, remain separate (small buildings, gardens, cafes), or form L-shaped buildings across chunk boundaries. District = 4×4 blocks = 512×512 sim tiles (256×256 visual tiles, 256m) per z-level, containing 16 blocks and 64 chunks. Large civic structures (gate terminals, horizon station installations, stadiums, parks) span multiple blocks. Three z-levels for the Transit District = ~1.35MB (trivial). This decision amends D-012 and overrides the ~150×150 visual estimate in D-014.
  • Rationale: Chunk size of 32×32 visual (64×64 sim) gives a 32m streaming cell — large enough to hold a meaningful space, small enough for efficient streaming. The 2×2-chunk block provides a generator planning unit with enough granularity for per-chunk variation. The 4×4 block district (256×256 visual) gives a full district footprint generalisable as a template for the Q-036 generator. The chunk-based fill system within blocks allows the generator to place buildings of varying scale without hard-coding building dimensions.
  • Raised by: Tyre (chunk/block spec and memory confirmation), confirmed by team. Lead ratified district = 4×4 blocks.
  • Dissent: Araminta preferred 32×32 visual chunk size (effectively halving the chunk to a 16m cell). Overruled by lead and team majority — 32m chunk is the minimum viable streaming cell for the simulation architecture.
  • Source: Station District Layout Workshop, Ticket #153, Sprint 20. Round document: docs/discussions/round-20-station-district-layout.md
  • Cross-reference: D-012 (tile spec — amended), D-014 (v0.1 map spec — district bounding box superseded), D-066 (dual-scale grid), D-093 (Sova Transit District layout using this hierarchy), Q-036 (district generator)

D-096: DistrictLayoutMode — Grid and Organic Support

  • Date: 2026-02-27
  • Decision: Two layout modes coexist for district generation. Grid: Commission-planned districts with rectilinear block placement. Organic: pioneer/growth districts with block offsets (±16 sim tiles per axis), rotation (03 steps, 15° increments), variable street width (0.752.0×). Hard technical ceiling: maximum rotation ±45°. Beyond 45°, tile-based pathfinding produces unacceptable movement artifacts. Organic districts produce curved-street impressions through angular jogs and irregular setbacks. Grid vs. Organic proportions must vary per seed to prevent predictable meta-level patterns.
  • Rationale: Grid = power imposed (Commission-planned). Organic = power negotiated (pioneer settlements, organic growth). Both modes encode political and settlement history in spatial form.
  • Source: Generator Architecture Workshop (#562), 2026-02-27. Full spec: docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-1.
  • Raised by: Tyre (technical architecture), Miri (cultural grammar). Full team sign-off.
  • Dissent: None.
  • Cross-reference: D-094 (spatial hierarchy), Q-036 (district generator)

D-097: Guarantee Tier System — Universal / Full-Only / Conditional

  • Date: 2026-02-27
  • Decision: The district generator runs a GuaranteeAuditResult with three tiers of spatial guarantees. Tier 1 — Universal (all inhabited): Social Hub, Informal Zone, Encounter Corridor. Tier 2 — Full-complexity: Traffic Chokepoint, Institutional Space, Insider Space, Economic Node, Horizon View Corridor (coastal), BreachOnly Zone (≥1), Rooftop Discovery Zone (tall structures). Tier 3 — Conditional: A-1 Elevated Vantage, A-2 Egress Multiplicity, A-3 Temporal Opacity Window, A-4 Non-Institutional Route, Economic Asymmetry Signal, Power Gradient Visibility. A Full-complexity coastal urban hub gets up to 13 checks. Archetype placement must vary in angular position (not just distance) across seeds — audit fails if archetypes cluster predictably across a test batch of N seeds.
  • Rationale: The generator makes contracts it keeps. Guaranteed affordances ensure every playstyle has spatial affordances in any district, without hand-crafting each location.
  • Source: Generator Architecture Workshop (#562), 2026-02-27. Full spec: docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-2.
  • Raised by: Gestalt (tier structure + assassin lens integration), Tyre (GuaranteeAuditResult struct). Full team sign-off.
  • Dissent: None.
  • Cross-reference: D-103 (assassin lens guarantees A-1 through A-4), D-102 (horizon view corridor — Tier 2 coastal)

D-099: WallBackside / TileBehindState — Dual Classification

  • Date: 2026-02-27
  • Decision: Two complementary enums classify tiles behind wall surfaces. WallBackside (structural): what is physically there — AdjacentSpace | StructuralFill | ServiceVoid | ChunkBoundary | Exterior. TileBehindState (gameplay): what kind of space this represents — StructuralFill | HiddenRoom | Interstitial. Mapping: ServiceVoid → Interstitial; AdjacentSpace → HiddenRoom or StructuralFill depending on access tier. Era-tagged infrastructure cavity contents with standardized color codes: Era 1 power conduit only (#c8b840), Era 2 power + water/coolant (#4888c8) + comm lines (#b8b8b8), Era 3 full bundle. Backside assignments within a template must have seed-driven variation — not fixed template values.
  • Rationale: "Every wall is a secret keeper." No tile is ever void. Dual classification separates structural truth (what's there physically) from gameplay meaning (what does this imply for the player's investigation).
  • Source: Generator Architecture Workshop (#562), 2026-02-27. docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-4.
  • Raised by: Tyre (WallBackside), Gestalt (TileBehindState). Full team sign-off.
  • Dissent: None.

D-100: Dynamic Modification via Overlay — DamageOverlay and RegenerationStrategy

  • Date: 2026-02-27
  • Decision: Generator output is immutable after Phase 1. All post-generation modifications are applied via overlay, not re-generation. DamageOverlay struct: overlay_type (GasExplosion | Fire | Structural { collapse_direction } | Flooding), epicenter: ChunkLocalPos, radius: f32, intensity: f32, scatter_seed: u64 (variation within zone only). RegenerationStrategy enum: LocalOverlay(DamageParameters) for in-playthrough events (MANDATORY), SoftReseed { seed_modifier: u64 } at scenario boundaries only, FullReseed at era-level discontinuities only. Trauma event → visual stage mapping: PhysicalDestruction/ViolenceEvent → Stage 2 (Fresh Aftermath), decays to Stage 3; EconomicDisruption/PoliticalShock/MigrationShock → quarter fill modifier. Full stage sequence: Stage 1 Active → Stage 2 Fresh Aftermath → Stage 3 Stabilized → Stage 4 Reconstruction → Stage 5 Healed Scar. Destruction palette is corruption-only: no new colors introduced by destruction. Single exception: #c8d8f0 open-sky tile appears when a roofed structure has its roof removed. See D-109 for the XOR prohibition as architectural mandate.
  • Rationale: Modification history diverges per playthrough on the same seed. Same world, different event histories, different delta layers — this is the replayability engine. Causal legibility requires the player to be able to read what happened from the world state.
  • Source: Generator Architecture Workshop (#562), 2026-02-27. docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-5.
  • Raised by: Tyre (structs), Gestalt (LocalOverlay mandate). Destruction stages and palette constraint: Araminta (Round 5).
  • Dissent: None.
  • Cross-reference: D-109 (XOR prohibition as architectural mandate), D-107 (trauma events — cultural track)

D-101: ZonePalette Modifier System

  • Date: 2026-02-27
  • Decision: Zone palettes use ZonePalette { base: BasePalette, modifiers: Vec<PaletteModifier> }. Eight canonical base terrain types: T1 temperate farmland (warm organic, natural lighting) / T2 industrial farmland (cool grey-green, artificial lighting) / T3 wilderness / T4 grassland / T5 coastal water (deep near-black blue, animated specular; referenced by D-102 horizon corridor guarantee) / T6 beach/coastal margin (warm dark tan) / T7 mountain/high terrain (dark blue-grey stone, snow at elevation) / T8 desert/arid. T1 and T2 are explicitly distinct farmland types. Additional terrain types must be specified with new numbers — not silent replacements for existing types. Modifier axes: A (heritage root → material character), B (economic tier → condition/density), C (era → material generation), plus faction overlay, climate, condition, season. Palette modifiers influence NPC appearance as well as environment (people dress like they're from here).
  • Rationale: A zone's visual identity must be legible at a glance. Palette modifiers create cultural visual identity without rewriting base terrain.
  • Source: Generator Architecture Workshop (#562), 2026-02-27. docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-6.
  • Raised by: Araminta (terrain types and color specs, canonical T5/T7 numbering corrected Round 5), Tyre (palette struct). Full team sign-off.
  • Dissent: None.
  • Cross-reference: D-102 (horizon view corridor — T5 coastal water is the referenced terrain type), D-104 (heritage grammar overlay — modifier axis A)

D-102: Horizon View Corridor as Coastal Guarantee

  • Date: 2026-02-27
  • Decision: A negative-space reservation for coastal districts: ≥8 visual tiles unobstructed view corridor from nearest public street to water's edge. No building, tree, or z=4 element may occupy this corridor. A low z=2 element (railing, bench, bollard) marks the waterfront point as a designed viewing location. Tier 2 Conditional guarantee — applies to Full-complexity coastal districts. Position within the district must vary per seed; the Wow Moment of seeing the horizon must be discovered, not expected.
  • Rationale: "Negative-space reservation" framing — the generator reserves space by prohibiting placement, not by placing something. The view of the horizon is a spatially guaranteed player experience.
  • Source: Generator Architecture Workshop (#562), 2026-02-27. docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-7.
  • Raised by: Araminta (visual grammar and negative-space framing), Tyre (implementation constraint). Full team sign-off.
  • Dissent: None.
  • Cross-reference: D-097 (guarantee tier system — Tier 2), D-101 (ZonePalette — T5 coastal water is the terrain type this guarantee references)

D-103: Assassin Lens Spatial Guarantees — A-1 through A-4

  • Date: 2026-02-27
  • Decision: Four derived spatial properties validated by the guarantee audit for Full-complexity districts. These are derived properties of existing spatial configuration, not assassin-tagged features — they add no generation cost; the audit validates existing output. A-1 Elevated Vantage (Tier 3): ≥1 position with clear LOS cone to Traffic Chokepoint. A-2 Egress Multiplicity (Tier 3): ≥2 exit routes to adjacent districts. A-3 Temporal Opacity Window (Tier 3): ≥1 time window where Social Hub has reduced ambient NPC coverage. A-4 Non-Institutional Route (mandatory Full-complexity): ≥1 route to any Insider zone not passing through high-security institutional spaces. A-1/A-2/A-3 are Tier 3 Conditional (trigger on complexity_tier == Full). A-4 is mandatory for all Full-complexity districts regardless of playstyle.
  • Rationale: The investigator/assassin playstyle needs guaranteed affordances without the generator explicitly building for assassination. Derived properties keep generation cost zero while ensuring spatial conditions exist.
  • Source: Generator Architecture Workshop (#562), 2026-02-27. docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-8.
  • Raised by: Gestalt (assassin lens framing and derived-properties insight). Full team sign-off.
  • Dissent: None.
  • Cross-reference: D-097 (guarantee tier system — Tier 3)

D-106: Vertical Scale Architecture and Rooftop Bar Clause

  • Date: 2026-02-27
  • Decision: Four height tiers: S1 (12 z-levels, surface + roof/mezzanine), S2 (310), S3 (1130), S4 (30+). Shadow length is the primary height signal in top-down view (240 visual tiles). Lazy z-level loading: ZLevelLoadState: Loaded | Skeleton | Ungenerated — only current + adjacent z-levels filled by Phase 2. Rooftop Bar Clause: Every tall structure (z_band_count ≥ 3) must assign RooftopConfig: Restricted | PublicWithHiddenLayer. Discovery layer mandatory in both configurations. Heritage root weights the probability between the two configs — it does not determine the outcome. Final config is seeded per-building; a minority of buildings of any heritage root may be the non-dominant type (a Frost building with a rooftop bar must be possible). Z-band floor boundaries must have seed-variation within cultural ordering constraints. Vertical access routes are playthrough-history dependent.
  • Rationale: Height has meaning — floor 30 has information floor 1 cannot have because it is harder to reach. Full determination of rooftop config by heritage root kills the discovery moment.
  • Source: Generator Architecture Workshop (#562), 2026-02-27. docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-11.
  • Raised by: Tyre (z-level architecture), Ozzie (Rooftop Bar Clause — discovery guarantee). Ozzie + Araminta corrected "determines" → "weights probability" in Round 5.
  • Dissent: None.
  • Cross-reference: D-094 (spatial hierarchy), D-097 (guarantee tier system — Rooftop Discovery Zone is Tier 2)

D-108: MobileChunk Specification

  • Date: 2026-02-27
  • Decision: Entity-carried interior space attached to a mobile world entity. Not a district — uses the same chunk fill primitives in a simpler flat structure (no Phase 1/Phase 2 split, no block grid, no zone negotiation). Key structs: MobileChunk, MobileInterior, VesselClass, MobileMovementState (Docked / InTransit / InterSystem / Idle), TransitSocialModifier, MobileNpcSlot, NpcPersistence (Crew | Passenger). Idle = vessel parked at a location but not docked to infrastructure (anchored ship, grounded shuttle). Vessels are persistent world entities — interior cache keyed by entity_id persists across voyages for crew state. Docked state requires dock_position, connected_chunk: Option<ChunkCoord>, docked_since: SimTick, scheduled_departure: Option<SimTick>. scheduled_departure must be populated by the generator; vessels without departure schedules are an error state. Cultural grammar: TransitSocialModifier with TransitVariant (BoundedLinear | BoundedMobile | InterSystem). Vessel visual grammar (5 rules): (1) hull uses vessel-identity material, not zone palette; (2) windows reveal exterior context (docked vs. transit); (3) compression modifier tightens proportions; (4) section transitions use vessel-identity threshold elements; (5) class stratification via proportion, not palette. Replayability requirements R-V-1 through R-V-6 in docs/workshops/generator-architecture/round-4-notes.md §5. Memory: ~0.54KB metadata + up to 64KB ChunkData per vessel; paged by streaming model.
  • Rationale: "The journey is content — mobile environments are social pressure cookers, not loading screens with chairs." Vessel persistence and crew state continuity make the world feel real.
  • Source: Generator Architecture Workshop (#562), 2026-02-27. docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-13.
  • Raised by: Tyre (struct design), Miri (cultural grammar — miri-round4.md), Nigel (replayability requirements), Ozzie (player experience). Visual grammar: Araminta (araminta-round4.md §2).
  • Dissent: Nigel initially proposed instanced districts for vessels; lead ruled entity-carried MobileChunk for persistence.
  • Note: The Idle movement state is the canonical primitive for player-owned stationary installations (space stations, orbital platforms, parked vessels as permanent bases). A MobileChunk in Idle with no scheduled_departure is architecturally equivalent to a static chunk from the simulation's perspective — it participates in the same tile/zone system. This design prevents future over-engineering of a separate 'player installation' system.
  • Cross-reference: D-100 (DamageOverlay applies to vessel damage), D-109 (LocalOverlay mandate), D-111 (MobileChunk Idle state as stationary installation primitive), Q-046 (departure schedule — resolved by this D-record)

D-109: DamageOverlay / RegenerationStrategy Prohibition — Architectural Mandate

  • Date: 2026-02-27
  • Decision: XOR reseeding for in-playthrough events is architecturally prohibited. LocalOverlay is the mandatory modification strategy for all events that occur while the player is present. SoftReseed and FullReseed are permitted only at scenario-boundary and era-level discontinuities respectively — events the player was not present for, where causal legibility is not required. This prohibition is filed as a separate D-record from D-100 because it establishes the modification principle for the entire game, not just the overlay mechanics.
  • Rationale: Causal legibility: the player must be able to look at a damaged district and understand what happened. XOR reseeding destroys the causal thread. Unanimous consensus across all workshop participants — the strongest architectural agreement of the entire workshop.
  • Source: Generator Architecture Workshop (#562), 2026-02-27. docs/workshops/generator-architecture/workshop-outcomes.md §D-READY-14.
  • Raised by: Gestalt (XOR prohibition framing), Tyre (RegenerationStrategy struct). Unanimous.
  • Dissent: None.
  • Cross-reference: D-100 (DamageOverlay + RegenerationStrategy full specification)

D-110: Signed Z-Level Addressing — base_z u8 → i8

  • Date: 2026-02-27
  • Decision: All z-level base fields use signed integers (i8) instead of unsigned (u8). Specifically: MultiBlockReservation.base_z: i8, FloorZone.z_level: i8, and any struct that references a z-level position (not a count). Z-level counts (z_levels: u8 on DistrictSkeleton) remain unsigned — they represent "how many floors", which is always positive. The distinction: base_z is "where does the bottom floor start" (can be negative for basements/subterranean spaces), z_levels is "how many floors total" (always ≥1).
  • Rationale: The Round 4 workshop designs (D-106, D-108) and the gestalt gas explosion example explicitly assume negative z-levels (basements at z=-1 to z=-3, utility sub-levels). The existing u8 type contradicts the design intent. A deep mine is structurally an inverted skyscraper with base_z: -30, z_levels: 30. Lazy z-level loading (ZLevelLoadState) works identically for negative z — the loading system cares about relative offsets between adjacent levels, not the sign.
  • Source: Tyre architectural analysis, 2026-02-27. Confirmed existing Round 4 design intent (D-106 §height tiers, Gestalt Round 4 §gas explosion, Araminta Round 2 §infrastructure routing).
  • Raised by: Tyre (type-level fix), lead (edge case prompt: basements, deep mines).
  • Dissent: None.
  • Cross-reference: D-106 (vertical scale architecture), D-094 (spatial hierarchy), D-108 (MobileChunk)

D-111: MobileChunk Idle State Covers Stationary Player Installations

  • Date: 2026-02-27
  • Decision: The MobileChunk Idle movement state (D-108) is the canonical primitive for player-owned stationary installations — space stations, orbital platforms, parked vessels used as bases, or any persistent interior space the player controls that is not part of the district chunk grid. A stationary installation is a MobileChunk that does not move: it uses the same streaming, save/load, NPC simulation, and LocalOverlay modification system as vessels. Player construction within a MobileChunk (building rooms, placing equipment) requires the DLC construction system to emit valid LocalOverlay modifications — the architectural pattern is ready, the construction system is DLC scope.
  • Rationale: A separate "location instance" system for player bases would require a new coordinate system, new streaming/loading path, new save/load path, portal/transition logic, and duplicate pathfinding/perception/simulation — Tier 4 difficulty for zero benefit over existing primitives. The Idle state already exists in D-108; documenting its design intent for stationary installations costs nothing and prevents future over-engineering.
  • Source: Tyre architectural analysis, 2026-02-27.
  • Raised by: Tyre (architectural mapping), lead (edge case prompt: player bases, fortresses).
  • Dissent: None.
  • Cross-reference: D-108 (MobileChunk specification — Idle state), D-100/D-109 (LocalOverlay for modifications)

D-112: No Separate Location Instancing System

  • Date: 2026-02-27
  • Decision: The game does not use instanced locations (separate spatial domains outside the world chunk grid). All subterranean spaces, player bases, and special locations are represented using existing primitives:
    • Basements / sub-levels: MultiBlockReservation with negative base_z (D-110)
    • Deep mines (shaft-style): Downward MultiBlockReservation, lazy-loaded via ZLevelLoadState
    • Deep mines (cave network): Organic-mode district (D-096) with mine-specific template
    • Player base in existing building: LocalOverlay modifications (D-100/D-109)
    • Player base as hidden bunker: MultiBlockReservation with negative z, generated at world-gen
    • Player-built space: LocalOverlay + construction system (DLC scope)
    • Stationary installation: MobileChunk in Idle state (D-111)
    • Vessel interior: MobileChunk (D-108)
  • Rationale: Instanced locations would require: (1) new coordinate system for instanced space, (2) new streaming/loading path, (3) new save/load path, (4) portal/transition logic between world space and instanced space, (5) duplicate pathfinding, perception, and simulation tier logic. Every edge case maps cleanly to an existing primitive. Nigel's vessel instancing proposal was already ruled out in favor of entity-carried MobileChunk (D-108 dissent note); the same reasoning extends to all "separate space" cases.
  • DLC/Mod implication: LocalOverlay is the universal post-generation modification layer. DLC quest locations, mod-injected dungeons, and scenario-specific spaces can be delivered as overlay packages applied to existing districts under strict conditions — without touching the generator or reseeding. The generator output remains sacred (D-109); overlays are how the world changes after generation. This makes the overlay system the canonical content injection point for all post-generation content, whether player-driven, storyteller-driven, or DLC/mod-driven.
  • Source: Tyre architectural analysis, 2026-02-27.
  • Raised by: Tyre (comprehensive edge case mapping), lead (edge case prompt: deep mines, player bases, fortresses).
  • Dissent: None.
  • Cross-reference: D-110 (signed z-levels), D-111 (MobileChunk Idle for installations), D-108 (MobileChunk), D-096 (organic layout), D-100/D-109 (LocalOverlay)

D-113: Tile data model — extensible per-tile properties

  • Date: 2026-03-05
  • Decision: Replace the current single-character tile encoding (F/W/V/R strings in location YAML) with a tile palette/registry system (option A from the design space). Tiles are typed by a palette ID; per-type properties are defined once in the palette and inherited by all tiles of that type. Per-tile overrides are supported via a sparse overlay map.
  • Current state: Tiles are single characters in string arrays. Each character maps to a TileKind enum (Floor, Wall, Door, Object) and a walkability bool. TileCell in WalkabilityMap stores { walkable: bool, kind: TileKind }. No per-tile properties (material, visual variant, sound, access lists, container contents, damage state, trigger zones) can be expressed.
  • Design survey — what systems need tile-level data:
    1. Doors — access lists (who can open), open/closed state, locked/unlocked. Currently no tile-level door data; TileKind::Door exists but carries no properties.
    2. Containers — contents, capacity, searched state. Currently handled by entity ObjectType::Container on separate entities, not tiles. Containers should remain entities, not tile properties.
    3. Damage stateDamageOverlay (D-100) modifies tiles post-generation. Damage needs to degrade tile properties (walkability, visual, material) without replacing the base tile type.
    4. Visual variants — same logical tile type (e.g., "industrial floor") with per-tile visual variation for visual richness. Currently impossible — all Floor tiles look identical to the client.
    5. Trigger zones — tile-level triggers for entry/exit events (zone transitions, alarms, dialogue triggers). Currently handled by ZoneMap at zone granularity, not per-tile.
    6. Material properties — footstep sound, movement speed modifier, surface type for particle effects. Currently all tiles produce the same footstep sound.
    7. WallBackside (D-099) — structural classification behind wall surfaces. Already defined as an enum but not yet integrated into tile data.
  • Chosen approach — Tile Palette + Sparse Override:
    • Tile palette (YAML, per-district or global): defines tile types by string ID. Each type specifies: walkable: bool, kind: TileKind, material: String (footstep/SFX), visual_base: String (client sprite), visual_variants: u8 (random variant count), los_blocking: bool, movement_cost: f32 (default 1.0), optional wall_backside: WallBackside (D-099). The palette is the type-level contract — most tiles need no per-instance data beyond their palette ID.
    • Tile map (YAML): retains the string-array format for human readability, but each character is a palette key (single char or short code). Backward-compatible: F, W, V, R are reserved palette keys that map to current behavior. New tile types use additional characters or a separate palette layer.
    • Sparse override map (YAML): overrides key on Location — a list of { x, y, properties } entries for tiles that differ from their palette type. Supports: door access lists, initial locked state, visual variant pinning, damage overlay data. Only tiles with non-default properties need entries. Keeps the string map clean for 90%+ of tiles.
    • Runtime representation:
      • TilePalette resource: BTreeMap<char, TileType> loaded at startup. Immutable after load.
      • TileCell extended: { palette_id: char, walkable: bool, kind: TileKind, material_id: u16 }. Material ID is a compact index into the palette's material table.
      • TileOverrideMap resource: BTreeMap<(i32, i32, i32), TileOverride> for per-tile overrides. Sparse — only tiles with overrides consume memory.
      • ECS queries: WalkabilityMap remains the primary interface for movement/pathfinding (unchanged API). TilePalette provides material/visual data when needed (snapshot construction, sound system). TileOverrideMap provides door state, access lists, damage overlays.
  • YAML authoring format:
    # Palette definition (loaded once, reusable across locations)
    palette:
      F: { walkable: true, kind: Floor, material: metal-grate, visual_base: floor_industrial }
      W: { walkable: false, kind: Wall, material: bulkhead, visual_base: wall_heavy, los_blocking: true }
      D: { walkable: true, kind: Door, material: metal-door, visual_base: door_standard }
      G: { walkable: true, kind: Floor, material: glass-panel, visual_base: floor_glass }
      R: { walkable: false, kind: Floor, material: metal-grate, visual_base: floor_restricted }
    
    # Location tile map (unchanged human-readable format)
    tiles:
      - "WWWWWWWWWWWWWW"
      - "WFFFFDFFFFFFFW"
      - "WFFFFFFFFFFGFW"
      - "WWWWWWWWWWWWWW"
    
    # Per-tile overrides (sparse, only for non-default properties)
    overrides:
      - { x: 5, y: 1, door_access: [faction.commission], locked: true }
      - { x: 12, y: 2, visual_variant: 3 }
    
  • Loader contract: ContentPlugin loads palette YAML first, then location tiles. The apply_location_tiles() function resolves each character via palette lookup instead of the current hardcoded match. Unknown characters fall back to Floor with a warning (same as current behavior). Overrides are loaded after tiles and applied to TileOverrideMap.
  • Migration effort for existing locations (5 files):
    • Zero-migration path: The default palette defines F/W/V/R with identical behavior to current hardcoded mapping. Existing location YAMLs work unchanged. No migration required for v0.1.
    • Incremental enrichment: Locations can opt into the new palette by adding a palette: key. Locations without palette: use the global default. Migration is per-location, at author pace.
    • Estimated effort: Palette definition = 0.5 day. Loader refactor = 1-2 days. Override system = 1 day. Total: 2-4 developer-days. No changes to location YAML files required for v0.1.
  • Alternatives considered:
    • (b) Per-tile property bags (arbitrary key-value per tile): Maximum flexibility but violates D-010 principle 4 (deterministic — dynamic typing makes serialization non-deterministic). Memory cost: ~100 bytes/tile vs ~6 bytes/tile with palette. Rejected.
    • (c) ECS-style tile components (tiles as entities): Each tile becomes a bevy_ecs entity with optional components. Elegant in theory but 150x150x3 = 67,500 entities per location, potentially 4M+ entities for a district. ECS entity overhead (~128 bytes each) makes this prohibitively expensive. Queries scale poorly at this count. Rejected for spatial data; tiles remain grid-based. Entities are reserved for interactive objects placed ON tiles.
    • (d) Hybrid (palette + entity overlay): Palette for base tiles, entities for interactive tile features (doors, containers, triggers). This is almost what we chose — the distinction is that our sparse override map is grid-indexed (O(1) lookup by position) rather than entity-query based. Interactive objects that have their own behavior (NPCs, containers, items) remain entities; tile properties that are spatial/static (material, visual variant, access) are grid data.
  • Key design principles:
    • Palette is the type; override is the instance. 90%+ of tiles need only a palette ID.
    • String-array tile maps remain human-readable and merge-friendly. No JSON, no complex nested structures.
    • WalkabilityMap API is unchanged — callers don't know about palettes.
    • BTreeMap for deterministic iteration per D-010 principle 4.
    • Palette keys are char (single Unicode codepoint) for direct mapping from tile string arrays.
  • Raised by: Tyre (architecture), requested by #586 (Epic: extensible tile data model).
  • Dissent: None anticipated — this is a design-only D-record for post-v0.1 implementation.
  • Cross-reference: D-054 (tile-based movement), D-066 (dual-scale grid), D-094 (spatial hierarchy), D-099 (WallBackside classification), D-100 (DamageOverlay), D-012 (chunk architecture)

D-133: Skills affect outcome — same verbs available, skill determines quality

  • Date: 2026-03-05
  • Decision: The skills-to-verb coupling model is: everyone sees the same verbs (mostly). Skills determine how well you execute — bad at social means you can still talk, just badly. Some advanced verbs may still be gated by skill level, but the default is outcome-based, not access-based. This is the simplest learnable model: try anything, skill determines result.
  • Rationale: Verb access gating (skill gates whether you can even attempt an action) creates invisible walls and punishes players for trying. Outcome-based (skill determines quality of result) lets players learn by doing and creates organic differentiation. A tycoon with low social can still negotiate — they just negotiate poorly, which produces interesting consequences.
  • Source: Where's the Fun? Workshop, Round 4 Interview, Decision 5
  • Raised by: Team Leader (Jeroen) — outcome model (option C)
  • Dissent: None
  • Cross-reference: D-120 (no skill ceiling in v0.2)

D-134: Full character customization — hair, clothing, colors at tile scale

  • Date: 2026-03-05
  • Decision: Full character appearance customization is in scope: hair, clothing, colors. Readability at top-down tile scale is solved through outline and highlight mechanics, not by limiting customization options. The character creation screen is an emotional investment moment — the player should feel this is their character.
  • Rationale: Customization at this scale was assumed to be a readability risk. The workshop decision: solve the readability problem rather than limit the player. Readability via outline/highlight is a solved problem in the tile rendering pipeline. Limiting customization would undermine the identity investment that makes life-sim attachment possible.
  • Source: Where's the Fun? Workshop, Round 4 Interview, Decision 11
  • Raised by: Team Leader (Jeroen)
  • Dissent: None

D-135: Setting delivery via both layers — visual world + insert in parallel

  • Date: 2026-03-05
  • Decision: Setting is delivered through two parallel layers: (1) the physical world — visuals and NPC behavior show context, atmosphere, place; (2) the neural insert — names, contextualizes, provides information the character would know from their background. Araminta (visual layer) and Mellanie (insert copy layer) work in parallel. Both layers are required from day one of the tycoon bookmark experience.
  • Rationale: Either layer alone is insufficient. Visuals without naming leave the player in a beautiful void with no cultural foothold. Naming without visuals produces an exposition dump. Both together produce the "this is a place" sensation the workshop identified as the missing ingredient of v0.1.
  • Source: Where's the Fun? Workshop, Round 4 Interview, Decision 12
  • Raised by: Team Leader (Jeroen) — both layered (option C)
  • Dissent: None
  • Cross-reference: D-128 (culture as context for insert copy)

D-136: First Settled Reach moment — auto-generated apartment + insert activation

  • Date: 2026-03-05
  • Decision: The first moment of The Settled Reach is two layered beats: (1) Waking up in YOUR auto-generated apartment (reflects your economic position from the tycoon bookmark; wealthy, modest, or constrained start matters). (2) Insert activation — the neural implant powering on is intimate, personal, tech-specific. The alarm clock is the Groundhog Day homage (D-126). The apartment reflects the character's economic position — auto-generated, not hand-built.
  • Rationale: The apartment establishes place, economic status, and self without exposition. Insert activation establishes the neural lattice as intimate and personal — this is your character's relationship with their technology. Both beats together create the "this is MY character in MY world" moment that v0.1 lacked.
  • Source: Where's the Fun? Workshop, Round 4 Interview, Decision 13
  • Raised by: Team Leader (Jeroen)
  • Dissent: None
  • Cross-reference: D-126 (alarm clock tone), D-135 (both layers active from first moment)

D-137: Generator produces both structural and cosmetic variety at different scales

  • Date: 2026-03-05
  • Decision: The generator must produce two types of variety simultaneously at different scales: (1) Structural variety — operates at seed level: different playthroughs have genuinely different world structures (economic landscape, faction power balance, crisis composition, NPC role distribution). (2) Cosmetic variety — operates within a structure: NPC names, faces, apartment layouts vary per instance. Structural variety is the higher-priority proof for the Sprint 25 spike (D-119).
  • Rationale: Cosmetic variety without structural variety produces "same game with different wallpaper." Structural variety without cosmetic variety produces identical-looking characters with different internal states. Both are load-bearing for the life-sim experience — structural variety drives replay value, cosmetic variety drives in-session believability.
  • Source: Where's the Fun? Workshop, Round 5 Interview, Decision 23
  • Raised by: Team Leader (Jeroen)
  • Dissent: None
  • Cross-reference: D-114 (generator proof-of-life), D-119 (Sprint 25 generator spike)

D-141: PlatformInfo — client-side OS abstraction autoload

  • Date: 2026-03-13
  • Decision: All OS-dependent queries on the client are centralized in a single PlatformInfo autoload (client/scripts/autoloads/platform_info.gd), registered first in the autoload order. Individual systems (HardwareDetector, voice pipeline, settings UI) consume PlatformInfo properties and signals — they never call OS.* directly. PlatformInfo owns: power state (with PowerProfile enum: FULL, BATTERY, POWER_SAVER), memory queries, platform identity, and platform-dependent file paths. Power state is polled on a 30-second timer with a power_profile_changed signal; memory is refreshed on demand. The PowerProfile enum is the abstraction seam for future power-saver detection (GDExtension) without consumer code changes. PlatformInfo is client-side only — the client never relies on the server for hardware info, because the server may not be on the same hardware in multiplayer/remote hosting scenarios. Each side detects independently.
  • Rationale: OS calls were scattered across HardwareDetector, AiDialogueDetector (duplicate), and SimBridge. A central abstraction prevents duplication, provides a single seam for platform-specific behavior, and keeps the client self-sufficient per D-010 (information boundaries) and future multiplayer readiness.
  • Raised by: Team Leader (Jeroen)
  • Dissent: Centralized server detection was considered and rejected — server may not share hardware with client in future multiplayer scenarios.
  • Cross-reference: D-138 (hardware detection for voice pipeline), D-010 (information boundaries), Q-059 (full interface scope — open)

D-148: 30° low-angle camera with 45° map rotation — supersedes D-019

  • Date: 2026-03-17
  • Decision: The default gameplay camera is an orthographic Camera3D at 30° tilt (60° from horizontal) with the tile map rotated 45° into a diamond grid. Not faked in art — an actual Camera3D tilt. Art direction reference: Hades, Divinity: Original Sin. Camera tilt cycle (T key in prototype): top-down → 45° isometric → 30° low-angle. 30° is the confirmed default. The 45° map rotation gives natural depth cues and the classic diamond-grid isometric layout.
  • Rationale: The 30° angle (versus 45° or top-down) gives significantly more character front visibility and wall depth. Players can see faces, clothing, and character detail rather than primarily hat and shoulder. The diamond grid provides natural spatial depth cues without requiring Z-ordering hacks. The T-key prototype confirmed 30° as the most readable angle at gameplay scale.
  • Architecture note: Unlike D-019's amendment (which faked tilt in sprite art), this is a real Camera3D setting. The 45° map rotation is a Transform3D applied to the tile grid root — it does not affect simulation coordinates, which remain axis-aligned. Vision cone math and all server-side systems remain in unrotated space; the client applies the visual rotation.
  • Raised by: Team Leader (Jeroen) — confirmed during Sprint 28 character visuals spike review
  • Dissent: None
  • Supersedes: D-019
  • Cross-reference: D-149 (3D rendering), D-151 (direction count)

D-149: 3D characters rendered live in scene — not pre-rendered sprites

  • Date: 2026-03-17
  • Decision: Characters are rendered as live 3D models in the Godot scene using a CharacterCompositor (Node3D). The camera is a real Camera3D at 30° tilt (D-148). Characters are not pre-rendered 2D sprite sheets. The 3D model is rotated to match the server-tracked 8-direction facing; the camera and lighting remain fixed. Clothing, hair, and accessories are separate mesh layers composited at runtime.
  • Rationale: The Sprint 28 spike used CSG placeholder characters (cylinders, spheres) and confirmed that even crude 3D shapes read as recognizable people at isometric scale — silhouette, proportion, and facing direction are all legible. Pre-rendered sprites would require 8× (or 4×) separate renders per outfit combination; live 3D compositing gives unlimited clothing/color combinations at negligible extra render cost. Direction changes are a model rotation, not a sprite swap. Future animation is natural.
  • Architecture note: The existing EntityRenderer (client/scripts/rendering/entity_renderer.gd) currently uses a single Sprite2D per entity. Under this decision, EntityRenderer is extended to instantiate a CharacterCompositor scene (Node3D subtree) instead. The compositor API is specified in docs/design/compositor-api-spec.md.
  • Raised by: Team Leader (Jeroen) — spike prototype confirmed; Sprint 28 workshop decision
  • Dissent: None
  • Cross-reference: D-148, D-150, D-151, D-152, ticket #693 (compositor implementation)

D-150: Character outline — inverted hull method

  • Date: 2026-03-17
  • Decision: Character outlines are rendered via the inverted hull method (GPU vertex extrusion on a back-face-only render pass). Color: #1e1e24 (very dark blue-grey) for all characters, always — not pure black. Specified by Araminta (art direction, Sprint 28 Round 2). No screen-space outline system. At LOD tier 2 (billboard impostor), the outline is baked into the impostor sprite — no separate draw call needed at that tier.
  • Rationale: Inverted hull is GPU-cheap, works correctly in 3D space, and produces clean consistent outlines. Screen-space methods (e.g., Sobel filter) are more expensive and produce artifacts at isometric angles. The billboard LOD tier naturally subsumes the outline into the baked sprite, so the system degrades gracefully under performance pressure without special outline handling.
  • Raised by: Sprint 28 workshop consensus (Tyre technical, Jeroen confirmed)
  • Dissent: None
  • Cross-reference: D-149, D-152, D-154

D-151: Direction count — 8 server-side facings, 4 visual groups client Sprint 28

  • Date: 2026-03-17
  • Decision: The server tracks 8 facing directions for all characters (N, NE, E, SE, S, SW, W, NW) — full resolution, future-proof. The client renders 4 visual groups for Sprint 28: North (covers N, NW), East (covers NE, E), South (covers SE, S), West (covers SW, W). The 3D model is rotated to the true 8-direction angle; only the visual mesh/asset groups are 4-way. E and W groups share mirrored assets. Post-Sprint 28: additional direction-specific mesh variants can be authored for diagonal facings without protocol changes.
  • Critical distinction — perception vs. rendering: The 8 server facings are perception system input (fog-of-war, vision cone direction, all simulation logic). The 4 visual groups are rendering output (what the player sees). These are not the same thing. Diagonal facings (NE, NW, SE, SW) do not exist as visible character states — a character facing NE renders as East. EntityRenderer maps the server's 8-direction value to a 4-group CharacterFacing before calling the compositor. ModelRoot body rotation uses the true 8-direction angle for subtle lean.
  • Rationale: Hybrid approach (Tyre): 8 server facings ensures the data model never needs migration. 4 client visual groups keeps Sprint 28 asset authoring cost manageable — each new clothing item needs 2 unique meshes (N, E) plus mirroring, not 4 or 8. The 3D rotation to true angle (before the visual group snap) gives subtle body lean and positioning cues even within a visual group. Characters snap to nearest cardinal with smooth rotation interpolation.
  • Raised by: Tyre (hybrid proposal), confirmed by Team Leader (Jeroen) — Sprint 28 Round 2
  • Dissent: None
  • Cross-reference: D-148, D-149

D-152: Character LOD — performance-driven budget, not distance threshold

  • Date: 2026-03-17
  • Decision: Character LOD degrades based on GPU frame budget, not distance or fixed character count. Three tiers:
    • Tier 0 (full): All characters at full 3D detail with all compositor layers active.
    • Tier 1 (simplified mesh): Reduced-poly model; clothing layers merged into combined mesh; inverted hull still active.
    • Tier 2 (billboard impostor): Flat sprite impostor. Outline baked in. Must visually preserve: (1) body size tier (slim/average/stocky silhouette), (2) dominant clothing color (cloth_primary). Applied to characters furthest from player first. LOD trigger is proactive on projected character count, not reactive on frame drop. Reactive triggering produces visible hitches; proactive demotion is invisible. LOD demotes characters outward from player: nearest characters always stay at Tier 0 longest. When paused, render budget is fully freed and all characters restore to Tier 0 — player can inspect the scene at leisure. In a chaotic moment (400+ characters on screen), peripheral detail naturally degrades, which matches the cognitive experience of chaos.
  • Rationale: A fixed distance or count threshold would produce visually jarring sudden LOD pops when character density changes (a crowd gathering). Performance-driven budget is adaptive and invisible to the player. The "paused = full detail" design is a deliberate player affordance — it makes the pause button feel powerful.
  • Raised by: Sprint 28 workshop (Jeroen — Q2 resolution)
  • Dissent: None
  • Cross-reference: D-149, D-150

D-160: Body meshes must be segmented into 15 bone-group regions

  • Date: 2026-03-19
  • Decision: Character body meshes are segmented into 15 bone-group regions (head, neck, torso, arm_upper_l/r, arm_lower_l/r, hand_l/r, leg_upper_l/r, leg_lower_l/r, foot_l/r) plus eyes and eyebrows. Each segment is a separate skinned GLB on the shared 65-bone skeleton. Segments have a 1-ring vertex overlap at boundaries to eliminate visible seams during animation. Segments can be individually hidden when clothing covers them.
  • Rationale: Validated in the Quaternius aesthetic spike. Monolithic body meshes block the compositor: hiding the body to show clothing also removes the head and limbs. Segmentation enables per-slot visibility, head separation for Trellis-generated faces, and limb loss mechanics. The 1-ring vertex overlap was validated as seamless at gameplay zoom.
  • Raised by: Jeroen + Tyre, during spike validation.
  • Cross-reference: D-159 (body type enum), D-149

D-161: Head is always a separate mesh on the Head bone

  • Date: 2026-03-19
  • Decision: Character heads are never baked into body or clothing meshes. The head is always a separate mesh attached to the Head bone via BoneAttachment3D. Trellis generates unique head shapes per character. Hair attaches to the same bone as a separate swappable mesh (enables hairdresser mechanic). The neck stays with the body mesh as a separate segment.
  • Rationale: Validated in the Quaternius spike. The Quaternius pack bakes the head into the body mesh — this blocks clothing display (hiding body = losing head) and prevents per-character face variation. Separating the head unlocks: Trellis-generated faces, idle look-around animation, helmet/hood equipment, hair as a swappable accessory.
  • Raised by: Jeroen, during spike validation.
  • Cross-reference: D-160, D-163

D-162: Clothing is pre-baked per body type via Blender Surface Deform

  • Date: 2026-03-19
  • Decision: Clothing meshes are authored on a reference body type, then batch-fitted to each of the 9 body types via Blender's Surface Deform modifier in an offline pipeline. The fitted variants are exported as separate GLBs. At runtime, the compositor loads the variant matching the character's body type. Runtime clothing scaling does not exist — it was tested (bone pose scaling in Godot) and failed catastrophically.
  • Rationale: Three approaches were tested during the spike: (1) runtime bone scaling — failed with mesh collapse at joints, (2) Blender bone scaling — same failure, (3) Blender Surface Deform — produced output (visual quality at extremes is Q-060). The Surface Deform pipeline is the only viable path. Pre-baking is required; runtime fitting is impossible.
  • Raised by: Tyre + Araminta, during spike research and validation.
  • Cross-reference: D-159, Q-060

D-163: Trellis generates unique heads per character via BoneAttachment3D

  • Date: 2026-03-19
  • Decision: Each character in the game receives a unique Trellis-generated head mesh. The head is a static (unskinned) mesh attached to the Head bone via BoneAttachment3D. Hair is a separate mesh on the same bone. Head generation happens offline during character creation/world generation. The concept image prompt controls face shape, ethnicity, age, and features.
  • Rationale: Validated in the spike: Trellis head + BoneAttachment3D + toon shader produces a coherent character at gameplay zoom. This is the only Trellis output that works on the Quaternius rig — clothing and body meshes both failed. Head generation gives per-character visual identity without hand-modeling.
  • Raised by: Jeroen (insight that Trellis output works as heads), validated by Tyre.
  • Cross-reference: D-161

D-164: Fork Quaternius skeleton, replace all body meshes

  • Date: 2026-03-19
  • Decision: The Quaternius 65-bone skeleton and Universal Animation Library are adopted as the character rig foundation. All body meshes are replaced with hand-authored meshes on the same skeleton. The CC0 license permits unrestricted forking. Quaternius Source tier ($5/mo Patreon, one month) is recommended for .blend source files to support the Blender batch scripting pipeline.
  • Rationale: The spike validated the skeleton and animation library as clean, compatible, and well-structured. The body meshes are rejected: Superhero proportions contradict the life-sim aesthetic (D-153), only 2 body types exist in the standard tier (Superhero Male/Female), and procedural body type generation via bone scaling failed. The skeleton is the expensive part to create from scratch — keeping it and replacing the meshes is the correct split.
  • Raised by: Tyre + Araminta, confirmed by Jeroen.
  • Cross-reference: D-159, D-160

49 decisions. Last updated: 2026-03-19 (D-160D-164 added — Quaternius aesthetic spike: segmentation, head separation, clothing pipeline, Trellis heads, Quaternius fork)