wip: current project state

This commit is contained in:
Emil
2026-06-21 22:27:20 +03:00
parent 88892db493
commit a5436897ed
48 changed files with 4583 additions and 760 deletions
+83 -18
View File
@@ -1,12 +1,18 @@
use verbatim::ai::GameSession;
use verbatim::ai::AiAction;
use verbatim::ai::GameSession;
use verbatim::ai::ReplayPlayer;
fn setup() -> GameSession {
let mut s = GameSession::new_seeded(42);
s.init_empty();
s.clear_area(90, 90, 50, 50);
s.perform_action(&AiAction::FillRect { x: 80, y: 130, w: 80, h: 15, material: "stone".into() });
s.perform_action(&AiAction::FillRect {
x: 80,
y: 130,
w: 80,
h: 15,
material: "stone".into(),
});
s
}
@@ -32,7 +38,11 @@ fn same_seed_same_entity_count() {
let mut s2 = GameSession::new_seeded(555);
s2.init();
s2.step(60);
assert_eq!(s1.get_entities().len(), s2.get_entities().len(), "entity count should match");
assert_eq!(
s1.get_entities().len(),
s2.get_entities().len(),
"entity count should match"
);
}
#[test]
@@ -47,7 +57,11 @@ fn same_seed_same_grid_state() {
for x in 100..150 {
let c1 = s1.get_cell(x, y);
let c2 = s2.get_cell(x, y);
assert_eq!(c1.material, c2.material, "material mismatch at ({},{})", x, y);
assert_eq!(
c1.material, c2.material,
"material mismatch at ({},{})",
x, y
);
}
}
}
@@ -69,7 +83,8 @@ fn replay_exact_match() {
s.perform_action(&AiAction::MoveLeft);
s.step(10);
let state_orig = s.get_state();
s.save_replay("/tmp/verbatim_replay_exact.json").expect("save");
s.save_replay("/tmp/verbatim_replay_exact.json")
.expect("save");
let player = ReplayPlayer::load("/tmp/verbatim_replay_exact.json").expect("load");
let s2 = player.play();
@@ -77,8 +92,18 @@ fn replay_exact_match() {
assert_eq!(state_orig.tick, state_replay.tick, "tick mismatch");
if let (Some(p1), Some(p2)) = (&state_orig.player, &state_replay.player) {
assert!((p1.pos[0] - p2.pos[0]).abs() < 0.1, "x mismatch: {} vs {}", p1.pos[0], p2.pos[0]);
assert!((p1.pos[1] - p2.pos[1]).abs() < 0.1, "y mismatch: {} vs {}", p1.pos[1], p2.pos[1]);
assert!(
(p1.pos[0] - p2.pos[0]).abs() < 0.1,
"x mismatch: {} vs {}",
p1.pos[0],
p2.pos[0]
);
assert!(
(p1.pos[1] - p2.pos[1]).abs() < 0.1,
"y mismatch: {} vs {}",
p1.pos[1],
p2.pos[1]
);
assert!((p1.health - p2.health).abs() < 1.0, "health mismatch");
}
}
@@ -91,14 +116,25 @@ fn replay_stop_at_tick() {
s.step(20);
s.perform_action(&AiAction::MoveRight);
s.step(20);
s.save_replay("/tmp/verbatim_replay_partial.json").expect("save");
s.save_replay("/tmp/verbatim_replay_partial.json")
.expect("save");
let player = ReplayPlayer::load("/tmp/verbatim_replay_partial.json").expect("load");
let s_half = player.play_until_tick(10);
assert_eq!(s_half.tick(), 10, "should stop at tick 10, got {}", s_half.tick());
assert_eq!(
s_half.tick(),
10,
"should stop at tick 10, got {}",
s_half.tick()
);
let s_full = player.play();
assert_eq!(s_full.tick(), 40, "full replay should reach tick 40, got {}", s_full.tick());
assert_eq!(
s_full.tick(),
40,
"full replay should reach tick 40, got {}",
s_full.tick()
);
}
#[test]
@@ -110,28 +146,52 @@ fn recording_captures_all_actions() {
s.perform_action(&AiAction::Jump);
s.perform_action(&AiAction::MoveLeft);
s.step(5);
s.save_replay("/tmp/verbatim_replay_capture.json").expect("save");
s.save_replay("/tmp/verbatim_replay_capture.json")
.expect("save");
let player = ReplayPlayer::load("/tmp/verbatim_replay_capture.json").expect("load");
let event_count = player.recording().events.len();
assert!(event_count >= 4, "recording should have at least 4 events (3 actions + 1 step), got {}", event_count);
assert!(
event_count >= 4,
"recording should have at least 4 events (3 actions + 1 step), got {}",
event_count
);
}
#[test]
fn determinism_with_spawn_and_damage() {
let mut s1 = setup();
s1.perform_action(&AiAction::Spawn { kind: "goblin".into(), x: 135.0, y: 120.0 });
s1.perform_action(&AiAction::DamageEntity { id: 1, amount: 20.0 });
s1.perform_action(&AiAction::Spawn {
kind: "goblin".into(),
x: 135.0,
y: 120.0,
});
s1.perform_action(&AiAction::DamageEntity {
id: 1,
amount: 20.0,
});
s1.step(30);
let mut s2 = setup();
s2.perform_action(&AiAction::Spawn { kind: "goblin".into(), x: 135.0, y: 120.0 });
s2.perform_action(&AiAction::DamageEntity { id: 1, amount: 20.0 });
s2.perform_action(&AiAction::Spawn {
kind: "goblin".into(),
x: 135.0,
y: 120.0,
});
s2.perform_action(&AiAction::DamageEntity {
id: 1,
amount: 20.0,
});
s2.step(30);
let e1 = s1.get_entities().into_iter().find(|e| e.id == 1).unwrap();
let e2 = s2.get_entities().into_iter().find(|e| e.id == 1).unwrap();
assert!((e1.health - e2.health).abs() < 0.01, "health should match: {} vs {}", e1.health, e2.health);
assert!(
(e1.health - e2.health).abs() < 0.01,
"health should match: {} vs {}",
e1.health,
e2.health
);
assert!((e1.pos[0] - e2.pos[0]).abs() < 0.1, "x should match");
assert!((e1.pos[1] - e2.pos[1]).abs() < 0.1, "y should match");
}
@@ -150,5 +210,10 @@ fn hundred_tick_determinism() {
let p1 = s1.get_player().unwrap();
let p2 = s2.get_player().unwrap();
assert!((p1.pos[0] - p2.pos[0]).abs() < 0.01, "100-tick determinism failed: {} vs {}", p1.pos[0], p2.pos[0]);
assert!(
(p1.pos[0] - p2.pos[0]).abs() < 0.01,
"100-tick determinism failed: {} vs {}",
p1.pos[0],
p2.pos[0]
);
}