fix(simulation): address PR #56 review — pipeline extraction, ActiveDialogue, range check

Tyre #1: Extract run_dialogue_pipeline() shared helper — eliminates ~60
lines of duplication between process_talk_interaction and
process_dialogue_response (L1-L4 pipeline).

Hoshe #1: process_dialogue_response now updates ActiveDialogue with
current tick on follow-up selection — prevents stale started_tick.

Tyre #4: process_dialogue_response now updates InteractionMemory on
follow-up — multi-turn conversations are visible in history.

Hoshe #6 / Tyre #6: handle_dialogue_response adds server-side range
check (CLOSE_RANGE), matching Talk/Confront pattern (D-010 info
boundary).

Hoshe #2: Weighted selection fallback replaced with unreachable!() —
score_line always returns >= 1, so the fallback was dead code.

Hoshe #3: assert!(false, ...) → panic!() in serialization.rs (clippy).

Hoshe #4: SetFacing and TeleportToHub added to roundtrip test.

Hoshe #5: setup_dialogue_response_world inlined (trivial pass-through).

Tyre #2: Doc comment on DialogueCooldownTracker explains per-player-global
design choice (line IDs are NPC-scoped per D-035, no collision risk).

Tyre #3: CONFRONTATION_LINES comment updated with TODO for D-028/D-035
migration.

Tyre #5: DialogueResponse fixture added for cross-language GDScript
testing (input_dialogue_response.msgpack).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 20:20:00 +01:00
co-authored by Claude Opus 4.6
parent efa2dc543f
commit 2189a9addd
5 changed files with 151 additions and 115 deletions
+23 -1
View File
@@ -288,6 +288,7 @@ pub fn process_player_input(
&mut commands,
&registry,
&player_query,
&all_positions,
target_entity_id,
response_id,
);
@@ -472,6 +473,10 @@ fn handle_talk(
/// Handle DialogueResponse action: set DialogueResponseRequest marker (#539).
/// The follow-up dialogue pipeline runs in process_dialogue_response (dialogue.rs).
///
/// Range check: same CLOSE_RANGE as Talk/Confront (D-010 info boundary). The player
/// must still be near the NPC to continue a conversation — walking away mid-dialogue
/// should not allow remote follow-ups.
#[allow(clippy::type_complexity)]
fn handle_dialogue_response(
commands: &mut Commands,
@@ -485,10 +490,11 @@ fn handle_dialogue_response(
),
With<PlayerCharacter>,
>,
all_positions: &Query<&TilePosition>,
target_entity_id: u64,
response_id: &str,
) {
let Ok((player_entity, _, _, _)) = player_query.single() else {
let Ok((player_entity, player_pos, _, _)) = player_query.single() else {
return;
};
@@ -498,6 +504,22 @@ fn handle_dialogue_response(
return;
};
// Server-side range check: reject DialogueResponse if target moved out of range
if let Ok(target_pos) = all_positions.get(target_entity) {
let distance = player_pos
.manhattan_distance(target_pos)
.unwrap_or(u32::MAX);
if distance > crate::simulation::interaction::CLOSE_RANGE {
tracing::info!(
target_entity_id,
distance,
"DialogueResponse: target out of range (max {})",
crate::simulation::interaction::CLOSE_RANGE,
);
return;
}
}
commands
.entity(player_entity)
.insert(crate::simulation::dialogue::DialogueResponseRequest {