- Resolution: 8x8 world cells, 2x2 UI cells (UI_SCALE=4) - GPU lighting: vertex-shader computed, light source list buffer (max 64) instead of O(N×R²) grid scan, O(N×S) per cell - Viewport-aware CA: iterate only active chunks, not all 250×250 - Flat array entity/item/shadow maps instead of HashMaps - Flat 128-entry ASCII atlas array instead of HashMap lookup - Partial grid upload: viewport + 30-cell margin only - Pre-allocated viewport arrays in renderer structs (zero alloc/frame) - Skip CPU lighting for GPU modes (pass None) - Benchmark mode: --mode benchmark with per-subsystem timing - GpuLightSource struct, light_count in push constants - gather_sources_in_range() for viewport-scoped source gathering Benchmark (600 ticks, release): Graphics: 531 FPS (was 386, +38%), render 1013us (was 1699us, -40%) ASCII: 402 FPS (was 313, +28%), render 1502us (was 2346us, -36%) All 171 tests + 14 scenarios pass.
153 lines
3.9 KiB
Rust
153 lines
3.9 KiB
Rust
use verbatim::ai::AiAction;
|
|
use verbatim::ai::GameSession;
|
|
|
|
fn setup() -> GameSession {
|
|
let mut s = GameSession::new_seeded(42);
|
|
s.init();
|
|
s
|
|
}
|
|
|
|
#[test]
|
|
fn descend_requires_stairs() {
|
|
let mut s = setup();
|
|
let start_depth = s.game.depth;
|
|
s.perform_action(&AiAction::Descend);
|
|
assert_eq!(
|
|
s.game.depth, start_depth,
|
|
"descend should not work without stairs"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn descend_increases_depth_on_stairs() {
|
|
let mut s = GameSession::new_seeded(42);
|
|
s.init_empty();
|
|
s.clear_area(90, 90, 50, 50);
|
|
let player = s.game.player.center(&s.game.entities);
|
|
let foot_x = player.0 as i32;
|
|
let foot_y = (player.1 + 3.0) as i32;
|
|
s.perform_action(&AiAction::SetCell {
|
|
x: foot_x,
|
|
y: foot_y,
|
|
material: "stairs".into(),
|
|
});
|
|
s.perform_action(&AiAction::SetCell {
|
|
x: foot_x,
|
|
y: foot_y + 1,
|
|
material: "stone".into(),
|
|
});
|
|
s.step(5);
|
|
let before = s.game.depth;
|
|
s.perform_action(&AiAction::Descend);
|
|
assert_eq!(
|
|
s.game.depth,
|
|
before + 1,
|
|
"descend should increase depth when on stairs"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn depth_shown_in_hud() {
|
|
let s = setup();
|
|
let player = s.game.entities.all()[0].clone();
|
|
let mut ui = verbatim::ui::UiLayer::new();
|
|
ui.draw_hud(
|
|
80,
|
|
25,
|
|
Some(&player),
|
|
s.game.tick,
|
|
verbatim::world::cell::MaterialId::Sand,
|
|
0,
|
|
0,
|
|
s.game.depth,
|
|
&s.game.player,
|
|
60.0,
|
|
);
|
|
let stats = format!(
|
|
"LV:{} XP:{} K:{} S:{} D:{} T:{}",
|
|
player.level, player.xp, 0, 0, s.game.depth, s.game.tick
|
|
);
|
|
let stats_w = verbatim::ui::UiLayer::text_width(&stats);
|
|
let stats_x = (80 - stats_w).max(0);
|
|
let line: String = (stats_x..80)
|
|
.step_by(3)
|
|
.map(|x| ui.get(x, 24).map(|c| c.ch).unwrap_or(' '))
|
|
.collect();
|
|
assert!(line.contains("D:1"), "HUD should show depth: {}", line);
|
|
}
|
|
|
|
#[test]
|
|
fn stairs_material_exists() {
|
|
let mut s = setup();
|
|
let cell = s.get_cell(0, 0);
|
|
assert_ne!(cell.material, "stairs", "empty corner should not be stairs");
|
|
s.perform_action(&AiAction::SetCell {
|
|
x: 50,
|
|
y: 50,
|
|
material: "stairs".into(),
|
|
});
|
|
let cell = s.get_cell(50, 50);
|
|
assert_eq!(
|
|
cell.material, "stairs",
|
|
"stairs material should be placeable"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cell_stairs_bytes_roundtrip() {
|
|
let cell = verbatim::world::cell::Cell::new(verbatim::world::cell::MaterialId::Stairs);
|
|
let bytes = cell.to_bytes();
|
|
let cell2 = verbatim::world::cell::Cell::from_bytes(&bytes);
|
|
assert_eq!(cell2.material, verbatim::world::cell::MaterialId::Stairs);
|
|
}
|
|
|
|
#[test]
|
|
fn material_name_stairs() {
|
|
let mut s = setup();
|
|
let cell = s.get_cell(50, 50);
|
|
assert_eq!(cell.material, "empty");
|
|
s.perform_action(&AiAction::SetCell {
|
|
x: 50,
|
|
y: 50,
|
|
material: "stairs".into(),
|
|
});
|
|
let cell = s.get_cell(50, 50);
|
|
assert_eq!(cell.material, "stairs");
|
|
}
|
|
|
|
#[test]
|
|
fn descend_resets_world() {
|
|
let mut s = GameSession::new_seeded(42);
|
|
s.init_empty();
|
|
s.clear_area(90, 90, 50, 50);
|
|
let player = s.game.player.center(&s.game.entities);
|
|
let foot_x = player.0 as i32;
|
|
let foot_y = (player.1 + 3.0) as i32;
|
|
s.perform_action(&AiAction::SetCell {
|
|
x: foot_x,
|
|
y: foot_y,
|
|
material: "stairs".into(),
|
|
});
|
|
s.perform_action(&AiAction::SetCell {
|
|
x: foot_x,
|
|
y: foot_y + 1,
|
|
material: "stone".into(),
|
|
});
|
|
s.step(5);
|
|
let before = s.game.depth;
|
|
s.perform_action(&AiAction::Descend);
|
|
assert_eq!(s.game.depth, before + 1);
|
|
assert!(
|
|
s.game.player.entity(&s.game.entities).is_some(),
|
|
"player should respawn"
|
|
);
|
|
assert!(
|
|
s.game
|
|
.entities
|
|
.all()
|
|
.iter()
|
|
.all(|e| e.alive || e.kind == verbatim::entity::EntityKind::Corpse),
|
|
"old corpses should be gone"
|
|
);
|
|
}
|