Files
settled-reach/decisions/architecture.md
T
jpmschweitzerandClaude Opus 4.6 4f5ee0274b docs(decisions): split DECISIONS.md into domain-scoped files
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>
2026-02-11 09:28:57 +01:00

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:
    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.
  • 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.

8 decisions. Last updated: 2026-02-11