Files
shacraft-core/client/tests/terrain-state.test.js
Emil c7e86663d8
MVP checks / mvp (push) Canceled after 0s
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

254 lines
9.7 KiB
JavaScript

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, []);
});