Files
shacraft-core/client/tests/actor-rendering.test.js
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

92 lines
4.6 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { Renderer } from "../renderer.js";
// Record actual draw-call state: a uniform belongs to one linked program,
// and changing programs does not reset that program's previous uniforms.
function fixture(shadows) {
let active, vao;
const offsets = new Map(), draws = [], errors = [];
const uniform = location => {
if (location.program !== active) {
errors.push(`uniform ${location.name} belongs to ${location.program}, active ${active}`);
return false;
}
return true;
};
const gl = new Proxy({
useProgram(program) { active = program; },
getUniformLocation(program, name) { return { program, name }; },
uniform3fv(location, values) {
if (uniform(location) && location.name === "uOffset") offsets.set(active, [...values]);
},
uniform1f: uniform, uniform1i: uniform, uniformMatrix4fv: uniform,
bindVertexArray(value) { vao = value; },
drawArrays() { draws.push({ program: active, vao, offset: offsets.get(active) }); },
}, { get(target, name) { return target[name] ?? (/^[A-Z_0-9]+$/.test(name) ? 0 : () => {}); } });
const renderer = Object.assign(Object.create(Renderer.prototype), {
blocks: new Map(),
gl, canvas: { clientWidth: 800, clientHeight: 600, width: 800, height: 600, dataset: {} },
program: "world", shadowProgram: "shadow", sky: "sky", line: "line",
renderOrigin: [0, 0, 0], sections: new Map(), dirty: new Set(),
dynamic: { vao: "actors", count: 0 }, lines: { count: 0 },
shadowsEnabled: shadows, shadowReady: true, shadowSize: 2048, shadowRadius: 48,
blockLighting: { status: "ready", milliseconds: 0 }, daylight: 1, bounceAt: -1000,
});
renderer.upload = (mesh, vertices) => {
mesh.vertices = new Float32Array(vertices);
mesh.count = vertices.length / 20;
};
return { renderer, draws, errors };
}
for (const shadows of [true, false]) {
test(`players and entities keep world positions through camera/section changes; shadows ${shadows}`, () => {
const previousRatio = globalThis.devicePixelRatio;
globalThis.devicePixelRatio = 1;
try {
for (const base of [0, -160, 29_999_840]) {
const { renderer, draws, errors } = fixture(shadows);
const player = { position: [base + 18.125, 77, -5.875], yaw: 0.3 };
const entity = { kind: "crate", position: [base + 21.125, 77, -6.875] };
let baseline;
for (const [x, y, z, reverse, empty] of [
[15.99, 79.99, 0.01, false, false],
[16.01, 80.01, -0.01, true, false],
[31.99, 79.99, -16.01, false, false],
[32.01, 80.01, -15.99, true, true],
]) {
const origins = [[base, 64, -16], [base + 32, 96, 16]];
if (reverse) origins.reverse();
renderer.sections = new Map(empty ? [] : origins.map((origin, i) => [
origin.map(v => v / 16).join(","),
{ origin, vao: `terrain-${i}`, count: 36, transparent: { vao: `glass-${i}`, count: 36 } },
]));
draws.length = 0;
const eye = [base + x, y, z];
const direction = [player.position[0] - eye[0], 79 - eye[1], player.position[2] - eye[2]];
const camera = { eye, yaw: Math.atan2(direction[0], -direction[2]), pitch: Math.atan2(direction[1], Math.hypot(direction[0], direction[2])) };
renderer.draw(camera, [player], [entity], new Map());
assert.deepEqual(errors, [], "uniform writes must target the active shader program");
const actorDraws = draws.filter(draw => draw.vao === "actors");
assert.equal(actorDraws.length, shadows ? 2 : 1);
for (const draw of actorDraws) assert.deepEqual(draw.offset, [0, 0, 0], `${draw.program} inherited terrain transform`);
// Reverse the GPU transform for every avatar and entity vertex.
// It must recover identical world geometry despite rebasing the camera.
const world = [];
for (let i = 0; i < renderer.dynamic.vertices.length; i += 20)
for (let axis = 0; axis < 3; axis++)
world.push(renderer.dynamic.vertices[i + axis] + renderer.renderOrigin[axis]);
if (baseline) world.forEach((v, i) => assert.ok(Math.abs(v - baseline[i]) < 0.00001, `vertex ${i} moved with camera`));
else baseline = world;
assert.ok(world.length > 36 * 3, "both an avatar and an entity were drawn");
assert.ok(renderer.project([player.position[0], 79, player.position[2]]), "label projection uses the same camera origin");
}
}
} finally {
if (previousRatio === undefined) delete globalThis.devicePixelRatio;
else globalThis.devicePixelRatio = previousRatio;
}
});
}