test(simulation): sprint 10 — replay loading, content scaling, serialization v9, observer tests
#483: Replay loading in test-client — JSONL file loading, tick-scheduled PlayerInput sending, 13 unit tests, 3 sample replay files. #500: Content scaling test — baseline + extra NPC comparative, tick budget assertion (D-026), determinism check across content packs. #514: Serialization tests for protocol v9 — blocked_entities roundtrip, backward compat (v5→v9, v8→v9), regenerated msgpack fixtures. Observer perception tests for confrontation + walk-away mechanics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ fn test_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot {
|
||||
current_monologue: None,
|
||||
pending_recognitions: vec![],
|
||||
dialogue_response: None,
|
||||
blocked_entities: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +251,7 @@ fn snapshot_v2_fields_roundtrip() {
|
||||
current_monologue: None,
|
||||
pending_recognitions: vec![],
|
||||
dialogue_response: None,
|
||||
blocked_entities: vec![],
|
||||
};
|
||||
|
||||
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
|
||||
@@ -304,7 +306,7 @@ fn protocol_version_constant_matches_snapshot() {
|
||||
let snapshot = test_snapshot(0, vec![]);
|
||||
assert_eq!(snapshot.version, PROTOCOL_VERSION);
|
||||
assert_eq!(
|
||||
PROTOCOL_VERSION, 8,
|
||||
PROTOCOL_VERSION, 9,
|
||||
"bump this assertion when protocol version changes"
|
||||
);
|
||||
}
|
||||
@@ -342,6 +344,7 @@ fn all_facing_direction_variants_roundtrip() {
|
||||
current_monologue: None,
|
||||
pending_recognitions: vec![],
|
||||
dialogue_response: None,
|
||||
blocked_entities: vec![],
|
||||
};
|
||||
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
|
||||
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
@@ -469,6 +472,10 @@ fn v5_payload_deserializes_into_v6_struct() {
|
||||
decoded.pending_recognitions.is_empty(),
|
||||
"missing pending_recognitions should default to empty"
|
||||
);
|
||||
assert!(
|
||||
decoded.blocked_entities.is_empty(),
|
||||
"missing blocked_entities should default to empty"
|
||||
);
|
||||
}
|
||||
|
||||
/// Full 9-slot inventory roundtrip (D-065: 3x3 grid = 9 slots universal)
|
||||
@@ -1095,6 +1102,90 @@ fn gdscript_generated_fixtures_deserialize() {
|
||||
eprintln!("Verified {} GDScript-generated fixtures", count);
|
||||
}
|
||||
|
||||
/// blocked_entities Vec<u64> round-trips through MessagePack (#514).
|
||||
/// Guards the debug field survives serialization.
|
||||
#[test]
|
||||
fn blocked_entities_roundtrip() {
|
||||
let mut snapshot = test_snapshot(0, vec![]);
|
||||
snapshot.blocked_entities = vec![42, 99, 1024];
|
||||
|
||||
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
|
||||
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
|
||||
assert_eq!(
|
||||
decoded.blocked_entities,
|
||||
vec![42, 99, 1024],
|
||||
"blocked_entities should survive roundtrip"
|
||||
);
|
||||
}
|
||||
|
||||
/// Empty blocked_entities round-trips correctly (#514).
|
||||
#[test]
|
||||
fn blocked_entities_empty_roundtrip() {
|
||||
let snapshot = test_snapshot(0, vec![]);
|
||||
assert!(snapshot.blocked_entities.is_empty());
|
||||
|
||||
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
|
||||
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
|
||||
assert!(
|
||||
decoded.blocked_entities.is_empty(),
|
||||
"empty blocked_entities should survive roundtrip"
|
||||
);
|
||||
}
|
||||
|
||||
/// v8 payloads (without blocked_entities) must deserialize into the v9 struct
|
||||
/// via #[serde(default)]. Guards backwards compat during migration (#514).
|
||||
#[test]
|
||||
fn v8_payload_deserializes_into_v9_struct() {
|
||||
#[derive(serde::Serialize)]
|
||||
struct ObserverSnapshotV8 {
|
||||
version: u8,
|
||||
tick: u64,
|
||||
game_time: GameTime,
|
||||
player_facing: FacingDirection,
|
||||
player_stance: MovementStance,
|
||||
player_inventory: Vec<InventoryItem>,
|
||||
entities: Vec<VisibleEntity>,
|
||||
visible_tiles: Vec<VisibleTile>,
|
||||
nearby_interactions: Vec<NearbyInteraction>,
|
||||
current_monologue: Option<MonologueEvent>,
|
||||
pending_recognitions: Vec<PendingRecognitionWire>,
|
||||
dialogue_response: Option<DialogueResponseEvent>,
|
||||
}
|
||||
|
||||
let v8 = ObserverSnapshotV8 {
|
||||
version: 8,
|
||||
tick: 100,
|
||||
game_time: GameTime {
|
||||
day: 0,
|
||||
time_of_day: 0,
|
||||
day_phase: DayPhase::Morning,
|
||||
tick_rate: TickRate::Full,
|
||||
},
|
||||
player_facing: FacingDirection::North,
|
||||
player_stance: MovementStance::Walk,
|
||||
player_inventory: vec![],
|
||||
entities: vec![],
|
||||
visible_tiles: vec![],
|
||||
nearby_interactions: vec![],
|
||||
current_monologue: None,
|
||||
pending_recognitions: vec![],
|
||||
dialogue_response: None,
|
||||
};
|
||||
|
||||
let bytes = rmp_serde::to_vec_named(&v8).expect("serialize v8");
|
||||
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes)
|
||||
.expect("v8 payload should deserialize into v9 struct via serde(default)");
|
||||
|
||||
assert_eq!(decoded.version, 8, "version field preserved from v8");
|
||||
assert_eq!(decoded.tick, 100);
|
||||
assert!(
|
||||
decoded.blocked_entities.is_empty(),
|
||||
"missing blocked_entities should default to empty"
|
||||
);
|
||||
}
|
||||
|
||||
/// NearbyInteraction.object_type round-trips through MessagePack (#422).
|
||||
/// Verifies object_type=Some(Container) survives the wire.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user