feat(simulation): sprint 10 — hub teleport, confrontation response, walk-away phase 2, gauntlet rooms 4-7, blocked_entities debug field

#491: PlayerAction::TeleportToHub — moves player to hub spawn, clears
dialogue/monologue/interaction buffer, Gauntlet-only with log warning.
#520: ConfrontationDelivered event — Tier 2 animation shift, relationship
state decrement (D-033), monologue spike emission.
#519: Walk-away Phase 2 — NPC animation shift + routine deviation on
dialogue exit (D-064).
#498: Four new Gauntlet rooms — Interaction Gallery, Fog Theater, Crowd
Plaza, Dialogue Room with constants and wiring.
#514: blocked_entities Vec<u64> on ObserverSnapshot, protocol v9.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 12:58:14 +01:00
co-authored by Claude Opus 4.6
parent b4a3784218
commit 273d29f26f
18 changed files with 1219 additions and 50 deletions
+3
View File
@@ -178,11 +178,14 @@ impl Plugin for BridgePlugin {
crate::simulation::dialogue::process_walk_away
.after(crate::simulation::input::process_player_input)
.after(crate::simulation::dialogue::process_talk_interaction),
crate::simulation::dialogue::process_confrontation_response
.after(crate::simulation::input::process_player_input),
crate::perception::observer::compute_observer_snapshot
.after(crate::perception::observer::compute_visibility_geometry)
.after(crate::simulation::interaction::compute_nearby_interactions)
.after(crate::simulation::monologue::process_sprint_anomaly_monologue)
.after(crate::simulation::dialogue::process_talk_interaction)
.after(crate::simulation::dialogue::process_confrontation_response)
.before(crate::simulation::time::advance_tick),
crate::perception::observation::emit_observation_events
.after(crate::perception::observer::compute_observer_snapshot),
+24
View File
@@ -159,6 +159,12 @@ pub fn format_snapshot_text(snapshot: &ObserverSnapshot) -> String {
.ok();
}
// 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();
}
writeln!(out, "===").ok();
out
}
@@ -281,6 +287,7 @@ mod tests {
}),
pending_recognitions: vec![],
dialogue_response: None,
blocked_entities: vec![],
}
}
@@ -402,10 +409,27 @@ mod tests {
current_monologue: None,
pending_recognitions: vec![],
dialogue_response: None,
blocked_entities: vec![],
};
let text = format_snapshot_text(&snap);
assert!(text.contains("Tick 0"));
assert!(text.contains("Player (-1,-1)"));
assert!(text.contains("Tiles: 0 visible"));
}
#[test]
fn blocked_entities_rendered() {
let mut snap = make_snapshot();
snap.blocked_entities = vec![42, 99, 1024];
let text = format_snapshot_text(&snap);
assert!(text.contains("Blocked (LOS): 3"));
assert!(text.contains("[42, 99, 1024]"));
}
#[test]
fn blocked_entities_empty_not_rendered() {
let snap = make_snapshot();
let text = format_snapshot_text(&snap);
assert!(!text.contains("Blocked (LOS)"));
}
}
+11 -1
View File
@@ -15,7 +15,7 @@ pub use crate::simulation::time::{DayPhase, TickRate};
/// negotiation is unnecessary. Client should reject snapshots with version !=
/// PROTOCOL_VERSION. New fields use #[serde(default)] only during the migration
/// period, then the default is removed once both sides are updated.
pub const PROTOCOL_VERSION: u8 = 8;
pub const PROTOCOL_VERSION: u8 = 9;
/// The ONLY data structure crossing the client-server boundary (D-020)
/// Contains all information visible to the observer at a given tick.
@@ -27,6 +27,7 @@ pub const PROTOCOL_VERSION: u8 = 8;
/// v6 adds: player_stance (#449, D-053), player_inventory (#449, D-065).
/// v7 adds: pending_recognitions (#423, D-060 cognitive delay).
/// v8 adds: dialogue_response (#305, D-028 dialogue pipeline).
/// v9 adds: blocked_entities (#514, debug field for LOS-blocked entities).
/// Future fields: ambient sound events, HUD state (D-020 expansion).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObserverSnapshot {
@@ -68,6 +69,11 @@ pub struct ObserverSnapshot {
/// Client shows speaker name + dialogue text in a dialogue box.
#[serde(default)]
pub dialogue_response: Option<DialogueResponseEvent>,
/// 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.
#[serde(default)]
pub blocked_entities: Vec<u64>,
}
/// Game time data for client display (D-031)
@@ -316,6 +322,10 @@ pub enum PlayerAction {
ToggleStanceUp,
/// Move one step down the stance ladder (toward Crouch) per D-053
ToggleStanceDown,
/// Teleport player to the Gauntlet hub spawn point (#491).
/// Clears dialogue, monologue, and interaction buffers.
/// Rejected with a log warning on non-Gauntlet maps.
TeleportToHub,
}
impl PlayerAction {