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); });