From c655878deed9fb28e0cb0751088bb1198a5adea6 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Tue, 24 Feb 2026 12:42:35 +0100 Subject: [PATCH] =?UTF-8?q?fix(simulation):=20review=20fixes=20=E2=80=94?= =?UTF-8?q?=20walk-away=20source=20and=20Loyal=20trait=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit record_incomplete_interaction now uses Heard/Close source instead of DirectObservation, preventing false inoculation against contradiction detection. Wired exclude_high_trust_entities (Loyal trait) filter into derive_disclosure_candidates — facts from Friendly-relationship source entities are excluded from disclosure pool. Co-Authored-By: Claude Opus 4.6 --- server/src/knowledge/graph.rs | 9 ++++++++- server/src/npc/disclosure.rs | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/server/src/knowledge/graph.rs b/server/src/knowledge/graph.rs index 5b9cf8bb9..24c795acd 100644 --- a/server/src/knowledge/graph.rs +++ b/server/src/knowledge/graph.rs @@ -210,7 +210,14 @@ impl KnowledgeGraph { last_observed_tick: 0, last_updated_tick: 0, confidence: KnowledgeConfidence::Suspects, - source: KnowledgeSource::DirectObservation { tick }, + // Heard/Close — not DirectObservation, because walk-away is + // not a confirmed sighting. Using DirectObservation here + // would falsely inoculate the entry against contradiction + // detection (pre-overwrite check only fires on ToldBy source). + source: KnowledgeSource::Heard { + tick, + range: super::types::SoundRange::Close, + }, state: KnowledgeState::Active, relationship: RelationshipState::Unknown, known_attributes: BTreeMap::new(), diff --git a/server/src/npc/disclosure.rs b/server/src/npc/disclosure.rs index 6fd6d8ac9..c1a749def 100644 --- a/server/src/npc/disclosure.rs +++ b/server/src/npc/disclosure.rs @@ -16,7 +16,7 @@ use crate::bridge::types::MonologueEvent; use crate::knowledge::events::{ KnowledgeEvent, KnowledgeEventType, ProcessedFactGrant, ProcessedKnowledgeGrant, }; -use crate::knowledge::types::{FactId, KnowledgeConfidence, KnowledgeSource, KnowledgeState, StableId}; +use crate::knowledge::types::{FactId, KnowledgeConfidence, KnowledgeSource, KnowledgeState, RelationshipState, StableId}; use crate::knowledge::{KnowledgeEventQueue, KnowledgeGraph, StableEntityId}; use crate::npc::mood::{MoodState, NpcMood}; use crate::npc::relationships::RelationshipGraph; @@ -162,6 +162,11 @@ pub fn derive_disclosure_candidates( .is_some_and(|m| m.stage1.exclude_told_by) }); + // Loyal trait: exclude facts sourced from high-trust entities. + // "Don't gossip about your friends" — if ToldBy source has Friendly + // relationship in this NPC's KG, suppress the fact. D-081. + let exclude_high_trust = trait_config.any_excludes_high_trust(&trait_keys); + let mut pool: Vec<(FactId, u64, KnowledgeConfidence)> = kg .known_facts_iter() .filter(|(fact_id, fact)| { @@ -185,6 +190,14 @@ pub fn derive_disclosure_candidates( if exclude_told_by && matches!(fact.source, KnowledgeSource::ToldBy { .. }) { return false; } + // Loyal: skip facts from high-trust (Friendly) source entities. + if exclude_high_trust { + if let KnowledgeSource::ToldBy { source_id, .. } = &fact.source { + if kg.relationship_with(source_id) == RelationshipState::Friendly { + return false; + } + } + } true }) .map(|(id, fact)| (id.clone(), fact.acquired_tick, fact.confidence))