Standardized YAML frontmatter on all 12 files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
31 KiB
title, description, type, status, workshop, agent, round, created
| title | description | type | status | workshop | agent | round | created |
|---|---|---|---|---|---|---|---|
| Workshop Outcomes — Knowledge Flow & NPC Information Boundaries | Final workshop outcomes with D-079 through D-083 decision records for grant architecture, NPC propagation, disclosure, boundaries, and contradiction detection | workshop | archived | knowledge-flow-npc-boundaries | qatux | 2 | 2026-02-24 |
Workshop Outcomes — Knowledge Flow & NPC Information Boundaries
Workshop date: 2026-02-23 — 2026-02-24 Documented by: Qatux Participants: Tyre, Gestalt, Dudley, Paula Source files: round-1-notes.md, round-2-notes.md, tyre-round1/2.md, gestalt-round1/2.md, dudley-round1/2.md, paula-round1/2.md
D-Record Drafts
The following D-records are produced by this workshop. They are ready for registration in the appropriate decisions/ domain files. The D-numbers D-079 through D-083 are assigned sequentially after D-078 (current highest in perception.md).
D-079: Knowledge Grant Architecture
- Date: 2026-02-24
- Decision: All knowledge input (dialogue testimony, physical evidence, POI discovery, NPC-authored initial knowledge) flows through a single
KnowledgeEventType::KnowledgeGrantedevent type. No separate event types for different grant sources. TheKnowledgeGrantYAML schema is extended to an untagged enum supportingFact { fact_id, confidence }andEntity { entity_ref, attributes, confidence }variants. The source field (KnowledgeSource) distinguishes provenance:ToldBy { source_id: StableId, tick }for NPC testimony,DirectObservation { tick }for physical evidence,Heard { tick, range }for overheard conversations. Grants fire at line selection time inprocess_talk_interaction, server-side, pushed toKnowledgeEventQueueand processed on the same tick. AContentEntityRegistryresource (BTreeMap<String, StableId>, populated at NPC spawn time) resolvesentity_refstrings toStableIds at grant processing time. Content-load validation enforces: confidence strings parse to validKnowledgeConfidencevariants; fact_ids conform to"category.topic"format; entity_refs resolve to registered StableIds. Runtime guardrail: if the granting NPC's KG does not contain the fact being granted, the grant is dropped withtracing::warn!. - Rationale: Unified event type keeps the knowledge system composable. Grant timing at line selection (not client display) ensures D-010 determinism — tick-stamped and server-authoritative. Entity grants are required for contradiction detection: testimony must create
EntityKnowledgewithToldBysource so that a subsequentDirectObservationcan detect a discrepancy. Physical evidence uses the same mechanism withDirectObservationsource, which is treated as higher-confidence and cannot be contradicted by the NPC-denial path. - Raised by: Workshop — unanimous on grant timing and unified event type. Entity grants in Sprint 17 per team lead decision (2026-02-24), overriding Paula's Round 2 acceptance of Dudley's FactId workaround. See round-2-notes.md §Tension A for full workshop split and rationale.
- Dissent: Paula (Round 2) accepted the FactId workaround with 3 binding conditions; team lead overrode in favour of the entity grants path championed by Tyre, Gestalt, and Dudley. Paula's conditions remain relevant where applicable: (1)
ToldBysource must flow throughContradictionDetectedevent payload — satisfied by D-083 design; (2) Sprint 18 entity grants (fullEntityGrant { attributes: BTreeMap }variant +Compoundvariant) are committed scope, not aspirational. Paula's Condition 3 is formally recorded here as a commitment. - Scope note —
Compoundgrant variant:Compound { grants: Vec<KnowledgeGrant> }(multiple grants from a single line) deferred to Sprint 18. Sprint 17 shipsFactandEntityvariants only.
D-080: NPC-to-NPC Knowledge Propagation
- Date: 2026-02-24
- Decision: NPC-to-NPC knowledge transfer is implemented via a
transfer_npc_knowledgeBevy system that runs immediately afterrun_npc_conversations(system ordering:transfer_npc_knowledge.after(run_npc_conversations)). The system useskg_query.get_many_mut([entity_a, entity_b])for dual-mutable KG access (required by Bevy ECS — two mutable borrows of the same component type cannot occur in a single query). Knowledge transfer occurs once per conversation at conversation start, not per-line. Rate: flatrng.random_range(1..=3)facts per conversation, drawn from eligible entries sorted bylast_updated_tickdescending (most recent first). Trust-gated filtering:RelationshipEdge.trustvalue determines eligible entries (None tier < 0: no transfer; Surface 0–3: Active facts at KnowsOf+ only; Real 3–7: facts at any confidence + entity observations; Secret 7–10: all Active entries). Confidence cap:transferred_confidence = min(source_confidence, KnowledgeConfidence::KnowsOf). Facts withdisclosure_blocked: trueare never transferred regardless of trust tier. Source construction:KnowledgeSource::ToldBy { source_id: speaker_sid, tick: time.tick }. Player overhearing: when the player is withinVOICE_RANGE_TILES(8) of a knowledge-transferring NPC pair, the player gains entity-level KG entries atSuspectsconfidence withKnowledgeSource::Heard { tick, range: Medium }. Specific overheard fact transfer to player deferred to Sprint 18. - Rationale: The existing
run_npc_conversationssystem already provides proximity detection, deterministic StableId-sorted pairing, and conversation lifecycle — reusing it avoids building a separate propagation system and keeps NPC conversations as the natural information-sharing hook. Confidence capping atKnowsOfprevents gossip chains from amplifying information; only direct observation producesKnowsDetailsorDirect.ToldBysource construction is the prerequisite for contradiction detection (Topic 5). Thedisclosure_blockedflag models secrets whose sharing is existentially dangerous regardless of relationship trust (D-034 NPC design requirement). Closes Q-024. - Raised by: Workshop — unanimous
- Dissent: None
D-081: Unprompted Disclosure Design
- Date: 2026-02-24
- Decision: Unprompted disclosure (ticket #172) is implemented as a filtered KG query producing a
DisclosureCandidatescomponent, consumed by Layer 4 of the dialogue pipeline. The system runs asderive_disclosure_candidates(Active-tier NPCs within player range only; recomputed every 30 ticks viacomputed_tickfreshness check). NPCs check their own KG only — no cross-entity KG queries (D-010 principle 2). Candidate selection filters: Active state only; confidence ≥ KnowsOf (Cautious trait raises threshold); not inper_fact_history(not already disclosed this cooldown period);disclosure_blocked != true. Trait two-stage filter: Stage 1 (what) filters candidate pool by trait-based predicates (Cautious raises confidence floor, Gossipy lowers it, Loyal suppresses facts about protected entities). Stage 2 (how) biases line pool scoring in Layer 4. Trigger gates (all must pass): trust tier ≥ Surface toward player; mood not Hostile; contentment ≥ −10; candidates not empty; witness inhibition (no non-trusted NPCs within 5 tiles, OR NPC-player trust = Secret tier, OR Talkative trait override); location privacy (disclosure candidate'sdisclosure_contexttag compatible with current zone type:private/semi_private/any); per-NPC cooldown (300 ticks / 30 game-minutes) not active; global rate limit not exceeded. Global rate limit: 1 disclosure per 10 ticks maximum across all NPCs; when multiple candidates ready in same window, select by ascending StableId (deterministic, D-010). Rate limiting layers: (1) per-fact per-NPCper_fact_history— primary, prevents repeats; (2) per-NPC 300-tick cooldown — prevents spam; (3) global 1/10-tick cap — prevents degenerate multi-NPC simultaneous fire. - Rationale: NPCs checking only their own KG (Option A) is both diegetically correct and mechanically richer: NPCs can say things the player already knows, which creates dramatic irony (Sera praising Kael's integrity after the player found discrepancies). Option B (NPC checks player's KG) would collapse this asymmetry — NPCs would go silent at exactly the moments their speech would be most dramatically charged. Separate
DisclosureCandidatescomponent (not added toDerivedTellState) prevents DerivedTellState serialization bloat and allows different update frequencies for different concerns. The location privacy gate creates learnable spatial behavior patterns: "Sera talks freely at Lera's; she clams up at The Terminal." The per-factper_fact_historyis the primary narrative quality gate — an NPC who repeats the same fact is a quest marker, not a person. Implements ticket #172. - Raised by: Workshop — unanimous on architecture; Paula and Gestalt on trigger gates; Tyre and Dudley on implementation structure
- Dissent: Gestalt (Round 1): opposed global rate limit as creating invisible NPC competition. Resolved in Round 2: Paula withdrew support, Gestalt conditionally accepted with deterministic StableId selection. Included.
D-082: NPC Information Boundaries — MVP Scope
- Date: 2026-02-24
- Decision: Sprint 17 MVP information boundary scope: (1)
tell_state.rsreads NPC relationship state from KG for OTHER-entity state (replaces directrelationships.entriesread for Friendly tell derivation); self-axis components (Secret, Contentment, Tolerance, Mood) continue as ground-truth reads. (2) Unprompted disclosure (D-081) uses KG for candidate selection. No other system retrofits in Sprint 17.derive_tell_stateaddsOption<&KnowledgeGraph>to query;Nonefalls through to current axis-based behavior (existing tests continue passing). All KG-driven behavior scoped toWith<ActiveSim>— Background-tier NPCs (D-026, 500–2000) receive no KG-based boundary changes. Deferred: conversation partner KG-awareness check (Sprint 18 candidate,kg.knows_entity(&partner_sid)in pairing loop); routine KG awareness (v0.2+); pathfinding KG integration (not planned — failure mode has no safe recovery). Fallback on missing KG entry: fall through to ground truth withtracing::debug!structured logging. - Rationale: The minimum viable boundary produces maximum gameplay-visible difference for minimum risk: NPCs whose tells reflect their actual relationship knowledge (not just raw axes) and NPCs who only volunteer what they know. Pathfinding from KG has an unresolvable failure mode (NPC forgets path nodes = stuck NPCs = undefined movement behavior) and must not be implemented. Self-knowledge always comes from axis components because the NPC is the continuous observer of its own state — the information boundary applies to external knowledge only. The
Option<&KnowledgeGraph>addition is backward-compatible. Implements ticket #142 MVP. - Raised by: Workshop — unanimous
- Dissent: None. All participants agreed MVP boundary is tell_state + disclosure. Paula adds that
tell_stateshould eventually incorporate KG-derived secret-exposure intensity (NPC whose KG shows investigation proximity acts more stressed) — deferred to a future sprint as enhancement.
D-083: Contradiction Detection Pipeline
- Date: 2026-02-24
- Decision: Contradiction detection is event-driven at KG write time — fires inside
observe_entity()(server/src/knowledge/graph.rs) before overwriting an existing entry. Detection algorithm: if the existing entry hassource: ToldBy { tick: told_tick }ANDlast_known_positiondiffers from incoming position AND|current_tick - told_tick| < CONTRADICTION_WINDOW_TICKS(default 600; configurable per contradiction type), then: (1)entry.state = KnowledgeState::Contradicted; (2)entry.contradicted_claim = Some(ContradictionClaim { source: entry.source.clone(), claimed_position: entry.last_known_position, detected_at_tick: current_tick }); (3) proceed to write new observation. AContradictionDetected { observer, entity_sid, source_display_name: Option<String>, subject_display_name: Option<String> }event is pushed; display names are resolved at detection time viaEntityRegistry + NpcNameso the monologue system is a pure string consumer.EntityKnowledgegains one new field:pub contradicted_claim: Option<ContradictionClaim>with#[serde(skip_serializing_if = "Option::is_none")]and#[serde(default)]— backward-compatible, zero migration required. Downstream event chain:ContradictionDetected→ monologue system (named contradiction line) → relationship shift (ToldBysource entity →PersonOfInterest) → D-033 amber via existing pipeline →AnomalyMarkerviadetect_anomalies(already tested). Both theToldByentry and theDirectObservationentry receiveContradictedstate (epistemic neutrality — the engine does not determine which is wrong). Location contradiction is automatic (position comparison + time window). Attribute contradiction uses content-authored YAML pairs (Sprint 18). Fact contradiction uses content-authoredcontradictsfield on KnowledgeGrant entries (Sprint 18).CONTRADICTION_WINDOW_TICKS = 600(1 game-hour) as default constant, with content-authored override per claim type. Closes Q-026. - Rationale: Event-driven detection avoids per-tick KG scan overhead (99% of ticks have no new information). The
ContradictionClaimstruct (typed) was chosen over string encoding inknown_attributes(Dudley's Option B) because: (1) the struct is accessed with direct field reads — no string parsing in the detection hot path; (2) the monologue system needs a typedKnowledgeSourceto resolve the source entity's name; (3) string encoding inknown_attributesmixes semantic metadata with internal bookkeeping; (4) one optional struct field is additive — no breaking changes, no migration. Steps 2–5 of the downstream chain (anomaly marker, D-033 color, relationship shift, monologue selection) are already implemented and passing tests. Contradiction detection is the missing link that enables THE FRIEND arc to fire its canonical sequence (D-034). - Raised by: Workshop — unanimous on architecture and downstream chain; Tyre and Gestalt on struct approach (converged independently); Dudley conceded Option B
- Dissent: Dudley (Round 1): Option B string encoding as Sprint 17 workaround. Conceded in Round 2. No standing dissent.
Question Closures
Q-024: NPC-to-NPC knowledge propagation timing
- Status: CLOSED
- Resolution: Knowledge transfer occurs via a separate
transfer_npc_knowledgeBevy system running afterrun_npc_conversations. Transfer fires once per conversation at conversation start. See D-080 for full specification. - Closed by: Workshop — unanimous
Q-025: KG memory pressure and eviction strategy
- Status: CLOSED
- Resolution: No memory cap or eviction strategy is needed for v0.1 or v0.2. Current analysis: ~14 KB per Active NPC KG (50 entities + 20 facts, D-041 budget). 80 Active NPCs = ~1.1 MB. 2,000 Background NPCs at 10 entries = ~5 MB. Total ~6 MB. With gossip propagation (1–3 facts per conversation): estimated ~12 MB peak at current NPC counts. Re-evaluation trigger: Active NPC count exceeds 200, OR profiling shows KG memory exceeding 50 MB. Neither condition expected before v0.3.
- Closed by: Workshop — Tyre, Gestalt, Dudley confirmed (Paula did not address; non-objection noted)
Q-026: Contradiction detection — when and how to detect
- Status: CLOSED
- Resolution: Event-driven detection at KG write time in
observe_entity(), usingContradictionClaimstruct. Location contradiction is automatic (Sprint 17). Attribute and fact contradiction are content-authored (Sprint 18). See D-083 for full specification. Entity grants ship in Sprint 17 (team lead decision, 2026-02-24); the contradiction chain fires viaEntityKnowledge.ToldByas designed. - Closed by: Workshop — unanimous on detection architecture. Unconditional.
Implementation Scope
Ticket #141 — Knowledge grant wiring (POI discovery)
Updated scope based on workshop:
- Grant mechanism:
KnowledgeEventType::KnowledgeGranted(D-079) handles POI discovery viaFactId("poi.dock_7_restricted")— no separate event type needed - When player enters POI trigger zone, push
KnowledgeGranted { fact_id: FactId("poi.*"), confidence, source: DirectObservation { tick } }to queue filter_by_access→KnowledgeGated("poi.dock_7_restricted")already gates content on this fact (graph.rs:299–302)- No new access control code; no new types; no new systems beyond the shared
KnowledgeGrantedevent - Depends on: Dudley's Ticket A + B (grant infrastructure)
- Effort: ~20 lines on top of Ticket B infrastructure; primarily content authoring for POI definitions
Ticket #142 — NPC information boundaries (MVP)
Updated scope based on workshop (D-082):
Sprint 17 deliverable:
derive_tell_stateaddsOption<&KnowledgeGraph>to query; uses KG for OTHER-entity relationship reads; falls back to axis components for self-state and when KG is absentDisclosureCandidatescomponent +derive_disclosure_candidatessystem (see #172 spec)process_unprompted_disclosuresystem stub (trigger gates operational; content-authored lines sparse for Sprint 17)
Explicitly out of Sprint 17 scope:
- Conversation partner KG-awareness check (Sprint 18)
- Routine KG awareness (v0.2+)
- Pathfinding KG integration (not planned)
Depends on: Ticket B (KG populated with meaningful content); Ticket D (NPC KGs populated via gossip)
Updated Specifications
Ticket #172 — Unprompted Disclosure
Full specification (supersedes any prior spec):
New components:
#[derive(Component, Debug, Default)]
pub struct DisclosureCandidates {
pub candidates: Vec<FactId>,
pub computed_tick: u64,
}
#[derive(Component, Debug, Default)]
pub struct DisclosureCooldown {
pub per_fact_history: BTreeSet<FactId>, // facts disclosed this cooldown period
pub npc_cooldown_until: u64,
}
derive_disclosure_candidates system:
- Runs every tick for Active NPCs; recomputes candidates when
time.tick - computed_tick > 30 - Filters NPC KG: Active state; confidence ≥ KnowsOf (trait-adjusted); not in
per_fact_history;disclosure_blocked != true; trust rough-gate (categories requiring Real trust excluded at Surface) - Trait Stage 1 predicates: Cautious = requires KnowsDetails+; Gossipy = includes Suspects; Loyal = suppresses entity-linked facts where entity is trusted by NPC
- Candidate list capped at 10; sorted by confidence descending, then
last_updated_tickdescending
process_unprompted_disclosure system:
- Trigger gates (all must pass): trust ≥ Surface; mood ≠ Hostile; contentment ≥ −10; candidates not empty; per-NPC cooldown clear; global rate limit (1/10 ticks, StableId-ordered); witness inhibition check; location privacy check
- On success: push
KnowledgeGrantedevent for player + push selected fact toper_fact_history; resetnpc_cooldown_until
Trait Stage 2 (line pool): Trait modifier tags in IndexedDialogueLine.tags bias Layer 4 selection weights for delivery style.
disclosure_context YAML field on disclosure candidates: private / semi_private / any. Checked at Layer 4 line selection against current location zone type.
Estimated effort: ~220 lines (Dudley Ticket G); requires Gestalt + Paula algorithm spec (now provided in round-2-notes.md).
Ticket #173 — Trait filtering for unprompted disclosure
Full specification (supersedes any prior spec):
Traits operate as two-stage filters, both implemented:
Stage 1 — candidate pool filter (WHAT):
| Trait | Filter applied |
|---|---|
| Cautious | Remove candidates with confidence < KnowsDetails; remove ToldBy-source facts (won't pass on rumors) |
| Gossipy | Include Suspects-confidence candidates (normally excluded) |
| Loyal | Remove facts linked to entities with trust_level ≥ Real in NPC Relationships |
| Talkative | Override witness inhibition gate; include candidates at all confidence levels |
Traits map to filter predicates via a content-authorable YAML configuration (not hard-coded enum dispatch).
Stage 2 — line pool scoring (HOW):
- Trait tags in
IndexedDialogueLine.tagsare matched by Layer 4 line selection - Tag examples:
"cautious_delivery","gossip_delivery","professional_delivery" - Same underlying candidate fact can have multiple authored line variants with different trait delivery tags
disclosure_eligible: bool in authored NPC KG YAML:
- Set to
falsefor facts that should never enter the candidate pool regardless of trust tier or trait - Implemented as
disclosure_blocked: boolfield onFactKnowledge(defaultfalse) - Used for Major secrets with survival stakes (e.g., Kael's ring membership)
Estimated effort: ~40 lines for trait filter predicates + content configuration schema
New Ticket Recommendations
The following new tickets are recommended based on workshop outputs. These are recommendations for SI to formalize:
Recommended Ticket A: KnowledgeGrant schema + ContentEntityRegistry
Scope: server/src/content/types.rs — replace KnowledgeGrant struct with untagged enum (Fact + Entity variants); add disclosure_blocked: bool to FactKnowledge. New file server/src/knowledge/content_registry.rs — ContentEntityRegistry resource. NPC spawn sites: add content_registry.register(content_id, sid). Content index validation: parse confidence strings, validate entity_refs.
Blocks: Tickets B, C, D, E, F, G (all downstream work) Effort estimate: ~120 lines Sprint: 17
Recommended Ticket B: KnowledgeGranted event + process_knowledge_events handler
Scope: server/src/knowledge/events.rs — add KnowledgeEventType::KnowledgeGranted variant with ProcessedKnowledgeGrant enum (Fact and Entity subtypes); add match arm handler. server/src/knowledge/types.rs — add TryFrom<&str> for KnowledgeConfidence. server/src/simulation/dialogue.rs — wire knowledge_grant field in process_talk_interaction after line selection. Runtime NPC KG guardrail: 3-line check in handler.
Blocks: Tickets D, F, G; ticket #141 POI discovery wiring Effort estimate: ~150 lines Sprint: 17
Recommended Ticket C: ContradictionClaim struct + detection in observe_entity
Scope: server/src/knowledge/types.rs — add ContradictionClaim struct; add contradicted_claim: Option<ContradictionClaim> to EntityKnowledge; add CONTRADICTION_WINDOW_TICKS: u64 = 600 constant. server/src/knowledge/graph.rs — add pre-overwrite contradiction check in observe_entity(). server/src/knowledge/events.rs — add ContradictionDetected event variant; add processing (relationship shift for ToldBy source entity). Tests: location contradiction, time window boundary, no false positive for non-ToldBy sources.
Blocks: Ticket F (requires ContradictionClaim + event for monologue integration) Effort estimate: ~120 lines + ~40 lines tests Sprint: 17
Recommended Ticket D: NPC-to-NPC knowledge transfer system
Scope: New file server/src/simulation/npc_knowledge_transfer.rs — transfer_npc_knowledge system. Trust-tier mapping; confidence downgrade via min(source_confidence, KnowsOf); disclosure_blocked check; ToldBy source construction; 1–3 random fact selection (most-recent-first); player overheard grant at Suspects confidence; system ordering: after(run_npc_conversations).
Blocks: Ticket F (ToldBy entries needed for contradiction to fire); Ticket G (NPC KGs need meaningful content before disclosure is useful) Effort estimate: ~160 lines Sprint: 17
Recommended Ticket E: tell_state.rs KG awareness (MVP boundary #142)
Scope: server/src/npc/tell_state.rs — add Option<&KnowledgeGraph> to derive_tell_state query; replace Friendly-tell relationships.entries direct read with kg.relationship_with(&entity_sid) for OTHER-entity state; self-axes remain ground-truth reads.
Blocks: Nothing (standalone improvement, existing tests continue passing) Effort estimate: ~35 lines Sprint: 17
Recommended Ticket F: Contradiction monologue + event chain completion
Scope: server/src/simulation/monologue.rs — add EntityRegistry + Query<&NpcName> to system signature (or read pre-resolved names from event payload); add resolve_name helper; add match arm for ContradictionDetected. server/src/knowledge/events.rs — in ContradictionDetected processing, call relationship shift for ToldBy source entity; pre-resolve display names into event struct. Monologue pool (content — Mellanie): generic fallback template line + hand-authored FRIEND-specific lines for Sera/Kael contradiction phases. Integration test: full THE FRIEND arc sequence (Tick T1 grant creates ToldBy → Tick T2 observation triggers ContradictionClaim → monologue fires with correct names → Sera/Kael shift to PersonOfInterest → AnomalyMarker set).
Blocks: Nothing (downstream consumers — anomaly.rs, D-033 pipeline — already built and tested) Effort estimate: ~90 lines + content (Mellanie) Sprint: 17
Recommended Ticket G: DisclosureCandidates + unprompted disclosure (#172 + #173)
Scope: New file server/src/npc/disclosure.rs — DisclosureCandidates component; DisclosureCooldown component; derive_disclosure_candidates system; process_unprompted_disclosure system; trait filter predicates (Cautious, Gossipy, Loyal, Talkative); global rate limit resource. server/src/simulation/dialogue.rs — Layer 4 reads DisclosureCandidates for NPC-initiated line selection. System ordering: derive_disclosure_candidates.after(process_knowledge_events), process_unprompted_disclosure.after(derive_disclosure_candidates).
Blocks: Nothing Depends on: Tickets B + D (KG needs meaningful NPC content before disclosure is useful) Effort estimate: ~220 lines Sprint: 17 (implement after Tickets B + D)
THE FRIEND Arc Validation Sequence
Status: All systems in steps 3–6 are already implemented and passing tests. Steps 1–2 are new work (Tickets A+B for grant; Ticket C for ContradictionClaim; Ticket D for ToldBy population in NPC KGs).
Canonical sequence (D-079/D-083 integration test, using entity grant path):
-
Tick T1 — Testimony: Detective talks to Sera →
process_talk_interactionselects line withknowledge_grant: { entity_ref: "kael", attributes: { location: "dock-7", shift: "second" }, confidence: "knows_of" }→KnowledgeGrantedevent pushed →process_knowledge_eventscreatesEntityKnowledge(kael_sid)in player KG:source: ToldBy { source_id: sera_sid, tick: T1 },last_known_position: dock_7_pos,confidence: KnowsOf,state: Active. -
Tick T2 — Observation: Player LOS includes Kael in corridor B-7 → Perception fires
DirectObservation→observe_entity(kael_sid, b7_pos, T2)runs → pre-overwrite contradiction check: existing entry hasToldBysource;b7_pos ≠ dock_7_pos;|T2 - T1| < 600→entry.state = Contradicted;entry.contradicted_claim = Some(ContradictionClaim { source: ToldBy { sera_sid, T1 }, claimed_position: dock_7_pos, detected_at_tick: T2 })→ entry overwritten withDirectObservation. -
Tick T2 — ContradictionDetected event pushed:
{ observer: detective_entity, entity_sid: kael_sid, source_display_name: Some("Sera"), subject_display_name: Some("Kael") }. -
Tick T2/T3 — Downstream cascade (existing systems, already tested):
detect_anomaliesfindsContradictedstate →AnomalyMarkeron Kael- Relationship system:
EntityKnowledge(kael_sid).relationship → PersonOfInterest;EntityKnowledge(sera_sid).relationship → PersonOfInterest - D-033 pipeline: both Kael and Sera → amber (#e8c547)
- Monologue system: fires contradiction line: "Sera said Kael was at the dock intake during second shift. I'm looking at him in corridor B-7 right now."
-
Next player Talk with Sera: Confrontation option unlocked for Sera and Kael. Sera behaves normally (she does not know the detective has seen Kael in B-7 — her KG has no such entry). The detective holds the contradiction alone until confrontation.
Sprint 17 vs Sprint 18 Scope Summary
Sprint 17 (this workshop produces)
| Component | Ticket | Lines est. |
|---|---|---|
KnowledgeGrant schema + ContentEntityRegistry |
A | ~120 |
KnowledgeGranted event + handler + dialogue wire |
B | ~150 |
ContradictionClaim struct + detection in observe_entity |
C | ~160 |
NPC-to-NPC transfer_npc_knowledge system |
D | ~160 |
tell_state.rs KG awareness |
E | ~35 |
| Contradiction monologue + event chain | F | ~90 |
DisclosureCandidates + unprompted disclosure |
G | ~220 |
| Total | ~935 lines |
Q-024, Q-025, Q-026 closures in decisions/questions.md.
Sprint 18 (committed scope from this workshop)
- Full
EntityGrant { attributes: BTreeMap }variant (currently onlyentity_ref+ position in Sprint 17) Compoundgrant variant (facts + entity in one line)- Conversation partner KG-awareness check (
kg.knows_entity(&partner_sid)in pairing loop) - Attribute contradiction via content-authored YAML pairs (typed attribute key conventions)
- Fact contradiction via
contradictsfield on KnowledgeGrant entries - Template monologue system for named contradictions (generic entity substitution for auto-generated NPCs)
- Specific overheard fact transfer to player (currently: entity-level Suspects grant only)
- Runtime NPC KG decay → disclosure candidate staleness handling
Deferred (v0.2+)
- Routine KG awareness
- Pathfinding KG integration (not planned — no safe failure recovery)
- "Ask around" emergent information-seeking behavior
- NPC disclosing own KG-contradicted knowledge ("I could have sworn Kael was at the dock...")
Team Lead Decisions
| Decision | Date | Override / Note |
|---|---|---|
| Entity grants ship in Sprint 17 | 2026-02-24 | Team lead call. Overrides Paula's Round 2 acceptance of Dudley's FactId workaround. Adopts Tyre/Gestalt/Dudley position: compound KnowledgeGrant enum with Entity variant, ContentEntityRegistry at NPC spawn (~0.5 day infrastructure). Paula's conditions (ToldBy in event, Sprint 18 formal commitment) honoured where applicable. See round-2-notes.md §Tension A for full workshop record. |
Open Questions Not Resolved by This Workshop
| ID | Question | Required for | Decision owner |
|---|---|---|---|
| — | CONTRADICTION_WINDOW_TICKS final value: 600 (majority) vs 1800 (Tyre) |
Contradiction detection sensitivity | Can ship 600; playtest adjusts |
Document produced from workshop rounds 1–2. D-records pending registration in decisions/ domain files by Qatux. New tickets A–G pending SI formalization.