Dead code removed (21 methods, 4 fields, 3 constants): - Cell: MaterialId::ALL, MaterialId::from_u8 (unsafe transmute) - Grid: get_mut, clear, fill_rect, swap, dump_region, next buffer field - Entity: move_center, EntityManager::iter_mut - Player: move_dir field, entity_mut - VerletSolver: step, SubBody::add_vel, SubBody::apply_force - CellularAutomaton: tick_count - InputHandler: release_all, poll, Action::None - WindowInput: clear - GameSession: perform_action_and_step, is_recording, grid_mut - ReplayPlayer: from_recording - Material: empty() - VulkanRenderer: tick_count field - MaterialBrush: name() Warnings fixed: - Remove unused MaterialRegistry imports from renderers - Remove unused reg variables in terminal/vulkan/graphics - Remove unused water_surface in game.rs - Remove unused p/y_death in tests - Remove unused qf_slice in graphics.rs Duplication eliminated: - main.rs: run_ascii_mode + run_graphics_mode → generic run_gpu_mode<R: GpuRenderer> ~140 lines of duplicated event loop code removed - GpuRenderer trait unifies VulkanRenderer and GraphicsRenderer API Unsafe code fixed: - rand_u8: static mut + unsafe → AtomicU8 + fetch_add (thread-safe) Module cleanup: - world/mod.rs: removed all unused re-exports - physics/mod.rs: removed all unused re-exports - entity/mod.rs: removed unused Entity/EntityId re-exports Result: ~6500 → ~5964 lines, 0 non-deprecation warnings, 109 tests pass
122 lines
4.5 KiB
Rust
122 lines
4.5 KiB
Rust
use verbatim::ai::GameSession;
|
|
use verbatim::ai::AiAction;
|
|
|
|
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
|
|
}
|
|
|
|
#[test]
|
|
fn death_transitions_rigid_to_ragdoll() {
|
|
let mut s = setup();
|
|
s.perform_action(&AiAction::Spawn { kind: "goblin".into(), x: 140.0, y: 120.0 });
|
|
s.step(20);
|
|
let entities = s.get_entities();
|
|
let goblin = entities.into_iter().find(|e| e.kind == "Goblin").unwrap();
|
|
assert!(goblin.alive, "goblin should be alive initially");
|
|
|
|
s.perform_action(&AiAction::DamageEntity { id: goblin.id, amount: 100.0 });
|
|
s.step(1);
|
|
let entities = s.get_entities();
|
|
let corpse = entities.into_iter().find(|e| e.id == goblin.id).unwrap();
|
|
assert!(!corpse.alive, "goblin should be dead after 100 damage");
|
|
assert_eq!(corpse.kind, "Corpse", "dead goblin should become corpse");
|
|
}
|
|
|
|
#[test]
|
|
fn ragdoll_falls_after_death() {
|
|
let mut s = setup();
|
|
s.perform_action(&AiAction::Spawn { kind: "goblin".into(), x: 140.0, y: 110.0 });
|
|
s.step(20);
|
|
let entities = s.get_entities();
|
|
let g = entities.into_iter().find(|e| e.kind == "Goblin").unwrap();
|
|
let y_before = g.pos[1];
|
|
|
|
s.perform_action(&AiAction::DamageEntity { id: g.id, amount: 100.0 });
|
|
s.step(30);
|
|
let y_after = {
|
|
let e = s.get_entities().into_iter().find(|e| e.id == g.id).unwrap();
|
|
e.pos[1]
|
|
};
|
|
assert!(y_after > y_before, "corpse should fall: y_before={} y_after={}", y_before, y_after);
|
|
}
|
|
|
|
#[test]
|
|
fn ragdoll_bodies_stay_near_each_other() {
|
|
let mut s = setup();
|
|
s.perform_action(&AiAction::Spawn { kind: "goblin".into(), x: 130.0, y: 110.0 });
|
|
s.step(20);
|
|
let entities = s.get_entities();
|
|
let g = entities.into_iter().find(|e| e.kind == "Goblin").unwrap();
|
|
s.perform_action(&AiAction::DamageEntity { id: g.id, amount: 100.0 });
|
|
s.step(10);
|
|
let entities = s.get_entities();
|
|
let corpse = entities.into_iter().find(|e| e.id == g.id).unwrap();
|
|
assert!(!corpse.alive, "corpse should be dead");
|
|
assert!(corpse.body_count > 0, "corpse should have some alive bodies");
|
|
}
|
|
|
|
#[test]
|
|
fn player_death_becomes_corpse() {
|
|
let mut s = setup();
|
|
s.step(30);
|
|
let p = s.get_player().unwrap();
|
|
s.perform_action(&AiAction::DamageEntity { id: p.id, amount: 200.0 });
|
|
s.step(1);
|
|
let p2 = s.get_player().unwrap();
|
|
assert!(!p2.alive, "player should be dead after 200 damage");
|
|
}
|
|
|
|
#[test]
|
|
fn damage_reduces_health_progressively() {
|
|
let mut s = setup();
|
|
s.perform_action(&AiAction::Spawn { kind: "goblin".into(), x: 140.0, y: 120.0 });
|
|
s.step(20);
|
|
let entities = s.get_entities();
|
|
let g = entities.into_iter().find(|e| e.kind == "Goblin").unwrap();
|
|
assert_eq!(g.health, 40.0, "goblin should start at 40 HP");
|
|
|
|
s.perform_action(&AiAction::DamageEntity { id: g.id, amount: 10.0 });
|
|
s.step(1);
|
|
let entities = s.get_entities();
|
|
let g2 = entities.into_iter().find(|e| e.id == g.id).unwrap();
|
|
assert!((g2.health - 30.0).abs() < 0.01, "goblin should have 30 HP after 10 damage, got {}", g2.health);
|
|
assert!(g2.alive, "goblin should survive 10 damage");
|
|
}
|
|
|
|
#[test]
|
|
fn small_damage_does_not_kill() {
|
|
let mut s = setup();
|
|
s.perform_action(&AiAction::Spawn { kind: "goblin".into(), x: 140.0, y: 120.0 });
|
|
s.step(20);
|
|
let entities = s.get_entities();
|
|
let g = entities.into_iter().find(|e| e.kind == "Goblin").unwrap();
|
|
s.perform_action(&AiAction::DamageEntity { id: g.id, amount: 39.0 });
|
|
s.step(1);
|
|
let entities = s.get_entities();
|
|
let g2 = entities.into_iter().find(|e| e.id == g.id).unwrap();
|
|
assert!(g2.alive, "goblin should survive 39 damage (HP=1)");
|
|
s.perform_action(&AiAction::DamageEntity { id: g.id, amount: 1.0 });
|
|
s.step(1);
|
|
let entities = s.get_entities();
|
|
let g3 = entities.into_iter().find(|e| e.id == g.id).unwrap();
|
|
assert!(!g3.alive, "goblin should die at 0 HP");
|
|
}
|
|
|
|
#[test]
|
|
fn corpse_exists_in_world() {
|
|
let mut s = setup();
|
|
s.perform_action(&AiAction::Spawn { kind: "goblin".into(), x: 140.0, y: 120.0 });
|
|
s.step(20);
|
|
let g_id = s.get_entities().into_iter().find(|e| e.kind == "Goblin").unwrap().id;
|
|
s.perform_action(&AiAction::DamageEntity { id: g_id, amount: 100.0 });
|
|
s.step(5);
|
|
let entities = s.get_entities();
|
|
let corpse = entities.into_iter().find(|e| e.id == g_id);
|
|
assert!(corpse.is_some(), "corpse entity should persist in world");
|
|
assert!(!corpse.unwrap().alive, "corpse should not be alive");
|
|
}
|