feat(simulation): wire triangle escalation and crisis events into protocol

Registers tick_triangle_escalation and apply_resolve_triangle systems
in SimulationPlugin. Adds TriangleCrisisEventWire to ObserverSnapshot
(protocol v16) for future client rendering of triangle crises (#250,
D-087). Observer emits empty vec by default; escalation system will
populate when triangles reach Active phase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 22:33:21 +01:00
co-authored by Claude Opus 4.6
parent 9eaca0b5a4
commit c436ae905f
4 changed files with 39 additions and 2 deletions
+2
View File
@@ -313,6 +313,7 @@ mod tests {
examine_result: None,
player_knowledge: None,
save_result: None,
triangle_crisis_events: vec![],
}
}
@@ -448,6 +449,7 @@ mod tests {
examine_result: None,
player_knowledge: None,
save_result: None,
triangle_crisis_events: vec![],
};
let text = format_snapshot_text(&snap);
assert!(text.contains("Tick 0"));
+25 -2
View File
@@ -17,7 +17,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 = 15;
pub const PROTOCOL_VERSION: u8 = 16;
/// Handshake message sent as the very first framed message after connection (#555).
/// Client reads this before entering the normal tick loop and validates
@@ -50,10 +50,11 @@ pub struct HandshakeMessage {
/// examine_result (#242, character-filtered examine observation text),
/// player_knowledge (#264, partial KG dump for journal/knowledge panel).
/// v15 adds: save_result (#553, save/load operation result for client confirmation).
/// v16 adds: triangle_crisis_events (#250, D-087 triangle escalation for future client rendering).
/// Future fields: ambient sound events, HUD state (D-020 expansion).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObserverSnapshot {
/// Protocol version for forward compatibility. Current: 15.
/// Protocol version for forward compatibility. Current: 16.
pub version: u8,
/// Simulation tick when this snapshot was produced
pub tick: u64,
@@ -151,6 +152,11 @@ pub struct ObserverSnapshot {
/// Client shows a confirmation toast (success) or error modal (failure).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub save_result: Option<SaveLoadResultWire>,
/// Triangle crisis events this tick (#250, D-087).
/// Emitted when a triangle enters Active phase. Client may render
/// a narrative event or HUD indicator. Empty when no crises occur.
#[serde(default)]
pub triangle_crisis_events: Vec<TriangleCrisisEventWire>,
}
/// Game time data for client display (D-031)
@@ -676,6 +682,23 @@ pub struct SaveLoadResultWire {
pub error: Option<String>,
}
/// Triangle crisis event for future client rendering (#250, D-087).
///
/// Emitted when a triangle transitions from Simmering to Active.
/// Client may display a narrative beat, HUD indicator, or tension meter.
/// Wire format uses primitives for cross-boundary safety.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TriangleCrisisEventWire {
/// Triangle identifier (TriangleId as u64).
pub triangle_id: u64,
/// NPC role assignments: (role_slug, stable_npc_id).
pub role_assignments: Vec<(String, u64)>,
/// The NPC whose tolerance threshold triggered the crisis.
pub trigger_npc_id: u64,
/// Tick when the crisis was triggered.
pub tick: u64,
}
/// Snapshot buffer resource for staging outgoing ObserverSnapshots
#[derive(Resource, Debug, Default)]
pub struct SnapshotBuffer {
+1
View File
@@ -414,6 +414,7 @@ pub fn compute_observer_snapshot(
examine_result,
player_knowledge,
save_result,
triangle_crisis_events: vec![],
});
}
+11
View File
@@ -51,6 +51,9 @@ impl Plugin for SimulationPlugin {
.init_resource::<follow::FollowEndEventQueue>()
.init_resource::<monologue::PostConversationQueue>()
.init_resource::<poi_discovery::PoiDiscoveryEventQueue>()
// Triangle escalation resources (#250)
.init_resource::<crate::content::template::TriangleCrisisEventQueue>()
.init_resource::<crate::content::template::ResolveTriangleQueue>()
// discover_pois reads VisibilityGeometry (also populated by PerceptionPlugin).
// Init here so SimulationPlugin works standalone in tests without PerceptionPlugin.
.init_resource::<crate::perception::query::VisibilityGeometry>()
@@ -96,6 +99,14 @@ impl Plugin for SimulationPlugin {
pressure::update_character_pressure
.after(crate::npc::awareness::detect_player_awareness)
.before(crate::perception::observer::compute_observer_snapshot),
// Triangle escalation (#250) — runs on game-minute boundaries (every 10 ticks)
crate::content::template::tick_triangle_escalation
.after(crate::npc::tolerance::check_tolerance_threshold)
.before(crate::perception::observer::compute_observer_snapshot),
// Triangle resolution (#250, D-089) — apply player resolve commands
crate::content::template::apply_resolve_triangle
.after(input::process_player_input)
.before(crate::perception::observer::compute_observer_snapshot),
time::advance_tick.after(path_follow::cleanup_path_blocked),
),
);