# Knowledge Graph Workshop — Round 2: Mechanics Validation **Role:** GESTALT (Systems Designer) **Focus:** Cross-validation of gameplay mechanics against proposed architecture **Date:** 2026-02-11 --- ## Executive Summary *cracks knuckles* Let me break down what this actually means mechanically. The good news: **The architecture serves gameplay.** Tyre and Dudley's proposed structs support every gameplay loop I identified in Round 1. The per-entity Component pattern, BTreeMap storage, StableId references, and 5-level confidence enum are ALL mechanically sound. I found zero architectural blockers. The hierarchy debate: **We need BOTH axes.** Tyre's SOURCE-based levels (Stale/Rumor/Inferred/Observed/Direct) and Paula's DEPTH-based levels (Unaware/Suspects/KnowsOf/KnowsDetails/Understands) are solving DIFFERENT problems. Source determines confidence decay and trust. Depth determines dialogue gating and monologue progression. The solution is NOT picking one — it's recognizing they're orthogonal and mapping them correctly. THE FRIEND arc validation: **It works.** I walked the proposed structs through THE FRIEND's lie detection sequence step-by-step. Every beat is supported: lie entry, contradiction detection, monologue trigger, relationship state shift, dialogue unlock. No gaps. The one gap: **Observable gossip is underspecified.** Tyre and Dudley defined the data structures for NPC-to-NPC knowledge transfer, but the GAMEPLAY loop isn't there yet. How does the player DETECT that two NPCs just shared information? This is critical for political intrigue and deferred to Sprint 3+, which is correct — but the design needs to exist now so the architecture doesn't preclude it. **Bottom line:** Ship the Sprint 2 stub as specified. It's the right foundation. --- ## 1. Architecture Serves Gameplay: YES ### What Gameplay Needs From my Round 1 analysis, the knowledge graph must support: 1. **Monologue gating** — lines filtered by confidence level 2. **Dialogue access** — conversation options unlocked by knowledge state 3. **Gossip observation** — player detects NPC-to-NPC knowledge transfer 4. **Contradiction detection** — conflicting knowledge sources trigger monologue 5. **Triangle pressure** — knowledge propagation increases social conflict ### What the Architecture Provides **Tyre's Component model + BTreeMap + StableId:** - Per-entity `KnowledgeGraph` Component ✓ Supports monologue/dialogue queries per observer - `BTreeMap` ✓ O(1) lookups for "what do I know about X?" - `KnowledgeSource` enum ✓ Tracks where knowledge came from (needed for contradiction detection) - `last_observed_tick` + `last_updated_tick` ✓ Supports decay mechanic - `RelationshipState` embedded in EntityKnowledge ✓ Drives D-033 entity color **Dudley's event queue + decay batching:** - `KnowledgeEventQueue` ✓ Decouples perception from knowledge updates - Batch decay (600-tick intervals) ✓ Prevents O(N×M) per-tick explosion - `EntityIdMap` for StableId ↔ Entity translation ✓ Solves save/load stability **Every gameplay requirement maps to a struct field or query method.** No missing pieces for Sprint 2 scope. ### What's NOT in Sprint 2 (Correctly Deferred) - NPC-to-NPC gossip propagation (architectural support exists, systems don't) - `ToldBy` source generation (enum variant exists, no system writes it yet) - Knowledge-driven NPC behavior changes (knowledge updates don't feed AI yet) - Misinformation (deliberately wrong entries — requires content flag) **This split is mechanically sound.** Sprint 2's goal is geometric fog, not social intrigue. The stub enables observer snapshot filtering. Full knowledge gameplay comes Sprint 3+. --- ## 2. Knowledge Hierarchy Debate: My Updated Position ### The Problem Statement We have four proposals: - **Tyre:** 5 levels (Stale/Rumor/Inferred/Observed/Direct) — SOURCE-based confidence - **Paula:** 5 levels (Unaware/Suspects/KnowsOf/KnowsDetails/Understands) — DEPTH-based understanding - **Me (Round 1):** 3 levels (Suspects/KnowsOf/KnowsDetails) — simplified gating - **Dudley:** 4 levels (Suspects/KnowsOf/KnowsDetails/DirectObservation) — hybrid ### The Realization These are NOT competing hierarchies. They're answering different questions: **Tyre's SOURCE-based axis:** - Question: "How reliable is this knowledge?" - Use case: Decay rates, trust modeling, CauseChain provenance - Example: "I heard this from Lera (Rumor) vs I saw it myself (Direct)" **Paula's DEPTH-based axis:** - Question: "How much do I understand about this situation?" - Use case: Monologue progression, dialogue unlocks, narrative revelation - Example: "I suspect Kael is compromised (Suspects) vs I know he's trying to exit the ring to protect Hael (Understands)" **These are ORTHOGONAL.** You can have: - High source confidence, low depth: "I directly observed Kael in corridor B-7 (Direct), but I don't know why (KnowsOf)" - Low source confidence, high depth: "Lera told me Kael is trying to exit the ring to protect Hael (Rumor), and it explains everything (Understands)" ### The Mechanical Solution The architecture should support BOTH: ```rust pub struct EntityKnowledge { // Tyre's SOURCE confidence (decay, trust) pub confidence: KnowledgeConfidence, // Stale < Rumor < Inferred < Observed < Direct // Paula's DEPTH understanding (gating, progression) pub understanding: UnderstandingLevel, // Suspects < KnowsOf < KnowsDetails < Understands pub source: KnowledgeSource, // Provenance // ... other fields } pub enum KnowledgeConfidence { Stale, // Old information, may be outdated Rumor, // Third-hand, low trust Inferred, // Deduced from other facts Observed, // First-hand but not currently visible Direct, // Currently in LOS } pub enum UnderstandingLevel { Suspects, // "Something's off" KnowsOf, // "X is happening" KnowsDetails, // "X is happening because Y" Understands, // "X is happening because Y, and it means Z" } ``` ### What Gates What **Monologue prerequisites** use DEPTH (Paula's axis): ```yaml prerequisite: knowledge: - subject: kael_davan understanding: knows_details # Not confidence level flag: trying_to_exit_ring ``` **Dialogue access tiers** use DEPTH + relationship: ```yaml access: [insider] trust: secret prerequisite: knowledge: - subject: voss understanding: knows_details flag: false_manifests ``` **Decay mechanics** use SOURCE (Tyre's axis): ```rust if knowledge.confidence == KnowledgeConfidence::Stale { // Decay faster or remove } ``` **Contradiction detection** compares SOURCES: ```rust if knowledge_a.source == DirectObservation && knowledge_b.source == ToldBy(sera) { // Sera lied to you trigger_contradiction_monologue(); } ``` ### Recommendation for Synthesis **Keep Tyre's `KnowledgeConfidence` enum as-is.** It's the correct model for SOURCE reliability and maps cleanly to decay/trust. **Add a second field: `understanding_level: UnderstandingLevel`** to EntityKnowledge for DEPTH tracking. **Initial implementation:** Both fields exist in Sprint 2 structs, but only `confidence` is written by perception systems. `understanding_level` defaults to `KnowsOf` (you observed the entity, so you know of them). Sprint 3+ adds systems that upgrade understanding through dialogue/evidence/inference. **This is "design for it now, build it later."** The struct supports both axes. Sprint 2 uses one axis. Sprint 3 unlocks the other. --- ## 3. Gossip as Observable Mechanic ### The Gameplay Loop (What I Proposed in Round 1) ``` 1. Player tells Voss about Kael's smuggling 2. Hours later, player observes Voss and Maret talking 3. Next day, Maret confronts Kael 4. Monologue: "Voss told Maret. Information spreads. I started this." ``` ### Does the Architecture Support This? **Step 1 (player tells NPC):** - Dialogue system sets flag: `voss.knowledge.entities[kael_id].knows_fact("smuggling") = true` - Source: `ToldBy { source_id: player_stable_id, tick }` - ✓ Supported by Dudley's `KnowledgeSource::ToldBy` variant **Step 2 (observe conversation):** - **GAP.** How does the player DETECT that Voss and Maret are having a "gossip-worthy" conversation vs casual small talk? - Proposed mechanic: Observable tell system - NPCs in conversation get `situation: private_conversation` tag - Visual: NPCs face each other, proximity threshold, gestures - Audio: D-018 sound ranges (player hears murmurs if within Medium range) - Monologue: "Voss and Maret, heads together. Plotting or gossiping?" **Step 3 (Maret confronts Kael):** - NPC AI system (Sprint 3+) queries Maret's knowledge graph - Finds: `kael.knows_fact("smuggling")` with high confidence - Triangle pressure increased (Q-017 mechanism) - Activates `situation: confrontation` for Maret's next interaction with Kael - ✓ Supported by proposed architecture (knowledge graph feeds AI decisions) **Step 4 (monologue commentary):** - Player observes Maret confronting Kael - Monologue system queries player knowledge: "Did I tell anyone about Kael?" - Meta-knowledge check: Player knows they told Voss, and they know Voss talked to Maret - ✓ Requires meta-knowledge tracking: "I know X knows Y" ### What's Missing **Observable gossip indicators:** - Visual tells when NPCs share information (not just "talking" but "sharing secrets") - Monologue commentary when player detects gossip propagation - Meta-knowledge structure: tracking "I know that X knows Y" **Is this a Sprint 2 blocker?** NO. Sprint 2 is geometric fog. Gossip observation is social intrigue (Sprint 3+). **Is this an architectural blocker?** NO. The knowledge graph structure supports it. The missing piece is the NPC conversation system and tell generation (separate from knowledge graph design). **Recommendation:** Gossip propagation is correctly deferred, but Tyre's Round 2 synthesis should explicitly state: "NPC-to-NPC knowledge transfer will require conversation system + observable tell generation (Sprint 3 dependency)." --- ## 4. THE FRIEND Arc Walkthrough Let me validate the architecture against Paula's canonical example: THE FRIEND lies to you, you discover the contradiction. ### The Sequence (Using Proposed Structs) **T=0: Session start** ```rust // Detective's knowledge graph at spawn detective.knowledge.entities[sera_id] = EntityKnowledge { confidence: KnowledgeConfidence::Observed, understanding: UnderstandingLevel::KnowsOf, source: KnowledgeSource::Background, relationship: RelationshipState::Friendly, // THE FRIEND last_observed_tick: 0, known_attributes: btreemap! { AttributeKey::Name => AttributeValue::Text("Sera Venn"), AttributeKey::Role => AttributeValue::Text("Scanner operator"), }, // ... }; ``` **T=100: Sera tells you Kael was at the dock** ```rust // Dialogue system writes to detective's knowledge graph detective.knowledge.entities[kael_id] .known_attributes .insert( AttributeKey::Custom("location_second_shift"), AttributeValue::Text("dock") ); detective.knowledge.facts.insert( FactId("kael_location_shift2_dock"), FactKnowledge { confidence: KnowledgeConfidence::Rumor, // Told by someone source: KnowledgeSource::ToldBy { source_id: sera_id, tick: 100 }, acquired_tick: 100, } ); ``` **T=200: You observe Kael in corridor B-7** ```rust // Perception system (after shadowcasting detects Kael in LOS) detective.knowledge.observe_entity( kael_id, TilePosition { x: 47, y: 12, z: 2 }, // corridor B-7 200 // current tick ); // This writes: detective.knowledge.entities[kael_id] = EntityKnowledge { last_known_position: Some(TilePosition { x: 47, y: 12, z: 2 }), last_observed_tick: 200, confidence: KnowledgeConfidence::Direct, source: KnowledgeSource::DirectObservation { tick: 200 }, // ... }; ``` **T=201: Contradiction detection** ```rust // Knowledge validation system (new system, Sprint 3) fn detect_contradictions( mut query: Query<(&mut KnowledgeGraph, &CharacterArchetype)>, time: Res, ) { for (mut knowledge, character) in &mut query { // Check for conflicting location facts let kael_facts: Vec<_> = knowledge.facts.iter() .filter(|(id, _)| id.0.starts_with("kael_location")) .collect(); if kael_facts.len() > 1 { // Multiple location claims let told_fact = kael_facts.iter() .find(|(_, f)| matches!(f.source, KnowledgeSource::ToldBy { .. })); let observed_fact = kael_facts.iter() .find(|(_, f)| matches!(f.source, KnowledgeSource::DirectObservation { .. })); if let (Some((told_id, told)), Some((obs_id, obs))) = (told_fact, observed_fact) { // Contradiction detected if let KnowledgeSource::ToldBy { source_id, .. } = told.source { // Sera lied knowledge.entities.get_mut(&source_id).map(|sera| { sera.relationship = RelationshipState::PersonOfInterest; }); // Emit monologue event events.send(MonologueEvent { character: *character, trigger: TriggerType::KnowledgeContradiction, chime: ChimeLevel::Urgent, text_id: "mon_sera_lied_location", }); } } } } } ``` **T=202: Monologue fires** ``` [Urgent chime] "Sera told me Kael was at the dock. But I just saw him in the corridor. Why did she lie?" ``` **T=203: Entity color shift** ```rust // Observer snapshot assembly (feeds client) let sera_relationship = detective.knowledge.relationship_with(&sera_id); // sera_relationship == RelationshipState::PersonOfInterest snapshot.entities.push(VisibleEntity { entity_id: sera_id.0, relationship: RelationshipState::PersonOfInterest, // Amber color // ... }); ``` **T=210: Dialogue unlock** ```rust // Dialogue system (Sprint 3) queries knowledge for confrontation access let can_confront_sera = detective.knowledge .entities .get(&sera_id) .map(|k| k.relationship == RelationshipState::PersonOfInterest) .unwrap_or(false); if can_confront_sera { dialogue_options.push(DialogueOption { text: "Why did you lie about Kael's location?", access: AccessTier::Insider, situation: Situation::Confrontation, topic: Topic::SeraLied, }); } ``` ### Validation Result ✓ **Every beat is supported:** - Lie entry (ToldBy source) - Contradiction detection (compare sources) - Monologue trigger (KnowledgeContradiction event) - Relationship state shift (PersonOfInterest enum) - Entity color change (RelationshipState in snapshot) - Dialogue unlock (relationship state gates access) ✓ **No gaps in data structures.** ✓ **Two new systems needed (Sprint 3):** 1. `detect_contradictions` system (runs after knowledge updates) 2. Dialogue access filtering by knowledge + relationship state **THE FRIEND arc proves the architecture.** If it supports this, it supports the game. --- ## 5. Gaps Found ### Gap 1: Meta-Knowledge (Who Knows What) **What's missing:** Tracking "I know that X knows Y." **Why it matters:** Gossip observation gameplay requires the player to infer knowledge propagation. **Example:** ``` Player tells Voss about Kael Player observes Voss talking to Maret Player infers: "Maret probably knows now" ``` **Proposed solution (Sprint 3):** ```rust pub struct MetaKnowledge { /// I know that entity X knows about entity Y pub knowledge_of_knowledge: BTreeMap>, } ``` **Is this a blocker?** No. Sprint 2 doesn't need it. Social intrigue does (Sprint 3+). ### Gap 2: Knowledge Update Triggers Monologue **What's underspecified:** How does a knowledge graph change trigger a monologue? **Tyre's architecture:** Knowledge update systems write to KnowledgeGraph Component. Monologue system reads Changed. **Missing detail:** What counts as "monologue-worthy" knowledge change? - New entity observed? (Always trigger) - Existing entity re-observed? (Don't spam) - Knowledge level upgraded? (Trigger) - Contradiction detected? (Urgent trigger) **Proposed solution:** ```rust pub enum KnowledgeChangeType { NewEntity(StableId), LevelUpgrade { entity: StableId, old: UnderstandingLevel, new: UnderstandingLevel }, Contradiction { entity: StableId, sources: Vec }, FactLearned(FactId), } #[derive(Resource)] pub struct KnowledgeChangeEvents { pub changes: Vec, } ``` Monologue system consumes these events, not Changed (too noisy). ### Gap 3: Dialogue System Integration Not Specified **What's missing:** The dialogue system needs to query the knowledge graph for access filtering. **Current state:** D-028 defines access tiers. D-035 defines prerequisite tags. But the INTEGRATION is not specified. **Needed (Sprint 3):** ```rust pub fn dialogue_available( npc_id: StableId, line: &DialogueLine, player_knowledge: &KnowledgeGraph, player_relationship: &RelationshipState, ) -> bool { // Check access tier if !line.access.contains(&player_relationship.tier()) { return false; } // Check knowledge prerequisites for prereq in &line.prerequisites { match prereq { Prerequisite::Knowledge { subject, understanding, flag } => { if !player_knowledge.has_understanding(subject, *understanding, flag) { return false; } } // ... } } true } ``` **Is this a blocker?** No. Dialogue gating is Sprint 3+. But the interface contract should be defined now. ### Gap 4: Observable Gossip (Already Covered in Section 3) See section 3. Not a blocker, but needs explicit acknowledgment in synthesis. --- ## 6. Recommendation for Synthesis ### What Tyre Should Include 1. **Accept the dual-axis model:** - `KnowledgeConfidence` (Tyre's SOURCE axis) for decay/trust - `UnderstandingLevel` (Paula's DEPTH axis) for gating/progression - Both fields in `EntityKnowledge` struct - Sprint 2 uses confidence only, Sprint 3 unlocks understanding 2. **Acknowledge gossip gap:** - "NPC-to-NPC knowledge transfer is architecturally supported but requires NPC conversation system (Sprint 3 dependency)" - "Observable gossip indicators (tells, situation tags) are separate from knowledge graph design" 3. **Define knowledge change events:** - `KnowledgeChangeEvents` resource for monologue triggering - Separates "graph changed" from "monologue-worthy change" 4. **Specify dialogue integration interface:** - `dialogue_available()` function signature - How knowledge prerequisites map to line filtering - Defer implementation to Sprint 3, but contract exists now 5. **Validate THE FRIEND arc:** - Include walkthrough from section 4 as proof the architecture works - Confirms no structural gaps for core gameplay ### What Tyre Should Change **Nothing architectural.** The Component model, BTreeMap, StableId, and 5-level confidence enum are all correct. **Additions only:** - Add `understanding_level: UnderstandingLevel` field to `EntityKnowledge` - Add `KnowledgeChangeEvents` resource to API - Add note about gossip system dependency ### Sprint 2 Stub Confirmation Ship exactly what Tyre specified: - Full `KnowledgeGraph` struct - `KnowledgeConfidence` enum (5 levels) - `observe_entity()` method (DirectObservation source) - `relationship_with()` query (entity color) - `knows_fact()` + `fact_at_least()` (monologue prerequisites) - Basic decay (batch, 600-tick intervals) This is the right foundation. No rework needed. --- ## 7. What's NOT Missing Let me be clear about what does NOT need to change: **Data structures:** Perfect as-is. BTreeMap, StableId, Component pattern, serialization strategy — all mechanically sound. **Performance:** Dudley's batch decay + event queue architecture prevents the O(N×M) explosion I was worried about in Round 1. Knowledge lookups are not on the critical path. **Save/load:** StableId solves entity reference stability. I have no concerns. **Tier serialization:** Knowledge graph rides the existing tier system. No special logic needed. **Observer snapshot integration:** `relationship_with()` + `known_entities()` queries are exactly what #112 needs. **The architecture is not the problem.** The missing pieces are SYSTEMS that use the architecture (gossip, dialogue integration, contradiction detection). Those are Sprint 3+, correctly deferred. --- ## 8. Final Verdict **Does the architecture serve gameplay?** YES. **Is the hierarchy debate resolved?** YES (dual-axis model). **Does THE FRIEND arc work?** YES (validated step-by-step). **Is gossip underspecified?** YES (but correctly deferred to Sprint 3). **Are there blocking gaps?** NO. **Should we ship the Sprint 2 stub?** YES, exactly as specified. **Bottom line:** This is interesting-complex, not annoying-complex. The knowledge graph creates DECISIONS (dialogue gates, monologue progression, social manipulation), not BOOKKEEPING (no Pokemon collection, no perfect memory, no hidden NPC knowledge). The player experiences knowledge through what actions are available, not through a sprawling journal UI. Let's build this. --- ## Files Referenced - `/var/home/jeroenschweitzer/Projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-tyre.md` - `/var/home/jeroenschweitzer/Projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-dudley.md` - `/var/home/jeroenschweitzer/Projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-paula.md` - `/var/home/jeroenschweitzer/Projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-gestalt.md` - `/var/home/jeroenschweitzer/Projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-si.md` - `/var/home/jeroenschweitzer/Projects/settled-reach/planning/decisions/perception.md` (D-011, D-015-D-019, D-033) - `/var/home/jeroenschweitzer/Projects/settled-reach/planning/decisions/content.md` (D-024, D-028, D-034, D-035) - `/var/home/jeroenschweitzer/Projects/settled-reach/planning/decisions/architecture.md` (D-010, D-020, D-026, D-030) - `/var/home/jeroenschweitzer/Projects/settled-reach/planning/decisions/questions.md` (Q-016, Q-017)