Replace monolithic 474-line DECISIONS.md with 7 domain files under decisions/: architecture, perception, content, scope, process, questions, rejected. Each file owns its decisions with cross-reference hyperlinks. Root DECISIONS.md becomes a redirect with domain index. Reduces per-agent context load by ~70-80% (80-150 lines vs 474). Taxonomy: architecture (D-008..D-031), perception (D-011..D-019), content (D-023..D-029), scope (D-001..D-027), process (D-004..D-022), questions (Q-001..Q-011), rejected (R-001..R-010). Restores D-002 original text (was a tombstone referencing D-005). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 KiB
14 KiB
Architecture Decisions
Technical foundation decisions that constrain implementation: engine, client-server, ECS, simulation, testability, performance budgets.
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.
8 decisions. Last updated: 2026-02-11