import test from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { TerrainState } from "../terrain-state.js"; import { MESH_VERTEX_STRIDE } from "../mesh-geometry.js"; const properties = JSON.parse( await readFile(new URL("../light-properties.json", import.meta.url)), ); const cube = { min: [0, 0, 0], max: [1, 1, 1] }; const stone = { state: "minecraft:stone", color: [128, 128, 128], render: [cube], }; const lamp = { state: "minecraft:sea_lantern", color: [240, 250, 250], render: [cube], light: 15, }; const bounds = { min: [-2, -2, -2], max: [18, 3, 3] }; function packet(overrides = {}) { return { type: "sync", epoch: 1, version: 1, reset: true, initial: new Int32Array([0, 0, 0, 1]), changes: new Int32Array(), materials: [ [1, stone], [2, lamp], ], textures: [], bounds, unload: [], ...overrides, }; } const request = (state, id = "0,0,0") => ({ epoch: state.epoch, version: state.version, id, job: 1, }); test("reset plus deltas preserves numeric coordinates and authoritative material lighting metadata", () => { const state = new TerrainState(properties); assert.equal( state.sync( packet({ initial: new Int32Array([-1, 0, 0, 1, 16, 0, 0, 1]), changes: new Int32Array([-1, 0, 0, 0, 16, 0, 0, 2]), }), ), true, ); assert.deepEqual([...state.blocks], [["16,0,0", 2]]); assert.deepEqual([...state.blocks.loadedSectionKeys()], ["1,0,0"]); assert.equal(state.materials.get(1).light_dampening, 15); assert.equal(stone.light_dampening, undefined); assert.equal(state.mesh(request(state, "1,0,0")).stale, true); const result = state.light(); assert.equal(result.field.sourceCount, 1); assert.equal(state.field.sample([16, 0, 0]).blockLevel, 15); assert.notEqual(result.field.data.buffer, state.field.data.buffer); assert.notEqual( result.field.blockLevels.buffer, state.field.blockLevels.buffer, ); assert.notEqual(result.field.skyLevels.buffer, state.field.skyLevels.buffer); assert.ok(state.mesh(request(state, "1,0,0")).vertices.length > 0); }); test("outdated syncs and mesh requests cannot overwrite a new version or world", () => { const state = new TerrainState(properties); state.sync(packet()); state.light(); state.sync( packet({ epoch: 2, version: 5, initial: new Int32Array([16, 0, 0, 2]) }), ); assert.equal(state.sync(packet({ version: 100 })), false); assert.equal(state.sync(packet({ epoch: 2, version: 4 })), false); assert.equal( state.sync(packet({ epoch: 3, version: 6, reset: false })), false, ); assert.deepEqual([...state.blocks], [["16,0,0", 2]]); assert.equal(state.field, null); assert.equal( state.mesh({ epoch: 1, version: 1, id: "0,0,0", job: 1 }).stale, true, ); state.light(); assert.equal( state.mesh({ epoch: 2, version: 4, id: "1,0,0", job: 2 }).stale, true, ); assert.ok(state.mesh(request(state, "1,0,0")).vertices.length > 0); }); test("unloaded sections disappear from persistent storage and cannot be resurrected by old syncs", () => { const state = new TerrainState(properties); state.sync(packet({ initial: new Int32Array([0, 0, 0, 1, 16, 0, 0, 2]) })); state.light(); state.mesh(request(state, "1,0,0")); assert.equal(state.meshed.has("1,0,0"), true); state.sync( packet({ reset: false, version: 2, initial: null, materials: [], unload: [[1, 0, 0]], }), ); assert.equal(state.blocks.has("16,0,0"), false); assert.deepEqual([...state.blocks.keysInSection("1,0,0")], []); assert.equal(state.meshed.has("1,0,0"), false); state.light(); assert.equal(state.mesh(request(state, "1,0,0")).vertices.length, 0); assert.equal( state.sync(packet({ initial: new Int32Array([16, 0, 0, 2]) })), false, ); assert.equal(state.blocks.has("16,0,0"), false); }); test("material and texture replacements affect the next mesh and invalidate face texture cache", () => { const state = new TerrainState(properties); state.sync(packet({ textures: [["stone", 3]] })); state.light(); const first = state.mesh(request(state)); assert.equal(first.vertices.length, 36 * MESH_VERTEX_STRIDE); assert.equal(first.vertices[13], 3); assert.equal(state.faceTextureCache.get("minecraft:stone")[0].layer, 3); state.sync( packet({ reset: false, version: 2, initial: null, materials: [ [ 1, { ...stone, render: [{ min: [0, 0, 0], max: [1, 0.5, 1] }], light: 7, }, ], ], textures: [["stone", 9]], }), ); assert.equal(state.faceTextureCache.size, 0); assert.equal(state.mesh(request(state)).stale, true); state.light(); const next = state.mesh(request(state)); for (let i = 0; i < next.vertices.length; i += MESH_VERTEX_STRIDE) { assert.ok(next.vertices[i + 1] <= 0.5); assert.equal(next.vertices[i + 13], 9); assert.ok(Math.abs(next.vertices[i + 15] - 7 / 15) < 1e-6); } assert.equal(state.faceTextureCache.get("minecraft:stone")[0].layer, 9); }); test("lighting changes keep emptied meshed sections dirty until their stale geometry is replaced", () => { const state = new TerrainState(properties); state.sync(packet({ initial: new Int32Array([0, 0, 0, 2]) })); state.light(); state.mesh(request(state)); state.sync( packet({ reset: false, version: 2, initial: null, materials: [], changes: new Int32Array([0, 0, 0, 0]), }), ); assert.equal(state.blocks.size, 0); assert.ok(state.light().dirty.includes("0,0,0")); assert.equal(state.mesh(request(state)).vertices.length, 0); }); test("a retained unknown cube remeshes when its same-light definition arrives", () => { const state = new TerrainState(properties); state.sync(packet({ initial: new Int32Array([15, 0, 0, 99, 16, 0, 0, 1]) })); state.light(); const before = state.mesh(request(state)); state.mesh(request(state, "1,0,0")); const oldLight = state.field.data.slice(), oldSky = state.field.skyLevels.slice(); const defined = { ...stone, color: [60, 70, 80] }; state.sync(packet({ reset: false, version: 2, initial: null, materials: [[99, defined]] })); const result = state.light(); assert.deepEqual(state.field.data, oldLight); assert.deepEqual(state.field.skyLevels, oldSky); assert.ok(result.dirty.includes("0,0,0"), "the placeholder's owner must be rebuilt even with identical lighting"); assert.ok(result.dirty.includes("1,0,0"), "neighbor culling/AO must also be refreshed"); const after = state.mesh(request(state)); assert.notDeepEqual(after.vertices.slice(3, 6), before.vertices.slice(3, 6)); assert.deepEqual([...after.vertices.slice(3, 6)], defined.color.map(value => Math.fround(value / 255))); }); test("repeated equivalent material definitions do not spuriously dirty retained sections", () => { const state = new TerrainState(properties); state.sync(packet()); state.light(); state.mesh(request(state)); const current = state.materials.get(1); const repeated = { render: [{ max: [1, 1, 1], min: [0, 0, 0] }], color: [128, 128, 128], state: "minecraft:stone", ignoredMetadata: "does not affect the transmitted material" }; state.sync(packet({ reset: false, version: 2, initial: null, materials: [[1, repeated]] })); assert.equal(state.materials.get(1), current); assert.deepEqual(state.light().dirty, []); }); test("a color-only definition replacement invalidates its mesh without a light delta", () => { const state = new TerrainState(properties); state.sync(packet()); state.light(); state.mesh(request(state)); const oldLight = state.field.data.slice(), oldSky = state.field.skyLevels.slice(); state.sync(packet({ reset: false, version: 2, initial: null, materials: [[1, { ...stone, color: [220, 70, 30] }]] })); assert.ok(state.light().dirty.includes("0,0,0")); assert.deepEqual(state.field.data, oldLight); assert.deepEqual(state.field.skyLevels, oldSky); assert.deepEqual([...state.mesh(request(state)).vertices.slice(3, 6)], [220, 70, 30].map(value => Math.fround(value / 255))); }); test("material invalidations from separate coalesced syncs retain their union", () => { const state = new TerrainState(properties), wide = { min: [-2, -2, -2], max: [50, 3, 3] }; state.sync(packet({ initial: new Int32Array([0, 0, 0, 1, 48, 0, 0, 3]), materials: [[1, stone], [3, stone]], bounds: wide })); state.light(); state.mesh(request(state)); state.mesh(request(state, "3,0,0")); state.sync(packet({ reset: false, version: 2, initial: null, materials: [[1, { ...stone, color: [10, 20, 30] }]], bounds: wide })); state.sync(packet({ reset: false, version: 3, initial: null, materials: [[3, { ...stone, color: [30, 20, 10] }]], bounds: wide })); assert.deepEqual(new Set(state.light().dirty), new Set(["0,0,0", "3,0,0"])); assert.deepEqual(state.light().dirty, [], "the material union is consumed after a successful field publication"); }); test("world reset clears pending material geometry invalidations", () => { const state = new TerrainState(properties), wide = { min: [-2, -2, -2], max: [50, 3, 3] }; state.sync(packet()); state.light(); state.mesh(request(state)); state.sync(packet({ reset: false, version: 2, initial: null, materials: [[1, { ...stone, color: [10, 20, 30] }]] })); state.sync(packet({ epoch: 2, version: 3, initial: new Int32Array([48, 0, 0, 1]), materials: [[1, stone]], bounds: wide })); assert.deepEqual(state.light().dirty, ["3,0,0"]); }); test("a new definition absent from the loaded blocks does not remesh terrain", () => { const state = new TerrainState(properties); state.sync(packet()); state.light(); state.mesh(request(state)); state.sync(packet({ reset: false, version: 2, initial: null, materials: [[99, lamp]] })); assert.deepEqual(state.light().dirty, []); });