chore(simulation): address review suggestions — comments and minor fixes
- monologue.rs: tracing::warn on unrecognized trigger in fallback arm - disclosure.rs: borrow-sequencing comment, first() simplification note - trait_modifiers.rs: delegate parse_confidence to KnowledgeConfidence::try_from - npc_knowledge_transfer.rs: fix misleading "draw randomly" comment, document one-directional transfer behavior - graph.rs: document intentional no-retrigger after contradiction - dialogue.rs: explain entity grant guardrail asymmetry (D-079) - knowledge/mod.rs: document one-tick monologue lag from system ordering - poi_discovery.rs: document D-079 carve-out for direct KG write Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -177,6 +177,12 @@ impl KnowledgeGraph {
|
||||
}
|
||||
// Contradicted entries without a new contradiction stay Contradicted —
|
||||
// the previous contradiction is still unresolved.
|
||||
//
|
||||
// After this write, entry.source is DirectObservation, so subsequent
|
||||
// observations will NOT re-trigger contradiction detection (the
|
||||
// pre-overwrite check only fires when existing.source is ToldBy).
|
||||
// This is intentional: once contradicted, the entry reflects the
|
||||
// observer's own eyes and cannot be "contradicted" again by looking.
|
||||
|
||||
contradiction
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ impl Plugin for KnowledgePlugin {
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
// Runs after snapshot: contradiction monologue and relationship
|
||||
// shifts from KnowledgeGranted events lag by one tick (~0.1s).
|
||||
// Acceptable — the player perceives the contradiction on the
|
||||
// next snapshot, which reads as a natural reaction delay.
|
||||
events::process_knowledge_events
|
||||
.after(crate::perception::observer::compute_observer_snapshot),
|
||||
events::decay_knowledge.after(events::process_knowledge_events),
|
||||
|
||||
@@ -293,6 +293,9 @@ pub fn process_unprompted_disclosure(
|
||||
let npc_sid = sid_opt?.0;
|
||||
|
||||
// Gate 2: candidate pool non-empty.
|
||||
// Takes highest-priority candidate (sorted by confidence desc,
|
||||
// then recency desc in derive_disclosure_candidates). Full Layer 4
|
||||
// line selection with variety tracking is deferred to #172.
|
||||
let fact_id = candidates.candidates.first()?.clone();
|
||||
|
||||
// Gate 3: NPC→player trust >= Surface.
|
||||
@@ -404,6 +407,10 @@ pub fn process_unprompted_disclosure(
|
||||
}
|
||||
|
||||
// 3. Update NPC cooldown state.
|
||||
// Note: re-queries npc_query mutably after the read pass above. This is
|
||||
// safe because the read pass only borrows shared refs and completes before
|
||||
// this point. The two-phase pattern (read → select winner → write) avoids
|
||||
// holding a mutable borrow during iteration.
|
||||
if let Ok((_, _, _, _, _, _, _, mut cooldown)) = npc_query.get_mut(winner.entity) {
|
||||
cooldown.per_fact_history.insert(winner.fact_id.clone());
|
||||
cooldown.npc_cooldown_until = current_tick + NPC_COOLDOWN_TICKS;
|
||||
|
||||
@@ -189,17 +189,12 @@ impl TraitModifierConfig {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Parse a confidence string from YAML config to enum value.
|
||||
/// Delegates to `KnowledgeConfidence::try_from` (which accepts both
|
||||
/// camelCase and underscore forms) rather than duplicating the match.
|
||||
fn parse_confidence(s: &str) -> Option<KnowledgeConfidence> {
|
||||
match s {
|
||||
"suspects" => Some(KnowledgeConfidence::Suspects),
|
||||
"knows_of" => Some(KnowledgeConfidence::KnowsOf),
|
||||
"knows_details" => Some(KnowledgeConfidence::KnowsDetails),
|
||||
"direct" => Some(KnowledgeConfidence::Direct),
|
||||
_ => {
|
||||
tracing::warn!("Unknown confidence level in trait config: {:?}", s);
|
||||
None
|
||||
}
|
||||
}
|
||||
KnowledgeConfidence::try_from(s).map_err(|e| {
|
||||
tracing::warn!("Unknown confidence level in trait config: {}", e);
|
||||
}).ok()
|
||||
}
|
||||
|
||||
/// Convert a `PersonalityTrait` to its lowercase YAML key.
|
||||
|
||||
@@ -674,6 +674,11 @@ fn emit_knowledge_grant(
|
||||
},
|
||||
});
|
||||
}
|
||||
// Entity grants have no "NPC knows this entity" guardrail (unlike Fact
|
||||
// grants above). This is intentional per D-079: entity grants introduce
|
||||
// NEW knowledge about an entity the NPC is talking about — the NPC
|
||||
// doesn't need to "know" the entity in their own KG to reference it
|
||||
// in dialogue. The entity_ref resolves via ContentEntityRegistry, not KG.
|
||||
KnowledgeGrant::Entity {
|
||||
entity_ref,
|
||||
attributes,
|
||||
|
||||
@@ -457,7 +457,10 @@ fn select_hardcoded_fallback(trigger: &str, rng: &mut impl Rng) -> (String, Stri
|
||||
"hear_sound" => HEAR_SOUND_LINES,
|
||||
"witness_interaction" => WITNESS_INTERACTION_LINES,
|
||||
"post_conversation" => POST_CONVERSATION_LINES,
|
||||
_ => OBSERVE_NPC_LINES,
|
||||
unknown => {
|
||||
tracing::warn!("select_hardcoded_fallback: unrecognized trigger '{}', using observe_npc", unknown);
|
||||
OBSERVE_NPC_LINES
|
||||
}
|
||||
};
|
||||
let index = rng.random_range(0..lines.len());
|
||||
(lines[index].0.to_string(), lines[index].1.to_string())
|
||||
|
||||
@@ -197,6 +197,10 @@ pub fn transfer_npc_knowledge(
|
||||
}
|
||||
|
||||
// --- Dual-mutable KG access ---
|
||||
// Transfer is one-directional per conversation tick: speaker → partner.
|
||||
// If both participants are Active NPCs, each fires as "speaker" in
|
||||
// separate conversation pairs (run_npc_conversations creates symmetric
|
||||
// pairs), so both directions are covered across two iterations.
|
||||
|
||||
let Ok([speaker_kg, mut partner_kg]) =
|
||||
kg_query.get_many_mut([speaker_entity, partner_entity])
|
||||
@@ -265,7 +269,8 @@ pub fn transfer_npc_knowledge(
|
||||
// Sort by most recently updated (deterministic: descending tick, stable by BTreeMap key order)
|
||||
candidates.sort_by(|a, b| b.sort_key().cmp(&a.sort_key()));
|
||||
|
||||
// Draw 1–3 entries
|
||||
// Take top 1–3 entries by recency (random count, deterministic selection).
|
||||
// The random element is HOW MANY facts transfer, not WHICH ones.
|
||||
let count = rng.rng.random_range(1u32..=3u32) as usize;
|
||||
let count = count.min(candidates.len());
|
||||
|
||||
|
||||
@@ -81,6 +81,11 @@ pub fn discover_pois(
|
||||
}
|
||||
|
||||
if can_discover(observer_pos, &geometry, &kg, poi) {
|
||||
// Direct KG write — bypasses the KnowledgeGranted event queue.
|
||||
// Justified for LOS-based physical discovery: the observer sees
|
||||
// the POI directly, no intermediary grant source. This is a D-079
|
||||
// carve-out; NPC tips and research-based POI discovery (Sprint 18)
|
||||
// will use the event queue path.
|
||||
kg.facts.insert(
|
||||
fact_id,
|
||||
FactKnowledge {
|
||||
|
||||
Reference in New Issue
Block a user