diff --git a/docs/diagrams/architecture/audio-bus.d2 b/docs/diagrams/architecture/audio-bus.d2
new file mode 100644
index 000000000..bc655b1ed
--- /dev/null
+++ b/docs/diagrams/architecture/audio-bus.d2
@@ -0,0 +1,121 @@
+# Audio Architecture — D-068, D-073
+# 5-bus audio system with zone crossfade and AudioManager singleton.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+direction: down
+
+server_event: Rust Server\n(ObserverSnapshot) {
+ shape: hexagon
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ zone_id: zone_id per tile {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ sound_events: sound events {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+manager: AudioManager\n(GDScript autoload singleton) {
+ shape: hexagon
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ registry: Directory-scan registry\n(res://audio/ on startup) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ crossfade: Zone crossfade\n1.5-2s tween on zone_id change {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+}
+
+buses: 5-Bus Mixer {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ music: Bus 1 — Music\n(reserved · empty v0.1) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 5
+ }
+ ambient: Bus 2 — Ambient\n(station hum + zone overlays) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ world_sfx: Bus 3 — World SFX\n(NPC footsteps · doors · env events) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ player_actions: Bus 4 — Player Actions\n(footsteps · future: combat · items) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ ui_sounds: Bus 5 — UI Sounds\n(cursor hover · implant open · chimes) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+sliders: Player Volume Sliders\n(1 per bus · in Settings) {
+ shape: queue
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+dips: Audio Dip Profiles (D-069) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ dialogue_dip: Dialogue\nAmbient -6 to -8dB · 300ms in {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ confrontation_dip: Confrontation\nAmbient -10 to -12dB + LPF · 500ms in {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ listen_boost: ListeningFocus\nWorld SFX +2 to +3dB\n(stationary 30+ ticks) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+server_event.zone_id -> manager.crossfade: "zone change" { style.stroke: ${color-accent} }
+server_event.sound_events -> manager: "typed events" { style.stroke: ${color-stroke} }
+manager.registry -> manager.crossfade: "filename → AudioStream" { style.stroke: ${color-stroke} }
+manager -> buses.ambient: "loop" { style.stroke: ${color-accent} }
+manager -> buses.world_sfx: "event" { style.stroke: ${color-stroke} }
+manager -> buses.player_actions: "event" { style.stroke: ${color-stroke} }
+manager -> buses.ui_sounds: "event" { style.stroke: ${color-stroke} }
+sliders -> buses: "proportional volume" { style.stroke: ${color-stroke} }
+dips -> buses.ambient: "proportional dip" { style.stroke: ${color-stroke}; style.stroke-dash: 4 }
+dips -> buses.world_sfx: "proportional dip" { style.stroke: ${color-stroke}; style.stroke-dash: 4 }
diff --git a/docs/diagrams/architecture/audio-bus.png b/docs/diagrams/architecture/audio-bus.png
new file mode 100644
index 000000000..e69c00baa
Binary files /dev/null and b/docs/diagrams/architecture/audio-bus.png differ
diff --git a/docs/diagrams/architecture/ipc-bridge.d2 b/docs/diagrams/architecture/ipc-bridge.d2
new file mode 100644
index 000000000..6e8453869
--- /dev/null
+++ b/docs/diagrams/architecture/ipc-bridge.d2
@@ -0,0 +1,146 @@
+# IPC Bridge Architecture — D-020
+# Godot client + Rust simulation server via subprocess/IPC
+# Single-player: subprocess + local socket. Multiplayer: TCP (same protocol).
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+direction: right
+
+godot: Godot Client {
+ shape: hexagon
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ renderer: Renderer {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ }
+ ui: UI + HUD {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ }
+ audio: AudioManager {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ }
+ input: Input Capture {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ }
+ bridge_client: SimBridge (LocalBridge / NetworkBridge) {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-accent}
+ }
+}
+
+transport: Transport Layer {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ sp: Single-player\nUnix socket / localhost TCP {
+ style.fill: ${color-bg}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+ }
+ mp: Multiplayer\nRemote TCP {
+ style.fill: ${color-bg}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+ }
+}
+
+rust: Rust Simulation Server {
+ shape: hexagon
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ bridge_server: SimBridge (recv end) {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-accent}
+ }
+ ecs: bevy_ecs World {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ }
+ perception: Perception System {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ }
+ ai: AI + Storyteller {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ }
+ kg: Knowledge Graph {
+ style.fill: ${color-dim}
+ style.font-color: ${color-text}
+ style.stroke: ${color-stroke}
+ }
+}
+
+msgpack_out: PlayerInput (MessagePack) {
+ shape: queue
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+msgpack_in: ObserverSnapshot (MessagePack) {
+ shape: queue
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+godot.input -> msgpack_out: "semantic actions\n(MoveNorth, Interact, UsePerceptionMode)" {
+ style.stroke: ${color-accent}
+}
+msgpack_out -> godot.bridge_client
+godot.bridge_client -> transport.sp: "single-player" {
+ style.stroke: ${color-accent}
+ style.stroke-dash: 4
+}
+godot.bridge_client -> transport.mp: "multiplayer" {
+ style.stroke: ${color-accent}
+ style.stroke-dash: 4
+}
+transport.sp -> rust.bridge_server {
+ style.stroke: ${color-accent}
+ style.stroke-dash: 4
+}
+transport.mp -> rust.bridge_server {
+ style.stroke: ${color-accent}
+ style.stroke-dash: 4
+}
+rust.bridge_server -> rust.ecs: "dispatch input" {
+ style.stroke: ${color-stroke}
+}
+rust.ecs -> rust.perception
+rust.ecs -> rust.ai
+rust.ecs -> rust.kg
+rust.ecs -> msgpack_in: "ObserverSnapshot\n(entities, fog, sound events,\nmonologue triggers, HUD widgets)" {
+ style.stroke: ${color-accent}
+}
+msgpack_in -> godot.bridge_client
+godot.bridge_client -> godot.renderer
+godot.bridge_client -> godot.ui
+godot.bridge_client -> godot.audio
diff --git a/docs/diagrams/architecture/ipc-bridge.png b/docs/diagrams/architecture/ipc-bridge.png
new file mode 100644
index 000000000..a8887b1f0
Binary files /dev/null and b/docs/diagrams/architecture/ipc-bridge.png differ
diff --git a/docs/diagrams/architecture/system-tiers.d2 b/docs/diagrams/architecture/system-tiers.d2
new file mode 100644
index 000000000..ba9286300
--- /dev/null
+++ b/docs/diagrams/architecture/system-tiers.d2
@@ -0,0 +1,133 @@
+# Simulation Tiers + Multiplayer-Ready Principles — D-026, D-010, D-009
+# Four simulation tiers with LRU eviction.
+# D-010: four non-negotiable architectural principles embedded as annotations.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+direction: down
+
+principles: Architectural Principles (D-010) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+
+ p1: Client-Server Separation\neven in single-player {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ p2: Information Boundaries\nas first-class system {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ p3: No Baked Player Identity\n"characters, some player-controlled" {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ p4: Deterministic Simulation\ntimestamped input events {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+tiers: Simulation Tiers (D-026) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+
+ active: Tier 1 — Active {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.bold: true
+ }
+ background: Tier 2 — Background {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ saved: Tier 3 — State-Saved {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ ungenerated: Tier 4 — Ungenerated {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 5
+ }
+}
+
+active_detail: 30-80 NPCs\nFull sim · 10-20 ticks/sec\nScope tags keep NPCs here {
+ shape: page
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+background_detail: 500-2,000 NPCs\nState machine · 1 tick/game-minute\n(schedule · mood · relationships · job) {
+ shape: page
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+saved_detail: 10,000+ NPCs\nFrozen serialized structs ~1-2KB\nReactivate on player return ~2-5ms {
+ shape: page
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+eviction: LRU Eviction\n(interaction-timestamp priority queue) {
+ shape: diamond
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+tiers.active -> active_detail {
+ style.stroke: ${color-accent}
+}
+tiers.background -> background_detail {
+ style.stroke: ${color-stroke}
+}
+tiers.saved -> saved_detail {
+ style.stroke: ${color-stroke}
+}
+
+tiers.active -> tiers.background: "evict (LRU)" {
+ style.stroke: ${color-accent}
+ style.stroke-dash: 4
+}
+tiers.background -> tiers.active: "player proximity\nor scope tag" {
+ style.stroke: ${color-accent}
+}
+tiers.background -> tiers.saved: "evict (LRU)" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+tiers.saved -> tiers.background: "player return" {
+ style.stroke: ${color-stroke}
+}
+tiers.ungenerated -> tiers.saved: "player approach\n(generated ahead of arrival)" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+
+eviction -> tiers {
+ style.stroke: ${color-accent}
+ style.stroke-dash: 4
+}
diff --git a/docs/diagrams/architecture/system-tiers.png b/docs/diagrams/architecture/system-tiers.png
new file mode 100644
index 000000000..74cfbf9fd
Binary files /dev/null and b/docs/diagrams/architecture/system-tiers.png differ
diff --git a/docs/diagrams/architecture/test-font-fixed.svg b/docs/diagrams/architecture/test-font-fixed.svg
deleted file mode 100644
index d4f3018e2..000000000
--- a/docs/diagrams/architecture/test-font-fixed.svg
+++ /dev/null
@@ -1,97 +0,0 @@
-
diff --git a/docs/diagrams/architecture/testability.d2 b/docs/diagrams/architecture/testability.d2
new file mode 100644
index 000000000..6d62dc81e
--- /dev/null
+++ b/docs/diagrams/architecture/testability.d2
@@ -0,0 +1,126 @@
+# Testability Architecture — D-030
+# Three-layer IPC testing, test runner tooling, and phase priorities.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+direction: down
+
+runners: Test Runners {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ rust_runner: cargo nextest\n(Rust) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ godot_runner: gdUnit4\n(Godot) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ wrapper: bash wrapper scripts\n(tests/ directory) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+output: Test Output {
+ shape: page
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+ipc_layers: IPC Test Layers {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ l1: Layer 1 — Fixture Serialization\nRoundtrip (fast · every edit) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.bold: true
+ }
+ l2: Layer 2 — Mock Subprocess Protocol\nState machine (medium · every PR) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l3: Layer 3 — Real Subprocess Integration\n(slow · daily / pre-merge) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+}
+
+phases: Test Phases {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ ph1: Phase 1 (Sprint 1-2)\nInfra + collision / pathfinding / time {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ ph2: Phase 2 (Sprint 3-4)\nMonologue pipeline + info boundary negatives {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ ph3: Phase 3 (Sprint 5+)\nCauseChain + divergent snapshots {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+}
+
+constraints: Production Code Constraints {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ c1: No cfg(test) in production code {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ c2: ECS World setup replaces mock injection {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ c3: CauseChain = production component\n(monologue provenance + debugging) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ c4: Deterministic replay (D-010 P4)\nSimulationTime · SimRng · InputQueue injectable {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+}
+
+runners.rust_runner -> output: "JSON summary\n+ JUnit XML (CI)" { style.stroke: ${color-stroke} }
+runners.godot_runner -> output: "JSON summary\n+ JUnit XML (CI)" { style.stroke: ${color-stroke} }
+runners.wrapper -> runners.rust_runner
+runners.wrapper -> runners.godot_runner
+
+ipc_layers.l1 -> ipc_layers.l2: "gates" { style.stroke: ${color-accent} }
+ipc_layers.l2 -> ipc_layers.l3: "gates" { style.stroke: ${color-accent} }
+
+phases.ph1 -> phases.ph2: "unblocks" { style.stroke: ${color-stroke} }
+phases.ph2 -> phases.ph3: "unblocks" { style.stroke: ${color-stroke} }
diff --git a/docs/diagrams/architecture/testability.png b/docs/diagrams/architecture/testability.png
new file mode 100644
index 000000000..055686c12
Binary files /dev/null and b/docs/diagrams/architecture/testability.png differ
diff --git a/docs/diagrams/data-flow/dialogue-pipeline.d2 b/docs/diagrams/data-flow/dialogue-pipeline.d2
new file mode 100644
index 000000000..21af2ae3b
--- /dev/null
+++ b/docs/diagrams/data-flow/dialogue-pipeline.d2
@@ -0,0 +1,171 @@
+# Dialogue Pipeline — D-028, D-062, D-063, D-064, D-075
+# Four relational layers, two-phase verb computation, invisible locks,
+# confrontation weight, walk-away consequences.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+direction: down
+
+player_action: Player Initiates Interaction {
+ shape: oval
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+verb_phase1: Phase 1 — Simulation\n(no KnowledgeGraph read) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+
+ object_type: ObjectType component\n→ max possible verb set {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+verb_phase2: Phase 2 — Observer\n(reads KnowledgeGraph) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+
+ kg_filter: Filter by KG confidence\n(Confront requires KnowsDetails+) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ poi_flip: POI priority flip\n+ contradiction markers {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ archetype_filter: Archetype filter rules\n(same crate: smuggler→Stash · detective→Flag) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+layer_filter: Four Relational Layers (D-028) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ l1: Layer 1 — Access Tier\n(RelationshipState only) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l2: Layer 2 — Relationship History\n(interaction log: greeting + topic) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l3: Layer 3 — Trust-gated Gossip\n(RelationshipState AND KnowledgeConfidence — D-075) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ l4: Layer 4 — Unprompted Disclosure\n(weighted by trust + mood) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+invisible_locks: Invisible Locked Options (D-062) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+
+ rule: Locked options = completely invisible\nNo grayed-out · no lock icons · no hints {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ reveal: New options appearing on repeat visits\n= the reward (dopamine) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+}
+
+confrontation: Confrontation Weight (D-063) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+
+ voice: Written in character's internal voice\n(italicized first-person) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ monologue_beat: Pre-delivery monologue beat\n1-2s: "No taking it back." {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ world_reacts: World reacts\n(NPC Tier 2 anim · D-033 fade · monologue spikes) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+walk_away: Walk-away (D-064)\nWASD during conversation {
+ shape: diamond
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+walk_away_phases: Walk-away Consequences {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ immediate: 1 — Dialogue fades 300ms\n(no close button) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ npc_reacts: 2 — NPC reacts\n(animation · may call after player) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ kg_record: 3 — KG records incompleteness\n(per-seed tolerance: no universal safe-to-leave rules) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+}
+
+player_action -> verb_phase1
+verb_phase1 -> verb_phase2: "max verb set" { style.stroke: ${color-accent} }
+verb_phase2 -> layer_filter: "filtered verb set" { style.stroke: ${color-accent} }
+layer_filter.l1 -> layer_filter.l2
+layer_filter.l2 -> layer_filter.l3
+layer_filter.l3 -> layer_filter.l4
+layer_filter -> invisible_locks: "visible options only\n(max 3 shown)" {
+ style.stroke: ${color-accent}
+}
+invisible_locks -> confrontation: "if Confront option present" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+invisible_locks -> walk_away
+walk_away -> walk_away_phases.immediate
+walk_away_phases.immediate -> walk_away_phases.npc_reacts
+walk_away_phases.npc_reacts -> walk_away_phases.kg_record
diff --git a/docs/diagrams/data-flow/dialogue-pipeline.png b/docs/diagrams/data-flow/dialogue-pipeline.png
new file mode 100644
index 000000000..e287b9279
Binary files /dev/null and b/docs/diagrams/data-flow/dialogue-pipeline.png differ
diff --git a/docs/diagrams/data-flow/eavesdrop-pipeline.d2 b/docs/diagrams/data-flow/eavesdrop-pipeline.d2
new file mode 100644
index 000000000..b8f08b44b
--- /dev/null
+++ b/docs/diagrams/data-flow/eavesdrop-pipeline.d2
@@ -0,0 +1,162 @@
+# Eavesdrop Pipeline — D-071, D-072, D-078
+# Overheard NPC-to-NPC conversation: occlusion filter (server-authoritative),
+# ListeningFocus boost, passive dialogue panel, dual-channel principle.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+direction: down
+
+npc_convo: NPC-to-NPC Conversation\n(authored at full fidelity by copy team) {
+ shape: oval
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+murmur: Universal Murmur Asset (D-072)\nsingle asset plays everywhere {
+ shape: package
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+zone_ambient: Zone Ambient Level {
+ shape: diamond
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+conspicuousness: Conversation Conspicuousness {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ bar: Bar (high ambient)\nhidden in murmur, needs proximity {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ workplace: Workplace (moderate)\nsemi-conspicuous, distance-dependent {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ corridor: Corridor (low ambient)\nconspicuous, audible from distance {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+}
+
+server_occlusion: Server-side Occlusion Filter (D-078)\nper-word probability roll {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+
+ factors: Factors: distance · ambient noise\n· ListeningFocus stance {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ stochastic: Stochastic per word\n(same scene can yield different fragments) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ seeded: Seedable RNG per tick\n→ deterministic replay (D-010) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+listen_focus: ListeningFocus Boost (D-071)\nWorld SFX +2 to +3dB\nstationary 30+ ticks {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+}
+
+no_ambient_dip: No Ambient Dip for Eavesdrop\n(opposite of direct conversation dip) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+}
+
+occluded_text: Pre-occluded text\n(words → "..." per roll) {
+ shape: page
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+passive_panel: Passive Dialogue Panel (D-078)\n(D-061 box, read-only) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+
+ speaker_label: Speaker format (e.g. Kael to Mira) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ reduced_opacity: Reduced opacity (0.9)\nvs active dialogue {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ no_responses: No response options {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+monologue_channel: Monologue Channel\n(D-016 — separate pipeline) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ witness_trigger: witness_interaction trigger\n→ character's reaction {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ separate: Character reaction\n(thought · never quoted speech) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+kg_entry: KnowledgeGraph entry\n(KnowsOf confidence unless\nListeningFocus + proximity → KnowsDetails) {
+ shape: cylinder
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+npc_convo -> murmur: "audio" { style.stroke: ${color-stroke} }
+npc_convo -> zone_ambient: "text goes through" { style.stroke: ${color-accent} }
+murmur -> zone_ambient: "conspicuousness shaped by" { style.stroke: ${color-stroke} }
+zone_ambient -> conspicuousness
+zone_ambient -> server_occlusion: "noise penalty" { style.stroke: ${color-stroke} }
+listen_focus -> server_occlusion: "probability curve shift" { style.stroke: ${color-accent} }
+no_ambient_dip -> listen_focus: "instead of dip" { style.stroke: ${color-stroke}; style.stroke-dash: 4 }
+server_occlusion -> occluded_text: "pre-occluded text sent to client" { style.stroke: ${color-accent} }
+occluded_text -> passive_panel: "client renders exactly what it receives" { style.stroke: ${color-accent} }
+passive_panel -> monologue_channel: "triggers witness_interaction" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+passive_panel -> kg_entry: "overheard info enters KG" { style.stroke: ${color-accent} }
diff --git a/docs/diagrams/data-flow/eavesdrop-pipeline.png b/docs/diagrams/data-flow/eavesdrop-pipeline.png
new file mode 100644
index 000000000..cf622f00d
Binary files /dev/null and b/docs/diagrams/data-flow/eavesdrop-pipeline.png differ
diff --git a/docs/diagrams/data-flow/perception-pipeline.d2 b/docs/diagrams/data-flow/perception-pipeline.d2
new file mode 100644
index 000000000..7b55a8e84
--- /dev/null
+++ b/docs/diagrams/data-flow/perception-pipeline.d2
@@ -0,0 +1,185 @@
+# Perception Pipeline — D-011, D-017, D-018, D-019, D-059, D-060, D-067
+# From simulation ECS through to rendered fog and monologue.
+# Covers: fog of perception, LOS (D-035), perception modes, three-range sound,
+# fog layers, cognitive delay, recognition chime.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+direction: down
+
+world: ECS World State {
+ shape: hexagon
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+los: LOS System\n(Albert Ford symmetric shadowcasting — D-035) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+
+ fov: compute_fov(is_opaque, origin, range, z)\n→ VisibilityMap {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ symmetry: Symmetry guarantee:\nif A sees B → B sees A {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+perception_modes: Perception Modes (D-017) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ natural: Natural Vision\n(walls block, cone model) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ thermal: Thermal\n(heat through walls, no identity) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+ camera: Camera Feeds\n(remote, spoofable) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+ unisphere: Unisphere Tracking\n(known inserts, maskable) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+ audio_analysis: Audio Analysis\n(signatures, no visual) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+}
+
+sound: Sound System (D-018)\nThree-Range Model {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ close: Close (in/near LOS)\nHigh accuracy · stereo audio {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ medium: Medium (outside LOS)\nVisual indicators at fog edge + monologue {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ long: Long (across map)\nInsert notifications · spoofable {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+}
+
+snapshot: ObserverSnapshot {
+ shape: queue
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+kg_read: KnowledgeGraph read\n(per-character — what THIS observer knows) {
+ shape: cylinder
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+fog: Fog Rendering (D-059)\n5-layer shader system {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ clear: Clear (vision cone)\nsoft gradient 6-8 sim tiles {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ light_fog: Light Fog (peripheral)\ndesaturated 40-50%, Perlin noise {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ deep_fog: Deep Fog (explored)\nnear-monochrome + zone tint {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ unexplored_maps: Unexplored (has maps)\ngeometric wireframe outlines {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ unexplored_dark: Unexplored (no maps)\nsolid near-black {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+recognition: Fog Entity Recognition {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ chime: Recognition chime fires (D-067)\nat ONSET of cognitive delay {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ delay: Cognitive delay (D-060)\n0.6s base · 0.3s for anomaly {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ monologue_fire: Monologue fires DURING delay\n"Those footsteps... that's Kael's walk" {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ transition: Grey blob → D-033 color\n+ silhouette over 0.3s {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+world -> los: "entity positions + opaque tiles" { style.stroke: ${color-accent} }
+world -> sound: "sound events" { style.stroke: ${color-stroke} }
+los -> perception_modes: "VisibilityMap" { style.stroke: ${color-accent} }
+perception_modes -> snapshot: "observer query result\n(what this sensor reveals)" {
+ style.stroke: ${color-accent}
+}
+sound -> snapshot: "sound events" { style.stroke: ${color-stroke} }
+kg_read -> snapshot: "per-entity recognition data" { style.stroke: ${color-accent} }
+snapshot -> fog: "visible entities + fog state" { style.stroke: ${color-accent} }
+snapshot -> recognition: "heard/sensed entity not in LOS" { style.stroke: ${color-stroke} }
+recognition.chime -> recognition.delay: "concurrent" { style.stroke: ${color-accent} }
+recognition.delay -> recognition.monologue_fire
+recognition.delay -> recognition.transition
diff --git a/docs/diagrams/data-flow/perception-pipeline.png b/docs/diagrams/data-flow/perception-pipeline.png
new file mode 100644
index 000000000..3b8df192f
Binary files /dev/null and b/docs/diagrams/data-flow/perception-pipeline.png differ
diff --git a/docs/diagrams/entity/dialogue-tags.d2 b/docs/diagrams/entity/dialogue-tags.d2
new file mode 100644
index 000000000..451517766
--- /dev/null
+++ b/docs/diagrams/entity/dialogue-tags.d2
@@ -0,0 +1,101 @@
+# Dialogue + Monologue Tag Taxonomy — D-035, D-028
+# Converged tag schema for tagged line pools.
+# Structural tags (required), selection tags (recommended), monologue additions.
+# Includes Sprint 8 + 14 + 15 amendments.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+dialogue_line: Dialogue Line {
+ shape: sql_table
+ id: "{npc-slug}_{d|m}_{###}" {constraint: primary_key}
+ text: String
+ role: enum (template-defined role)
+ access: list (public · insider · authority · peer · hostile)
+ trust: enum (surface · real · secret)
+ situation: list (14 situations)
+ topic: list (9 topics — optional)
+ mood: list (8 moods — optional)
+ tags: list (freeform — optional)
+}
+
+monologue_line: Monologue Line {
+ shape: sql_table
+ id: "{npc-slug}_m_{s|d}_{###}" {constraint: primary_key}
+ character: enum (smuggler · detective) {constraint: not_null}
+ trigger: enum (9 triggers) {constraint: not_null}
+ prerequisite: map or null
+ dual_lens: map (authoring-only)
+ notes: String (authoring-only)
+}
+
+access_values: AccessTier values {
+ shape: sql_table
+ public: Public — default, anyone
+ insider: Insider — social network member
+ authority: Authority — institutional role
+ peer: Peer — known colleague
+ hostile: Hostile — adversarial context
+}
+
+trust_values: TrustTier values {
+ shape: sql_table
+ surface: Surface — any relationship + any confidence
+ real: Real — (Friendly|Known) + KnowsOf+
+ secret: Secret — Friendly + KnowsDetails+
+}
+
+situation_values: Situation (14) {
+ shape: sql_table
+ s1: first_meeting
+ s2: regular_contact
+ s3: after_conflict
+ s4: work_context
+ s5: social_context
+ s6: private_context
+ s7: witnessed_something
+ s8: under_suspicion
+ s9: post_confrontation
+ s10: trust_broken
+ s11: urgent
+ s12: relaxed
+ s13: farewell
+ s14: greeting
+}
+
+mood_values: Mood (8) {
+ shape: sql_table
+ m1: anxious
+ m2: frustrated
+ m3: content
+ m4: suspicious
+ m5: warm
+ m6: hostile
+ m7: relieved
+ m8: focused
+}
+
+trigger_values: Monologue Triggers (9) {
+ shape: sql_table
+ t1: enter_location
+ t2: observe_npc
+ t3: hear_sound
+ t4: observe_anomaly
+ t5: post_conversation
+ t6: discover_evidence
+ t7: witness_interaction
+ t8: time_idle
+ t9: return_visit
+}
+
+dialogue_line -> access_values: "access field" { style.stroke: ${color-accent} }
+dialogue_line -> trust_values: "trust field" { style.stroke: ${color-accent} }
+dialogue_line -> situation_values: "situation field" { style.stroke: ${color-stroke} }
+dialogue_line -> mood_values: "mood field" { style.stroke: ${color-stroke} }
+monologue_line -> dialogue_line: "extends" { style.stroke: ${color-accent} }
+monologue_line -> trigger_values: "trigger field" { style.stroke: ${color-accent} }
diff --git a/docs/diagrams/entity/dialogue-tags.png b/docs/diagrams/entity/dialogue-tags.png
new file mode 100644
index 000000000..0ecde5204
Binary files /dev/null and b/docs/diagrams/entity/dialogue-tags.png differ
diff --git a/docs/diagrams/entity/knowledge-graph.d2 b/docs/diagrams/entity/knowledge-graph.d2
new file mode 100644
index 000000000..baa84f5c9
--- /dev/null
+++ b/docs/diagrams/entity/knowledge-graph.d2
@@ -0,0 +1,88 @@
+# Knowledge Graph Data Model — D-041
+# Per-entity bevy_ecs Component with BTreeMap storage.
+# Confidence hierarchy, knowledge state, knowledge source, event-driven updates.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+KnowledgeGraph: KnowledgeGraph {
+ shape: sql_table
+ entity_knowledge: BTreeMap
+ fact_knowledge: BTreeMap
+}
+
+EntityKnowledge: EntityKnowledge {
+ shape: sql_table
+ stable_id: StableId(u64) {constraint: foreign_key}
+ confidence: KnowledgeConfidence
+ state: KnowledgeState
+ source: KnowledgeSource
+ last_observed_tick: u64
+}
+
+FactKnowledge: FactKnowledge {
+ shape: sql_table
+ fact_id: FactId {constraint: primary_key}
+ confidence: KnowledgeConfidence
+ state: KnowledgeState
+ source: KnowledgeSource
+ last_observed_tick: u64
+}
+
+KnowledgeConfidence: KnowledgeConfidence {
+ shape: sql_table
+ direct: Direct — currently in LOS
+ knows_details: KnowsDetails — actionable (secret tier)
+ knows_of: KnowsOf — involved in Y (real tier)
+ suspects: Suspects — something off (surface tier)
+}
+
+KnowledgeState: KnowledgeState {
+ shape: sql_table
+ active: Active — believed true
+ contradicted: Contradicted — conflict detected (Sprint 3+)
+ stale: Stale — aged beyond threshold (Sprint 3+)
+}
+
+KnowledgeSource: KnowledgeSource {
+ shape: sql_table
+ direct_observation: DirectObservation
+ heard: Heard
+ told_by: ToldBy (Sprint 3+)
+ inferred: Inferred (Sprint 3+)
+ background: Background
+}
+
+EntityRegistry: EntityRegistry {
+ shape: sql_table
+ stable_to_entity: BTreeMap
+ entity_to_stable: BTreeMap
+}
+
+KnowledgeEventQueue: KnowledgeEventQueue\n(bevy_ecs Resource) {
+ shape: queue
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+KnowledgeGraph -> EntityKnowledge: "contains many"
+KnowledgeGraph -> FactKnowledge: "contains many"
+EntityKnowledge -> KnowledgeConfidence: "uses"
+EntityKnowledge -> KnowledgeState: "uses"
+EntityKnowledge -> KnowledgeSource: "uses"
+FactKnowledge -> KnowledgeConfidence: "uses"
+FactKnowledge -> KnowledgeState: "uses"
+FactKnowledge -> KnowledgeSource: "uses"
+EntityKnowledge -> EntityRegistry: "StableId lookup" {
+ style.stroke: ${color-accent}
+ style.stroke-dash: 4
+}
+KnowledgeEventQueue -> KnowledgeGraph: "drained each tick\n→ writes components" {
+ style.stroke: ${color-accent}
+}
diff --git a/docs/diagrams/entity/knowledge-graph.png b/docs/diagrams/entity/knowledge-graph.png
new file mode 100644
index 000000000..2ed4c0789
Binary files /dev/null and b/docs/diagrams/entity/knowledge-graph.png differ
diff --git a/docs/diagrams/entity/npc-generation.d2 b/docs/diagrams/entity/npc-generation.d2
new file mode 100644
index 000000000..ff62c21c5
--- /dev/null
+++ b/docs/diagrams/entity/npc-generation.d2
@@ -0,0 +1,154 @@
+# NPC Generation Model — D-024, D-025
+# 10 axes + combat component. Social site / functional cluster as atomic template unit.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+direction: right
+
+npc: NPC (generated) {
+ shape: person
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+essential: 7 Essential Axes {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ want: Want {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ secret: Secret / Vulnerability {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ relationships: Relationships (1-3) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ tolerance: Tolerance Threshold {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ routine: Daily Routine {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ info_inv: Information Inventory {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ contentment: Contentment {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+supporting: 3 Supporting Axes {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ personality: Personality Traits (2-3) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ tell: Tell System {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ skills: Skill Set {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+combat_comp: CombatCapability\n(optional ECS component) {
+ shape: package
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+}
+
+key_interactions: Key Contradiction Interactions {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ i1: Want × Secret {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ i2: Routine × Secret {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ i3: Tolerance × Relationships {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ i4: Want × Relationships {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ i5: Personality × Tolerance {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+social_site: Social Site\n(atomic Tier 2 template unit) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ group: Social Group (4-8 NPCs)\n2+ triangles + 1 cross-template triangle {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ cluster: Functional Cluster\n15-40 tiles, internal sightlines {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ ownership: NPC Ownership\nowned by 1 template, referenced by others {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+npc -> essential: "generated with" { style.stroke: ${color-accent} }
+npc -> supporting: "generated with" { style.stroke: ${color-stroke} }
+supporting.skills -> combat_comp: "combat tag → component" {
+ style.stroke: ${color-accent}
+ style.stroke-dash: 4
+}
+essential -> key_interactions: "produces" { style.stroke: ${color-stroke} }
+npc -> social_site: "placed in" { style.stroke: ${color-accent} }
diff --git a/docs/diagrams/entity/npc-generation.png b/docs/diagrams/entity/npc-generation.png
new file mode 100644
index 000000000..27a7f786c
Binary files /dev/null and b/docs/diagrams/entity/npc-generation.png differ
diff --git a/docs/diagrams/state/knowledge-confidence.d2 b/docs/diagrams/state/knowledge-confidence.d2
new file mode 100644
index 000000000..26281433c
--- /dev/null
+++ b/docs/diagrams/state/knowledge-confidence.d2
@@ -0,0 +1,152 @@
+# Knowledge Confidence State Machine — D-041
+# 4-level confidence hierarchy with state tracking and decay.
+# Maps to dialogue trust tiers (D-075) and fog recognition (D-059).
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+none: No Knowledge\n(entity unknown) {
+ shape: oval
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+suspects: Suspects\n"something off" {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+knows_of: KnowsOf\n"X is involved in Y" {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+knows_details: KnowsDetails\n"actionable detail" {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+direct: Direct\n"currently in LOS" {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.bold: true
+}
+
+dialogue_gates: Dialogue Trust Gate (D-075) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ surface: Surface tier\n(any confidence) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ real: Real tier\n(KnowsOf+ AND Friendly/Known) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ secret: Secret tier\n(KnowsDetails+ AND Friendly) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+}
+
+fog_gate: Fog Recognition (D-059) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ recognized: Recognized entity\n(D-033 color + silhouette) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ unrecognized: Grey blob\n(no identity) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+knowledge_states: KnowledgeState {
+ shape: package
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ active: Active (Sprint 2) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ }
+ contradicted: Contradicted (Sprint 3+)\nToldBy conflicts DirectObservation {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+ stale: Stale (Sprint 3+)\naged past DecayThreshold {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+}
+
+none -> suspects: "observe anomaly\nor receive tip" {
+ style.stroke: ${color-stroke}
+}
+suspects -> knows_of: "further observation\nor testimony" {
+ style.stroke: ${color-stroke}
+}
+knows_of -> knows_details: "direct evidence\nor confrontation" {
+ style.stroke: ${color-accent}
+}
+knows_details -> direct: "enter LOS" {
+ style.stroke: ${color-accent}
+}
+direct -> knows_details: "exit LOS" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+
+# Decay path (D-041: once per game-minute)
+knows_details -> knows_of: "decay\n(last_observed_tick age)" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+knows_of -> suspects: "decay" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+
+suspects -> dialogue_gates.surface
+knows_of -> dialogue_gates.real
+knows_details -> dialogue_gates.secret
+
+suspects -> fog_gate.unrecognized
+knows_of -> fog_gate.recognized
+knows_details -> fog_gate.recognized
+
+knows_details -> knowledge_states.contradicted: "ToldBy vs DirectObservation\n(Sprint 3+)" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+knows_of -> knowledge_states.stale: "decay (Sprint 3+)" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+none -> knowledge_states.active
diff --git a/docs/diagrams/state/knowledge-confidence.png b/docs/diagrams/state/knowledge-confidence.png
new file mode 100644
index 000000000..2a39389f5
Binary files /dev/null and b/docs/diagrams/state/knowledge-confidence.png differ
diff --git a/docs/diagrams/state/movement-stances.d2 b/docs/diagrams/state/movement-stances.d2
new file mode 100644
index 000000000..41883dd4b
--- /dev/null
+++ b/docs/diagrams/state/movement-stances.d2
@@ -0,0 +1,111 @@
+# Movement Stance State Machine — D-053, D-054, D-055
+# Toggle ladder: Sprint / Walk / Careful / Crouch / (Prone future).
+# Server-authoritative tile-based movement. Client Tween interpolation.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+sprint: Sprint {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.bold: true
+}
+
+sprint_detail: 1 tile/tick · loud footsteps\nMonologue 40% (urgent only)\nInteraction buffer CLEARED (D-055)\nSprint double-take: anomaly monologue fires retroactively {
+ shape: page
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+walk: Walk\n(default stance) {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+walk_detail: 1 tile/2 ticks · normal footsteps\nMonologue 100%\nDefault — MovementProfile component {
+ shape: page
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+careful: Careful {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+careful_detail: 1 tile/3 ticks · quiet footsteps\nMonologue 150% + tell notice bonus\nListeningFocus accumulates (stationary_ticks)\nEavesdrop bonus: ListeningFocus +2-3dB (D-071) {
+ shape: page
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+crouch: Crouch {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+crouch_detail: 1 tile/4 ticks · very quiet\nUses Prone/Seated occupancy layer (D-054) {
+ shape: page
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+prone: Prone\n(future) {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 5
+}
+
+tile_occupancy: TilePresence (D-054)\nSame-tile occupancy layers {
+ shape: package
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ standing: Standing {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ prone_layer: Prone / Seated {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ fixture_layer: Fixture {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+sprint -> sprint_detail
+walk -> walk_detail
+careful -> careful_detail
+crouch -> crouch_detail
+
+sprint -> walk: "toggle" { style.stroke: ${color-accent} }
+walk -> sprint: "toggle" { style.stroke: ${color-accent} }
+walk -> careful: "toggle" { style.stroke: ${color-stroke} }
+careful -> walk: "toggle" { style.stroke: ${color-stroke} }
+careful -> crouch: "toggle" { style.stroke: ${color-stroke} }
+crouch -> careful: "toggle" { style.stroke: ${color-stroke} }
+crouch -> prone: "toggle (future)" { style.stroke: ${color-stroke}; style.stroke-dash: 4 }
+prone -> crouch: "toggle-out only" { style.stroke: ${color-stroke}; style.stroke-dash: 4 }
+
+crouch -> tile_occupancy.prone_layer: "occupies" { style.stroke: ${color-stroke} }
+walk -> tile_occupancy.standing: "occupies" { style.stroke: ${color-stroke} }
diff --git a/docs/diagrams/state/movement-stances.png b/docs/diagrams/state/movement-stances.png
new file mode 100644
index 000000000..1c1280900
Binary files /dev/null and b/docs/diagrams/state/movement-stances.png differ
diff --git a/docs/diagrams/state/npc-simulation-tiers.d2 b/docs/diagrams/state/npc-simulation-tiers.d2
new file mode 100644
index 000000000..9e4010174
--- /dev/null
+++ b/docs/diagrams/state/npc-simulation-tiers.d2
@@ -0,0 +1,96 @@
+# NPC Simulation Tier State Machine — D-026
+# bevy_ecs dynamic component add/remove makes tier transitions seamless.
+# Scope tags keep NPCs in Active tier.
+
+vars: {
+ color-bg: "#2a3040"
+ color-stroke: "#333340"
+ color-text: "#c8d0e0"
+ color-accent: "#c8d8f0"
+ color-dim: "#1a1e24"
+}
+
+ungenerated: Tier 4\nUngenerated {
+ style.fill: ${color-dim}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 5
+}
+
+state_saved: Tier 3\nState-Saved\n~1-2KB frozen struct {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+background: Tier 2\nBackground\n500-2,000 NPCs {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+active: Tier 1\nActive\n30-80 NPCs · full sim {
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+ style.bold: true
+}
+
+scope_tags: Scope Tags (prevent eviction) {
+ shape: package
+ style.fill: ${color-dim}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+
+ neighborhood: neighborhood {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ active_quest: active-quest {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ colleague: colleague {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ known_contact: known-contact {
+ style.fill: ${color-bg}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+lru: LRU Priority Queue\n(interaction-timestamp) {
+ shape: diamond
+ style.fill: ${color-bg}
+ style.stroke: ${color-accent}
+ style.font-color: ${color-text}
+}
+
+ungenerated -> state_saved: "player approach\n(generated ahead)" {
+ style.stroke: ${color-stroke}
+}
+state_saved -> background: "player return\n(~2-5ms reactivation)" {
+ style.stroke: ${color-stroke}
+}
+background -> state_saved: "LRU eviction" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+background -> active: "player proximity\nor scope tag match" {
+ style.stroke: ${color-accent}
+}
+active -> background: "LRU eviction\n(no scope tag)" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+scope_tags -> active: "prevents eviction" {
+ style.stroke: ${color-accent}
+ style.stroke-dash: 4
+}
+lru -> active: "manages" { style.stroke: ${color-accent} }
+lru -> background: "manages" { style.stroke: ${color-stroke} }
diff --git a/docs/diagrams/state/npc-simulation-tiers.png b/docs/diagrams/state/npc-simulation-tiers.png
new file mode 100644
index 000000000..403e849b0
Binary files /dev/null and b/docs/diagrams/state/npc-simulation-tiers.png differ
diff --git a/docs/diagrams/ui/fog-layers.d2 b/docs/diagrams/ui/fog-layers.d2
new file mode 100644
index 000000000..64eb08d46
--- /dev/null
+++ b/docs/diagrams/ui/fog-layers.d2
@@ -0,0 +1,108 @@
+# Fog System — Five Layers + Entity Rendering in Fog — D-059
+# Shader-based, knowledge-graph-driven. Covers all five fog states
+# and the entity types rendered within fogged areas.
+
+vars: {
+ color-screen: "#1a1e24"
+ color-panel: "#2a3040"
+ color-text: "#c8d0e0"
+ color-nav: "#c8d8f0"
+ color-stroke: "#333340"
+}
+
+direction: right
+
+kg_read: KnowledgeGraph\n(per character — D-041) {
+ shape: cylinder
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+}
+
+fog_layers: Five Fog Layers {
+ style.fill: ${color-screen}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+
+ l1: Layer 1 — Clear (vision cone)\nSoft gradient edge 6-8 sim tiles\n(= 3-4 visual tiles per D-066)\nNo hard line · Darkwood approach {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ style.bold: true
+ }
+ l2: Layer 2 — Light Fog (peripheral)\nDesaturated 40-50% · brightness -30%\nAnimated Perlin noise (8-10s cycle)\nD-033 entity colors visible but reduced {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l3: Layer 3 — Deep Fog (explored)\nNear-monochrome + ~10% zone tint\n(bar=warm dark · hub=cool dark)\nMore pronounced noise (15-20s cycle)\nzone_id from ObserverSnapshot (D-077) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l4: Layer 4 — Unexplored (has maps)\nGeometric wireframe outlines\nInsert data aesthetic {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l5: Layer 5 — Unexplored (no maps)\nSolid near-black\nInformation zero {
+ style.fill: ${color-screen}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+fog_entities: Entities in Fog {
+ style.fill: ${color-screen}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ sound_ping: Sound Ping\n2-3 concentric expanding rings\n(sonar-style · insert white-blue)\nLoud=3 rings bright · quiet=1 ring faint\nFade over 1.5s {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+ recognized: Recognized Entity\nD-033 color glow + silhouette feature\n(e.g. identifying clothing detail)\n0.8s breathing pulse\n+/-0.5 tile position drift {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+ unrecognized: Unrecognized Entity\nneutral grey blob\nno silhouette · no identity {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+divergence: "Same fog, different information per character\nSmuggler: recognizes dock workers = green icons\nDetective: same entities = grey blobs\n\nFog is not darkness —\nit is the absence of your attention." {
+ shape: page
+ style.fill: ${color-screen}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ style.italic: true
+}
+
+perf: Performance Budget\n<1ms/frame total\nVision cone = PointLight2D\nFog = CanvasGroup noise shader (Layer 5)\nSound pings = 0-5 sprites\nEntity ghosts = 0-10 sprites typical {
+ shape: package
+ style.fill: ${color-screen}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+}
+
+kg_read -> fog_layers: "determines entity recognition\n(which observer sees which entities)" {
+ style.stroke: ${color-nav}
+}
+kg_read -> fog_entities.recognized: "KnowsOf+ → recognized" {
+ style.stroke: ${color-nav}
+}
+kg_read -> fog_entities.unrecognized: "Suspects / none → blob" {
+ style.stroke: ${color-stroke}
+}
+fog_layers -> fog_entities: "entities rendered within fog state" {
+ style.stroke: ${color-stroke}
+}
+fog_layers -> divergence: "same shader, per-character KG" {
+ style.stroke: ${color-nav}
+ style.stroke-dash: 4
+}
+fog_layers -> perf
diff --git a/docs/diagrams/ui/fog-layers.png b/docs/diagrams/ui/fog-layers.png
new file mode 100644
index 000000000..f1e2a3aa3
Binary files /dev/null and b/docs/diagrams/ui/fog-layers.png differ
diff --git a/docs/diagrams/ui/hud-layout.d2 b/docs/diagrams/ui/hud-layout.d2
new file mode 100644
index 000000000..f53d1ba86
--- /dev/null
+++ b/docs/diagrams/ui/hud-layout.d2
@@ -0,0 +1,146 @@
+# HUD Layout and Z-Stack — D-049, D-048, D-061, D-076
+# 8-layer z-stack, insert overlay, dialogue box constraints.
+# Dual-scale grid context: 64x64px visual tiles (D-066).
+
+vars: {
+ color-screen: "#1a1e24"
+ color-panel: "#2a3040"
+ color-text: "#c8d0e0"
+ color-nav: "#c8d8f0"
+ color-stroke: "#333340"
+}
+
+direction: down
+
+zstack: Rendering Z-Stack (D-049)\n8 layers — bottom to top {
+ style.fill: ${color-screen}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+
+ l0: Layer 0 — Floor Tiles\n(zone identity · movement surface) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l1: Layer 1 — Floor Objects\n(cosmetic · walked over) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l2: Layer 2 — Furniture / Placed Objects\n(y-sorted with entities) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l3: Layer 3 — Entity Sprites\n(y-sorted · D-033 relationship color\n24x32px footprint in 64x64 tile) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+ l4: Layer 4 — Overhead / Wall Tops\n(pipes · ducts · fixtures · signage\nsemi-transparent partial occlusion) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ l5: Layer 5 — Fog of Perception\n(vision cone mask · fog shader\naffects layers 0-4) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+ l6: Layer 6 — Insert Overlay\n(lattice HUD · bloom-rendered\nNOT affected by fog\nInteraction labels render here) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+ l7: Layer 7 — Monologue / UI\n(always visible · top of stack\nDialogue box and response options) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ style.bold: true
+ }
+}
+
+insert_overlay: Insert Overlay Design (D-048) {
+ style.fill: ${color-screen}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+
+ bloom: Soft bloom shader\n~2-3px gaussian · ~40% blend {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ smuggler_overlay: Smuggler\nthinner · sparser (baseline lattice) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ detective_overlay: Detective\ndenser · crisper (augmented lattice) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+ insert_off: Insert OFF behavior (OQ-07)\nCursor shape changes\nInteraction labels hidden {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+}
+
+dialogue_box: Dialogue Box (D-061, D-076) {
+ style.fill: ${color-screen}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+
+ position: Bottom of screen\nmax 20% height {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ max_width: Max-width 1200px\n(centered · ~62% of 1920px) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ log: Unified conversation log\n(active + overheard — chronological) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+ speaker_format: Speaker to Target format\nper-character name colours {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ responses: Response options below log\nmax 3 visible {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ no_portraits: No portraits\n(NPC is on screen) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ timeout: Entry timeout\n15s + 3s fade (configurable) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+monologue_position: Monologue floats ABOVE dialogue box\n(z-layer 7 — spatial separation\ncharacter thinks while NPC speaks) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ style.border-radius: 4
+}
+
+zstack.l5 -> insert_overlay: "does NOT affect" {
+ style.stroke: ${color-nav}
+ style.stroke-dash: 4
+}
+zstack.l6 -> insert_overlay: "rendered at" { style.stroke: ${color-nav} }
+zstack.l7 -> dialogue_box: "rendered at" { style.stroke: ${color-nav} }
+dialogue_box -> monologue_position: "monologue above" { style.stroke: ${color-nav} }
diff --git a/docs/diagrams/ui/hud-layout.png b/docs/diagrams/ui/hud-layout.png
new file mode 100644
index 000000000..e247a9221
Binary files /dev/null and b/docs/diagrams/ui/hud-layout.png differ
diff --git a/docs/diagrams/ui/interaction-flow.d2 b/docs/diagrams/ui/interaction-flow.d2
new file mode 100644
index 000000000..67840c3d6
--- /dev/null
+++ b/docs/diagrams/ui/interaction-flow.d2
@@ -0,0 +1,153 @@
+# Interaction UI Flow — D-056, D-057, D-058
+# Cursor states, entity interaction list, world radial menu.
+# All insert-styled: geometric, bloom, diegetic.
+
+vars: {
+ color-screen: "#1a1e24"
+ color-panel: "#2a3040"
+ color-text: "#c8d0e0"
+ color-nav: "#c8d8f0"
+ color-stroke: "#333340"
+}
+
+direction: down
+
+cursor: Cursor States (D-056)\n(insert's own interface element · z-layer 7) {
+ style.fill: ${color-screen}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+
+ default_cursor: Default\n4 thin inward ticks + bloom\nWhite-blue · barely visible {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ entity_hover: Entity Hover\nTicks expand outward (150ms)\nCorner brackets · D-033 relationship color\nVerb tooltip in insert styling {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+ object_hover: Object Hover\nTicks rotate 45° → X-shape\nMuted grey (amber if flagged) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ weapon_aim: Weapon Aim\nTicks extend · center gap widens\nLines 1 to 2px · warm white\nNO bloom · D-033 tint on target {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+}
+
+cursor_rules: Cursor Rules {
+ style.fill: ${color-screen}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+
+ los_trigger: Changes on LOS · not just proximity {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ range: Interaction range ~2 sim tiles (= 1m) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ weapon_suppress: Weapon mode suppresses interaction prompts\n(unless Shift held) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ insert_off: Insert OFF: cursor shape still changes\n(body reacts) · verb labels hidden\n(insert doesn't process targets) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+}
+
+entity_list: Entity Interaction List (D-057)\n(left-click on entity) {
+ style.fill: ${color-screen}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+
+ format: Compact vertical list\nanchored to entity position\n2-4 options max {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ new_glow: New options: gradient glow background\n(knowledge change signal) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+ z_layer: Labels on z-layer 6\n(insert data — hidden if insert off) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ phase1: Phase 1 (simulation): max verb set\nfrom ObjectType component {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ phase2: Phase 2 (observer): filter by KG\narchetype variation · POI flip {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ }
+}
+
+world_menu: World Radial Menu (D-058)\n(right-click) {
+ style.fill: ${color-screen}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+
+ v01_spokes: v0.1: 2 spokes\nObserve (eye) · Insert (phone) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-nav}
+ style.font-color: ${color-text}
+ style.bold: true
+ }
+ future_spokes: Future: up to 6 spokes\n+ Comms (signal) · Wait (clock) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ style.stroke-dash: 4
+ }
+ input: Drag-release (power users)\nClick-click (newcomers) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+ why_radial: Fixed categories → spatial memory\n(unlike entity verbs which vary by knowledge) {
+ style.fill: ${color-panel}
+ style.stroke: ${color-stroke}
+ style.font-color: ${color-text}
+ }
+}
+
+cursor.default_cursor -> cursor.entity_hover: "entity in LOS\n+ proximity" {
+ style.stroke: ${color-nav}
+}
+cursor.default_cursor -> cursor.object_hover: "object in LOS\n+ proximity" {
+ style.stroke: ${color-stroke}
+}
+cursor.entity_hover -> cursor.weapon_aim: "weapon selected" {
+ style.stroke: ${color-nav}
+}
+cursor.weapon_aim -> cursor.entity_hover: "weapon deselected" {
+ style.stroke: ${color-stroke}
+ style.stroke-dash: 4
+}
+
+cursor.entity_hover -> entity_list: "left-click\n→ shows interaction list" {
+ style.stroke: ${color-nav}
+}
+cursor.default_cursor -> world_menu: "right-click\n→ opens radial" {
+ style.stroke: ${color-nav}
+}
+
+cursor -> cursor_rules
+entity_list.phase1 -> entity_list.phase2: "filtered" { style.stroke: ${color-nav} }
diff --git a/docs/diagrams/ui/interaction-flow.png b/docs/diagrams/ui/interaction-flow.png
new file mode 100644
index 000000000..13e13a622
Binary files /dev/null and b/docs/diagrams/ui/interaction-flow.png differ