Files
Emil c7e86663d8
MVP checks / mvp (push) Waiting to run
Expand voxel gameplay, lighting, full-height streaming and world imports
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
2026-09-17 02:10:53 +03:00

359 lines
11 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { SectionBlockMap } from "../section-block-map.js";
import {
getViewBounds,
mergeChunkUpdate,
mergeSnapshot,
positionInView,
} from "../world-view.js";
function view() {
return {
world: "lobby",
revision: 7,
viewCenter: [0, 0, 0],
viewBounds: { min: [-32, -16, -32], max: [31, 31, 31] },
blocks: new Map([
["0,0,0", 1],
["31,0,0", 2],
["-32,0,0", 3],
]),
};
}
function moveEast() {
const message = {
type: "chunks",
world: "lobby",
revision: 7,
from_center: [0, 0, 0],
view_center: [16, 0, 0],
view_min: [-16, -16, -32],
view_max: [47, 31, 31],
unload: [],
sections: [],
};
for (const y of [-1, 0, 1])
for (const z of [-2, -1, 0, 1]) {
message.unload.push([-2, y, z]);
message.sections.push({ section: [2, y, z], blocks: [] });
}
message.sections.find((s) => s.section.join(",") === "2,0,0").blocks = [
{ pos: [32, 0, 0], block: 4 },
];
return message;
}
test("crossing a section boundary retains overlapping blocks and map identity", () => {
const current = view(),
blocks = current.blocks,
result = mergeChunkUpdate(current, moveEast());
assert.equal(result.status, "applied");
assert.equal(current.blocks, blocks);
assert.equal(current.revision, 7);
assert.deepEqual(current.viewCenter, [16, 0, 0]);
assert.deepEqual(
[...current.blocks],
[
["0,0,0", 1],
["31,0,0", 2],
["32,0,0", 4],
],
);
assert.deepEqual(result.changes, [
{ pos: [-32, 0, 0], block: 0 },
{ pos: [32, 0, 0], block: 4 },
]);
});
test("an empty incoming section clears any stale block without touching overlap", () => {
const current = view();
current.blocks.set("32,0,16", 99);
const result = mergeChunkUpdate(current, moveEast());
assert.equal(result.status, "applied");
assert.equal(current.blocks.has("32,0,16"), false);
assert.ok(
result.changes.some(
(change) => change.pos.join(",") === "32,0,16" && !change.block,
),
);
assert.equal(current.blocks.get("0,0,0"), 1);
});
test("negative coordinates use floor division when loading and unloading sections", () => {
const current = view(),
message = moveEast();
current.viewCenter = [-16, 0, 0];
current.blocks = new Map([
["-1,0,0", 1],
["0,0,0", 2],
["-48,0,0", 3],
]);
message.from_center = [-16, 0, 0];
message.view_center = [-32, 0, 0];
message.view_min = [-64, -16, -32];
message.view_max = [-1, 31, 31];
message.unload = message.unload.map(([, y, z]) => [0, y, z]);
message.sections = message.sections.map(({ section: [, y, z] }) => ({
section: [-4, y, z],
blocks: y === 0 && z === 0 ? [{ pos: [-49, 0, 0], block: 4 }] : [],
}));
const result = mergeChunkUpdate(current, message);
assert.equal(result.status, "applied");
assert.deepEqual(
[...current.blocks],
[
["-1,0,0", 1],
["-48,0,0", 3],
["-49,0,0", 4],
],
);
});
test("same-world full snapshots only report actual changes and retain the map", () => {
const current = view(),
blocks = current.blocks,
records = [...blocks].map(([id, block]) => ({
pos: id.split(",").map(Number),
block,
}));
assert.deepEqual(mergeSnapshot(blocks, records), []);
assert.equal(current.blocks, blocks);
records.pop();
records.push({ pos: [32, 0, 0], block: 4 });
assert.deepEqual(mergeSnapshot(blocks, records), [
{ pos: [-32, 0, 0], block: 0 },
{ pos: [32, 0, 0], block: 4 },
]);
assert.equal(blocks.get("0,0,0"), 1);
});
test("newer or older chunk revisions request resync without advancing revision", () => {
for (const revision of [6, 8]) {
const current = view(),
before = structuredClone(current),
message = { ...moveEast(), revision };
assert.equal(mergeChunkUpdate(current, message).status, "resync");
assert.deepEqual(current, before);
}
});
test("an out-of-order or duplicate transition cannot overwrite the current window", () => {
const current = view(),
message = moveEast();
assert.equal(mergeChunkUpdate(current, message).status, "applied");
const before = structuredClone(current);
assert.equal(mergeChunkUpdate(current, message).status, "resync");
assert.deepEqual(current, before);
assert.equal(
mergeChunkUpdate(current, { ...message, world: "other" }).status,
"ignored",
);
assert.deepEqual(current, before);
});
test("incomplete or malformed transitions do not partially unload a live world", () => {
const invalidMessages = [
(m) => m.sections.pop(),
(m) => m.unload.pop(),
(m) => m.unload.push([0, 0, 0]),
(m) => (m.view_min[1] = -8),
(m) => m.sections[0].blocks.push({ pos: [0, 0, 0], block: 5 }),
(m) => m.sections[0].blocks.push({ pos: [32, -16, -32], block: -1 }),
];
for (const invalidate of invalidMessages) {
const current = view(),
before = structuredClone(current),
message = moveEast();
invalidate(message);
assert.equal(mergeChunkUpdate(current, message).status, "resync");
assert.deepEqual(current, before);
}
});
test("explicit view bounds include the entire bottom section with legacy fallback", () => {
const streamed = getViewBounds(moveEast()),
legacy = getViewBounds({ view_center: [16, 0, 0] });
assert.equal(positionInView([0, -16, 0], streamed), true);
assert.equal(positionInView([0, -17, 0], streamed), false);
assert.equal(positionInView([0, -16, 0], legacy), false);
assert.equal(positionInView([0, -8, 0], legacy), true);
assert.equal(positionInView([48, 0, 0], streamed), false);
});
test("indexed transitions never iterate or read retained blocks", () => {
class IndexedBlocks extends SectionBlockMap {
[Symbol.iterator]() {
throw Error("A chunk transition must not scan the complete map");
}
get(key) {
assert.ok(!["0,0,0", "31,0,0"].includes(key), "Retained block was read");
return super.get(key);
}
}
const current = view();
current.blocks = new IndexedBlocks(current.blocks);
current.blocks.set("-31,0,0", 8);
current.blocks.set("32,0,16", 99);
current.blocks.set("32,0,0", 3);
const result = mergeChunkUpdate(current, moveEast());
assert.equal(result.status, "applied");
assert.deepEqual(
[...current.blocks.entries()],
[
["0,0,0", 1],
["31,0,0", 2],
["32,0,0", 4],
],
);
assert.deepEqual([...current.blocks.loadedSectionKeys()].sort(), [
"0,0,0",
"1,0,0",
"2,0,0",
]);
assert.deepEqual(
result.changes.map(({ pos, block }) => [pos.join(","), block]).sort(),
[
["-31,0,0", 0],
["-32,0,0", 0],
["32,0,0", 4],
["32,0,16", 0],
],
);
});
test("malformed transitions preserve the section index before any indexed access", () => {
class IndexedBlocks extends SectionBlockMap {
keysInSection() {
throw Error("Incomplete transition accessed the index");
}
}
const current = view();
current.blocks = new IndexedBlocks(current.blocks);
const beforeBlocks = [...current.blocks],
beforeSections = [...current.blocks.loadedSectionKeys()],
beforeCenter = current.viewCenter,
beforeBounds = current.viewBounds,
message = moveEast();
message.sections.at(-1).blocks.push({ pos: [32, 16, 16], block: -1 });
assert.equal(mergeChunkUpdate(current, message).status, "resync");
assert.deepEqual([...current.blocks], beforeBlocks);
assert.deepEqual([...current.blocks.loadedSectionKeys()], beforeSections);
assert.equal(current.viewCenter, beforeCenter);
assert.equal(current.viewBounds, beforeBounds);
});
test("full snapshot merges maintain the section index alongside the block map", () => {
const blocks = new SectionBlockMap(view().blocks);
mergeSnapshot(blocks, [
{ pos: [0, 0, 0], block: 1 },
{ pos: [32, -1, 0], block: 4 },
]);
assert.deepEqual([...blocks.loadedSectionKeys()], ["0,0,0", "2,-1,0"]);
assert.deepEqual([...blocks.keysInSection("-2,0,0")], []);
assert.deepEqual([...blocks.keysInSection("2,-1,0")], ["32,-1,0"]);
});
test("optional section unloads omit only departing changes for indexed and ordinary maps", () => {
for (const BlockMap of [Map, SectionBlockMap]) {
const current = view();
current.blocks = new BlockMap(current.blocks);
current.blocks.set("32,0,16", 99);
const result = mergeChunkUpdate(current, moveEast(), {
includeUnloadedChanges: false,
});
assert.equal(result.status, "applied");
assert.equal(current.blocks.has("-32,0,0"), false);
assert.deepEqual(result.changes, [
{ pos: [32, 0, 16], block: 0 },
{ pos: [32, 0, 0], block: 4 },
]);
assert.deepEqual(
[...current.blocks],
[
["0,0,0", 1],
["31,0,0", 2],
["32,0,0", 4],
],
);
}
});
test("bulk streamed merges avoid departing key access and use validated sections for insertion", () => {
class BulkBlocks extends SectionBlockMap {
set(key, value) {
assert.equal(
this.locked,
undefined,
"Incoming records reparsed through ordinary set",
);
return super.set(key, value);
}
delete() {
throw Error("Departing blocks were deleted individually");
}
keysInSection(section) {
assert.ok(
!section.startsWith("-2,"),
"Departing block keys were requested",
);
return super.keysInSection(section);
}
[Symbol.iterator]() {
throw Error("Streamed merge scanned the full map");
}
}
const current = view(),
message = moveEast();
current.blocks = new BulkBlocks(current.blocks);
current.blocks.locked = true;
const result = mergeChunkUpdate(current, message, {
includeUnloadedChanges: false,
});
assert.equal(result.status, "applied");
assert.equal(current.blocks.has("-32,0,0"), false);
assert.equal(current.blocks.get("32,0,0"), 4);
assert.deepEqual(result.changes, [{ pos: [32, 0, 0], block: 4 }]);
assert.deepEqual(
[...current.blocks.loadedSectionKeys()],
["0,0,0", "1,0,0", "2,0,0"],
);
});
test("bulk merges still validate all incoming records before deleting any section", () => {
for (const invalidRecord of [
{ pos: [32, 16, 16], block: -1 },
{ pos: [31, 16, 16], block: 4 },
{ pos: [32, 16.5, 16], block: 4 },
]) {
const current = view(),
message = moveEast();
current.blocks = new SectionBlockMap(current.blocks);
const before = [...current.blocks],
sectionsBefore = [...current.blocks.loadedSectionKeys()],
centerBefore = current.viewCenter;
message.sections.at(-1).blocks.push(invalidRecord);
assert.equal(
mergeChunkUpdate(current, message, { includeUnloadedChanges: false })
.status,
"resync",
);
assert.deepEqual([...current.blocks], before);
assert.deepEqual([...current.blocks.loadedSectionKeys()], sectionsBefore);
assert.equal(current.viewCenter, centerBefore);
}
const current = view(),
message = moveEast();
current.blocks = new SectionBlockMap(current.blocks);
message.sections
.find((s) => s.section.join(",") === "2,0,0")
.blocks.push({ pos: [32, 0, 0], block: 0 });
assert.equal(
mergeChunkUpdate(current, message, { includeUnloadedChanges: false })
.status,
"resync",
);
assert.equal(current.blocks.get("-32,0,0"), 3);
});