fix(simulation): register interaction systems and persist door state (#246)
Follow-up to b6a9b78: register TerminalInteractedQueue resource and
door/terminal interaction systems in SimulationPlugin; add door state
save/load in save_io (open doors round-trip through SaveStateV1);
make WalkabilityMap param optional in process_door_interaction so
plugin-only tests work without a loaded map; fix information_boundaries
test missing open_doors field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -407,7 +407,7 @@ pub struct TerminalInteractRequest {
|
||||
/// Ordering: after `process_player_input`, before movement validation.
|
||||
pub fn process_door_interaction(
|
||||
mut commands: Commands,
|
||||
mut walkability: ResMut<WalkabilityMap>,
|
||||
walkability: Option<ResMut<WalkabilityMap>>,
|
||||
player_query: Query<(Entity, &DoorInteractRequest), With<PlayerCharacter>>,
|
||||
mut door_query: Query<&mut DoorState>,
|
||||
) {
|
||||
@@ -428,7 +428,9 @@ pub fn process_door_interaction(
|
||||
let walkable = door.is_open;
|
||||
let tile = door.blocking_tile;
|
||||
|
||||
walkability.set_walkable(&tile, walkable);
|
||||
if let Some(mut walkability) = walkability {
|
||||
walkability.set_walkable(&tile, walkable);
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
?tile,
|
||||
|
||||
@@ -62,6 +62,7 @@ impl Plugin for SimulationPlugin {
|
||||
// Init here so SimulationPlugin works standalone in tests without those plugins.
|
||||
.init_resource::<crate::npc::relationships::RelationshipGraph>()
|
||||
.init_resource::<crate::knowledge::KnowledgeEventQueue>()
|
||||
.init_resource::<interaction::TerminalInteractedQueue>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
@@ -92,6 +93,12 @@ impl Plugin for SimulationPlugin {
|
||||
poi_discovery::discover_pois
|
||||
.after(crate::perception::observer::compute_visibility_geometry)
|
||||
.before(crate::perception::observer::compute_observer_snapshot),
|
||||
interaction::process_door_interaction
|
||||
.after(input::process_player_input)
|
||||
.before(movement::validate_movement),
|
||||
interaction::process_terminal_interaction
|
||||
.after(input::process_player_input)
|
||||
.before(crate::perception::observer::compute_observer_snapshot),
|
||||
examine::process_examine_interaction
|
||||
.after(input::process_player_input)
|
||||
.before(crate::perception::observer::compute_observer_snapshot),
|
||||
|
||||
@@ -26,6 +26,8 @@ use crate::simulation::rng::SimRng;
|
||||
use crate::simulation::save_state::{
|
||||
deserialize_npc_from_frozen, serialize_npc_to_frozen, SaveStateV1, SAVE_FORMAT_VERSION,
|
||||
};
|
||||
use crate::knowledge::types::StableId;
|
||||
use crate::simulation::interaction::DoorState;
|
||||
use crate::simulation::tier::BackgroundSim;
|
||||
use crate::simulation::time::SimulationTime;
|
||||
|
||||
@@ -130,6 +132,17 @@ pub fn save_to_file(path: &Path, world: &mut World) -> Result<(), SaveLoadError>
|
||||
npc_states,
|
||||
template_references,
|
||||
triangle_states,
|
||||
open_doors: {
|
||||
use crate::knowledge::registry::StableEntityId;
|
||||
let mut q = world.query::<(&DoorState, &StableEntityId)>();
|
||||
let mut ids: Vec<_> = q
|
||||
.iter(world)
|
||||
.filter(|(ds, _)| ds.is_open)
|
||||
.map(|(_, sid)| sid.0)
|
||||
.collect();
|
||||
ids.sort_by_key(|id| id.0);
|
||||
ids
|
||||
},
|
||||
};
|
||||
|
||||
let bytes = state
|
||||
@@ -230,6 +243,28 @@ pub fn load_from_file(path: &Path, world: &mut World) -> Result<(), SaveLoadErro
|
||||
}
|
||||
world.insert_resource(SimRng::new(state.seed));
|
||||
|
||||
// Restore door open states (#246) — find door entities by StableId and toggle.
|
||||
if !state.open_doors.is_empty() {
|
||||
let open_set: std::collections::HashSet<_> = state.open_doors.iter().copied().collect();
|
||||
let door_entities: Vec<(Entity, StableId)> = {
|
||||
let mut q = world.query::<(Entity, &crate::knowledge::registry::StableEntityId, &DoorState)>();
|
||||
q.iter(world)
|
||||
.filter(|(_, sid, _)| open_set.contains(&sid.0))
|
||||
.map(|(e, sid, _)| (e, sid.0))
|
||||
.collect()
|
||||
};
|
||||
for (entity, sid) in door_entities {
|
||||
if let Some(mut door) = world.get_mut::<DoorState>(entity) {
|
||||
door.is_open = true;
|
||||
let tile = door.blocking_tile;
|
||||
if let Some(mut wmap) = world.get_resource_mut::<crate::simulation::movement::WalkabilityMap>() {
|
||||
wmap.set_walkable(&tile, true);
|
||||
}
|
||||
tracing::debug!(stable_id = sid.0, "load: restored open door state");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the player entity's KnowledgeGraph if a player exists.
|
||||
let player_entity = {
|
||||
let mut q = world.query_filtered::<Entity, With<PlayerCharacter>>();
|
||||
@@ -522,6 +557,7 @@ mod tests {
|
||||
npc_states: vec![],
|
||||
template_references: TemplateReferenceMap::default(),
|
||||
triangle_states: vec![],
|
||||
open_doors: vec![],
|
||||
};
|
||||
let bytes = bad_state.to_bytes().expect("serialize");
|
||||
let path = temp_path();
|
||||
|
||||
@@ -205,6 +205,7 @@ fn save_state_npc_kg_isolation() {
|
||||
npc_states: vec![npc_a_state, npc_b_state],
|
||||
template_references: Default::default(),
|
||||
triangle_states: vec![],
|
||||
open_doors: vec![],
|
||||
};
|
||||
|
||||
// Roundtrip: serialize → deserialize.
|
||||
|
||||
Reference in New Issue
Block a user