Files
settled-reach/decisions/architecture.md
T
jpmschweitzerandClaude Opus 4.6 93abd9b9d0 docs(workshops): complete Knowledge Graph & Information Boundaries workshop
Two-round workshop producing D-041 (Knowledge Graph Data Model):
- Round 1: independent analyses from Dudley, Gestalt, SI, Tyre, Paula
- Round 2: synthesis resolving debates + Gestalt mechanics validation

Key decisions:
- 4-level confidence hierarchy (Suspects < KnowsOf < KnowsDetails < Direct)
- BTreeMap for deterministic iteration (D-010 principle 4)
- Per-entity Component model, not centralized Resource
- StableEntityId + EntityRegistry for save/load stability (partial Q-019)
- Sprint 2 stub: structs + direct observation + basic decay (~6.5 dev-days)

Resolved Q-016 (knowledge hierarchy), raised Q-024/Q-025/Q-026.
Created tickets #361-#368 under epic #351, reconciled #49 children.
Updated sprint 2 briefings, agent briefings, and decision files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:24:37 +01:00

138 lines
19 KiB
Markdown

# 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](perception.md#d-017-perception-modes-as-character-build-system)). 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](content.md#d-029-population-entanglement-ratio--305020).
- **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](scope.md#d-013-diegetic-insertpoi-navigation-system).
- **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").
---
*9 decisions. Last updated: 2026-02-11*