Sprint 37 dead-code sweep closing out two stale supersession chains: #877 (D-167, 2026-03-24): Removes HeritageRoot type alias and ZonePaletteModifier::Heritage variant from server/src/simulation/ generator.rs. The 7 abstract heritage roots were retired in favour of the corridor cultural system; these two stubs were the only remaining references. #878 (D-032 + cascade rule): Strips the entire CharacterArchetype (Smuggler/Detective) trace from the server. Per lead direction 2026-04-21 and the development cascade (CLAUDE.md), character/NPC/ verb-differentiation/monologue code is Phase 6 detail that should not exist in code yet. The running archetype trace was pre-cascade filler, not production — production is only the client's character- creation UI and insert screens (client follow-up in #882). Deleted: - CharacterArchetype enum + StartupMessage.character_archetype field - archetype_verb_label() + archetype branch of apply_phase2_verb_filter (D-057 character-verb differentiation — marked superseded) - MonologueState.character partitioning - Gauntlet archetype plumbing (setup_gauntlet no longer takes an archetype) - server/content/schemas/drama_module.schema.yaml (zero Rust consumers) - server/content/modules/tier1/smuggling_ring_v0_1.yaml - server/tests/archetype_monologue.rs (regression guard for the removed system) - server/tests/v01_integration_playthrough.rs (archetype-dependent) Decision updates: - decisions/content.md D-032 supersession rewritten to cite the cascade (v0.2 drop invalidated the prior D-117 framing). - decisions/content.md D-035 tag taxonomy: `character` enum footnote updated; field noted as unused, do not reintroduce without a confirmed Phase 6 design. - decisions/perception.md D-057: archetype-verb differentiation marked superseded. Also bundles the types.rs version-field removal from #874 since the file was already touched here. Full trace audit in docs/architecture/sprint-37-878-audit.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
102 lines
3.9 KiB
Rust
102 lines
3.9 KiB
Rust
//! E2E integration test: full game loop with input processing and snapshot generation
|
|
//! Tests the complete pipeline: client sends input -> server processes -> server sends snapshot
|
|
|
|
use bevy_app::prelude::*;
|
|
use settled_reach_server::bridge::framing::{read_framed, write_framed};
|
|
use settled_reach_server::bridge::tcp::TcpBridge;
|
|
use settled_reach_server::bridge::types::*;
|
|
use settled_reach_server::bridge::{BridgePlugin, BridgeResource};
|
|
use settled_reach_server::knowledge::{KnowledgeGraph, KnowledgePlugin};
|
|
use settled_reach_server::npc::relationships::TrustEventQueue;
|
|
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};
|
|
use std::net::{TcpListener, TcpStream};
|
|
use std::thread;
|
|
|
|
#[test]
|
|
fn player_moves_north_through_full_pipeline() {
|
|
// Bind listener first — port guaranteed ready, no sleep needed
|
|
let listener = TcpListener::bind("127.0.0.1:0").expect("bind listener");
|
|
let server_addr = listener.local_addr().expect("get local addr");
|
|
|
|
// Spawn server thread
|
|
let server_handle = thread::spawn(move || {
|
|
let bridge = TcpBridge::accept_on(listener).expect("accept connection");
|
|
|
|
// Build app
|
|
let mut app = App::new();
|
|
app.add_plugins(SimulationPlugin { seed: 0 });
|
|
app.add_plugins(BridgePlugin);
|
|
app.add_plugins(KnowledgePlugin);
|
|
app.init_resource::<TrustEventQueue>();
|
|
app.insert_resource(BridgeResource::new(bridge));
|
|
app.insert_resource(WalkabilityMap::new(32, 32, 1));
|
|
app.world_mut().spawn((
|
|
PlayerCharacter,
|
|
TilePosition::new(16, 16, 0),
|
|
KnowledgeGraph::new(),
|
|
NearbyInteractionBuffer::default(),
|
|
MonologueBuffer::default(),
|
|
MonologueState::default(),
|
|
));
|
|
|
|
// Run one tick: receive input, process, validate movement, generate snapshot, send
|
|
app.update();
|
|
});
|
|
|
|
// Client: connect and send input (no sleep — listener was pre-bound)
|
|
let stream = TcpStream::connect(server_addr).expect("client connect");
|
|
let mut reader = BufReader::new(stream.try_clone().expect("clone for reader"));
|
|
let mut writer = BufWriter::new(stream);
|
|
|
|
// Send PlayerInput: MoveNorth at tick 0
|
|
let inputs = vec![PlayerInput {
|
|
tick: 0,
|
|
action: PlayerAction::MoveNorth,
|
|
}];
|
|
let payload = rmp_serde::to_vec_named(&inputs).expect("serialize inputs");
|
|
write_framed(&mut writer, &payload).expect("send inputs");
|
|
|
|
// Receive ObserverSnapshot
|
|
let response = read_framed(&mut reader)
|
|
.expect("read snapshot")
|
|
.expect("not EOF");
|
|
let snapshot: ObserverSnapshot =
|
|
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.tick, 0);
|
|
assert_eq!(snapshot.entities.len(), 1);
|
|
|
|
// v2 fields populated
|
|
assert_eq!(snapshot.game_time.day, 0);
|
|
assert_eq!(
|
|
snapshot.game_time.day_phase,
|
|
settled_reach_server::simulation::time::DayPhase::Morning
|
|
);
|
|
assert_eq!(
|
|
snapshot.game_time.tick_rate,
|
|
settled_reach_server::simulation::time::TickRate::Full
|
|
);
|
|
|
|
let player_entity = &snapshot.entities[0];
|
|
// Player started at (16, 16, 0), moved north (y-1) to (16, 15, 0)
|
|
// Render coords: (16.5, 15.5, 0)
|
|
assert_eq!(player_entity.x, 16.5);
|
|
assert_eq!(player_entity.y, 15.5);
|
|
assert_eq!(player_entity.z, 0);
|
|
assert!(matches!(player_entity.kind, EntityKind::Player));
|
|
assert!(matches!(
|
|
player_entity.visibility,
|
|
VisibilitySector::Forward
|
|
));
|
|
|
|
// Clean up
|
|
drop(reader);
|
|
drop(writer);
|
|
server_handle.join().expect("server thread panicked");
|
|
}
|