MVP checks / mvp (push) Canceled after 0s
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.
133 lines
7.2 KiB
JavaScript
133 lines
7.2 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { boxVertices, buildSectionMesh, materialType, MESH_VERTEX_STRIDE } from "../mesh-geometry.js";
|
|
import { buildBlockLight } from "../block-light.js";
|
|
import { unitBox } from "../math.js";
|
|
|
|
const stone = { state: "minecraft:stone", color: [128, 144, 160], render: [unitBox], opacity: 1, light: 0 };
|
|
const materialMap = () => new Map([
|
|
[1, stone],
|
|
[2, { ...stone, state: "minecraft:water[level=0]", color: [50, 90, 180], opacity: 0.55, transparent: true }],
|
|
[3, { ...stone, state: "minecraft:glass", opacity: 0.35, transparent: true }],
|
|
[4, { ...stone, state: "minecraft:oak_leaves", transparent: true }],
|
|
[5, { ...stone, state: "minecraft:torch", light: 14, render: [], light_dampening: 0, light_occlusion: [] }],
|
|
[6, { ...stone, state: "minecraft:redstone_lamp[lit=true]", light: 15 }],
|
|
]);
|
|
const blockMap = (...cells) => new Map(cells.map(([pos, id = 1]) => [pos.join(","), id]));
|
|
const build = (blocks, options = {}) => buildSectionMesh({ id: "0,0,0", blocks, materials: materialMap(), ...options });
|
|
const rows = (vertices) => Array.from({ length: vertices.length / MESH_VERTEX_STRIDE }, (_, index) =>
|
|
[...vertices.slice(index * MESH_VERTEX_STRIDE, (index + 1) * MESH_VERTEX_STRIDE)]);
|
|
|
|
test("empty sections return transferable empty Float32 arrays", () => {
|
|
const result = build(new Map());
|
|
assert.ok(result.vertices instanceof Float32Array);
|
|
assert.ok(result.transparent instanceof Float32Array);
|
|
assert.equal(result.vertices.length, 0);
|
|
assert.equal(result.transparent.length, 0);
|
|
const invisible = build(blockMap([[1, 1, 1], 5]));
|
|
assert.equal(invisible.vertices.length, 0);
|
|
});
|
|
|
|
test("an isolated cube keeps all six faces and the complete 20-float format", () => {
|
|
const result = build(blockMap([[2, 3, 4], 1])), vertices = rows(result.vertices);
|
|
assert.equal(MESH_VERTEX_STRIDE, 20);
|
|
assert.equal(vertices.length, 36);
|
|
assert.equal(result.transparent.length, 0);
|
|
for (const vertex of vertices) {
|
|
assert.ok(vertex.slice(0, 3).every((value, axis) => [2, 3, 4][axis] <= value && value <= [3, 4, 5][axis]));
|
|
assert.deepEqual(vertex.slice(3, 6), [128, 144, 160].map((value) => Math.fround(value / 255)));
|
|
assert.equal(Math.hypot(...vertex.slice(6, 9)), 1);
|
|
assert.deepEqual(vertex.slice(13), [-1, 1, 0, 0, 0, 0, 1]);
|
|
}
|
|
});
|
|
|
|
test("opaque neighbors cull their shared faces, including across section boundaries", () => {
|
|
assert.equal(build(blockMap([[1, 1, 1], 1], [[2, 1, 1], 1])).vertices.length / 20, 60);
|
|
const result = build(blockMap([[15, 1, 1], 1], [[16, 1, 1], 1]));
|
|
assert.equal(result.vertices.length / 20, 30);
|
|
assert.ok(rows(result.vertices).every((vertex) => vertex[6] !== 1));
|
|
const negative = build(blockMap([[-1, 1, 1], 1], [[0, 1, 1], 1]), { id: "-1,0,0" });
|
|
assert.equal(negative.vertices.length / 20, 30);
|
|
assert.ok(rows(negative.vertices).every((vertex) => vertex[0] <= 0));
|
|
});
|
|
|
|
test("water goes into the translucent buffer and does not hide a stone face", () => {
|
|
const result = build(blockMap([[1, 1, 1], 1], [[2, 1, 1], 2]));
|
|
assert.equal(result.vertices.length / 20, 36);
|
|
assert.equal(result.transparent.length / 20, 36);
|
|
for (const vertex of rows(result.transparent)) {
|
|
assert.equal(vertex[9], 6);
|
|
assert.equal(vertex[10], Math.fround(0.55));
|
|
}
|
|
});
|
|
|
|
test("texture-pack glass and leaves retain cutout faces in the opaque pass", () => {
|
|
const blocks = blockMap([[1, 1, 1], 3], [[2, 1, 1], 4]);
|
|
const fallback = build(blocks);
|
|
assert.equal(fallback.transparent.length / 20, 36);
|
|
const result = build(blocks, { textureLayers: new Map([["glass", 2], ["oak_leaves", 3]]) });
|
|
assert.equal(result.vertices.length / 20, 72);
|
|
assert.equal(result.transparent.length, 0);
|
|
const vertices = rows(result.vertices);
|
|
assert.ok(vertices.every((vertex) => vertex[10] === 1));
|
|
assert.equal(vertices.filter((vertex) => vertex[13] === 2).length, 36);
|
|
assert.equal(vertices.filter((vertex) => vertex[13] === 3).length, 36);
|
|
assert.ok(vertices.filter((vertex) => vertex[13] === 3).every((vertex) => vertex[4] > vertex[3]));
|
|
});
|
|
|
|
test("partial geometry preserves its real height and crops texture coordinates", () => {
|
|
const materials = materialMap();
|
|
materials.set(7, { ...stone, state: "minecraft:stone_slab[type=bottom]", render: [{ min: [0, 0, 0], max: [1, 0.5, 1] }] });
|
|
const result = build(blockMap([[1, 1, 1], 7]), { materials, textureLayers: new Map([["stone", 8]]) });
|
|
const vertices = rows(result.vertices);
|
|
assert.equal(Math.max(...vertices.map((vertex) => vertex[1])), 1.5);
|
|
assert.ok(vertices.every((vertex) => vertex[13] === 8));
|
|
const sides = vertices.filter((vertex) => vertex[7] === 0);
|
|
assert.ok(sides.every((vertex) => vertex[12] >= 0.5));
|
|
});
|
|
|
|
test("baked AO shades contact corners while exposed faces remain bright", () => {
|
|
const result = build(blockMap([[1, 0, 1], 1], [[0, 1, 1], 1], [[1, 1, 0], 1]));
|
|
const top = rows(result.vertices).filter((vertex) => vertex[1] === 1 && vertex[7] === 1);
|
|
assert.equal(top.length, 6);
|
|
assert.ok(top.some((vertex) => vertex[14] === Math.fround(0.76)));
|
|
assert.ok(top.some((vertex) => vertex[14] === 1));
|
|
});
|
|
|
|
test("actual propagated torch light and sky are baked without changing emission", () => {
|
|
const blocks = blockMap([[4, 0, 4], 1], [[4, 2, 4], 5], [[10, 0, 10], 6]);
|
|
const materials = materialMap();
|
|
const blockLightField = buildBlockLight(blocks, materials, { min: [-1, -1, -1], max: [16, 16, 16] });
|
|
const result = build(blocks, { materials, blockLightField });
|
|
const vertices = rows(result.vertices);
|
|
const top = vertices.filter((vertex) => vertex[1] === 1 && vertex[7] === 1 && vertex[0] < 6);
|
|
assert.ok(top.every((vertex) => vertex[16] > 0 && vertex[16] > vertex[17] && vertex[17] > vertex[18]));
|
|
assert.ok(top.every((vertex) => vertex[19] === 1 && vertex[15] === 0));
|
|
assert.ok(vertices.some((vertex) => vertex[15] === 1));
|
|
});
|
|
|
|
test("geometry extraction keeps yaw and per-vertex dynamic lighting for actors", () => {
|
|
const out = [], sampled = [];
|
|
const field = { sample(point) { sampled.push(point); return { block: [0.1, 0.2, 0.3], sky: 0.4 }; } };
|
|
boxVertices(out, [5, 6, 7], unitBox, [1, 1, 1], 0, () => false, Math.PI / 2,
|
|
1, null, "", null, 0, field, true);
|
|
const vertices = rows(new Float32Array(out));
|
|
assert.equal(sampled.length, 36);
|
|
assert.ok(vertices.every((vertex) => vertex[0] >= 4 && vertex[0] <= 5 && vertex[2] >= 7 && vertex[2] <= 8));
|
|
assert.deepEqual(vertices[0].slice(16), [0.1, 0.2, 0.3, 0.4].map(Math.fround));
|
|
assert.equal(materialType("minecraft:water[level=0]"), 6);
|
|
assert.equal(materialType("shacraft:trampoline", "bounce"), 5);
|
|
});
|
|
|
|
test("reused texture caches keep section output byte-identical", () => {
|
|
const blocks = blockMap([[1, 1, 1], 1], [[2, 1, 1], 3]);
|
|
const textureLayers = new Map([["stone", 4], ["glass", 5]]), faceTextureCache = new Map();
|
|
const first = build(blocks, { textureLayers, faceTextureCache });
|
|
const second = build(blocks, { textureLayers, faceTextureCache });
|
|
assert.deepEqual(second.vertices, first.vertices);
|
|
assert.deepEqual(second.transparent, first.transparent);
|
|
assert.ok(faceTextureCache.size > 0);
|
|
const transferred = structuredClone(first, { transfer: [first.vertices.buffer, first.transparent.buffer] });
|
|
assert.deepEqual(transferred.vertices, second.vertices);
|
|
});
|