Files
settled-reach/server/tests/game_loop.rs
T
jpmschweitzerandClaude Opus 4.6 7864bfdf7d feat(simulation): add input processing, snapshot gen, and game loop
Implements the full server-side tick pipeline:
- process_player_input drains InputQueue, converts PlayerActions to
  MoveIntent components or pause/unpause toggles
- generate_snapshot builds ObserverSnapshot from ECS state with
  render coordinate conversion
- receive_bridge_inputs/send_bridge_snapshot handle bridge I/O with
  graceful disconnect detection via ServerRunning resource
- main.rs now accepts TCP connections and runs a proper game loop
- PlayerCharacter marker, Player EntityKind, SnapshotBuffer resource

Closes server side of #81, #82, #83.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 21:01:28 +01:00

111 lines
4.1 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::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");
}