Tests added: - physics_materials.rs: 15 tests (fire, smoke, steam, grass, dirt, bone, wood, stone, lava temp) - physics_interactions.rs: 12 tests (lava+water, fire spread, acid interactions, sand+water) - player_controls.rs: 12 tests (move L/R, jump, double jump, rapid input, wait, continuous) - collision_robust.rs: 10 tests (walls, ceiling, sliding, corridor, slope, dirt wall, unstick) - ragdoll_death.rs: 7 tests (death transition, ragdoll fall, progressive damage, corpse) - determinism.rs: 8 tests (same seed, replay exact, replay partial, recording, 100-tick) - edge_cases.rs: 19 tests (boundary, stress 20 goblins, fill/clear/paint, gravity, camera) - 6 new JSON scenarios (fire chain, lava pool, sand bury, entity fall, steam, acid wall) Fixes: - AABB resolve_aabb_y: add vertical overlap check (was pushing player down when jumping) - AABB resolve_aabb_x: velocity-aware resolution (player was stuck against walls) - check_on_ground: use fractional position check (was always true) - jump_force 1.5 (was 0.8) for 27-body entity - Gravity read from self.verlet (was from clone, SetGravity didn't work) - u8 overflow in lava color (saturating_add) 109 Rust tests, 14 JSON scenarios, 0 warnings, 0 failures
109 lines
3.8 KiB
Rust
109 lines
3.8 KiB
Rust
use verbatim::ai::GameSession;
|
|
use verbatim::ai::AiAction;
|
|
use verbatim::ai::ReplayPlayer;
|
|
|
|
#[test]
|
|
fn replay_deterministic() {
|
|
let mut s1 = GameSession::new_seeded(123);
|
|
s1.init();
|
|
s1.set_recording(true);
|
|
s1.perform_action(&AiAction::MoveRight);
|
|
s1.step(10);
|
|
s1.perform_action(&AiAction::Jump);
|
|
s1.step(10);
|
|
let state1 = s1.get_state();
|
|
|
|
s1.save_replay("/tmp/verbatim_test_replay.json").expect("save replay");
|
|
|
|
let player = ReplayPlayer::load("/tmp/verbatim_test_replay.json").expect("load replay");
|
|
let s2 = player.play();
|
|
let state2 = s2.get_state();
|
|
|
|
assert_eq!(state1.tick, state2.tick, "ticks should match");
|
|
if let (Some(p1), Some(p2)) = (&state1.player, &state2.player) {
|
|
assert_eq!(p1.health, p2.health, "player health should match");
|
|
assert!((p1.pos[0] - p2.pos[0]).abs() < 0.01, "player x should match: {} vs {}", p1.pos[0], p2.pos[0]);
|
|
assert!((p1.pos[1] - p2.pos[1]).abs() < 0.01, "player y should match: {} vs {}", p1.pos[1], p2.pos[1]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn replay_play_until_tick() {
|
|
let mut s = GameSession::new_seeded(999);
|
|
s.init();
|
|
s.set_recording(true);
|
|
s.perform_action(&AiAction::MoveRight);
|
|
s.step(5);
|
|
s.perform_action(&AiAction::MoveLeft);
|
|
s.step(5);
|
|
s.perform_action(&AiAction::Jump);
|
|
s.step(10);
|
|
s.save_replay("/tmp/verbatim_test_replay2.json").expect("save");
|
|
|
|
let player = ReplayPlayer::load("/tmp/verbatim_test_replay2.json").expect("load");
|
|
let s_half = player.play_until_tick(5);
|
|
assert_eq!(s_half.tick(), 5, "should stop at tick 5, got {}", s_half.tick());
|
|
|
|
let s_full = player.play();
|
|
assert_eq!(s_full.tick(), 20, "full replay should reach tick 20, got {}", s_full.tick());
|
|
}
|
|
|
|
#[test]
|
|
fn same_seed_same_state() {
|
|
let mut s1 = GameSession::new_seeded(42);
|
|
s1.init();
|
|
s1.step(30);
|
|
let state1 = s1.get_state();
|
|
|
|
let mut s2 = GameSession::new_seeded(42);
|
|
s2.init();
|
|
s2.step(30);
|
|
let state2 = s2.get_state();
|
|
|
|
assert_eq!(state1.tick, state2.tick);
|
|
if let (Some(p1), Some(p2)) = (&state1.player, &state2.player) {
|
|
assert!((p1.pos[0] - p2.pos[0]).abs() < 0.01, "x mismatch: {} vs {}", p1.pos[0], p2.pos[0]);
|
|
assert!((p1.pos[1] - p2.pos[1]).abs() < 0.01, "y mismatch: {} vs {}", p1.pos[1], p2.pos[1]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn pipe_protocol_init_and_step() {
|
|
use std::io::Write;
|
|
use std::process::{Command, Stdio};
|
|
|
|
let mut child = Command::new(env!("CARGO_BIN_EXE_verbatim"))
|
|
.arg("--mode").arg("pipe")
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.stderr(Stdio::null())
|
|
.spawn()
|
|
.expect("failed to start process");
|
|
|
|
let stdin = child.stdin.as_mut().expect("failed to open stdin");
|
|
writeln!(stdin, "{{\"cmd\":\"init\",\"seed\":42}}").expect("write init");
|
|
stdin.flush().expect("flush");
|
|
|
|
let mut output = String::new();
|
|
let stdout = child.stdout.as_mut().expect("failed to open stdout");
|
|
use std::io::Read;
|
|
let mut buf = [0u8; 65536];
|
|
let n = stdout.read(&mut buf).expect("read");
|
|
output.push_str(&String::from_utf8_lossy(&buf[..n]));
|
|
|
|
let json: serde_json::Value = serde_json::from_str(output.trim()).expect("parse response");
|
|
assert_eq!(json["ok"], true, "init should succeed: {}", output);
|
|
|
|
writeln!(stdin, "{{\"cmd\":\"step\",\"n\":10}}").expect("write step");
|
|
stdin.flush().expect("flush");
|
|
let n = stdout.read(&mut buf).expect("read");
|
|
let output2 = String::from_utf8_lossy(&buf[..n]).to_string();
|
|
let json2: serde_json::Value = serde_json::from_str(output2.trim()).expect("parse step response");
|
|
assert_eq!(json2["ok"], true);
|
|
assert_eq!(json2["state"]["tick"], 10, "tick should be 10 after stepping 10");
|
|
|
|
writeln!(stdin, "{{\"cmd\":\"quit\"}}").expect("write quit");
|
|
stdin.flush().expect("flush");
|
|
child.wait().expect("wait");
|
|
}
|