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