Recognition chime timing (D-067), 5-bus audio architecture (D-068), audio dip profiles (D-069), confrontation as cognitive vulnerability (D-070), monologue chime placeholder strategy (D-071), universal conversation murmur (D-072), zone crossfade (D-073), hybrid audio generation (D-074). Amends D-038 scope, resolves Q-014. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
31 KiB
31 KiB
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:
- 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.
- 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.
- 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.
- 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
ObserverSnapshotdata, 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.
- Eliminates entire risk categories: gdext pre-1.0 API churn, Godot version ABI breakage, FFI thread safety (
- 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.SimBridgetrait: abstracts transport.LocalBridge(subprocess, channels) andNetworkBridge(TCP, MessagePack) implement the same interface.
- 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:
- Build Rust simulation as standalone binary (testable via terminal/logs)
- Build Godot renderer as standalone project (hardcoded test data)
- 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):
- Rust test organization = Hybrid.
#[cfg(test)]for unit tests inside modules +tests/directory for integration tests. Both viacargo nextest run. - Godot test framework = gdUnit4 (changed from GUT). Native JSON output, stable headless via
GdUnitCmdTool,GdUnitSceneRunnerfor scene lifecycle tests, organizational maintenance. - 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).
- 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. - Test runner tooling.
cargo-nextest(Rust) + gdUnit4 (Godot) + bash wrapper scripts intest/directory, whitelistable for agent use. - Test output format = JSON summary. Consistent schema across all runners (suite, total, passed, failed, failures array). JUnit XML as secondary CI format.
- #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. - 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.
- Rust test organization = Hybrid.
- 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
KnowledgeGraphcomponent 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 viaEntityRegistryresource for bidirectionalStableId <-> Entitylookup. Knowledge updates flow through event-driven architecture: perception systems emitKnowledgeEventtoKnowledgeEventQueueresource, knowledge update system drains queue and writes toKnowledgeGraphcomponents. Basic decay runs once per game-minute (every 10 ticks per D-031), downgrading confidence levels based onlast_observed_tickage against configurableDecayThresholds. - Sprint 2 scope: Full data structures + direct observation flow + basic decay + observer snapshot integration (#112). Deferred to Sprint 3+: NPC-to-NPC gossip,
ToldBy/Inferredsource generation,Contradictedstate detection,Stalestate logic, knowledge-driven dialogue filtering, monologue triggering, misinformation. - Canonical reference: Full Rust struct definitions at
docs/workshops/knowledge-graph-information-boundaries/round2-synthesis.mdPart 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:surfaceavailable at any level,realat KnowsOf+,secretat KnowsDetails+. - KnowledgeState for contradiction detection: THE FRIEND arc (D-034, D-039 wow moment #3) requires detecting when a
ToldByentry conflicts with aDirectObservationentry. Both entries receiveContradictedstate, triggering monologue event and relationship state shift (PersonOfInterest). Sprint 2 only usesActivestate; 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_ecsEntity(generational index).EntityRegistrymaintains 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.yamland loaded via a dedicated GDScript autoload singleton (UIStrings). UI strings are NOT hardcoded as GDScript constants inclient/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:
- Music — future/empty in v0.1. Reserved for diegetic Meridian music in social spaces.
- Ambient — station hum (D-038 asset 1) + zone overlays (assets 2-4). Continuous soundscape.
- World SFX — NPC footsteps, doors, environmental events. Diegetic world sounds not caused by player.
- Player Actions — player footsteps (assets 5-6), future: combat sounds, item interactions. Sounds player directly causes.
- 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
16 decisions. Last updated: 2026-02-16