feat(simulation): contraband detection + dialogue confidence gate (D-075) (#425, #524)

Contraband detection (Task #425, D-065):
- New module: simulation/contraband.rs — NPC scan checks carried items
  against the KnowledgeGraph confidence gate. NPCs with Authority access
  can initiate a scan; scan outcome depends on item CarriedBy + KG entry.
- Adds ContrabandScanResult event type and ContrabanEntry component.
- Wired into simulation/mod.rs module list.

Dialogue confidence gate (Task #524, D-075 — OQ-18 resolution):
- relationship_to_trust() gains confidence parameter (KnowledgeConfidence).
- Trust tier mapping: (Friendly, KnowsDetails+)→Secret, (Friendly|Known,
  KnowsOf+)→Real, otherwise Surface. Access tier (Layer 1) unchanged.
- Caller process_talk_interaction passes observer KG confidence_of target.
- Resolves OQ-18: confidence co-gates TrustTier, not AccessTier.

Supporting changes:
- decisions/content.md: add D-075 (16 decisions, dated 2026-02-19)
- knowledge/types.rs: expose KnowledgeConfidence comparison helpers
- knowledge/registry.rs: minor API polish
- bridge/types.rs: ContrabandScanResult wire type
- bridge/text_renderer.rs: render contraband scan status
- perception/observer: include carried item count in snapshot
- npc/mod.rs: NPC scan range constant, authority flag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 11:12:24 +01:00
co-authored by Claude Sonnet 4.6
parent 54754e4080
commit d103e445e7
12 changed files with 670 additions and 46 deletions
+14 -2
View File
@@ -161,8 +161,18 @@ pub fn format_snapshot_text(snapshot: &ObserverSnapshot) -> String {
// Blocked entities (debug, #514)
if !snapshot.blocked_entities.is_empty() {
let ids: Vec<String> = snapshot.blocked_entities.iter().map(|id| id.to_string()).collect();
writeln!(out, "Blocked (LOS): {} [{}]", snapshot.blocked_entities.len(), ids.join(", ")).ok();
let ids: Vec<String> = snapshot
.blocked_entities
.iter()
.map(|id| id.to_string())
.collect();
writeln!(
out,
"Blocked (LOS): {} [{}]",
snapshot.blocked_entities.len(),
ids.join(", ")
)
.ok();
}
writeln!(out, "===").ok();
@@ -288,6 +298,7 @@ mod tests {
pending_recognitions: vec![],
dialogue_response: None,
blocked_entities: vec![],
scan_events: vec![],
}
}
@@ -410,6 +421,7 @@ mod tests {
pending_recognitions: vec![],
dialogue_response: None,
blocked_entities: vec![],
scan_events: vec![],
};
let text = format_snapshot_text(&snap);
assert!(text.contains("Tick 0"));
+5
View File
@@ -69,6 +69,11 @@ pub struct ObserverSnapshot {
/// Client shows speaker name + dialogue text in a dialogue box.
#[serde(default)]
pub dialogue_response: Option<DialogueResponseEvent>,
/// Scan events from NPCs with ScanAuthority this tick (#425, D-065).
/// Present when an NPC scanned the player's inventory. Client renders
/// scan indicator on the scanning NPC. Empty when no scans occurred.
#[serde(default)]
pub scan_events: Vec<crate::simulation::contraband::ScanEvent>,
/// Debug field: entity IDs on the same z-level that are not visible due to
/// LOS obstruction or being outside the vision cone (#514).
/// Sorted ascending for deterministic output. Client can safely ignore.