Three fixes to make the gameplay loop functional end-to-end: - Add Interactable component to NPC spawn so E-prompt detection works - Build monologue trigger system (enter_location + time_idle) with MonologueBuffer/MonologueState components, wire through ObserverSnapshot as current_monologue field, decode on client and display via HUD - Change PlayerAction::Interact from unit to struct variant carrying optional target_entity_id and verb fields Bumps protocol version from 4 to 5. Regenerates MessagePack fixtures. All 200 tests pass (170 unit + 30 integration). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,7 @@ fn snapshot_roundtrip_over_unix_socket() {
|
||||
let bridge = LocalBridge::accept(&server_path).expect("failed to accept");
|
||||
|
||||
let snapshot = ObserverSnapshot {
|
||||
version: 4,
|
||||
version: PROTOCOL_VERSION,
|
||||
tick: 42,
|
||||
game_time: GameTime {
|
||||
day: 0,
|
||||
@@ -54,6 +54,7 @@ fn snapshot_roundtrip_over_unix_socket() {
|
||||
}],
|
||||
visible_tiles: vec![],
|
||||
nearby_interactions: vec![],
|
||||
current_monologue: None,
|
||||
};
|
||||
|
||||
bridge
|
||||
@@ -116,7 +117,7 @@ fn input_roundtrip_over_unix_socket() {
|
||||
},
|
||||
PlayerInput {
|
||||
tick: 11,
|
||||
action: PlayerAction::Interact,
|
||||
action: PlayerAction::Interact { target_entity_id: None, verb: None },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -134,7 +135,7 @@ fn input_roundtrip_over_unix_socket() {
|
||||
_ => panic!("expected MoveNorth action"),
|
||||
}
|
||||
match &received_inputs[1].action {
|
||||
PlayerAction::Interact => {}
|
||||
PlayerAction::Interact { .. } => {}
|
||||
_ => panic!("expected Interact action"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ fn snapshot_roundtrip_over_tcp() {
|
||||
let bridge = TcpBridge::accept_on(listener).expect("failed to accept");
|
||||
|
||||
let snapshot = ObserverSnapshot {
|
||||
version: 4,
|
||||
version: PROTOCOL_VERSION,
|
||||
tick: 42,
|
||||
game_time: GameTime {
|
||||
day: 0,
|
||||
@@ -40,6 +40,7 @@ fn snapshot_roundtrip_over_tcp() {
|
||||
}],
|
||||
visible_tiles: vec![],
|
||||
nearby_interactions: vec![],
|
||||
current_monologue: None,
|
||||
};
|
||||
|
||||
bridge
|
||||
@@ -95,7 +96,7 @@ fn input_roundtrip_over_tcp() {
|
||||
},
|
||||
PlayerInput {
|
||||
tick: 11,
|
||||
action: PlayerAction::Interact,
|
||||
action: PlayerAction::Interact { target_entity_id: None, verb: None },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -112,7 +113,7 @@ fn input_roundtrip_over_tcp() {
|
||||
_ => panic!("expected MoveNorth action"),
|
||||
}
|
||||
match &received_inputs[1].action {
|
||||
PlayerAction::Interact => {}
|
||||
PlayerAction::Interact { .. } => {}
|
||||
_ => panic!("expected Interact action"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use settled_reach_server::bridge::types::*;
|
||||
use settled_reach_server::bridge::{BridgePlugin, BridgeResource};
|
||||
use settled_reach_server::knowledge::{KnowledgeGraph, KnowledgePlugin};
|
||||
use settled_reach_server::simulation::interaction::NearbyInteractionBuffer;
|
||||
use settled_reach_server::simulation::monologue::{MonologueBuffer, MonologueState};
|
||||
use settled_reach_server::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
|
||||
use settled_reach_server::simulation::SimulationPlugin;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
@@ -36,6 +37,8 @@ fn player_moves_north_through_full_pipeline() {
|
||||
TilePosition::new(16, 16, 0),
|
||||
KnowledgeGraph::new(),
|
||||
NearbyInteractionBuffer::default(),
|
||||
MonologueBuffer::default(),
|
||||
MonologueState::default(),
|
||||
));
|
||||
|
||||
// Run one tick: receive input, process, validate movement, generate snapshot, send
|
||||
@@ -63,7 +66,7 @@ fn player_moves_north_through_full_pipeline() {
|
||||
rmp_serde::from_slice(&response).expect("deserialize snapshot");
|
||||
|
||||
// Snapshot captures state at end of tick 0 (before advance_tick increments to 1)
|
||||
assert_eq!(snapshot.version, 4);
|
||||
assert_eq!(snapshot.version, 5);
|
||||
assert_eq!(snapshot.tick, 0);
|
||||
assert_eq!(snapshot.entities.len(), 1);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ fn write_fixture(name: &str, bytes: &[u8]) {
|
||||
/// Helper to create a minimal v2 snapshot for fixtures
|
||||
fn fixture_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot {
|
||||
ObserverSnapshot {
|
||||
version: 4,
|
||||
version: PROTOCOL_VERSION,
|
||||
tick,
|
||||
game_time: GameTime {
|
||||
day: 0,
|
||||
@@ -30,6 +30,7 @@ fn fixture_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot
|
||||
entities,
|
||||
visible_tiles: vec![],
|
||||
nearby_interactions: vec![],
|
||||
current_monologue: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ fn generate_msgpack_fixtures() {
|
||||
|
||||
// v2 snapshot with visible_tiles and game_time populated
|
||||
let snapshot_v2_full = ObserverSnapshot {
|
||||
version: 4,
|
||||
version: PROTOCOL_VERSION,
|
||||
tick: 500,
|
||||
game_time: GameTime {
|
||||
day: 1,
|
||||
@@ -194,6 +195,7 @@ fn generate_msgpack_fixtures() {
|
||||
},
|
||||
],
|
||||
nearby_interactions: vec![],
|
||||
current_monologue: None,
|
||||
};
|
||||
write_fixture(
|
||||
"snapshot_v2_full",
|
||||
@@ -208,7 +210,7 @@ fn generate_msgpack_fixtures() {
|
||||
},
|
||||
PlayerInput {
|
||||
tick: 0,
|
||||
action: PlayerAction::Interact,
|
||||
action: PlayerAction::Interact { target_entity_id: None, verb: None },
|
||||
},
|
||||
];
|
||||
write_fixture(
|
||||
|
||||
@@ -19,6 +19,7 @@ fn test_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot {
|
||||
entities,
|
||||
visible_tiles: vec![],
|
||||
nearby_interactions: vec![],
|
||||
current_monologue: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +42,7 @@ fn observer_snapshot_roundtrip() {
|
||||
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
|
||||
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
|
||||
assert_eq!(decoded.version, 4);
|
||||
assert_eq!(decoded.version, PROTOCOL_VERSION);
|
||||
assert_eq!(decoded.tick, 42);
|
||||
assert_eq!(decoded.entities.len(), 1);
|
||||
assert_eq!(decoded.entities[0].entity_id, 1);
|
||||
@@ -83,7 +84,7 @@ fn all_player_action_variants_roundtrip() {
|
||||
PlayerAction::MoveNorthwest,
|
||||
PlayerAction::MoveSoutheast,
|
||||
PlayerAction::MoveSouthwest,
|
||||
PlayerAction::Interact,
|
||||
PlayerAction::Interact { target_entity_id: None, verb: None },
|
||||
PlayerAction::UsePerceptionMode("thermal".to_string()),
|
||||
PlayerAction::Pause,
|
||||
PlayerAction::Unpause,
|
||||
@@ -128,7 +129,7 @@ fn all_fixtures_deserialize() {
|
||||
if name.starts_with("snapshot") {
|
||||
let snap = rmp_serde::from_slice::<ObserverSnapshot>(&bytes)
|
||||
.unwrap_or_else(|e| panic!("deserialize snapshot fixture {}: {}", name, e));
|
||||
assert_eq!(snap.version, 4, "fixture {} has wrong version", name);
|
||||
assert_eq!(snap.version, PROTOCOL_VERSION, "fixture {} has wrong version", name);
|
||||
} else if name.starts_with("input_batch") {
|
||||
rmp_serde::from_slice::<Vec<PlayerInput>>(&bytes)
|
||||
.unwrap_or_else(|e| panic!("deserialize batch input fixture {}: {}", name, e));
|
||||
@@ -180,7 +181,7 @@ fn all_entity_kind_variants_roundtrip() {
|
||||
#[test]
|
||||
fn snapshot_v2_fields_roundtrip() {
|
||||
let snapshot = ObserverSnapshot {
|
||||
version: 4,
|
||||
version: PROTOCOL_VERSION,
|
||||
tick: 100,
|
||||
game_time: GameTime {
|
||||
day: 3,
|
||||
@@ -216,12 +217,13 @@ fn snapshot_v2_fields_roundtrip() {
|
||||
},
|
||||
],
|
||||
nearby_interactions: vec![],
|
||||
current_monologue: None,
|
||||
};
|
||||
|
||||
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
|
||||
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
|
||||
assert_eq!(decoded.version, 4);
|
||||
assert_eq!(decoded.version, PROTOCOL_VERSION);
|
||||
assert_eq!(decoded.game_time.day, 3);
|
||||
assert_eq!(decoded.game_time.time_of_day, 720);
|
||||
assert_eq!(decoded.game_time.day_phase, DayPhase::Evening);
|
||||
@@ -259,7 +261,7 @@ fn entity_to_bits_roundtrip() {
|
||||
fn protocol_version_constant_matches_snapshot() {
|
||||
let snapshot = test_snapshot(0, vec![]);
|
||||
assert_eq!(snapshot.version, PROTOCOL_VERSION);
|
||||
assert_eq!(PROTOCOL_VERSION, 4, "bump this assertion when protocol version changes");
|
||||
assert_eq!(PROTOCOL_VERSION, 5, "bump this assertion when protocol version changes");
|
||||
}
|
||||
|
||||
/// All FacingDirection variants round-trip
|
||||
@@ -278,7 +280,7 @@ fn all_facing_direction_variants_roundtrip() {
|
||||
|
||||
for dir in directions {
|
||||
let snapshot = ObserverSnapshot {
|
||||
version: 4,
|
||||
version: PROTOCOL_VERSION,
|
||||
tick: 0,
|
||||
game_time: GameTime {
|
||||
day: 0,
|
||||
@@ -290,6 +292,7 @@ fn all_facing_direction_variants_roundtrip() {
|
||||
entities: vec![],
|
||||
visible_tiles: vec![],
|
||||
nearby_interactions: vec![],
|
||||
current_monologue: None,
|
||||
};
|
||||
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
|
||||
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");
|
||||
|
||||
Reference in New Issue
Block a user