trueno/src/tests/world_test.jai
2026-03-24 18:36:48 +02:00

279 lines
10 KiB
Plaintext

test_floor_div_mod :: () {
s := begin_suite("floor_div / floor_mod");
check(*s, "floor_div( 0, 32) == 0", floor_div( 0, 32) == 0);
check(*s, "floor_div( 1, 32) == 0", floor_div( 1, 32) == 0);
check(*s, "floor_div(31, 32) == 0", floor_div(31, 32) == 0);
check(*s, "floor_div(32, 32) == 1", floor_div(32, 32) == 1);
check(*s, "floor_div(33, 32) == 1", floor_div(33, 32) == 1);
check(*s, "floor_div(-1, 32) == -1", floor_div(-1, 32) == -1);
check(*s, "floor_div(-32, 32) == -1", floor_div(-32, 32) == -1);
check(*s, "floor_div(-33, 32) == -2", floor_div(-33, 32) == -2);
check(*s, "floor_mod( 0, 32) == 0", floor_mod( 0, 32) == 0);
check(*s, "floor_mod( 1, 32) == 1", floor_mod( 1, 32) == 1);
check(*s, "floor_mod(31, 32) == 31", floor_mod(31, 32) == 31);
check(*s, "floor_mod(32, 32) == 0", floor_mod(32, 32) == 0);
check(*s, "floor_mod(33, 32) == 1", floor_mod(33, 32) == 1);
check(*s, "floor_mod(-1, 32) == 31", floor_mod(-1, 32) == 31);
check(*s, "floor_mod(-32, 32) == 0", floor_mod(-32, 32) == 0);
check(*s, "floor_mod(-33, 32) == 31", floor_mod(-33, 32) == 31);
end_suite(s);
}
test_coord_roundtrip :: () {
s := begin_suite("world coord round-trip");
roundtrip_check :: (suite: *Test_Suite, wx: s32, wy: s32, wz: s32) {
ck := world_to_chunk_coord(wx, wy, wz);
lx, ly, lz := world_to_local(wx, wy, wz);
rx, ry, rz := chunk_local_to_world(ck, lx, ly, lz);
check(suite, tprint("(%, %, %) round-trips", wx, wy, wz), rx == wx && ry == wy && rz == wz);
}
roundtrip_check(*s, 0, 0, 0);
roundtrip_check(*s, 1, 1, 1);
roundtrip_check(*s, 31, 31, 31);
roundtrip_check(*s, 32, 32, 32);
roundtrip_check(*s, 33, 33, 33);
roundtrip_check(*s, -1, -1, -1);
roundtrip_check(*s, -32, -32, -32);
roundtrip_check(*s, -33, -33, -33);
roundtrip_check(*s, 63, -1, 32);
end_suite(s);
}
test_chunk_coord_values :: () {
s := begin_suite("world_to_chunk_coord values");
check(*s, "( 0, 0, 0) -> chunk ( 0, 0, 0)", world_to_chunk_coord( 0, 0, 0) == .{ 0, 0, 0});
check(*s, "(31, 0, 0) -> chunk ( 0, 0, 0)", world_to_chunk_coord(31, 0, 0) == .{ 0, 0, 0});
check(*s, "(32, 0, 0) -> chunk ( 1, 0, 0)", world_to_chunk_coord(32, 0, 0) == .{ 1, 0, 0});
check(*s, "(-1, 0, 0) -> chunk (-1, 0, 0)", world_to_chunk_coord(-1, 0, 0) == .{-1, 0, 0});
check(*s, "(-32,0, 0) -> chunk (-1, 0, 0)", world_to_chunk_coord(-32, 0, 0) == .{-1, 0, 0});
check(*s, "(-33,0, 0) -> chunk (-2, 0, 0)", world_to_chunk_coord(-33, 0, 0) == .{-2, 0, 0});
end_suite(s);
}
make_test_world :: () -> World {
world: World;
world.name = "test_world";
world.conf.skyBase = .{0.1, 0.2, 0.3};
world.conf.sunIntensity = 5.0;
world.conf.hasClouds = 0;
world.conf.planeHeight = 2.5;
chunk1: Chunk;
chunk1.coord = .{x = 0, y = 0, z = 0};
group1: Chunk_Trile_Group;
group1.trile_name = "stone";
array_add(*group1.instances, Trile_Instance.{x = 1, y = 2, z = 3, orientation = 5});
array_add(*group1.instances, Trile_Instance.{x = 10, y = 20, z = 30, orientation = 12});
array_add(*chunk1.groups, group1);
table_set(*world.chunks, chunk1.coord, chunk1);
chunk2: Chunk;
chunk2.coord = .{x = -1, y = 0, z = 2};
group2: Chunk_Trile_Group;
group2.trile_name = "grass";
array_add(*group2.instances, Trile_Instance.{x = 0, y = 0, z = 0, orientation = 0});
array_add(*chunk2.groups, group2);
group3: Chunk_Trile_Group;
group3.trile_name = "dirt";
array_add(*group3.instances, Trile_Instance.{x = 5, y = 5, z = 5, orientation = 3});
array_add(*chunk2.groups, group3);
table_set(*world.chunks, chunk2.coord, chunk2);
e1: Particle_Emitter_Instance;
e1.definition_name = "fire";
e1.position = .{10.5, 0.0, 5.5};
e1.active = true;
array_add(*world.emitter_instances, e1);
n1: Editor_Note;
n1.text = "spawn point";
n1.position = .{x = 0, y = 0, z = 0};
array_add(*world.notes, n1);
return world;
}
test_world_save_load_roundtrip :: () {
s := begin_suite("world JSON save/load roundtrip");
world := make_test_world();
json_str, bin_data := save_world(*world);
bin_bytes: []u8;
bin_bytes.data = bin_data.data;
bin_bytes.count = bin_data.count;
loaded, ok := load_world_from_json(json_str, bin_bytes);
check(*s, "load succeeds", ok);
check(*s, "name matches", loaded.name == "test_world");
check(*s, "skyBase.x", loaded.conf.skyBase.x == 0.1);
check(*s, "skyBase.y", loaded.conf.skyBase.y == 0.2);
check(*s, "skyBase.z", loaded.conf.skyBase.z == 0.3);
check(*s, "sunIntensity", loaded.conf.sunIntensity == 5.0);
check(*s, "hasClouds", loaded.conf.hasClouds == 0);
check(*s, "planeHeight", loaded.conf.planeHeight == 2.5);
chunk0 := table_find_pointer(*loaded.chunks, Chunk_Key.{x=0, y=0, z=0});
check(*s, "chunk (0,0,0) exists", chunk0 != null);
if chunk0 {
check(*s, "chunk0 has 1 group", chunk0.groups.count == 1);
if chunk0.groups.count >= 1 {
check(*s, "chunk0 group name", chunk0.groups[0].trile_name == "stone");
check(*s, "chunk0 group has 2 instances", chunk0.groups[0].instances.count == 2);
if chunk0.groups[0].instances.count >= 2 {
inst := chunk0.groups[0].instances[0];
check(*s, "inst0 pos", inst.x == 1 && inst.y == 2 && inst.z == 3);
check(*s, "inst0 orient", inst.orientation == 5);
}
}
}
chunk_neg := table_find_pointer(*loaded.chunks, Chunk_Key.{x=-1, y=0, z=2});
check(*s, "chunk (-1,0,2) exists", chunk_neg != null);
if chunk_neg {
check(*s, "chunk_neg has 2 groups", chunk_neg.groups.count == 2);
}
check(*s, "1 emitter", loaded.emitter_instances.count == 1);
if loaded.emitter_instances.count >= 1 {
check(*s, "emitter name", loaded.emitter_instances[0].definition_name == "fire");
check(*s, "emitter pos.x", loaded.emitter_instances[0].position.x == 10.5);
}
check(*s, "1 note", loaded.notes.count == 1);
if loaded.notes.count >= 1 {
check(*s, "note text", loaded.notes[0].text == "spawn point");
check(*s, "note pos", loaded.notes[0].position == Chunk_Key.{x=0, y=0, z=0});
}
end_suite(s);
}
test_world_json_chunk_offsets :: () {
s := begin_suite("world JSON chunk offsets");
world := make_test_world();
json_str, bin_data := save_world(*world);
Jaison :: #import "Jaison";
ok, wj := Jaison.json_parse_string(json_str, World_Json);
check(*s, "JSON parses", ok);
check(*s, "2 chunk entries", wj.chunks.count == 2);
if wj.chunks.count == 2 {
for jc: wj.chunks {
check(*s, tprint("chunk %: offset+size <= bin", it_index),
cast(s64)(jc.offset + jc.size) <= bin_data.count);
}
a := wj.chunks[0];
b := wj.chunks[1];
no_overlap := (a.offset + a.size <= b.offset) || (b.offset + b.size <= a.offset);
check(*s, "chunks don't overlap", no_overlap);
}
end_suite(s);
}
test_legacy_load_cursor_fix :: () {
s := begin_suite("legacy binary cursor fix");
world := make_test_world();
builder: String_Builder;
write_value(*builder, WORLD_MAGIC);
write_value(*builder, cast(u16) 3);
name_len := cast(u16) world.name.count;
write_value(*builder, name_len);
append(*builder, world.name);
conf_bin := world_config_to_binary(*world.conf);
write_value(*builder, conf_bin);
Chunk_Data_Entry :: struct { coord: Chunk_Key; data: string; }
chunk_entries: [..]Chunk_Data_Entry;
chunk_entries.allocator = temp;
num_chunks: u32 = 0;
for chunk: world.chunks {
if chunk.groups.count == 0 then continue;
num_chunks += 1;
cb: String_Builder;
cb.allocator = temp;
write_value(*cb, cast(u16) chunk.groups.count);
for group: chunk.groups {
write_value(*cb, cast(u16) group.trile_name.count);
append(*cb, group.trile_name);
write_value(*cb, cast(u16) group.instances.count);
for inst: group.instances { write_value(*cb, inst); }
}
array_add(*chunk_entries, .{coord = chunk.coord, data = builder_to_string(*cb,, temp)});
}
write_value(*builder, num_chunks);
current_header_size := builder_string_length(*builder);
chunk_table_entry_size : s64 = size_of(s32)*3 + size_of(u32)*2;
chunk_table_size := cast(s64) num_chunks * chunk_table_entry_size;
data_start := current_header_size + chunk_table_size;
running_offset := cast(u32) data_start;
for entry: chunk_entries {
write_value(*builder, entry.coord.x);
write_value(*builder, entry.coord.y);
write_value(*builder, entry.coord.z);
write_value(*builder, running_offset);
write_value(*builder, cast(u32) entry.data.count);
running_offset += cast(u32) entry.data.count;
}
for entry: chunk_entries { append(*builder, entry.data); }
write_value(*builder, cast(u16) world.emitter_instances.count);
for inst: world.emitter_instances {
write_value(*builder, cast(u16) inst.definition_name.count);
append(*builder, inst.definition_name);
write_value(*builder, inst.position.x);
write_value(*builder, inst.position.y);
write_value(*builder, inst.position.z);
}
write_value(*builder, cast(u16) world.notes.count);
for note: world.notes {
write_value(*builder, cast(u16) note.text.count);
append(*builder, note.text);
write_value(*builder, note.position.x);
write_value(*builder, note.position.y);
write_value(*builder, note.position.z);
}
binary := builder_to_string(*builder);
data: []u8;
data.data = binary.data;
data.count = binary.count;
loaded, ok := load_world_from_data(data);
check(*s, "legacy load succeeds", ok);
check(*s, "legacy emitter count", loaded.emitter_instances.count == 1);
if loaded.emitter_instances.count >= 1 {
check(*s, "legacy emitter name", loaded.emitter_instances[0].definition_name == "fire");
}
check(*s, "legacy note count", loaded.notes.count == 1);
if loaded.notes.count >= 1 {
check(*s, "legacy note text", loaded.notes[0].text == "spawn point");
}
end_suite(s);
}
#run {
test_floor_div_mod();
test_coord_roundtrip();
test_chunk_coord_values();
test_world_save_load_roundtrip();
test_world_json_chunk_offsets();
test_legacy_load_cursor_fix();
}