Chunks keep the player's bytes apart from the composed density: plan presses land in an owner-keyed PlanTerrainPatch that is retracted with its feature and never touches history, so removing a railway takes its bed with it and a human fill under it survives. Surface queries answer Surface | Hole | Outside instead of pretending the heightfield continues over a hole or past the border; landforms issued inside a plan eval are plan, outside it history. Seven tests pin the layering, including same-inputs-twice equality. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
218 lines
8 KiB
Rust
218 lines
8 KiB
Rust
//! The plan layer over the destructible terrain (worldgen DESIGN.md,
|
|
//! amendment B): plan presses compose over history at read time and never
|
|
//! touch history bytes; a retracted feature leaves nothing; a human fill
|
|
//! under a plan press survives it; holes and the border answer as
|
|
//! themselves; the same (plan, history) composes to the same bytes twice.
|
|
|
|
use makepad_game_sim::voxel::{ChunkKey, DigMode};
|
|
use makepad_game_sim::*;
|
|
use makepad_math::*;
|
|
|
|
fn flat_terrain(h: f32) -> Terrain {
|
|
let cells = 33;
|
|
let cell_size = 2.0;
|
|
let origin = -32.0;
|
|
Terrain {
|
|
cells,
|
|
cell_size,
|
|
origin,
|
|
heights: vec![h; cells * cells],
|
|
colors: vec![vec4f(0.4, 0.6, 0.4, 1.0); cells * cells],
|
|
revision: 1,
|
|
}
|
|
}
|
|
|
|
fn world() -> GameWorld {
|
|
let mut w = GameWorld::new();
|
|
w.terrain = Some(flat_terrain(4.0));
|
|
w
|
|
}
|
|
|
|
fn field(w: &mut GameWorld) -> &mut makepad_game_sim::voxel::VoxelField {
|
|
w.voxel
|
|
.get_or_insert_with(|| Box::new(makepad_game_sim::voxel::VoxelField::new(0.5)))
|
|
}
|
|
|
|
/// A history op that materializes the chunks around the origin: a shallow
|
|
/// fill dome, so the surface there is ABOVE the plane a press will ask for.
|
|
fn fill_mound(w: &mut GameWorld) {
|
|
w.apply_voxel_op(VoxelOp::Dig {
|
|
pos: vec3f(0.0, 4.0, 0.0),
|
|
r: 3.0,
|
|
mode: DigMode::Fill,
|
|
material: 2,
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn a_plan_press_never_touches_history_bytes() {
|
|
let mut w = world();
|
|
fill_mound(&mut w);
|
|
let before = w.surface_height_at(0.0, 0.0).expect("mound owns the surface");
|
|
assert!(before > 4.5, "the fill raised the ground: {before}");
|
|
let field = w.voxel.as_deref().unwrap();
|
|
let key = ChunkKey::of_site(field.world_site(vec3f(0.0, 4.0, 0.0)));
|
|
let history_before = field.chunk_history(key).unwrap().to_vec();
|
|
let hash_before = field.field_hash();
|
|
|
|
// The plan presses a pad plane at y = 4.2 over the mound.
|
|
w.voxel
|
|
.as_deref_mut()
|
|
.unwrap()
|
|
.plan_press(7, vec3f(-4.0, 0.0, -4.0), vec3f(4.0, 20.0, 4.0), 4.2);
|
|
let pressed = w.surface_height_at(0.0, 0.0).expect("still owned");
|
|
assert!(
|
|
pressed <= 4.2 + 0.6 && pressed < before,
|
|
"the press clips the mound to its plane: {pressed} (was {before})"
|
|
);
|
|
let field = w.voxel.as_deref().unwrap();
|
|
assert_eq!(
|
|
field.chunk_history(key).unwrap(),
|
|
history_before.as_slice(),
|
|
"history bytes are untouched by a plan press"
|
|
);
|
|
assert_ne!(field.field_hash(), hash_before, "the composed field did change");
|
|
|
|
// Retracting the owner gives the mound back, byte-exact.
|
|
w.voxel.as_deref_mut().unwrap().retract_plan(7);
|
|
let after = w.surface_height_at(0.0, 0.0).unwrap();
|
|
assert!((after - before).abs() < 1e-4, "retraction restores the surface: {after} vs {before}");
|
|
assert_eq!(w.voxel.as_deref().unwrap().field_hash(), hash_before, "retraction is exact");
|
|
}
|
|
|
|
#[test]
|
|
fn a_human_fill_under_a_plan_press_survives_the_press() {
|
|
let mut w = world();
|
|
// Plan first (a railway bed), then the player fills under it.
|
|
field(&mut w).plan_press(3, vec3f(-6.0, 0.0, -6.0), vec3f(6.0, 20.0, 6.0), 3.0);
|
|
fill_mound(&mut w);
|
|
// While pressed, the surface is the pad plane.
|
|
let pressed = w.surface_height_at(0.0, 0.0).unwrap();
|
|
assert!(pressed <= 3.6, "pressed under the railway: {pressed}");
|
|
// The railway is removed: the fill the player made is still there.
|
|
w.voxel.as_deref_mut().unwrap().retract_plan(3);
|
|
let free = w.surface_height_at(0.0, 0.0).unwrap();
|
|
assert!(free > 4.5, "the human fill survived the plan press: {free}");
|
|
}
|
|
|
|
#[test]
|
|
fn reset_content_clears_the_plan_but_keeps_history() {
|
|
let mut w = world();
|
|
fill_mound(&mut w);
|
|
let mound = w.surface_height_at(0.0, 0.0).unwrap();
|
|
w.voxel
|
|
.as_deref_mut()
|
|
.unwrap()
|
|
.plan_press(1, vec3f(-4.0, 0.0, -4.0), vec3f(4.0, 20.0, 4.0), 4.0);
|
|
assert!(w.surface_height_at(0.0, 0.0).unwrap() < mound);
|
|
w.reset_content();
|
|
w.terrain = Some(flat_terrain(4.0));
|
|
assert!(w.voxel.as_deref().unwrap().plan.is_empty(), "the plan layer is script content");
|
|
let back = w.surface_height_at(0.0, 0.0).unwrap();
|
|
assert!((back - mound).abs() < 1e-4, "history survives reset_content: {back} vs {mound}");
|
|
}
|
|
|
|
#[test]
|
|
fn the_wire_press_op_lands_in_the_plan_not_the_chunks() {
|
|
let mut w = world();
|
|
fill_mound(&mut w);
|
|
let key = ChunkKey::of_site(w.voxel.as_deref().unwrap().world_site(vec3f(0.0, 4.0, 0.0)));
|
|
let history = w.voxel.as_deref().unwrap().chunk_history(key).unwrap().to_vec();
|
|
w.apply_voxel_op(VoxelOp::Press {
|
|
min: vec3f(-4.0, 0.0, -4.0),
|
|
max: vec3f(4.0, 20.0, 4.0),
|
|
y: 4.1,
|
|
});
|
|
let field = w.voxel.as_deref().unwrap();
|
|
assert_eq!(field.plan.presses.len(), 1, "a Press op is a plan press");
|
|
assert_eq!(field.chunk_history(key).unwrap(), history.as_slice());
|
|
assert!(w.surface_height_at(0.0, 0.0).unwrap() <= 4.7);
|
|
}
|
|
|
|
#[test]
|
|
fn holes_answer_hole_and_the_border_answers_outside() {
|
|
let mut w = world();
|
|
// Carve every solid site of the column inside the one chunk band the
|
|
// capsule materializes ([0, 16) m): the ground beneath is gone there,
|
|
// and the base layer below the band is not the column's business.
|
|
// A carve whose box sits exactly on the chunk band [0, 16) — its sphere
|
|
// removes every solid site of the column there (ground is at 4).
|
|
w.apply_voxel_op(VoxelOp::Dig {
|
|
pos: vec3f(0.0, 2.0, 0.0),
|
|
r: 2.0,
|
|
mode: DigMode::Carve,
|
|
material: 0,
|
|
});
|
|
match w.surface_sample_at(0.0, 0.0) {
|
|
SurfaceSample::Hole => {}
|
|
other => panic!("a column carved through is a Hole, not {other:?}"),
|
|
}
|
|
assert_eq!(w.surface_height_at(0.0, 0.0), None, "no fake heightfield ground over a hole");
|
|
assert_eq!(w.surface_sample_at(500.0, 500.0), SurfaceSample::Outside);
|
|
assert_eq!(w.surface_sample_at(-32.0, -32.0).height(), Some(4.0), "the near edge is inside");
|
|
assert!(w.surface_sample_at(10.0, 10.0).is_ground());
|
|
// A punched heightfield cell is a hole even with no voxel chunk there.
|
|
let t = w.terrain.as_ref().unwrap();
|
|
let side = t.cells - 1;
|
|
let mut m = TerrainMaterials::default();
|
|
m.indices = vec![0u8; side * side];
|
|
let ix = ((20.0 - t.origin) / t.cell_size).floor() as usize;
|
|
let iz = ((20.0 - t.origin) / t.cell_size).floor() as usize;
|
|
m.indices[iz * side + ix] = 0xFF;
|
|
w.terrain_materials = Some(m);
|
|
assert_eq!(w.surface_sample_at(20.5, 20.5), SurfaceSample::Hole);
|
|
assert!(w.surface_sample_at(24.5, 24.5).is_ground());
|
|
}
|
|
|
|
#[test]
|
|
fn plan_landforms_are_not_history() {
|
|
let mut w = world();
|
|
w.in_plan_eval = true;
|
|
landform::host_apply_landform(
|
|
&mut w,
|
|
VoxelOp::Landform {
|
|
pos: vec3f(10.0, 4.0, 10.0),
|
|
kind: LandKind::Hill.to_u8(),
|
|
r: 12.0,
|
|
height: 6.0,
|
|
seed: 3,
|
|
},
|
|
);
|
|
w.in_plan_eval = false;
|
|
assert!(w.surface_height_at(10.0, 10.0).unwrap() > 6.0, "the plan hill rose");
|
|
let recorded = w.voxel.as_deref().map_or(0, |f| f.land_ops.len() + f.persist_ops.len());
|
|
assert_eq!(recorded, 0, "a plan landform is never recorded or persisted as history");
|
|
// A landform issued outside eval IS history.
|
|
landform::host_apply_landform(
|
|
&mut w,
|
|
VoxelOp::Landform {
|
|
pos: vec3f(-10.0, 4.0, -10.0),
|
|
kind: LandKind::Hill.to_u8(),
|
|
r: 8.0,
|
|
height: 3.0,
|
|
seed: 4,
|
|
},
|
|
);
|
|
let field = w.voxel.as_deref().unwrap();
|
|
assert_eq!(field.land_ops.len(), 1, "a brush landform is history");
|
|
}
|
|
|
|
#[test]
|
|
fn the_same_plan_and_history_compose_identically() {
|
|
let build = || {
|
|
let mut w = world();
|
|
fill_mound(&mut w);
|
|
w.apply_voxel_op(VoxelOp::Dig {
|
|
pos: vec3f(6.0, 4.0, 2.0),
|
|
r: 2.0,
|
|
mode: DigMode::Carve,
|
|
material: 0,
|
|
});
|
|
w.voxel
|
|
.as_deref_mut()
|
|
.unwrap()
|
|
.plan_press(9, vec3f(-3.0, 0.0, -3.0), vec3f(5.0, 20.0, 5.0), 4.3);
|
|
w.voxel.as_deref().unwrap().field_hash()
|
|
};
|
|
assert_eq!(build(), build());
|
|
}
|