import assert from "node:assert/strict"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { gunzipSync } from "node:zlib"; import sharp from "sharp"; import { renderSnapshot } from "../renderer/render.mjs"; import { assertTerrainImage } from "../renderer/image-validation.mjs"; // Exercise the product's rendering worker without launching a Minecraft server // or driving the website. A cached real export can also be supplied as argv[2]. const output = ".cache/renderer-regression"; const snapshot = { bounds: { minX: -24, minZ: -24, maxX: 24, maxZ: 24 }, coverage: [], blocks: [], }; for (let x = -24; x < 24; x++) { for (let z = -24; z < 24; z++) { snapshot.blocks.push([x, 63, z, "minecraft:dirt"]); snapshot.blocks.push([ x, 64, z, x > 12 && z < -12 ? "minecraft:water[level=0]" : "minecraft:grass_block[snowy=false]", ]); } } for (let x = -4; x <= 4; x++) { for (let z = -4; z <= 4; z++) { for (let y = 65; y <= 71; y++) { if (Math.abs(x) === 4 || Math.abs(z) === 4 || y === 71) { snapshot.blocks.push([ x, y, z, y === 71 ? "minecraft:oak_planks" : "minecraft:stone_bricks", ]); } } } } for (let y = 65; y <= 70; y++) snapshot.blocks.push([-16, y, 0, "minecraft:oak_log[axis=y]"]); for (let x = -18; x <= -14; x++) { for (let z = -2; z <= 2; z++) { snapshot.blocks.push([ x, 71, z, "minecraft:oak_leaves[distance=1,persistent=true,waterlogged=false]", ]); } } snapshot.blocks.push([ 5, 65, 0, "minecraft:oak_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]", ]); let terrain = snapshot; let points = [ [-12.5, 12.5], [12.5, 12.5], [12.5, -12.5], [-12.5, -12.5], [0.5, 18.5], ].map(([x, z], i) => ({ id: `p${i}`, x, y: 66.62, z })); let source = "grass, dirt, water, tree and building regression fixture"; if (process.argv[2]) { const directory = process.argv[2]; const manifest = JSON.parse( await readFile(`${directory}/manifest.json`, "utf8"), ); terrain = JSON.parse( gunzipSync(await readFile(`${directory}/terrain.json.gz`)).toString(), ); points = manifest.viewpoints; source = "cached Paper export (no server started)"; } await mkdir(output, { recursive: true }); const report = await renderSnapshot(terrain, points, output, { size: 256 }); assert.ok(report.meshes > 0, "terrain must produce meshes"); assert.ok(report.emittedFaces > 0, "terrain must produce faces"); assert.ok(report.hiddenFaces > 0, "interior terrain faces must be culled"); assert.ok(report.geometryBytes > 0, "geometry must reach the renderer"); assert.equal( report.unsupported.some((s) => /minecraft:(stone|dirt|grass_block|water)(\[|$)/.test(s), ), false, ); await assertTerrainImage(await readFile(`${output}/map.webp`), "Surface map"); for (const point of points) { for (const face of ["px", "nx", "py", "ny", "pz", "nz"]) { const metadata = await sharp( await readFile(`${output}/${point.id}/${face}.webp`), ).metadata(); assert.equal(metadata.format, "webp"); assert.equal(metadata.width, 256); assert.equal(metadata.height, 256); } await assertTerrainImage( await readFile(`${output}/${point.id}/ny.webp`), `Panorama ${point.id}, downward face`, ); } // The earlier renderer silently accepted this case with zero meshes and faces. const missingModel = { ...snapshot, blocks: [[0, 64, 0, "minecraft:minegeo_missing_model"]], }; await assert.rejects( renderSnapshot(missingModel, [points[0]], `${output}/missing-model`, { size: 256, }), /No terrain models could be rendered/, ); await writeFile( `${output}/verification.json`, JSON.stringify( { source, ...report, checks: [ "nonempty terrain", "valid WebP cubemap faces", "surface visible below every viewpoint", "missing model rejected", ], }, null, 2, ), ); console.log( "Renderer regression passed:", JSON.stringify({ source, ...report }), );