feat(simulation): sprint 16 dialogue server — response handler, trust gossip, variety tracker

Move dialogue system registrations from BridgePlugin to NpcPlugin (#538):
game logic that depends on NPC-layer resources now registers where it
belongs. BridgePlugin retains only wire protocol concerns.

Implement DialogueResponse verb handler (#539): new process_dialogue_response
system runs the full D-028 four-layer pipeline to select follow-up lines
when the player picks a dialogue option. Clears ActiveDialogue when no
candidates remain. Fix latent schedule ambiguity — emit_observation_events
now has explicit .before(advance_tick) constraint.

Verify trust-gated gossip pipeline (#171): confirmed process_talk_interaction
correctly passes KnowledgeConfidence through relationship_to_trust() per
D-075. Added integration tests for Secret-tier access (Friendly+KnowsDetails)
and Surface-only fallback (Friendly+Suspects).

Wire DialogueCooldownTracker into selection (#338): added regression test
confirming no line_id repeats within the 600-tick cooldown window across
10 consecutive Talk interactions.

Closes #538, #539, #171, #338

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 20:01:39 +01:00
co-authored by Claude Opus 4.6
parent cc43f14c39
commit efa2dc543f
7 changed files with 760 additions and 10 deletions
+52 -1
View File
@@ -280,6 +280,18 @@ pub fn process_player_input(
PlayerAction::TeleportToHub => {
handle_teleport_to_hub(&mut player_query, &mut commands);
}
PlayerAction::DialogueResponse {
target_entity_id,
ref response_id,
} => {
handle_dialogue_response(
&mut commands,
&registry,
&player_query,
target_entity_id,
response_id,
);
}
PlayerAction::UsePerceptionMode(ref mode) => {
tracing::trace!("UsePerceptionMode({}) — no-op for Sprint 1", mode);
}
@@ -458,6 +470,44 @@ fn handle_talk(
tracing::debug!(target_id, "Talk: TalkRequest marker set on player");
}
/// Handle DialogueResponse action: set DialogueResponseRequest marker (#539).
/// The follow-up dialogue pipeline runs in process_dialogue_response (dialogue.rs).
#[allow(clippy::type_complexity)]
fn handle_dialogue_response(
commands: &mut Commands,
registry: &EntityRegistry,
player_query: &Query<
(
Entity,
&TilePosition,
Option<&mut Stance>,
Option<&mut PlayerMoveCooldown>,
),
With<PlayerCharacter>,
>,
target_entity_id: u64,
response_id: &str,
) {
let Ok((player_entity, _, _, _)) = player_query.single() else {
return;
};
let target_stable = StableId(target_entity_id);
let Some(target_entity) = registry.to_entity(&target_stable) else {
tracing::warn!(target_entity_id, "DialogueResponse: target entity not in registry");
return;
};
commands
.entity(player_entity)
.insert(crate::simulation::dialogue::DialogueResponseRequest {
target: target_entity,
response_id: response_id.to_string(),
});
tracing::debug!(target_entity_id, response_id, "DialogueResponse: marker set on player");
}
/// Handle Confront verb: set ConfrontationDelivered marker on the player entity (#520, D-063).
/// The confrontation response system runs in process_confrontation_response (dialogue.rs).
/// Server-side range check: Confront requires CLOSE_RANGE (same as Talk).
@@ -741,7 +791,8 @@ fn handle_teleport_to_hub(
.remove::<crate::simulation::dialogue::TalkRequest>()
.remove::<crate::simulation::dialogue::ActiveDialogue>()
.remove::<crate::simulation::dialogue::WalkAwayRequest>()
.remove::<crate::simulation::dialogue::ConfrontationDelivered>();
.remove::<crate::simulation::dialogue::ConfrontationDelivered>()
.remove::<crate::simulation::dialogue::DialogueResponseRequest>();
tracing::info!(
x = hub_spawn.x,