Files
settled-reach/docs/workshops/knowledge-graph-information-boundaries/round2-gestalt.md
T
jpmschweitzerandClaude Opus 4.6 da63a93446 docs(docs): add frontmatter to knowledge-graph-information-boundaries workshop
Standardized YAML frontmatter on all 10 files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 23:40:51 +01:00

22 KiB
Raw Blame History

title, description, type, status, workshop, agent, round, created
title description type status workshop agent round created
Knowledge Graph Workshop — Round 2: Mechanics Validation Gestalt's cross-validation of gameplay mechanics against the proposed knowledge graph architecture workshop archived knowledge-graph-information-boundaries gestalt 2 2026-02-11

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<StableId, EntityKnowledge> ✓ 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:

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):

prerequisite:
  knowledge:
    - subject: kael_davan
      understanding: knows_details  # Not confidence level
      flag: trying_to_exit_ring

Dialogue access tiers use DEPTH + relationship:

access: [insider]
trust: secret
prerequisite:
  knowledge:
    - subject: voss
      understanding: knows_details
      flag: false_manifests

Decay mechanics use SOURCE (Tyre's axis):

if knowledge.confidence == KnowledgeConfidence::Stale {
    // Decay faster or remove
}

Contradiction detection compares SOURCES:

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

// 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

// 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

// 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

// Knowledge validation system (new system, Sprint 3)
fn detect_contradictions(
    mut query: Query<(&mut KnowledgeGraph, &CharacterArchetype)>,
    time: Res<SimulationTime>,
) {
    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

// 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

// 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):

pub struct MetaKnowledge {
    /// I know that entity X knows about entity Y
    pub knowledge_of_knowledge: BTreeMap<StableId, BTreeSet<StableId>>,
}

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:

pub enum KnowledgeChangeType {
    NewEntity(StableId),
    LevelUpgrade { entity: StableId, old: UnderstandingLevel, new: UnderstandingLevel },
    Contradiction { entity: StableId, sources: Vec<KnowledgeSource> },
    FactLearned(FactId),
}

#[derive(Resource)]
pub struct KnowledgeChangeEvents {
    pub changes: Vec<KnowledgeChangeType>,
}

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):

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/mnt/data/projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-tyre.md
  • /var/mnt/data/projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-dudley.md
  • /var/mnt/data/projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-paula.md
  • /var/mnt/data/projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-gestalt.md
  • /var/mnt/data/projects/settled-reach/planning/docs/workshops/knowledge-graph-information-boundaries/round1-si.md
  • /var/mnt/data/projects/settled-reach/planning/decisions/perception.md (D-011, D-015-D-019, D-033)
  • /var/mnt/data/projects/settled-reach/planning/decisions/content.md (D-024, D-028, D-034, D-035)
  • /var/mnt/data/projects/settled-reach/planning/decisions/architecture.md (D-010, D-020, D-026, D-030)
  • /var/mnt/data/projects/settled-reach/planning/decisions/questions.md (Q-016, Q-017)