#![allow(clippy::similar_names)]
use glam::DVec3;
use roxlap_core::opticast::{opticast, OpticastOutcome, OpticastSettings};
use roxlap_core::rasterizer::ScratchPool;
use roxlap_core::scalar_rasterizer::ScalarRasterizer;
use roxlap_core::sky::Sky;
use roxlap_core::Camera;
use crate::billboard::{self, BillboardCache, DEFAULT_RESOLUTION as BILLBOARD_RESOLUTION};
use crate::chunks;
use crate::lod::Lod;
use crate::{GridTransform, Scene, CHUNK_SIZE_XY};
const SKY_MASK_SENTINEL: u32 = 0x00_DE_AD_BE;
fn world_camera_to_grid_local(camera: &Camera, transform: &GridTransform) -> Camera {
let inv = transform.rotation.inverse();
let world_offset = DVec3::from_array(camera.pos) - transform.origin;
let local_pos = inv * world_offset;
let local_right = inv * DVec3::from_array(camera.right);
let local_down = inv * DVec3::from_array(camera.down);
let local_forward = inv * DVec3::from_array(camera.forward);
Camera {
pos: local_pos.to_array(),
right: local_right.to_array(),
down: local_down.to_array(),
forward: local_forward.to_array(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderOutcome {
Rendered {
grids_drawn: usize,
},
Empty,
}
fn single_chunk_fast_path<'a>(
backing: &'a chunks::ChunkXyBacking<'a>,
cg: &'a roxlap_core::ChunkGrid<'a>,
) -> roxlap_core::GridView<'a> {
if backing.chunks_x == 1
&& backing.chunks_y == 1
&& backing.chunks_z == 1
&& backing.origin_chunk_xy == [0, 0]
&& backing.origin_chunk_z == 0
{
if let Some(single) = backing.chunks[0] {
return single;
}
}
roxlap_core::GridView::from_chunk_grid(cg, CHUNK_SIZE_XY)
}
#[allow(clippy::too_many_arguments)]
pub fn render_scene(
fb: &mut [u32],
zb: &mut [f32],
pitch_pixels: usize,
width: u32,
height: u32,
pool: &mut ScratchPool,
scene: &mut Scene,
camera: &Camera,
settings: &OpticastSettings,
sky: Option<&Sky>,
) -> RenderOutcome {
debug_assert_eq!(fb.len(), zb.len());
let pixel_count = (width as usize) * (height as usize);
debug_assert_eq!(fb.len(), pixel_count);
let mut grids_drawn = 0usize;
for (_id, grid) in scene.grids_mut() {
let Some(backing) = grid.chunk_xyz_backing() else {
continue;
};
let local_cam = world_camera_to_grid_local(camera, &grid.transform);
let cg = roxlap_core::ChunkGrid {
chunks: &backing.chunks,
origin_chunk_xy: backing.origin_chunk_xy,
origin_chunk_z: backing.origin_chunk_z,
chunks_x: backing.chunks_x,
chunks_y: backing.chunks_y,
chunks_z: backing.chunks_z,
};
let grid_view = single_chunk_fast_path(&backing, &cg);
let outcome = {
let mut rasterizer = ScalarRasterizer::new(fb, zb, pitch_pixels, grid_view);
if let Some(sky_ref) = sky {
rasterizer = rasterizer.with_sky(sky_ref);
}
opticast(&mut rasterizer, pool, &local_cam, settings, grid_view)
};
if outcome == OpticastOutcome::Rendered {
grids_drawn += 1;
}
}
if grids_drawn == 0 {
RenderOutcome::Empty
} else {
RenderOutcome::Rendered { grids_drawn }
}
}
pub fn compose_into(
shared_fb: &mut [u32],
shared_zb: &mut [f32],
temp_fb: &[u32],
temp_zb: &[f32],
) {
debug_assert_eq!(shared_fb.len(), shared_zb.len());
debug_assert_eq!(shared_fb.len(), temp_fb.len());
debug_assert_eq!(shared_fb.len(), temp_zb.len());
for i in 0..shared_fb.len() {
if temp_zb[i] < shared_zb[i] {
shared_fb[i] = temp_fb[i];
shared_zb[i] = temp_zb[i];
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn render_scene_composed(
fb: &mut [u32],
zb: &mut [f32],
pitch_pixels: usize,
width: u32,
height: u32,
pool: &mut ScratchPool,
scene: &mut Scene,
camera: &Camera,
settings: &OpticastSettings,
sky_color: u32,
sky: Option<&Sky>,
) -> RenderOutcome {
debug_assert_eq!(fb.len(), zb.len());
let pixel_count = (width as usize) * (height as usize);
debug_assert_eq!(fb.len(), pixel_count);
let mut grids_drawn = 0usize;
let mut temp_fb = vec![sky_color; pixel_count];
let mut temp_zb = vec![f32::INFINITY; pixel_count];
for (_id, grid) in scene.grids_mut() {
let lod = grid.select_lod(DVec3::from_array(camera.pos));
if lod == Lod::Far {
if grid.chunks.is_empty() {
continue;
}
if grid.billboards.is_none() {
let cache = BillboardCache::build(grid, BILLBOARD_RESOLUTION);
grid.billboards = Some(cache);
}
let bounds = billboard::grid_bounds(grid);
let centre_world = grid.transform.origin + grid.transform.rotation * bounds.centre;
let cam_pos = DVec3::from_array(camera.pos);
let centre_to_cam_world = cam_pos - centre_world;
let ctc_len = centre_to_cam_world.length();
if !ctc_len.is_finite() || ctc_len < 1e-9 {
continue;
}
let query_dir_world = centre_to_cam_world / ctc_len;
let query_dir_local = grid.transform.rotation.inverse() * query_dir_world;
let cache = grid.billboards.as_ref().unwrap();
let snapshot = cache
.pick_nearest(query_dir_local)
.expect("billboard cache populated above");
billboard::billboard_blit_into(
fb,
zb,
pitch_pixels,
width,
height,
snapshot,
centre_world,
bounds.radius,
camera,
settings,
);
grids_drawn += 1;
continue;
}
let Some(backing) = grid.chunk_xyz_backing() else {
continue;
};
let bounds = billboard::grid_bounds(grid);
let centre_world = grid.transform.origin + grid.transform.rotation * bounds.centre;
let cam_pos = DVec3::from_array(camera.pos);
let dist_to_centre = (centre_world - cam_pos).length();
if dist_to_centre - bounds.radius > f64::from(settings.max_scan_dist) {
continue;
}
let owns_sky = grid.render_sky;
let local_sky_color = if owns_sky {
sky_color
} else {
SKY_MASK_SENTINEL
};
if !owns_sky {
pool.set_skycast(SKY_MASK_SENTINEL as i32, 0);
}
temp_fb.fill(local_sky_color);
temp_zb.fill(f32::INFINITY);
let local_cam = world_camera_to_grid_local(camera, &grid.transform);
let cg = roxlap_core::ChunkGrid {
chunks: &backing.chunks,
origin_chunk_xy: backing.origin_chunk_xy,
origin_chunk_z: backing.origin_chunk_z,
chunks_x: backing.chunks_x,
chunks_y: backing.chunks_y,
chunks_z: backing.chunks_z,
};
let grid_view = single_chunk_fast_path(&backing, &cg);
let per_grid_settings;
let active_settings = {
let base_mip_levels = settings.mip_levels;
let base_mip_scan = settings.mip_scan_dist;
let lod_mip_levels = match lod {
Lod::Mid => grid.lod_thresholds.mid_mip_levels,
Lod::Near | Lod::Far => None,
};
let lod_mip_scan = match lod {
Lod::Mid => grid.lod_thresholds.mid_mip_scan_dist,
Lod::Near | Lod::Far => None,
};
let global_mip_cap = grid.mip_levels_override;
let needs_override =
lod_mip_levels.is_some() || lod_mip_scan.is_some() || global_mip_cap.is_some();
if needs_override {
let mut mip_levels =
lod_mip_levels.map_or(base_mip_levels, |n| n.clamp(1, base_mip_levels));
if let Some(cap) = global_mip_cap {
mip_levels = mip_levels.min(cap.clamp(1, base_mip_levels));
}
let mip_scan_dist = lod_mip_scan.map_or(base_mip_scan, |d| base_mip_scan.min(d));
per_grid_settings = OpticastSettings {
mip_levels,
mip_scan_dist,
..*settings
};
&per_grid_settings
} else {
settings
}
};
let outcome = {
let mut rasterizer =
ScalarRasterizer::new(&mut temp_fb, &mut temp_zb, pitch_pixels, grid_view);
if owns_sky {
if let Some(sky_ref) = sky {
rasterizer = rasterizer.with_sky(sky_ref);
}
}
opticast(
&mut rasterizer,
pool,
&local_cam,
active_settings,
grid_view,
)
};
if !owns_sky {
for (px, z) in temp_fb.iter().zip(temp_zb.iter_mut()) {
if *px == SKY_MASK_SENTINEL {
*z = f32::INFINITY;
}
}
pool.set_skycast(sky_color as i32, 0);
}
if outcome == OpticastOutcome::Rendered {
compose_into(fb, zb, &temp_fb, &temp_zb);
grids_drawn += 1;
}
}
if grids_drawn == 0 {
RenderOutcome::Empty
} else {
RenderOutcome::Rendered { grids_drawn }
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
use crate::{GridTransform, Scene, CHUNK_SIZE_XY};
use glam::{DVec3, IVec3};
use roxlap_core::opticast::{opticast as core_opticast, OpticastSettings};
use roxlap_core::rasterizer::ScratchPool;
use roxlap_core::scalar_rasterizer::ScalarRasterizer;
use roxlap_core::{Camera, Engine};
const XRES: u32 = 320;
const YRES: u32 = 200;
fn build_one_grid_scene(world_origin: DVec3) -> (Scene, crate::GridId) {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(world_origin));
let grid = scene.grid_mut(id).unwrap();
grid.set_rect(
IVec3::new(40, 40, 40),
IVec3::new(55, 55, 55),
Some(0x80_88_88_88),
);
grid.set_sphere(IVec3::new(80, 80, 80), 6, Some(0x80_22_aa_22));
(scene, id)
}
fn camera_at(pos: [f64; 3]) -> Camera {
Camera {
pos,
right: [-1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, 1.0, 0.0],
}
}
fn render_setup(pool_vsid: u32) -> (Engine, ScratchPool, Vec<u32>, Vec<f32>) {
let engine = Engine::new();
let mut pool = ScratchPool::new(XRES, YRES, pool_vsid);
let sky = engine.sky_color();
let sky_col_i = i32::from_ne_bytes(sky.to_ne_bytes());
pool.set_skycast(sky_col_i, 0);
let fog_col_i = i32::from_ne_bytes(engine.fog_color().to_ne_bytes());
pool.set_fog(fog_col_i, engine.fog_max_scan_dist());
pool.set_treat_z_max_as_air(true);
let pixel_count = (XRES as usize) * (YRES as usize);
let framebuffer = vec![sky; pixel_count];
let zbuffer = vec![0.0f32; pixel_count];
(engine, pool, framebuffer, zbuffer)
}
fn render_via_scene(scene: &mut Scene, camera: &Camera) -> Vec<u32> {
let (_engine, mut pool, mut fb, mut zb) = render_setup(CHUNK_SIZE_XY);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
scene,
camera,
&settings,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
fb
}
fn render_via_direct_opticast(scene: &Scene, local_camera: &Camera) -> Vec<u32> {
let (_engine, mut pool, mut fb, mut zb) = render_setup(CHUNK_SIZE_XY);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let grid = scene.grids().next().unwrap().1;
let chunk = grid.chunk(IVec3::ZERO).unwrap();
let grid_view = roxlap_core::GridView::from_single_vxl(chunk);
let mut rasterizer = ScalarRasterizer::new(&mut fb, &mut zb, XRES as usize, grid_view);
let _ = core_opticast(
&mut rasterizer,
&mut pool,
local_camera,
&settings,
grid_view,
);
drop(rasterizer);
fb
}
#[test]
fn world_camera_to_grid_local_identity_rotation_translates_pos_only() {
let camera = Camera {
pos: [110.0, 220.0, 330.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, 1.0, 0.0],
};
let transform = GridTransform::at(DVec3::new(100.0, 200.0, 300.0));
let local = super::world_camera_to_grid_local(&camera, &transform);
assert_eq!(local.right, camera.right);
assert_eq!(local.down, camera.down);
assert_eq!(local.forward, camera.forward);
for (got, want) in local.pos.iter().zip([10.0, 20.0, 30.0].iter()) {
assert!((got - want).abs() < 1e-12, "pos got={got} want={want}");
}
}
#[test]
fn world_camera_to_grid_local_90deg_z_rotates_basis_and_pos() {
use glam::DQuat;
let camera = Camera {
pos: [0.0, 10.0, 0.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, 1.0, 0.0],
};
let transform = GridTransform {
origin: DVec3::ZERO,
rotation: DQuat::from_rotation_z(std::f64::consts::FRAC_PI_2),
};
let local = super::world_camera_to_grid_local(&camera, &transform);
let approx_eq =
|a: [f64; 3], b: [f64; 3]| a.iter().zip(b.iter()).all(|(x, y)| (x - y).abs() < 1e-9);
assert!(
approx_eq(local.pos, [10.0, 0.0, 0.0]),
"pos={:?} expected ~(10, 0, 0)",
local.pos
);
assert!(
approx_eq(local.right, [0.0, -1.0, 0.0]),
"right={:?} expected ~(0, -1, 0)",
local.right
);
assert!(
approx_eq(local.down, [0.0, 0.0, 1.0]),
"down={:?} expected ~(0, 0, 1)",
local.down
);
assert!(
approx_eq(local.forward, [1.0, 0.0, 0.0]),
"forward={:?} expected ~(1, 0, 0)",
local.forward
);
}
#[test]
fn world_camera_to_grid_local_preserves_basis_orthonormality() {
use glam::DQuat;
let camera = Camera {
pos: [3.0, -5.0, 7.0],
right: [-1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, 1.0, 0.0],
};
let transform = GridTransform {
origin: DVec3::new(1.0, 2.0, 3.0),
rotation: DQuat::from_axis_angle(glam::DVec3::new(0.3, 0.8, 0.5).normalize(), 0.7),
};
let local = super::world_camera_to_grid_local(&camera, &transform);
let r = DVec3::from_array(local.right);
let d = DVec3::from_array(local.down);
let f = DVec3::from_array(local.forward);
for v in [r, d, f] {
assert!(
(v.length_squared() - 1.0).abs() < 1e-12,
"basis vec {v:?} not unit length"
);
}
assert!(r.dot(d).abs() < 1e-12, "right·down = {}", r.dot(d));
assert!(r.dot(f).abs() < 1e-12, "right·forward = {}", r.dot(f));
assert!(d.dot(f).abs() < 1e-12, "down·forward = {}", d.dot(f));
let cross = r.cross(d);
assert!(
(cross - f).length() < 1e-12,
"right×down={cross:?} forward={f:?}"
);
}
fn build_one_grid_marker_scene(transform: GridTransform) -> (Scene, crate::GridId, u32) {
let mut scene = Scene::new();
let id = scene.add_grid(transform);
let grid = scene.grid_mut(id).unwrap();
grid.set_rect(
IVec3::new(40, 40, 40),
IVec3::new(55, 55, 55),
Some(0x80_55_aa_22), );
(scene, id, 0x80_55_aa_22)
}
#[test]
fn s5_1_180deg_z_rotated_grid_byte_identical_to_axis_aligned() {
use glam::DQuat;
let axis_aligned_camera = Camera {
pos: [40.0, -20.0, 50.0],
right: [-1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, 1.0, 0.0],
};
let rotated_camera = Camera {
pos: [-40.0, 20.0, 50.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, -1.0, 0.0],
};
let q = DQuat::from_xyzw(0.0, 0.0, 1.0, 0.0);
let rot_pos = q * DVec3::from_array(axis_aligned_camera.pos);
let rot_fwd = q * DVec3::from_array(axis_aligned_camera.forward);
assert_eq!(rot_pos.to_array(), rotated_camera.pos);
assert_eq!(rot_fwd.to_array(), rotated_camera.forward);
let (mut scene_a, _, _) = build_one_grid_marker_scene(GridTransform::identity());
let fb_a = render_via_scene(&mut scene_a, &axis_aligned_camera);
let (mut scene_b, _, _) = build_one_grid_marker_scene(GridTransform {
origin: DVec3::ZERO,
rotation: q,
});
let fb_b = render_via_scene(&mut scene_b, &rotated_camera);
assert_eq!(
fb_a, fb_b,
"rotating both grid and camera by R about the grid origin must leave the framebuffer unchanged"
);
}
#[test]
fn s5_1_45deg_z_rotated_grid_renders_marker() {
use glam::DQuat;
let rotation = DQuat::from_rotation_z(std::f64::consts::FRAC_PI_4);
let (mut scene, _, marker) = build_one_grid_marker_scene(GridTransform {
origin: DVec3::ZERO,
rotation,
});
let marker_world = rotation * DVec3::new(47.5, 47.5, 47.5);
let camera = Camera {
pos: [marker_world.x, marker_world.y - 80.0, marker_world.z],
right: [-1.0, 0.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [0.0, 1.0, 0.0],
};
let (_engine, mut pool, mut fb, mut zb) = render_setup(CHUNK_SIZE_XY);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let marker_count = fb.iter().filter(|&&p| p == marker).count();
assert!(
marker_count > 50,
"45°-rotated marker box should be visible — got {marker_count} marker pixels"
);
}
#[test]
fn render_sky_false_drops_grid_sky_pixels() {
use crate::{GridId, GridTransform};
let mut scene = Scene::new();
let _b_id: GridId = scene.add_grid(GridTransform::at(DVec3::new(0.0, 600.0, 0.0)));
let b_id = scene.grids().next().unwrap().0;
scene.grid_mut(b_id).unwrap().set_rect(
IVec3::new(0, 0, 100),
IVec3::new(127, 127, 110),
Some(0x80_22_88_22), );
let a_id = scene.add_grid(GridTransform::at(DVec3::new(0.0, 200.0, 0.0)));
scene.grid_mut(a_id).unwrap().set_rect(
IVec3::new(60, 60, 60),
IVec3::new(67, 67, 67),
Some(0x80_aa_22_22), );
scene.grid_mut(a_id).unwrap().render_sky = false;
let unique_sky: u32 = 0xFF_AB_CD_EF;
let (_engine, mut pool, _) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![unique_sky; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([64.0, 0.0, 100.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
unique_sky,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 2 });
let leaked = fb
.iter()
.filter(|&&p| p == super::SKY_MASK_SENTINEL)
.count();
assert_eq!(
leaked, 0,
"SKY_MASK_SENTINEL leaked into composed framebuffer ({leaked} pixels)"
);
let red_count = fb.iter().filter(|&&p| p == 0x80_aa_22_22).count();
assert!(
red_count > 0,
"red cube from sky-disabled grid A is missing — render_sky=false should only mask sky"
);
let green_count = fb.iter().filter(|&&p| p == 0x80_22_88_22).count();
assert!(
green_count > 0,
"grid B's floor invisible — grid A's masked sky may have overwritten it"
);
}
#[test]
fn render_sky_false_single_grid_no_sentinel_leak() {
let (mut scene, id, _) = build_one_grid_marker_scene(GridTransform::identity());
scene.grid_mut(id).unwrap().render_sky = false;
let unique_sky: u32 = 0xFF_12_34_56;
let (_engine, mut pool, _) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![unique_sky; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([64.0, 0.0, 64.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
unique_sky,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let leaked = fb
.iter()
.filter(|&&p| p == super::SKY_MASK_SENTINEL)
.count();
assert_eq!(leaked, 0, "SKY_MASK_SENTINEL leaked ({leaked} pixels)");
let prefill_count = fb.iter().filter(|&&p| p == unique_sky).count();
assert!(
prefill_count > 0,
"no pre-fill pixels survived — render_sky=false should leave non-hit pixels untouched"
);
}
#[test]
fn render_scene_at_origin_matches_direct_opticast() {
let (mut scene, _) = build_one_grid_scene(DVec3::ZERO);
let cam = camera_at([64.0, 0.0, 64.0]);
let via_scene = render_via_scene(&mut scene, &cam);
let via_direct = render_via_direct_opticast(&scene, &cam);
assert_eq!(
via_scene, via_direct,
"render_scene with single 1-chunk grid at origin should match direct opticast"
);
}
#[test]
fn render_scene_translated_grid_matches_grid_local_opticast() {
let world_origin = DVec3::new(1000.0, 2000.0, 3000.0);
let (mut scene, _) = build_one_grid_scene(world_origin);
let world_cam = camera_at([1064.0, 2000.0, 3064.0]);
let local_cam = camera_at([64.0, 0.0, 64.0]);
let via_scene = render_via_scene(&mut scene, &world_cam);
let via_direct = render_via_direct_opticast(&scene, &local_cam);
assert_eq!(
via_scene, via_direct,
"render_scene of translated grid should match opticast with grid-local camera"
);
}
#[test]
fn empty_scene_returns_empty_outcome() {
let mut scene = Scene::new();
let (_engine, mut pool, mut fb, mut zb) = render_setup(CHUNK_SIZE_XY);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera_at([0.0, 0.0, 0.0]),
&settings,
None,
);
assert_eq!(outcome, RenderOutcome::Empty);
}
fn build_two_grid_side_by_side() -> (Scene, u32, u32) {
let mut scene = Scene::new();
let g0 = scene.add_grid(GridTransform::at(DVec3::new(0.0, 200.0, 0.0)));
scene.grid_mut(g0).unwrap().set_rect(
IVec3::new(56, 56, 92),
IVec3::new(71, 71, 107),
Some(0x80_88_22_22), );
let _g1 = scene.add_grid(GridTransform::at(DVec3::new(200.0, 200.0, 0.0)));
let g1_id = scene
.grids()
.filter(|(id, _)| *id != g0)
.map(|(id, _)| id)
.next()
.unwrap();
scene.grid_mut(g1_id).unwrap().set_rect(
IVec3::new(56, 56, 92),
IVec3::new(71, 71, 107),
Some(0x80_22_22_88), );
(scene, 0x80_88_22_22, 0x80_22_22_88)
}
fn make_composed_pool(pool_vsid: u32) -> (Engine, ScratchPool, u32) {
let engine = Engine::new();
let mut pool = ScratchPool::new(XRES, YRES, pool_vsid);
let sky_color = engine.sky_color();
let sky_col_i = i32::from_ne_bytes(sky_color.to_ne_bytes());
pool.set_skycast(sky_col_i, 0);
let fog_col_i = i32::from_ne_bytes(engine.fog_color().to_ne_bytes());
pool.set_fog(fog_col_i, engine.fog_max_scan_dist());
pool.set_treat_z_max_as_air(true);
(engine, pool, sky_color)
}
fn pixel_count(width: u32, height: u32) -> usize {
(width as usize) * (height as usize)
}
#[test]
fn compose_into_takes_smaller_z() {
let mut shared_fb = vec![0xff_ff_ff_ff_u32; 4];
let mut shared_zb = vec![10.0f32; 4];
let temp_fb = [0xaa_aa_aa_aa, 0x11_22_33_44, 0x55_66_77_88, 0xde_ad_be_ef];
let temp_zb = [5.0f32, 20.0, 10.0, f32::INFINITY];
compose_into(&mut shared_fb, &mut shared_zb, &temp_fb, &temp_zb);
assert_eq!(shared_fb[0], 0xaa_aa_aa_aa);
assert_eq!(shared_zb[0], 5.0);
assert_eq!(shared_fb[1], 0xff_ff_ff_ff);
assert_eq!(shared_zb[1], 10.0);
assert_eq!(shared_fb[2], 0xff_ff_ff_ff);
assert_eq!(shared_fb[3], 0xff_ff_ff_ff);
}
#[test]
fn render_scene_composed_two_grids_both_visible() {
let (mut scene, red, blue) = build_two_grid_side_by_side();
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([160.0, 100.0, 100.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 2 });
let red_count = fb.iter().filter(|&&p| p == red).count();
let blue_count = fb.iter().filter(|&&p| p == blue).count();
assert!(
red_count > 0,
"no red pixels: grid 0 (red box) not visible after compose"
);
assert!(
blue_count > 0,
"no blue pixels: grid 1 (blue box) not visible after compose"
);
}
#[test]
fn render_scene_composed_grid_a_in_front_of_grid_b() {
let mut scene = Scene::new();
let g_a = scene.add_grid(GridTransform::at(DVec3::new(0.0, 50.0, 0.0)));
scene.grid_mut(g_a).unwrap().set_rect(
IVec3::new(56, 56, 92),
IVec3::new(71, 71, 107),
Some(0x80_aa_00_00), );
let _g_b = scene.add_grid(GridTransform::at(DVec3::new(0.0, 200.0, 0.0)));
let g_b_id = scene
.grids()
.filter(|(id, _)| *id != g_a)
.map(|(id, _)| id)
.next()
.unwrap();
scene.grid_mut(g_b_id).unwrap().set_rect(
IVec3::new(56, 56, 92),
IVec3::new(71, 71, 107),
Some(0x80_00_00_aa), );
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([64.0, -10.0, 100.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 2 });
let red_count = fb.iter().filter(|&&p| p == 0x80_aa_00_00).count();
assert!(
red_count > 0,
"expected red pixels (closer box should win z-test)"
);
let mut scene2 = Scene::new();
let g_b2 = scene2.add_grid(GridTransform::at(DVec3::new(0.0, 200.0, 0.0)));
scene2.grid_mut(g_b2).unwrap().set_rect(
IVec3::new(56, 56, 92),
IVec3::new(71, 71, 107),
Some(0x80_00_00_aa),
);
let g_a2 = scene2.add_grid(GridTransform::at(DVec3::new(0.0, 50.0, 0.0)));
scene2.grid_mut(g_a2).unwrap().set_rect(
IVec3::new(56, 56, 92),
IVec3::new(71, 71, 107),
Some(0x80_aa_00_00),
);
let mut fb2 = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb2 = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let outcome2 = render_scene_composed(
&mut fb2,
&mut zb2,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene2,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome2, RenderOutcome::Rendered { grids_drawn: 2 });
assert_eq!(
fb, fb2,
"composition should be order-independent — same scene in different add order should produce identical output"
);
}
fn build_mip_visible_grid(world_origin: DVec3) -> (Scene, crate::GridId) {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(world_origin));
let grid = scene.grid_mut(id).unwrap();
grid.set_rect(
IVec3::new(0, 0, 100),
IVec3::new(127, 127, 254),
Some(0x80_88_88_88),
);
grid.chunk_mut(IVec3::ZERO).unwrap().generate_mips(3);
(scene, id)
}
fn fb_hash(fb: &[u32]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for px in fb {
for b in px.to_le_bytes() {
h ^= u64::from(b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
}
h
}
fn render_with_multi_mip(scene: &mut Scene, camera: &Camera) -> Vec<u32> {
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let mut settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
settings.mip_levels = 3;
settings.mip_scan_dist = 32;
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
scene,
camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
fb
}
#[test]
fn s6_1_mid_overrides_produce_different_framebuffer_than_near() {
let camera = camera_at([64.0, 0.0, 64.0]);
let (mut scene_a, _) = build_mip_visible_grid(DVec3::ZERO);
let fb_near = render_with_multi_mip(&mut scene_a, &camera);
let (mut scene_b, b_id) = build_mip_visible_grid(DVec3::ZERO);
scene_b.grid_mut(b_id).unwrap().lod_thresholds = crate::LodThresholds {
r_near: 0.0,
r_mid: f64::INFINITY,
mid_mip_levels: Some(1),
mid_mip_scan_dist: None,
};
let lod = scene_b
.grid(b_id)
.unwrap()
.select_lod(DVec3::from_array(camera.pos));
assert_eq!(lod, Lod::Mid, "expected Mid tier for forced thresholds");
let fb_mid = render_with_multi_mip(&mut scene_b, &camera);
let (_engine, _, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let non_sky_near = fb_near.iter().filter(|&&p| p != sky_color).count();
let non_sky_mid = fb_mid.iter().filter(|&&p| p != sky_color).count();
assert!(
non_sky_near > 100,
"Near render too sparse ({non_sky_near})"
);
assert!(non_sky_mid > 100, "Mid render too sparse ({non_sky_mid})");
let h_near = fb_hash(&fb_near);
let h_mid = fb_hash(&fb_mid);
assert_ne!(
h_near, h_mid,
"Mid tier with mid_mip_levels=Some(1) must differ from Near (h_near={h_near:016x})"
);
}
#[test]
fn s6_1_mid_without_overrides_byte_identical_to_near() {
let camera = camera_at([64.0, 0.0, 64.0]);
let (mut scene_a, _) = build_mip_visible_grid(DVec3::ZERO);
let fb_near = render_with_multi_mip(&mut scene_a, &camera);
let (mut scene_b, b_id) = build_mip_visible_grid(DVec3::ZERO);
scene_b.grid_mut(b_id).unwrap().lod_thresholds = crate::LodThresholds {
r_near: 0.0,
r_mid: f64::INFINITY,
mid_mip_levels: None,
mid_mip_scan_dist: None,
};
let lod = scene_b
.grid(b_id)
.unwrap()
.select_lod(DVec3::from_array(camera.pos));
assert_eq!(lod, Lod::Mid);
let fb_mid = render_with_multi_mip(&mut scene_b, &camera);
assert_eq!(
fb_near, fb_mid,
"Mid with both overrides=None must byte-match Near"
);
}
#[test]
fn s6_1_global_mip_cap_survives_mid_tier() {
let camera = camera_at([64.0, 0.0, 64.0]);
let (mut scene_a, a_id) = build_mip_visible_grid(DVec3::ZERO);
scene_a.grid_mut(a_id).unwrap().mip_levels_override = Some(1);
let fb_a = render_with_multi_mip(&mut scene_a, &camera);
let (mut scene_b, b_id) = build_mip_visible_grid(DVec3::ZERO);
scene_b.grid_mut(b_id).unwrap().mip_levels_override = Some(1);
scene_b.grid_mut(b_id).unwrap().lod_thresholds = crate::LodThresholds {
r_near: 0.0,
r_mid: f64::INFINITY,
mid_mip_levels: Some(4),
mid_mip_scan_dist: None,
};
let fb_b = render_with_multi_mip(&mut scene_b, &camera);
assert_eq!(
fb_a, fb_b,
"global mip_levels_override should clamp Mid override (ship workaround survives Mid tier)"
);
}
#[test]
fn s6_3_far_tier_blits_non_sky_pixels() {
let (mut scene, id) = build_one_grid_scene(DVec3::new(0.0, 200.0, 0.0));
scene.grid_mut(id).unwrap().lod_thresholds = crate::LodThresholds {
r_near: 0.0,
r_mid: 0.0,
mid_mip_levels: None,
mid_mip_scan_dist: None,
};
let camera = camera_at([64.0, 0.0, 100.0]);
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let lod = scene
.grid(id)
.unwrap()
.select_lod(DVec3::from_array(camera.pos));
assert_eq!(lod, Lod::Far);
let non_sky = fb.iter().filter(|&&p| p != sky_color).count();
assert!(
non_sky > 0,
"Far-tier render produced no non-sky pixels — billboard blit not firing"
);
}
#[test]
fn s6_3_far_render_lazily_populates_cache() {
let (mut scene, id) = build_one_grid_scene(DVec3::new(0.0, 200.0, 0.0));
scene.grid_mut(id).unwrap().lod_thresholds = crate::LodThresholds {
r_near: 0.0,
r_mid: 0.0,
mid_mip_levels: None,
mid_mip_scan_dist: None,
};
assert!(scene.grid(id).unwrap().billboards.is_none());
let camera = camera_at([64.0, 0.0, 100.0]);
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let _ = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
let cache = scene
.grid(id)
.unwrap()
.billboards
.as_ref()
.expect("Far render should have populated billboards");
assert_eq!(cache.len(), 26);
}
#[test]
fn s6_3_edit_invalidates_then_far_render_rebuilds() {
let (mut scene, id) = build_one_grid_scene(DVec3::new(0.0, 200.0, 0.0));
scene.grid_mut(id).unwrap().lod_thresholds = crate::LodThresholds {
r_near: 0.0,
r_mid: 0.0,
mid_mip_levels: None,
mid_mip_scan_dist: None,
};
let camera = camera_at([64.0, 0.0, 100.0]);
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let mut fb1 = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb1 = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let _ = render_scene_composed(
&mut fb1,
&mut zb1,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert!(scene.grid(id).unwrap().billboards.is_some());
scene
.grid_mut(id)
.unwrap()
.set_voxel(IVec3::new(70, 70, 70), Some(0x80_aa_aa_22));
assert!(scene.grid(id).unwrap().billboards.is_none());
let mut fb2 = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb2 = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let _ = render_scene_composed(
&mut fb2,
&mut zb2,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert!(scene.grid(id).unwrap().billboards.is_some());
}
#[test]
fn s6_3_near_and_far_grids_in_same_scene() {
let mut scene = Scene::new();
let a_id = scene.add_grid(GridTransform::at(DVec3::new(-100.0, 200.0, 0.0)));
scene.grid_mut(a_id).unwrap().set_rect(
IVec3::new(70, 0, 50),
IVec3::new(85, 15, 70),
Some(0x80_22_88_22), );
let b_id = scene.add_grid(GridTransform::at(DVec3::new(100.0, 200.0, 0.0)));
scene.grid_mut(b_id).unwrap().set_rect(
IVec3::new(0, 0, 80),
IVec3::new(20, 20, 110),
Some(0x80_aa_22_22), );
scene.grid_mut(b_id).unwrap().lod_thresholds = crate::LodThresholds {
r_near: 0.0,
r_mid: 0.0,
mid_mip_levels: None,
mid_mip_scan_dist: None,
};
let camera = camera_at([0.0, 0.0, 80.0]);
assert_eq!(
scene
.grid(a_id)
.unwrap()
.select_lod(DVec3::from_array(camera.pos)),
Lod::Near
);
assert_eq!(
scene
.grid(b_id)
.unwrap()
.select_lod(DVec3::from_array(camera.pos)),
Lod::Far
);
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 2 });
let non_sky = fb.iter().filter(|&&p| p != sky_color).count();
assert!(
non_sky > 20,
"hybrid scene produced too few non-sky pixels ({non_sky}); one tier may have failed"
);
}
#[test]
fn s6_3_empty_grid_at_far_is_skipped() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::new(100.0, 200.0, 0.0)));
scene.grid_mut(id).unwrap().lod_thresholds = crate::LodThresholds {
r_near: 0.0,
r_mid: 0.0,
mid_mip_levels: None,
mid_mip_scan_dist: None,
};
let camera = camera_at([0.0, 0.0, 100.0]);
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Empty);
assert!(scene.grid(id).unwrap().billboards.is_none());
assert!(fb.iter().all(|&p| p == sky_color));
}
#[test]
fn render_scene_composed_lod_threshold_invariance() {
let (mut scene_a, _a_id) = build_one_grid_scene(DVec3::new(0.0, 200.0, 0.0));
let cam = camera_at([64.0, 0.0, 100.0]);
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb_a = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb_a = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome_a = render_scene_composed(
&mut fb_a,
&mut zb_a,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene_a,
&cam,
&settings,
sky_color,
None,
);
assert_eq!(outcome_a, RenderOutcome::Rendered { grids_drawn: 1 });
let (mut scene_b, b_id) = build_one_grid_scene(DVec3::new(0.0, 200.0, 0.0));
let radius = scene_b.grid(b_id).unwrap().bounding_radius();
assert!(
radius > 0.0,
"bounding_radius should be > 0 for a populated grid"
);
scene_b.grid_mut(b_id).unwrap().lod_thresholds = crate::LodThresholds::from_radius(radius);
let lod = scene_b
.grid(b_id)
.unwrap()
.select_lod(DVec3::from_array(cam.pos));
assert_ne!(
lod,
Lod::Near,
"camera should land in Mid or Far for derived thresholds — got {lod:?}",
);
let mut fb_b = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb_b = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let outcome_b = render_scene_composed(
&mut fb_b,
&mut zb_b,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene_b,
&cam,
&settings,
sky_color,
None,
);
assert_eq!(outcome_b, RenderOutcome::Rendered { grids_drawn: 1 });
assert_eq!(
fb_a, fb_b,
"S6.0 framebuffer must be byte-identical regardless of LOD thresholds"
);
}
#[test]
fn render_scene_composed_empty_scene_returns_empty() {
let mut scene = Scene::new();
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([0.0, 0.0, 0.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Empty);
assert!(fb.iter().all(|&p| p == sky_color));
}
fn fnv1a64(data: &[u8]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for &b in data {
h ^= u64::from(b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
#[test]
fn render_scene_two_chunk_x_grid_no_seam() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::new(0.0, 200.0, 0.0)));
let g = scene.grid_mut(id).unwrap();
g.set_rect(
IVec3::new(120, 60, 200),
IVec3::new(136, 67, 215),
Some(0x80_aa_55_22),
);
assert_eq!(g.chunk_count(), 2);
let (_engine, mut pool, sky_color) = make_composed_pool(2 * CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([128.0, 100.0, 207.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let stripe = 0x80_aa_55_22;
let stripe_count = fb.iter().filter(|&&p| p == stripe).count();
assert!(
stripe_count > 200,
"stripe rendered too few pixels ({stripe_count}) — chunks may not be stitching"
);
let centre_y = (YRES / 2) as usize;
let row_start = centre_y * (XRES as usize);
let row = &fb[row_start..row_start + (XRES as usize)];
let mut in_stripe = false;
let mut seam_gaps = 0usize;
for &px in row {
if px == stripe {
in_stripe = true;
} else if in_stripe && px == sky_color {
if row.iter().skip_while(|&&p| p != px).any(|&p| p == stripe) {
seam_gaps += 1;
}
in_stripe = false;
}
}
assert!(
seam_gaps <= 1,
"centre row has {seam_gaps} disjoint stripe runs — expected 1 (chunk-edge seam suspected)"
);
}
#[test]
fn vxl_generate_mips_on_set_voxel_chunk_renders() {
let mut grid = crate::Grid::new(GridTransform::identity());
grid.set_rect(
IVec3::new(0, 0, 100),
IVec3::new(127, 127, 254),
Some(0x80_88_88_88),
);
let chunk = grid.chunks.get_mut(&IVec3::ZERO).unwrap();
chunk.generate_mips(3);
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([64.0, 0.0, 64.0]);
let mut settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
settings.mip_levels = 3;
settings.mip_scan_dist = 32;
let grid_view = roxlap_core::GridView::from_single_vxl(&chunk);
let mut rasterizer = ScalarRasterizer::new(&mut fb, &mut zb, XRES as usize, grid_view);
let _ = core_opticast(&mut rasterizer, &mut pool, &camera, &settings, grid_view);
drop(rasterizer);
let non_sky = fb.iter().filter(|&&p| p != sky_color).count();
assert!(
non_sky > 0,
"Vxl::generate_mips on a set_voxel-built chunk should render to something non-sky (got {non_sky})"
);
}
#[test]
fn render_with_mips_present_still_renders_mip0() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
scene.grid_mut(id).unwrap().set_rect(
IVec3::new(40, 40, 40),
IVec3::new(55, 55, 55),
Some(0x80_88_88_88),
);
{
let grid = scene.grid_mut(id).unwrap();
let chunk = grid.chunks.get_mut(&IVec3::ZERO).unwrap();
chunk.generate_mips(3);
}
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([64.0, 0.0, 64.0]);
let mut settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
settings.mip_scan_dist = 100_000;
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let non_sky = fb.iter().filter(|&&p| p != sky_color).count();
assert!(
non_sky > 0,
"render of single-grid scene with mips present rendered all-sky: mip-0 may be corrupted by generate_mips"
);
}
#[test]
fn render_scene_two_chunk_x_grid_hash_is_stable() {
const GOLDEN: u64 = 0x215e_d66d_7359_4725;
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::new(0.0, 200.0, 0.0)));
scene.grid_mut(id).unwrap().set_rect(
IVec3::new(120, 60, 200),
IVec3::new(136, 67, 215),
Some(0x80_aa_55_22),
);
let (_engine, mut pool, sky_color) = make_composed_pool(2 * CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([128.0, 100.0, 207.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let bytes: Vec<u8> = fb.iter().flat_map(|p| p.to_ne_bytes()).collect();
let hash = fnv1a64(&bytes);
if GOLDEN == SENTINEL {
eprintln!("render_scene_two_chunk_x_grid_hash_is_stable: capture hash = 0x{hash:016x}");
panic!("GOLDEN is the SENTINEL placeholder — paste 0x{hash:016x} into GOLDEN above");
}
assert_eq!(
hash, GOLDEN,
"2-chunk render hash drifted: expected 0x{GOLDEN:016x}, got 0x{hash:016x}"
);
}
const SENTINEL: u64 = 0xDEAD_BEEF_DEAD_BEEF;
#[test]
fn approach_b_renders_two_chunk_x_stripe_via_chunk_grid() {
const SENTINEL_B: u64 = 0xDEAD_BEEF_DEAD_BEEF;
const GOLDEN_B: u64 = 0x5ee1_e81c_66a8_d1f1;
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::identity());
let g = scene.grid_mut(id).unwrap();
g.set_rect(
IVec3::new(0, 0, 200),
IVec3::new(127, 127, 205),
Some(0x80_44_44_aa),
);
g.set_rect(
IVec3::new(160, 50, 150),
IVec3::new(170, 60, 165),
Some(0x80_aa_55_22),
);
assert_eq!(g.chunk_count(), 2);
let backing = g.chunk_xyz_backing().expect("at least one chunk populated");
assert_eq!(backing.chunks_x, 2);
assert_eq!(backing.chunks_y, 1);
assert_eq!(backing.origin_chunk_xy, [0, 0]);
let cg = roxlap_core::ChunkGrid {
chunks: &backing.chunks,
origin_chunk_xy: backing.origin_chunk_xy,
origin_chunk_z: backing.origin_chunk_z,
chunks_x: backing.chunks_x,
chunks_y: backing.chunks_y,
chunks_z: backing.chunks_z,
};
let grid_view = roxlap_core::GridView::from_chunk_grid(&cg, CHUNK_SIZE_XY);
let camera = Camera {
pos: [10.0, 64.0, 160.0],
right: [0.0, 1.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [1.0, 0.0, 0.0],
};
let (_engine, mut pool, mut fb, mut zb) = render_setup(2 * CHUNK_SIZE_XY);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let mut rasterizer = ScalarRasterizer::new(&mut fb, &mut zb, XRES as usize, grid_view);
let outcome = core_opticast(&mut rasterizer, &mut pool, &camera, &settings, grid_view);
drop(rasterizer);
assert_eq!(outcome, OpticastOutcome::Rendered);
let floor_count = fb.iter().filter(|&&p| p == 0x80_44_44_aa).count();
let box_count = fb.iter().filter(|&&p| p == 0x80_aa_55_22).count();
assert!(
floor_count > 1000,
"floor not visible — only {floor_count} floor pixels (single-chunk path?)"
);
assert!(
box_count > 50,
"box in chunk (1, 0) not visible — only {box_count} box pixels — cross-chunk DDA may have failed to fire"
);
let bytes: Vec<u8> = fb.iter().flat_map(|p| p.to_ne_bytes()).collect();
let hash = fnv1a64(&bytes);
if GOLDEN_B == SENTINEL_B {
eprintln!("approach_b_renders_two_chunk_x_stripe_via_chunk_grid: capture hash = 0x{hash:016x}");
panic!(
"GOLDEN_B is the SENTINEL placeholder — paste 0x{hash:016x} into GOLDEN_B above"
);
}
assert_eq!(
hash, GOLDEN_B,
"Approach B 2-chunk render hash drifted: expected 0x{GOLDEN_B:016x}, got 0x{hash:016x}"
);
}
#[test]
fn approach_b_camera_in_chunk_1_0_renders_neighbour() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::identity());
let g = scene.grid_mut(id).unwrap();
g.set_rect(
IVec3::new(128, 0, 200),
IVec3::new(255, 127, 205),
Some(0x80_44_44_aa),
);
g.set_rect(
IVec3::new(20, 50, 150),
IVec3::new(30, 60, 165),
Some(0x80_aa_55_22),
);
assert_eq!(g.chunk_count(), 2);
let backing = g.chunk_xyz_backing().expect("populated");
let cg = roxlap_core::ChunkGrid {
chunks: &backing.chunks,
origin_chunk_xy: backing.origin_chunk_xy,
origin_chunk_z: backing.origin_chunk_z,
chunks_x: backing.chunks_x,
chunks_y: backing.chunks_y,
chunks_z: backing.chunks_z,
};
let grid_view = roxlap_core::GridView::from_chunk_grid(&cg, CHUNK_SIZE_XY);
let (aabb_min, aabb_max) = grid_view.aabb_xy();
assert_eq!(aabb_min, [0, 0]);
assert_eq!(aabb_max, [256, 128]);
let camera = Camera {
pos: [200.0, 64.0, 160.0],
right: [0.0, -1.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [-1.0, 0.0, 0.0],
};
let (_engine, mut pool, mut fb, mut zb) = render_setup(2 * CHUNK_SIZE_XY);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let mut rasterizer = ScalarRasterizer::new(&mut fb, &mut zb, XRES as usize, grid_view);
let outcome = core_opticast(&mut rasterizer, &mut pool, &camera, &settings, grid_view);
drop(rasterizer);
assert_eq!(outcome, OpticastOutcome::Rendered);
let floor_count = fb.iter().filter(|&&p| p == 0x80_44_44_aa).count();
let box_count = fb.iter().filter(|&&p| p == 0x80_aa_55_22).count();
assert!(
floor_count > 1000,
"floor under camera in chunk (1, 0) not visible — only {floor_count} floor pixels — in_bounds_xy fix may not have taken effect"
);
assert!(
box_count > 50,
"box in chunk (0, 0) not visible — only {box_count} box pixels — westward cross-chunk DDA failed"
);
}
#[test]
fn stacked_two_chunk_z_camera_in_chz1_sees_own_chunk_floor() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let g = scene.grid_mut(id).unwrap();
g.ensure_chunk(IVec3::new(0, 0, 0));
g.set_rect(
IVec3::new(60, 60, 306),
IVec3::new(72, 72, 310),
Some(0x80_33_66_99),
);
assert!(g.chunk(IVec3::new(0, 0, 1)).is_some());
let (_engine, mut pool, sky_color) = make_composed_pool(2 * CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
pool.set_treat_z_max_as_air(true);
let camera = Camera {
pos: [66.0, 66.0, 280.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 1.0, 0.0],
forward: [0.0, 0.0, 1.0],
};
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let floor_count = fb.iter().filter(|&&p| p == 0x80_33_66_99).count();
assert!(
floor_count > 100,
"camera at chz=1 with floor in same chunk should see it — got {floor_count} floor pixels"
);
}
#[test]
fn stacked_two_chunk_z_camera_in_chz0_sees_chz1_floor() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let g = scene.grid_mut(id).unwrap();
g.ensure_chunk(IVec3::new(0, 0, 0));
g.set_rect(
IVec3::new(60, 60, 306),
IVec3::new(72, 72, 310),
Some(0x80_77_aa_44),
);
assert!(g.chunk(IVec3::new(0, 0, 1)).is_some());
let (_engine, mut pool, sky_color) = make_composed_pool(2 * CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
pool.set_treat_z_max_as_air(true);
let camera = Camera {
pos: [66.0, 66.0, 100.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 1.0, 0.0],
forward: [0.0, 0.0, 1.0],
};
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let floor_count = fb.iter().filter(|&&p| p == 0x80_77_aa_44).count();
assert!(
floor_count > 50,
"camera in chz=0 air-gap should see chz=1 floor via cross-chunk look-down — got {floor_count} floor pixels"
);
}
#[test]
fn stacked_chz0_distant_mountain_visible_from_chz0_camera() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let g = scene.grid_mut(id).unwrap();
g.set_rect(
IVec3::new(100, 100, 100),
IVec3::new(124, 124, 200),
Some(0x80_aa_55_22), );
g.set_rect(
IVec3::new(0, 0, 336),
IVec3::new(128, 128, 360),
Some(0x80_22_88_44),
);
g.set_rect(IVec3::new(100, 100, 336), IVec3::new(124, 124, 360), None);
assert!(g.chunk(IVec3::new(0, 0, 0)).is_some());
assert!(g.chunk(IVec3::new(0, 0, 1)).is_some());
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
pool.set_treat_z_max_as_air(true);
let (sy, cy) = (std::f64::consts::FRAC_PI_4).sin_cos();
let (sp, cp) = 0.72_f64.sin_cos();
let camera = Camera {
pos: [40.0, 40.0, 60.0],
right: [-sy, cy, 0.0],
down: [-cy * sp, -sy * sp, cp],
forward: [cy * cp, sy * cp, sp],
};
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let mountain_count = fb.iter().filter(|&&p| p == 0x80_aa_55_22).count();
let hill_count = fb.iter().filter(|&&p| p == 0x80_22_88_44).count();
eprintln!("chz0-distant-mountain: mountain_chz0={mountain_count} hill_chz1={hill_count}");
assert!(
hill_count > 50,
"expected chz=1 hills via cross-chunk look-down — got {hill_count}"
);
assert!(
mountain_count > 50,
"expected chz=0 distant mountain visible — got {mountain_count} (S4B.6.l limitation)"
);
}
#[test]
fn mid_render_handoff_reveals_chz1_hills_under_mountain_camera() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let g = scene.grid_mut(id).unwrap();
g.set_rect(
IVec3::new(60, 60, 150),
IVec3::new(72, 72, 200),
Some(0x80_88_44_22), );
g.set_rect(
IVec3::new(0, 0, 336),
IVec3::new(128, 128, 360),
Some(0x80_22_88_44), );
g.set_rect(IVec3::new(60, 60, 336), IVec3::new(72, 72, 360), None);
assert!(g.chunk(IVec3::new(0, 0, 0)).is_some());
assert!(g.chunk(IVec3::new(0, 0, 1)).is_some());
let (_engine, mut pool, sky_color) = make_composed_pool(2 * CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
pool.set_treat_z_max_as_air(true);
let camera = Camera {
pos: [66.0, 66.0, 100.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 1.0, 0.0],
forward: [0.0, 0.0, 1.0],
};
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let mountain_count = fb.iter().filter(|&&p| p == 0x80_88_44_22).count();
let hill_count = fb.iter().filter(|&&p| p == 0x80_22_88_44).count();
let mut hill_depths: Vec<f32> = fb
.iter()
.zip(zb.iter())
.filter_map(|(&p, &d)| if p == 0x80_22_88_44 { Some(d) } else { None })
.collect();
hill_depths.sort_by(|a, b| a.partial_cmp(b).unwrap());
let median_hill_depth = hill_depths[hill_depths.len() / 2];
eprintln!(
"mid-render handoff: mountain={mountain_count} hill={hill_count} median_hill_depth={median_hill_depth:.1}"
);
assert!(
mountain_count > 50,
"should see mountain peak via chz=0 — got {mountain_count} mountain pixels"
);
assert!(
hill_count > 50,
"should see chz=1 hills via mid-render handoff — got {hill_count} hill pixels"
);
assert!(
(median_hill_depth - 236.0).abs() < 80.0,
"hill median depth should be ≈236 (camera→z=336); got {median_hill_depth:.1} — state.z1 may be stale at the mountain peak's z"
);
}
#[test]
fn stacked_two_chunk_z_camera_in_chz0_sees_chz1_floor_multi_mip() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let g = scene.grid_mut(id).unwrap();
g.ensure_chunk(IVec3::new(0, 0, 0));
g.set_rect(
IVec3::new(60, 60, 306),
IVec3::new(72, 72, 310),
Some(0x80_77_aa_44),
);
assert!(g.chunk(IVec3::new(0, 0, 1)).is_some());
let (_engine, mut pool, sky_color) = make_composed_pool(2 * CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
pool.set_treat_z_max_as_air(true);
let camera = Camera {
pos: [66.0, 66.0, 100.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 1.0, 0.0],
forward: [0.0, 0.0, 1.0],
};
let mut settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
settings.mip_levels = 2;
settings.mip_scan_dist = 16;
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let floor_count = fb.iter().filter(|&&p| p == 0x80_77_aa_44).count();
assert!(
floor_count > 50,
"multi-mip cross-chunk look-down should still see chz=1 floor — got {floor_count} floor pixels"
);
}
#[test]
fn stacked_three_chunk_z_camera_in_chz2_sees_own_chunk_floor_multi_mip() {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let g = scene.grid_mut(id).unwrap();
g.ensure_chunk(IVec3::new(0, 0, 0));
g.ensure_chunk(IVec3::new(0, 0, 1));
g.set_rect(
IVec3::new(60, 60, 562),
IVec3::new(72, 72, 566),
Some(0x80_aa_55_22),
);
assert!(g.chunk(IVec3::new(0, 0, 2)).is_some());
let (_engine, mut pool, sky_color) = make_composed_pool(2 * CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
pool.set_treat_z_max_as_air(true);
let camera = Camera {
pos: [66.0, 66.0, 540.0],
right: [1.0, 0.0, 0.0],
down: [0.0, 1.0, 0.0],
forward: [0.0, 0.0, 1.0],
};
let mut settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
settings.mip_levels = 2;
settings.mip_scan_dist = 16;
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let floor_count = fb.iter().filter(|&&p| p == 0x80_aa_55_22).count();
assert!(
floor_count > 100,
"camera at chz=2 with floor in same chunk should see it — got {floor_count} floor pixels"
);
}
#[derive(Debug)]
struct FloorGenerator;
impl crate::ChunkGenerator for FloorGenerator {
fn generate(&self, _chunk_idx: IVec3) -> roxlap_formats::vxl::Vxl {
let mut tmp = crate::Grid::new(GridTransform::identity());
tmp.ensure_chunk(IVec3::ZERO);
let mut vxl = tmp.chunks.remove(&IVec3::ZERO).unwrap();
#[allow(clippy::cast_possible_wrap)]
roxlap_formats::edit::set_rect(
&mut vxl,
glam::IVec3::new(0, 0, 230).into(),
glam::IVec3::new((CHUNK_SIZE_XY - 1) as i32, (CHUNK_SIZE_XY - 1) as i32, 239)
.into(),
Some(0x80_22_aa_22),
);
vxl
}
}
#[test]
fn render_scene_composed_unpumped_streaming_grid_renders_all_sky() {
use std::sync::Arc;
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let g = scene.grid_mut(id).unwrap();
g.set_generator(Some(Arc::new(FloorGenerator)));
g.stream_radius = crate::StreamRadius::new(300.0, 600.0);
assert!(g.chunks.is_empty(), "no pump yet → no chunks");
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([64.0, -100.0, 200.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let _ = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert!(
fb.iter().all(|&p| p == sky_color),
"unpumped streaming grid must render as all sky"
);
}
#[test]
fn render_scene_composed_picks_up_streamed_chunks_after_sync_pump() {
use std::sync::Arc;
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let g = scene.grid_mut(id).unwrap();
g.set_generator(Some(Arc::new(FloorGenerator)));
g.stream_radius = crate::StreamRadius::new(300.0, 600.0);
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([64.0, -100.0, 200.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let _ = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
let pre_floor = fb.iter().filter(|&&p| p == 0x80_22_aa_22).count();
assert_eq!(pre_floor, 0, "pre-pump frame has no streamed chunks");
scene.pump_streaming_sync(DVec3::new(64.0, -100.0, 200.0));
let g = scene.grid(id).unwrap();
assert!(
!g.chunks.is_empty(),
"pump should have streamed at least one chunk"
);
fb.iter_mut().for_each(|p| *p = sky_color);
zb.iter_mut().for_each(|z| *z = f32::INFINITY);
let outcome = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
assert_eq!(outcome, RenderOutcome::Rendered { grids_drawn: 1 });
let post_floor = fb.iter().filter(|&&p| p == 0x80_22_aa_22).count();
assert!(
post_floor > 100,
"post-pump frame should show the streamed floor — got {post_floor} green pixels"
);
}
#[test]
fn render_scene_composed_partial_streaming_renders_pending_chunks_as_air() {
use std::sync::Arc;
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let g = scene.grid_mut(id).unwrap();
g.set_generator(Some(Arc::new(FloorGenerator)));
g.stream_radius = crate::StreamRadius::new(400.0, 800.0);
let installed = g.ensure_chunk_generated(IVec3::ZERO);
assert!(installed, "manual install of one chunk");
assert_eq!(g.chunks.len(), 1);
assert!(g.chunk(IVec3::new(0, 1, 0)).is_none());
assert!(g.chunk(IVec3::new(0, 2, 0)).is_none());
let (_engine, mut pool, sky_color) = make_composed_pool(CHUNK_SIZE_XY);
let mut fb = vec![sky_color; pixel_count(XRES, YRES)];
let mut zb = vec![f32::INFINITY; pixel_count(XRES, YRES)];
let camera = camera_at([64.0, 32.0, 200.0]);
let settings = OpticastSettings::for_oracle_framebuffer(XRES, YRES);
let _ = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
let floor_pixels = fb.iter().filter(|&&p| p == 0x80_22_aa_22).count();
assert!(
floor_pixels > 0,
"should see at least some floor from the loaded chunk"
);
scene.pump_streaming_sync(DVec3::new(64.0, 32.0, 200.0));
assert!(scene.grid(id).unwrap().chunk_count() >= 2);
fb.iter_mut().for_each(|p| *p = sky_color);
zb.iter_mut().for_each(|z| *z = f32::INFINITY);
let _ = render_scene_composed(
&mut fb,
&mut zb,
XRES as usize,
XRES,
YRES,
&mut pool,
&mut scene,
&camera,
&settings,
sky_color,
None,
);
let floor_pixels_full = fb.iter().filter(|&&p| p == 0x80_22_aa_22).count();
assert!(
floor_pixels_full > floor_pixels,
"fully-streamed scene should show more floor than partial: \
partial={floor_pixels} full={floor_pixels_full}"
);
}
}