use dreamwell_engine::game_object::PrimitiveKind;
use dreamwell_runtime::game_state::GameState;
use dreamwell_runtime::scene::Scene;
use dreamwell_runtime::time::FrameTimer;
#[test]
fn scene_spawn_object() {
let mut scene = Scene::default();
let _id = scene.game_objects.spawn("Cube".into()).unwrap();
assert_eq!(scene.object_count(), 1);
}
#[test]
fn scene_despawn_object() {
let mut scene = Scene::default();
let id1 = scene.game_objects.spawn("A".into()).unwrap();
let _id2 = scene.game_objects.spawn("B".into()).unwrap();
assert_eq!(scene.object_count(), 2);
scene.game_objects.despawn(id1);
assert_eq!(scene.object_count(), 1);
}
#[test]
fn scene_despawn_nonexistent_no_panic() {
let mut scene = Scene::default();
scene.game_objects.despawn(999); assert_eq!(scene.object_count(), 0);
}
#[test]
fn scene_default_has_camera() {
let scene = Scene::default();
assert!(scene.camera.position.z > 0.0);
assert_eq!(scene.object_count(), 0);
}
#[test]
fn game_state_default() {
let state = GameState::default();
assert_eq!(state.tick, 0);
assert!(!state.paused);
}
#[test]
fn game_state_update_increments_tick() {
let mut state = GameState::default();
state.update(0.060);
assert_eq!(state.tick, 1);
state.update(0.060);
assert_eq!(state.tick, 2);
}
#[test]
fn game_state_paused_does_not_advance() {
let mut state = GameState::default();
state.paused = true;
state.update(0.1);
assert_eq!(state.tick, 0);
}
#[test]
fn game_state_toggle_pause() {
let mut state = GameState::default();
assert!(!state.paused);
state.toggle_pause();
assert!(state.paused);
state.toggle_pause();
assert!(!state.paused);
}
#[test]
fn frame_timer_default() {
let timer = FrameTimer::new();
assert_eq!(timer.frame_count(), 0);
assert_eq!(timer.delta_time(), 0.0);
}
#[test]
fn frame_timer_tick_increments() {
let mut timer = FrameTimer::new();
timer.tick();
assert_eq!(timer.frame_count(), 1);
timer.tick();
assert_eq!(timer.frame_count(), 2);
assert!(timer.delta_time() < 1.0);
}
#[test]
fn frame_timer_fps_stabilizes() {
let mut timer = FrameTimer::new();
for _ in 0..100 {
timer.tick();
}
assert!(timer.fps() > 0.0);
}
#[test]
fn camera_default_position() {
let cam = dreamwell_gpu::camera::Camera::default();
assert_eq!(cam.center, glam::Vec3::ZERO);
assert!(cam.position.z > 0.0, "Camera should be at +Z by default");
assert_eq!(cam.zoom_factor, 10.0);
}
#[test]
fn camera_zoom_changes_position() {
let mut cam = dreamwell_gpu::camera::Camera::default();
let old_z = cam.position.z;
cam.zoom(2.0);
assert!(cam.position.z > old_z, "Zooming out should increase Z");
}
#[test]
fn camera_reset_restores_default() {
let mut cam = dreamwell_gpu::camera::Camera::default();
cam.zoom(5.0);
cam.pan(1.0, 1.0);
cam.reset();
assert_eq!(cam.zoom_factor, 10.0);
assert_eq!(cam.center, glam::Vec3::ZERO);
}
#[test]
fn scene_spawn_1000_objects() {
let mut scene = Scene::default();
for _ in 0..1000 {
scene.game_objects.spawn("Obj".into()).unwrap();
}
assert_eq!(scene.object_count(), 1000);
}
#[test]
fn scene_despawn_half() {
let mut scene = Scene::default();
let mut ids = Vec::new();
for _ in 0..100 {
ids.push(scene.game_objects.spawn("Obj".into()).unwrap());
}
for i in (0..100).step_by(2) {
scene.game_objects.despawn(ids[i]);
}
assert_eq!(scene.object_count(), 50);
}
#[test]
fn scene_object_visibility_toggle() {
let mut scene = Scene::default();
let id = scene
.game_objects
.spawn_primitive("Cube".into(), PrimitiveKind::Cube)
.unwrap();
assert!(scene.game_objects.find(id).unwrap().visible);
scene.game_objects.find_mut(id).unwrap().visible = false;
assert!(!scene.game_objects.find(id).unwrap().visible);
}
#[test]
fn scene_object_position_update() {
let mut scene = Scene::default();
let id = scene.game_objects.spawn("Obj".into()).unwrap();
scene.game_objects.find_mut(id).unwrap().transform.position = [10.0, 20.0, 30.0];
let pos = scene.game_objects.find(id).unwrap().transform.position;
assert_eq!(pos, [10.0, 20.0, 30.0]);
}
#[test]
fn camera_pan_moves_center() {
let mut cam = dreamwell_gpu::camera::Camera::default();
let old_center = cam.center;
cam.pan(5.0, 3.0);
assert_ne!(cam.center, old_center, "Pan should move camera center");
}
#[test]
fn camera_zoom_clamps_minimum() {
let mut cam = dreamwell_gpu::camera::Camera::default();
for _ in 0..100 {
cam.zoom(-1.0);
}
assert!(cam.zoom_factor > 0.0, "Zoom factor must remain positive");
assert!(cam.position.z > 0.0, "Camera Z must remain positive");
}
#[test]
fn camera_position_offset_from_center() {
let cam = dreamwell_gpu::camera::Camera::default();
assert_ne!(cam.position, cam.center, "Camera position should differ from center");
assert!(cam.position.z > cam.center.z, "Camera should be at positive Z offset");
}
#[test]
fn game_state_multiple_updates() {
let mut state = GameState::default();
for _ in 0..100 {
state.update(0.060);
}
assert_eq!(state.tick, 120);
}
#[test]
fn game_state_pause_resume_preserves_tick() {
let mut state = GameState::default();
state.update(0.100);
let tick_before = state.tick;
assert_eq!(tick_before, 2);
state.paused = true;
state.update(0.100);
assert_eq!(state.tick, tick_before);
state.paused = false;
state.update(0.060);
assert_eq!(state.tick, tick_before + 1);
}
#[test]
fn frame_timer_delta_time_positive() {
let mut timer = FrameTimer::new();
timer.tick();
std::thread::sleep(std::time::Duration::from_millis(1));
timer.tick();
assert!(timer.delta_time() > 0.0);
}
#[test]
fn gpu_headless_adapter_probe() {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
}));
match adapter {
Ok(a) => {
let info = a.get_info();
eprintln!("runtime: GPU detected — {} ({:?})", info.name, info.backend);
}
Err(_) => {
eprintln!("runtime: no GPU adapter available (headless OK)");
}
}
}
#[test]
fn engine_tile_constants_accessible() {
assert_ne!(dreamwell_engine::tile::GROUND, 0);
assert_ne!(dreamwell_engine::tile::PLAYER_GLYPH, 0);
}
#[test]
fn engine_spatial_grid_accessible() {
let _grid = dreamwell_engine::spatial::SpatialGrid::new();
}
#[test]
fn engine_hash_fnv1a_accessible() {
let hash = dreamwell_engine::hash::fnv1a_64(b"runtime_test");
assert_ne!(hash, 0);
}
#[test]
fn engine_validation_accessible() {
let result = dreamwell_engine::validation::validate_scope_key("timeline:1/world:1");
assert!(result.is_ok());
}
#[test]
fn engine_tile_glyph_layers() {
let ground_layer = dreamwell_engine::tile::glyph_layer(dreamwell_engine::tile::GROUND);
let player_layer = dreamwell_engine::tile::glyph_layer(dreamwell_engine::tile::PLAYER_GLYPH);
assert_ne!(ground_layer, player_layer);
}
#[test]
fn engine_spatial_grid_insert_query() {
let mut grid = dreamwell_engine::spatial::SpatialGrid::new();
grid.insert("npc_1", 5, 5);
grid.insert("npc_2", 5, 6);
grid.insert("tree", 500, 500);
let nearby = grid.query_radius(5, 5, 10);
let ids: Vec<&str> = nearby.iter().map(|(id, _, _)| *id).collect();
assert!(ids.contains(&"npc_1"));
assert!(ids.contains(&"npc_2"));
assert!(!ids.contains(&"tree"));
}
#[test]
fn input_state_default_orbit_fields() {
let input = dreamwell_runtime::input::InputState::new();
assert_eq!(input.orbit_dx, 0.0);
assert_eq!(input.orbit_dy, 0.0);
assert_eq!(input.scroll_delta, 0.0);
}
#[test]
fn input_state_end_frame_resets_orbit() {
let mut input = dreamwell_runtime::input::InputState::new();
input.orbit_dx = 1.5;
input.orbit_dy = -0.3;
input.scroll_delta = 2.0;
input.end_frame();
assert_eq!(input.orbit_dx, 0.0);
assert_eq!(input.orbit_dy, 0.0);
assert_eq!(input.scroll_delta, 0.0);
}
#[test]
fn input_state_orbit_accumulates() {
let mut input = dreamwell_runtime::input::InputState::new();
input.orbit_dx += 0.01;
input.orbit_dx += 0.02;
input.orbit_dy += -0.005;
input.orbit_dy += -0.003;
assert!((input.orbit_dx - 0.03).abs() < 1e-6);
assert!((input.orbit_dy - (-0.008)).abs() < 1e-6);
}
#[test]
fn input_state_scroll_delta_independent_of_orbit() {
let mut input = dreamwell_runtime::input::InputState::new();
input.orbit_dx = 1.0;
input.scroll_delta = 3.0;
input.end_frame();
assert_eq!(input.orbit_dx, 0.0);
assert_eq!(input.scroll_delta, 0.0);
}
#[test]
fn chase_camera_offset_behind_player() {
let yaw: f32 = 0.0;
let pitch: f32 = 0.35;
let dist: f32 = 6.0;
let shoulder: f32 = 0.5;
let horiz = pitch.cos() * dist;
let height = pitch.sin() * dist;
let offset_x = -yaw.cos() * horiz + yaw.sin() * shoulder;
let offset_y = height;
let offset_z = -yaw.sin() * horiz - yaw.cos() * shoulder;
assert!(
offset_x < 0.0,
"camera X offset should be negative (behind player): {offset_x}"
);
assert!(offset_y > 0.0, "camera should be above player: {offset_y}");
assert!(
(offset_z - (-shoulder)).abs() < 0.01,
"Z should be shoulder offset: {offset_z}"
);
}
#[test]
fn chase_camera_offset_at_90_degrees() {
let yaw: f32 = std::f32::consts::FRAC_PI_2; let pitch: f32 = 0.35;
let dist: f32 = 6.0;
let shoulder: f32 = 0.5;
let horiz = pitch.cos() * dist;
let offset_x = -yaw.cos() * horiz + yaw.sin() * shoulder;
let offset_z = -yaw.sin() * horiz - yaw.cos() * shoulder;
assert!(
offset_z < 0.0,
"at yaw=90°, camera Z should be negative (behind): {offset_z}"
);
assert!(offset_x > 0.0, "at yaw=90°, X should be shoulder offset: {offset_x}");
}
#[test]
fn chase_camera_pitch_clamp_range() {
let min_pitch: f32 = 0.175; let max_pitch: f32 = 1.396;
assert!(min_pitch > 0.0, "min pitch must be above horizon");
assert!(
max_pitch < std::f32::consts::FRAC_PI_2,
"max pitch must be below vertical"
);
assert!(min_pitch < max_pitch, "min must be less than max");
let clamped_low = (-1.0f32).clamp(min_pitch, max_pitch);
assert_eq!(clamped_low, min_pitch);
let clamped_high = (2.0f32).clamp(min_pitch, max_pitch);
assert_eq!(clamped_high, max_pitch);
}
#[test]
fn smooth_zoom_converges() {
let mut distance: f32 = 6.0;
let target: f32 = 10.0;
let rate: f32 = 12.0;
let dt: f32 = 1.0 / 60.0;
for _ in 0..120 {
let t = (rate * dt).min(1.0);
distance += (target - distance) * t;
}
assert!((distance - target).abs() < 0.1, "distance should converge: {distance}");
}
#[test]
fn sprint_pull_back_increases_distance() {
let base_dist: f32 = 6.0;
let sprint_extra: f32 = 1.5;
let effective = base_dist + sprint_extra;
assert!(
effective > base_dist,
"sprint should increase effective camera distance"
);
assert_eq!(effective, 7.5);
}
#[test]
fn camera_height_varies_with_pitch() {
let dist: f32 = 6.0;
let low_pitch: f32 = 0.175; let high_pitch: f32 = 1.2;
let low_height = low_pitch.sin() * dist;
let high_height = high_pitch.sin() * dist;
assert!(high_height > low_height, "higher pitch should raise camera");
assert!(low_height > 0.0, "even low pitch should have positive height");
}
#[test]
fn packet_idle_produces_valid_bridge() {
use dreamwell_attention::encoder::CausalEngineEncoder;
use dreamwell_attention::InputPacket;
use dreamwell_gpu::quantum_bridge::QuantumBridge;
use dreamwell_metaphors::{TagInfluenceFrame, DEFAULT_PARTICLE_COUNT};
let mut encoder = CausalEngineEncoder::new([0.0, 0.5, 0.0], DEFAULT_PARTICLE_COUNT, 42);
let mut bridge = QuantumBridge::new(DEFAULT_PARTICLE_COUNT);
let input = InputPacket::idle(1.0 / 60.0, 0);
let encode = encoder.encode_frame(&input, &[]);
bridge.set_tag_frame(TagInfluenceFrame::empty());
let (packet, validation) = bridge.bridge(&encode, 1.0 / 60.0, 0.0);
assert!(validation.context_valid, "idle packet should be valid");
assert!(!packet.particle_params.spring_k.is_nan());
assert!(packet.particle_params.spring_k > 0.0);
assert!(packet.wave_params.decoherence >= 0.0);
assert_eq!(
packet.schema_version,
dreamwell_metaphors::constants::BRIDGE_SCHEMA_VERSION
);
}
#[test]
fn packet_moving_produces_distinct_metaphor() {
use dreamwell_attention::encoder::CausalEngineEncoder;
use dreamwell_attention::InputPacket;
use dreamwell_gpu::quantum_bridge::QuantumBridge;
use dreamwell_metaphors::{TagInfluenceFrame, DEFAULT_PARTICLE_COUNT};
let mut encoder = CausalEngineEncoder::new([0.0, 0.5, 0.0], DEFAULT_PARTICLE_COUNT, 42);
let mut bridge = QuantumBridge::new(DEFAULT_PARTICLE_COUNT);
let dt = 1.0 / 60.0;
for i in 0..60 {
let input = InputPacket {
timestamp: i as f64 * dt as f64,
..InputPacket::idle(dt, i)
};
let encode = encoder.encode_frame(&input, &[]);
bridge.set_tag_frame(TagInfluenceFrame::empty());
bridge.bridge(&encode, dt, i as f32 * dt);
}
let idle_input = InputPacket {
timestamp: 1.0,
..InputPacket::idle(dt, 60)
};
let idle_encode = encoder.encode_frame(&idle_input, &[]);
bridge.set_tag_frame(TagInfluenceFrame::empty());
let (idle_packet, _) = bridge.bridge(&idle_encode, dt, 1.0);
for i in 0..60 {
let input = InputPacket {
movement: [0.0, 1.0],
timestamp: 1.0 + i as f64 * dt as f64,
..InputPacket::idle(dt, 61 + i)
};
let encode = encoder.encode_frame(&input, &[]);
bridge.set_tag_frame(TagInfluenceFrame::empty());
bridge.bridge(&encode, dt, 1.0 + i as f32 * dt);
}
let move_input = InputPacket {
movement: [0.0, 1.0],
timestamp: 2.0,
..InputPacket::idle(dt, 121)
};
let move_encode = encoder.encode_frame(&move_input, &[]);
bridge.set_tag_frame(TagInfluenceFrame::empty());
let (move_packet, _) = bridge.bridge(&move_encode, dt, 2.0);
assert!(
move_packet.particle_params.cloud_radius != idle_packet.particle_params.cloud_radius
|| move_packet.wave_params.coherence != idle_packet.wave_params.coherence,
"idle and moving packets should differ: idle_coh={}, move_coh={}",
idle_packet.wave_params.coherence,
move_packet.wave_params.coherence
);
}
#[test]
fn packet_template_scene_initializes() {
let tmpl = dreamwell_sdk::templates::default_3d_scene();
assert!(
tmpl.objects
.iter()
.any(|o| o.tags.iter().any(|t| t == "isInputReceiver")),
"template must have an input receiver"
);
assert!(
tmpl.objects
.iter()
.any(|o| o.tags.iter().any(|t| t == "isWaveController")),
"template must have a wave controller"
);
assert!(
tmpl.objects.iter().any(|o| o.tags.iter().any(|t| t == "isSkybox")),
"template must have a skybox"
);
let spawn_pos = tmpl
.objects
.iter()
.find(|o| o.tags.iter().any(|t| t == "isInputReceiver"))
.map(|o| o.position)
.unwrap();
assert_eq!(spawn_pos, [0.0, 0.5, 0.0], "player should spawn at origin");
}