//! 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::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap}; use settled_reach_server::simulation::SimulationPlugin; use std::io::{BufReader, BufWriter}; use std::net::{TcpListener, TcpStream}; use std::sync::mpsc; use std::thread; use std::time::Duration; #[test] fn player_moves_north_through_full_pipeline() { // Pre-bind listener to discover the port let listener = TcpListener::bind("127.0.0.1:0").expect("bind listener"); let server_addr = listener.local_addr().expect("get local addr"); let addr_str = server_addr.to_string(); // Channel to signal when server is ready to accept let (ready_tx, ready_rx) = mpsc::channel(); // Spawn server thread let server_handle = thread::spawn(move || { // Drop the pre-bound listener since TcpBridge::accept will bind its own drop(listener); // Signal we're about to accept ready_tx.send(()).expect("send ready signal"); // Accept connection let bridge = TcpBridge::accept(&addr_str).expect("accept connection"); eprintln!("Test server accepted connection"); // Build app let mut app = App::new(); app.add_plugins(SimulationPlugin); app.add_plugins(BridgePlugin); app.insert_resource(BridgeResource::new(bridge)); app.insert_resource(WalkabilityMap::new(32, 32, 1)); app.world_mut() .spawn((PlayerCharacter, TilePosition::new(16, 16, 0))); // Run one tick: receive input, process, validate movement, generate snapshot, send app.update(); eprintln!("Server completed one update cycle"); }); // Wait for server thread to start ready_rx .recv_timeout(Duration::from_secs(2)) .expect("server did not become ready"); // Give server time to bind and call accept() thread::sleep(Duration::from_millis(100)); // Client: connect and send input { 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"); eprintln!("Client sent MoveNorth input"); // 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"); eprintln!( "Client received snapshot: tick={}, entities={}", snapshot.tick, snapshot.entities.len() ); // Verify snapshot assert_eq!(snapshot.tick, 1); // After one tick assert_eq!(snapshot.entities.len(), 1); // One entity (player) 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)); eprintln!( "Client verified player moved to ({}, {}, {})", player_entity.x, player_entity.y, player_entity.z ); } // Wait for server thread server_handle.join().expect("server thread panicked"); }