fix(simulation): address PR #81 review — critical and high-priority issues

Critical fixes:
- storyteller: replace .expect() with guard + log in activation_pass (Hoshe #1)
- content/loader: validate inverted tile_bounds before iteration (Hoshe #C)
- save_io: persist ActivationState on save/load (Tyre #7)

High-priority fixes:
- storyteller: f64 intermediate for observation_time_ticks scoring (Hoshe #A)
- storyteller: deduplicate copresent entities before scoring (Hoshe #B)
- storyteller: skip activation_pass at tick 0 (Hoshe #G)
- storyteller: explicit .before(advance_tick) ordering (Tyre #9)
- save_state: insert EngagementRecord on NPC deserialize (Tyre #10)
- save_io: reset TriangleActivatedQueue + MovementHistoryBuffer on load (Tyre #8)
- debug: validate teleport target walkability (Hoshe #E)
- debug: reject SkipToContamination when tick past delay (Hoshe #F)
- debug: DebugEnabled defaults to cfg!(debug_assertions) (Hoshe #3)
- debug: log when response overwritten (Hoshe #2)
- content/loader: error on tile dimension mismatch (Hoshe #5)
- content/types: DistrictMeta.description optional (Hoshe #D)
- types: version docs updated to v18 (Tyre #1, #2, #3)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 23:40:59 +01:00
co-authored by Claude Opus 4.6
parent eb64f23b77
commit ac68ec6eef
9 changed files with 241 additions and 22 deletions
+16
View File
@@ -55,6 +55,7 @@ use crate::npc::relationships::RelationshipGraph;
use crate::npc::vision::{NpcMemory, NpcVisionState};
use crate::simulation::movement::TilePosition;
use crate::simulation::time::TickRate;
use crate::storyteller::EngagementRecord;
/// Current format version. Bump on any breaking schema change.
pub const SAVE_FORMAT_VERSION: u8 = 1;
@@ -111,6 +112,16 @@ pub struct SaveStateV1 {
/// and apply a duplicate tension delta to all ActiveFork triangles.
#[serde(default)]
pub contamination_active: bool,
/// Number of triangles activated this session (#572).
/// Persisted to prevent double-activation on save/load — without this,
/// reloading a save after activation would reset the one-shot guard
/// and allow a second triangle to be activated.
#[serde(default)]
pub activated_count: u32,
/// Tick at which the most recent triangle activation occurred (#572).
/// `None` if no activation yet. Persisted alongside `activated_count`.
#[serde(default)]
pub last_activation_tick: Option<u64>,
}
/// Per-NPC state snapshot for `SaveStateV1`.
@@ -363,6 +374,7 @@ pub fn deserialize_npc_from_frozen(state: &NpcSaveState, world: &mut World) -> E
NpcVisionState::default(),
NpcMemory::default(),
PlayerAwareness::default(),
EngagementRecord::default(),
))
.id();
@@ -425,6 +437,8 @@ mod tests {
open_doors: vec![],
modifications: vec![],
contamination_active: false,
activated_count: 0,
last_activation_tick: None,
}
}
@@ -824,6 +838,8 @@ mod tests {
open_doors: vec![],
modifications: vec![],
contamination_active: false,
activated_count: 0,
last_activation_tick: None,
};
let bytes = save.to_bytes().expect("serialize");