diff --git a/server/src/simulation/mod.rs b/server/src/simulation/mod.rs index dc101ad62..e82407744 100644 --- a/server/src/simulation/mod.rs +++ b/server/src/simulation/mod.rs @@ -24,8 +24,8 @@ impl Plugin for SimulationPlugin { Update, ( input::process_player_input, - time::advance_tick.after(input::process_player_input), - movement::validate_movement.after(time::advance_tick), + movement::validate_movement.after(input::process_player_input), + time::advance_tick.after(movement::validate_movement), ), ); diff --git a/server/tests/game_loop.rs b/server/tests/game_loop.rs index f2a53e760..97109f6ca 100644 --- a/server/tests/game_loop.rs +++ b/server/tests/game_loop.rs @@ -1,5 +1,5 @@ //! E2E integration test: full game loop with input processing and snapshot generation -//! Tests the complete pipeline: client sends input → server processes → server sends snapshot +//! 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}; @@ -10,31 +10,17 @@ use settled_reach_server::simulation::movement::{PlayerCharacter, TilePosition, 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 + // 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"); - 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"); + let bridge = TcpBridge::accept_on(listener).expect("accept connection"); // Build app let mut app = App::new(); @@ -47,64 +33,42 @@ fn player_moves_north_through_full_pipeline() { // 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"); + // 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); - // Give server time to bind and call accept() - thread::sleep(Duration::from_millis(100)); + // 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"); - // 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); + // 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"); - // 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"); + // 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); - // 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"); + 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 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 + // Clean up + drop(reader); + drop(writer); server_handle.join().expect("server thread panicked"); }