Add Minecraft 26.x support and version Java artifacts
This commit is contained in:
@@ -2,7 +2,11 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { unzipSync } from "fflate";
|
||||
export const assetsRoot = ".cache/minecraft/1.21.11";
|
||||
const completionMarker = `${assetsRoot}/.minegeo-assets.json`;
|
||||
export function assetsDirectory(version = "1.21.11") {
|
||||
if (!/^(?:1\.21\.11|26\.[12](?:\.\d+)*)$/.test(version))
|
||||
throw new Error("Unsupported Minecraft asset version");
|
||||
return `.cache/minecraft/${version}`;
|
||||
}
|
||||
const requiredAssets = [
|
||||
"blockstates/grass_block.json",
|
||||
"blockstates/stone.json",
|
||||
@@ -14,12 +18,14 @@ const requiredAssets = [
|
||||
"textures/block/stone.png",
|
||||
"textures/block/water_still.png",
|
||||
];
|
||||
async function assetsReady() {
|
||||
async function assetsReady(minecraftVersion) {
|
||||
const assetsRoot = assetsDirectory(minecraftVersion);
|
||||
const completionMarker = `${assetsRoot}/.minegeo-assets.json`;
|
||||
try {
|
||||
const marker = JSON.parse(await readFile(completionMarker, "utf8"));
|
||||
if (
|
||||
marker.format !== "minegeo-assets/v2" ||
|
||||
marker.minecraftVersion !== "1.21.11" ||
|
||||
marker.minecraftVersion !== minecraftVersion ||
|
||||
!marker.files
|
||||
)
|
||||
return false;
|
||||
@@ -39,10 +45,12 @@ async function assetsReady() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export async function prepareAssets() {
|
||||
if (await assetsReady()) return;
|
||||
export async function prepareAssets(minecraftVersion = "1.21.11") {
|
||||
const assetsRoot = assetsDirectory(minecraftVersion);
|
||||
const completionMarker = `${assetsRoot}/.minegeo-assets.json`;
|
||||
if (await assetsReady(minecraftVersion)) return assetsRoot;
|
||||
console.log(
|
||||
"Preparing Minecraft 1.21.11 models and textures for terrain rendering…",
|
||||
`Preparing Minecraft ${minecraftVersion} models and textures for terrain rendering…`,
|
||||
);
|
||||
const manifest = await (
|
||||
await fetch(
|
||||
@@ -50,13 +58,14 @@ export async function prepareAssets() {
|
||||
{ signal: AbortSignal.timeout(60000) },
|
||||
)
|
||||
).json();
|
||||
const entry = manifest.versions.find((v) => v.id === "1.21.11");
|
||||
if (!entry) throw new Error("Official Minecraft 1.21.11 metadata missing");
|
||||
const entry = manifest.versions.find((v) => v.id === minecraftVersion);
|
||||
if (!entry)
|
||||
throw new Error(`Official Minecraft ${minecraftVersion} metadata missing`);
|
||||
const version = await (
|
||||
await fetch(entry.url, { signal: AbortSignal.timeout(60000) })
|
||||
).json();
|
||||
await mkdir(".cache/minecraft", { recursive: true });
|
||||
const jarPath = ".cache/minecraft/client-1.21.11.jar";
|
||||
const jarPath = `.cache/minecraft/client-${minecraftVersion}.jar`;
|
||||
let jar;
|
||||
try {
|
||||
jar = await readFile(jarPath);
|
||||
@@ -101,16 +110,17 @@ export async function prepareAssets() {
|
||||
completionMarker,
|
||||
JSON.stringify({
|
||||
format: "minegeo-assets/v2",
|
||||
minecraftVersion: "1.21.11",
|
||||
minecraftVersion,
|
||||
clientSha1: version.downloads.client.sha1,
|
||||
files: Object.keys(entries).length,
|
||||
}),
|
||||
);
|
||||
if (!(await assetsReady()))
|
||||
if (!(await assetsReady(minecraftVersion)))
|
||||
throw new Error(
|
||||
"Minecraft rendering assets are incomplete or corrupt. Run npm run setup to repair them.",
|
||||
);
|
||||
console.log(
|
||||
`Prepared ${Object.keys(entries).length} verified vanilla model/texture assets.`,
|
||||
);
|
||||
return assetsRoot;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, copyFile } from "node:fs/promises";
|
||||
import { mkdir, copyFile, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
const windows = process.platform === "win32";
|
||||
const command = windows ? "gradlew.bat" : "sh";
|
||||
const targets = process.argv.slice(2);
|
||||
if (!targets.some((target) => !target.startsWith("-")))
|
||||
targets.unshift(":plugin:build", ":fabric:build");
|
||||
targets.unshift(
|
||||
":plugin:build",
|
||||
":fabric:build",
|
||||
":fabric261:build",
|
||||
":fabric262:build",
|
||||
);
|
||||
const version = (await readFile("minecraft/build.gradle", "utf8")).match(
|
||||
/version = '([^']+)'/,
|
||||
)?.[1];
|
||||
if (!version) throw new Error("Minecraft tools release version is missing");
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn(command, windows ? targets : ["./gradlew", ...targets], {
|
||||
cwd: "minecraft",
|
||||
@@ -18,15 +28,28 @@ await new Promise((resolve, reject) => {
|
||||
code === 0 ? resolve() : reject(new Error(`Gradle exited ${code}`)),
|
||||
);
|
||||
});
|
||||
for (const name of ["plugin", "fabric"]) {
|
||||
const directory = `public/tools/${name === "fabric" ? "mod" : "plugin"}`;
|
||||
const file = `minegeo-${name}-1.21.11.jar`,
|
||||
path = `minecraft/${name}/build/libs/${file}`;
|
||||
if (existsSync(path)) {
|
||||
for (const [name, kind, minecraftVersion] of [
|
||||
["plugin", "plugin", "1.21.11"],
|
||||
["fabric", "fabric", "1.21.11"],
|
||||
["fabric261", "fabric", "26.1"],
|
||||
["fabric262", "fabric", "26.2"],
|
||||
]) {
|
||||
const directory = `public/tools/${kind === "fabric" ? "mod" : "plugin"}`;
|
||||
const file = `minegeo-${kind}-${minecraftVersion}.jar`;
|
||||
const buildRoot = process.env.MINEGEO_JAVA_BUILD_ROOT
|
||||
? path.join(process.env.MINEGEO_JAVA_BUILD_ROOT, name)
|
||||
: `minecraft/${name}/build`;
|
||||
const artifact = path.join(buildRoot, "libs", file);
|
||||
if (existsSync(artifact)) {
|
||||
await mkdir(directory, { recursive: true });
|
||||
await copyFile(path, `${directory}/${file}`);
|
||||
await copyFile(artifact, `${directory}/${file}`);
|
||||
const releaseFile =
|
||||
kind === "plugin"
|
||||
? `minegeo-plugin-${version}.jar`
|
||||
: `minegeo-fabric-${minecraftVersion}-${version}.jar`;
|
||||
await copyFile(artifact, `${directory}/${releaseFile}`);
|
||||
console.log(
|
||||
`Updated ${directory}/${file}. Include this JAR in your next commit.`,
|
||||
`Updated ${directory}/${releaseFile} and its compatibility download.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,8 @@ if (
|
||||
throw new Error(
|
||||
"This draft contains accepted contributions. Upload a new capture instead of reprocessing mixed media.",
|
||||
);
|
||||
if (dataset.source === "plugin") await prepareAssets();
|
||||
if (dataset.source === "plugin")
|
||||
await prepareAssets(input.manifest.minecraftVersion);
|
||||
|
||||
// This maintenance command runs with the worker stopped. Compare-and-set
|
||||
// guards reject intervening publication, ownership of a job, or another repair.
|
||||
|
||||
205
scripts/test-minecraft-versions.mjs
Normal file
205
scripts/test-minecraft-versions.mjs
Normal file
@@ -0,0 +1,205 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawn } from "node:child_process";
|
||||
import { mkdir, readFile, writeFile, copyFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { createHash } from "node:crypto";
|
||||
import { gunzipSync } from "node:zlib";
|
||||
import path from "node:path";
|
||||
import { stripVTControlCharacters } from "node:util";
|
||||
|
||||
// Disposable servers listen only on loopback. No fixture uploads or tokens are
|
||||
// sent to MineGeo. Keep exports/logs locally as compatibility evidence.
|
||||
const root = path.resolve(".cache/minecraft-compat");
|
||||
const java = process.env.JAVA_HOME
|
||||
? path.join(
|
||||
process.env.JAVA_HOME,
|
||||
"bin",
|
||||
process.platform === "win32" ? "java.exe" : "java",
|
||||
)
|
||||
: "java";
|
||||
async function serverJar(version) {
|
||||
await mkdir(root, { recursive: true });
|
||||
const file = `${root}/paper-${version}.jar`;
|
||||
if (version === "1.21.11" && !existsSync(file))
|
||||
await copyFile(".cache/paper-proof/paper.jar", file);
|
||||
if (existsSync(file)) return file;
|
||||
const response = await fetch(
|
||||
`https://fill.papermc.io/v3/projects/paper/versions/${version}/builds`,
|
||||
{
|
||||
headers: {
|
||||
"User-Agent": "MineGeo/0.1.3 local compatibility verification",
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
const builds = await response.json();
|
||||
const download = (builds.find((b) => b.channel === "STABLE") ?? builds[0])
|
||||
.downloads["server:default"];
|
||||
const bytes = Buffer.from(await (await fetch(download.url)).arrayBuffer());
|
||||
assert.equal(
|
||||
createHash("sha256").update(bytes).digest("hex"),
|
||||
download.checksums.sha256,
|
||||
);
|
||||
await writeFile(file, bytes);
|
||||
return file;
|
||||
}
|
||||
function monitor(child, label) {
|
||||
let output = "";
|
||||
const waiters = new Set();
|
||||
let ended = false;
|
||||
const record = (bytes) => {
|
||||
output += stripVTControlCharacters(bytes.toString());
|
||||
for (const w of waiters)
|
||||
if (w.pattern.test(output.slice(w.start))) {
|
||||
clearTimeout(w.timer);
|
||||
waiters.delete(w);
|
||||
w.resolve();
|
||||
}
|
||||
};
|
||||
child.stdout.on("data", record);
|
||||
child.stderr.on("data", record);
|
||||
const exit = new Promise((resolve) =>
|
||||
child.on("exit", (code) => {
|
||||
ended = true;
|
||||
for (const w of waiters) {
|
||||
clearTimeout(w.timer);
|
||||
w.reject(new Error(`${label} exited ${code}: ${output.slice(-5000)}`));
|
||||
}
|
||||
waiters.clear();
|
||||
resolve(code);
|
||||
}),
|
||||
);
|
||||
return {
|
||||
get output() {
|
||||
return output;
|
||||
},
|
||||
exit,
|
||||
wait(pattern, start = output.length, timeout = 180000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (pattern.test(output.slice(start))) return resolve();
|
||||
if (ended)
|
||||
return reject(
|
||||
new Error(`${label} already exited: ${output.slice(-5000)}`),
|
||||
);
|
||||
const w = {
|
||||
pattern,
|
||||
start,
|
||||
resolve,
|
||||
reject,
|
||||
timer: setTimeout(() => {
|
||||
waiters.delete(w);
|
||||
reject(
|
||||
new Error(
|
||||
`${label} timed out for ${pattern}: ${output.slice(-5000)}`,
|
||||
),
|
||||
);
|
||||
}, timeout),
|
||||
};
|
||||
waiters.add(w);
|
||||
});
|
||||
},
|
||||
async command(command, pattern) {
|
||||
const ready = this.wait(pattern);
|
||||
child.stdin.write(command + "\n");
|
||||
await ready;
|
||||
},
|
||||
};
|
||||
}
|
||||
if (process.argv.includes("--fabric"))
|
||||
throw new Error(
|
||||
"This check runs PaperMC servers only; Minecraft clients are disabled.",
|
||||
);
|
||||
const selected = process.argv
|
||||
.find((arg) => arg.startsWith("--version="))
|
||||
?.split("=")[1];
|
||||
const matrix = ["1.21.11", "26.1.2", "26.2"].filter(
|
||||
(v) => !selected || v === selected,
|
||||
);
|
||||
for (const version of matrix) {
|
||||
const directory = `${root}/plugin-${version}-${Date.now()}`;
|
||||
await mkdir(`${directory}/plugins`, { recursive: true });
|
||||
await copyFile(await serverJar(version), `${directory}/paper.jar`);
|
||||
await copyFile(
|
||||
"public/tools/plugin/minegeo-plugin-0.1.3.jar",
|
||||
`${directory}/plugins/minegeo.jar`,
|
||||
);
|
||||
await writeFile(`${directory}/eula.txt`, "eula=true\n");
|
||||
await writeFile(
|
||||
`${directory}/server.properties`,
|
||||
[
|
||||
"server-ip=127.0.0.1",
|
||||
`server-port=25582`,
|
||||
"online-mode=false",
|
||||
"level-type=minecraft:flat",
|
||||
'generator-settings={"layers":[{"block":"minecraft:bedrock","height":1},{"block":"minecraft:dirt","height":2},{"block":"minecraft:grass_block","height":1}],"biome":"minecraft:plains"}',
|
||||
"generate-structures=false",
|
||||
"view-distance=3",
|
||||
"simulation-distance=2",
|
||||
"max-players=1",
|
||||
"spawn-protection=0",
|
||||
].join("\n") + "\n",
|
||||
);
|
||||
const server = spawn(java, ["-Xmx1536M", "-jar", "paper.jar", "--nogui"], {
|
||||
cwd: directory,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
});
|
||||
const logs = monitor(server, `Paper ${version}`);
|
||||
try {
|
||||
await logs.wait(/Done \(/, 0);
|
||||
console.log(`Paper ${version} started.`);
|
||||
|
||||
await logs.command("version MineGeo", /MineGeo version 0\.1\.3/);
|
||||
await logs.command(
|
||||
"minegeo website",
|
||||
/MineGeo website: https:\/\/minegeo\.vercel\.app/,
|
||||
);
|
||||
await logs.command(
|
||||
"minegeo website http://127.0.0.1:9999",
|
||||
/website is fixed to https:\/\/minegeo\.vercel\.app/,
|
||||
);
|
||||
await logs.command(
|
||||
"minegeo server play.compat.example",
|
||||
/Capture server address saved/,
|
||||
);
|
||||
await logs.command("minegeo area world -16 -16 16 16", /area selected/);
|
||||
await logs.command("forceload add -16 -16 15 15", /Marked|already marked/);
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
await logs.command(
|
||||
"minegeo export Compatibility World",
|
||||
/Export complete:/,
|
||||
);
|
||||
const relative = (
|
||||
await readFile(`${directory}/plugins/MineGeo/latest-export.txt`, "utf8")
|
||||
).trim();
|
||||
const manifest = JSON.parse(
|
||||
await readFile(
|
||||
`${directory}/plugins/MineGeo/${relative}/manifest.json`,
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
assert.equal(manifest.minecraftVersion, version);
|
||||
assert.equal(manifest.viewpoints.length, 24);
|
||||
const terrain = JSON.parse(
|
||||
gunzipSync(
|
||||
await readFile(
|
||||
`${directory}/plugins/MineGeo/${relative}/terrain.json.gz`,
|
||||
),
|
||||
).toString(),
|
||||
);
|
||||
assert.ok(terrain.blocks.length > 0);
|
||||
await logs.command(
|
||||
"minegeo upload",
|
||||
/Create a token in the website dashboard/,
|
||||
);
|
||||
console.log(
|
||||
`PLUGIN ${version} PASSED: fixed endpoint, commands, real export, correct version, ${terrain.blocks.length} blocks.`,
|
||||
);
|
||||
} finally {
|
||||
server.stdin.write("stop\n");
|
||||
const timeout = setTimeout(() => server.kill(), 20000);
|
||||
await logs.exit;
|
||||
clearTimeout(timeout);
|
||||
await writeFile(`${directory}/server.log`, logs.output);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { createServer } from "node:http";
|
||||
import { connect } from "node:net";
|
||||
import { spawn } from "node:child_process";
|
||||
import { spawn, execFile } from "node:child_process";
|
||||
import { mkdir, copyFile, writeFile, readFile } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { gunzipSync } from "node:zlib";
|
||||
import assert from "node:assert/strict";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { datasetSchema } from "../shared/protocol";
|
||||
|
||||
// Disposable, loopback-only Paper test. No game client is launched.
|
||||
@@ -102,8 +104,50 @@ const api = createServer(async (req, res) => {
|
||||
await new Promise<void>((r) => api.listen(0, "127.0.0.1", r));
|
||||
await writeFile(
|
||||
`${directory}/plugins/MineGeo/config.yml`,
|
||||
`website: 'http://127.0.0.1:${(api.address() as any).port}'\ntoken: 'fixture-token'\nserver-name: 'Regression world'\nserver-address: 'play.regression.example.com'\nchunks-per-tick: 2\nviewpoints: 24\nmax-area-size: 2048\n`,
|
||||
`website: 'http://127.0.0.1:${(api.address() as any).port}'\ntoken: ''\nserver-name: 'Regression world'\nserver-address: 'play.regression.example.com'\nchunks-per-tick: 2\nviewpoints: 24\nmax-area-size: 2048\n`,
|
||||
);
|
||||
// Exercise the shared upload transport against the loopback mock separately.
|
||||
// The deployed plugin always uses its hardcoded production endpoint.
|
||||
const execute = promisify(execFile);
|
||||
let exportDir: string;
|
||||
const pluginJar = path.resolve("public/tools/plugin/minegeo-plugin-0.1.3.jar");
|
||||
await execute("javac", [
|
||||
"-cp",
|
||||
pluginJar,
|
||||
"-d",
|
||||
directory,
|
||||
"tests/java/UploadSmoke.java",
|
||||
]);
|
||||
await writeFile(
|
||||
directory + "/fixture-auth.json",
|
||||
JSON.stringify({ token: "fixture-token" }),
|
||||
);
|
||||
const upload = async (
|
||||
target: string | null,
|
||||
expected: RegExp,
|
||||
fails = false,
|
||||
) => {
|
||||
const args = [
|
||||
"-cp",
|
||||
directory + path.delimiter + pluginJar,
|
||||
"UploadSmoke",
|
||||
"http://127.0.0.1:" + (api.address() as any).port,
|
||||
exportDir,
|
||||
directory + "/fixture-auth.json",
|
||||
];
|
||||
if (target) args.push(target);
|
||||
let output = "",
|
||||
code = 0;
|
||||
try {
|
||||
const result = await execute("java", args, { windowsHide: true });
|
||||
output = result.stdout + result.stderr;
|
||||
} catch (error: any) {
|
||||
output = error.stdout + error.stderr;
|
||||
code = error.code;
|
||||
}
|
||||
assert.equal(code === 0, !fails, output);
|
||||
assert.match(output, expected);
|
||||
};
|
||||
const child = spawn("java", ["-Xmx1536M", "-jar", "paper.jar", "--nogui"], {
|
||||
cwd: directory,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
@@ -165,46 +209,53 @@ const command = async (text: string, pattern: RegExp) => {
|
||||
child.stdin.write(text + "\n");
|
||||
await pending;
|
||||
};
|
||||
const rconCommand = (text: string, expected: RegExp) => new Promise<void>((resolve, reject) => {
|
||||
const socket = connect(25581, "127.0.0.1");
|
||||
let buffer = Buffer.alloc(0);
|
||||
let authenticated = false;
|
||||
const timeout = setTimeout(() => finish(new Error(`RCON timed out waiting for ${expected}`)), 10000);
|
||||
const finish = (error?: Error) => {
|
||||
clearTimeout(timeout);
|
||||
socket.destroy();
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
};
|
||||
const packet = (id: number, type: number, body: string) => {
|
||||
const payload = Buffer.from(body);
|
||||
const frame = Buffer.alloc(payload.length + 14);
|
||||
frame.writeInt32LE(payload.length + 10, 0);
|
||||
frame.writeInt32LE(id, 4);
|
||||
frame.writeInt32LE(type, 8);
|
||||
payload.copy(frame, 12);
|
||||
return frame;
|
||||
};
|
||||
socket.on("connect", () => socket.write(packet(1, 3, "minegeo-fixture")));
|
||||
socket.on("error", finish);
|
||||
socket.on("data", (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
while (buffer.length >= 4 && buffer.length >= buffer.readInt32LE(0) + 4) {
|
||||
const length = buffer.readInt32LE(0);
|
||||
const frame = buffer.subarray(0, length + 4);
|
||||
buffer = buffer.subarray(length + 4);
|
||||
const id = frame.readInt32LE(4);
|
||||
const type = frame.readInt32LE(8);
|
||||
if (id === -1) return finish(new Error("RCON authentication failed"));
|
||||
if (id === 1 && type === 2 && !authenticated) {
|
||||
authenticated = true;
|
||||
socket.write(packet(2, 2, text));
|
||||
} else if (id === 2 && expected.test(frame.toString("utf8", 12, length + 2))) {
|
||||
return finish();
|
||||
const rconCommand = (text: string, expected: RegExp) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const socket = connect(25581, "127.0.0.1");
|
||||
let buffer = Buffer.alloc(0);
|
||||
let authenticated = false;
|
||||
const timeout = setTimeout(
|
||||
() => finish(new Error(`RCON timed out waiting for ${expected}`)),
|
||||
10000,
|
||||
);
|
||||
const finish = (error?: Error) => {
|
||||
clearTimeout(timeout);
|
||||
socket.destroy();
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
};
|
||||
const packet = (id: number, type: number, body: string) => {
|
||||
const payload = Buffer.from(body);
|
||||
const frame = Buffer.alloc(payload.length + 14);
|
||||
frame.writeInt32LE(payload.length + 10, 0);
|
||||
frame.writeInt32LE(id, 4);
|
||||
frame.writeInt32LE(type, 8);
|
||||
payload.copy(frame, 12);
|
||||
return frame;
|
||||
};
|
||||
socket.on("connect", () => socket.write(packet(1, 3, "minegeo-fixture")));
|
||||
socket.on("error", finish);
|
||||
socket.on("data", (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
while (buffer.length >= 4 && buffer.length >= buffer.readInt32LE(0) + 4) {
|
||||
const length = buffer.readInt32LE(0);
|
||||
const frame = buffer.subarray(0, length + 4);
|
||||
buffer = buffer.subarray(length + 4);
|
||||
const id = frame.readInt32LE(4);
|
||||
const type = frame.readInt32LE(8);
|
||||
if (id === -1) return finish(new Error("RCON authentication failed"));
|
||||
if (id === 1 && type === 2 && !authenticated) {
|
||||
authenticated = true;
|
||||
socket.write(packet(2, 2, text));
|
||||
} else if (
|
||||
id === 2 &&
|
||||
expected.test(frame.toString("utf8", 12, length + 2))
|
||||
) {
|
||||
return finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
try {
|
||||
await wait(/Done \(/, 0, 180000);
|
||||
await command("minegeo help", /MineGeo commands:/);
|
||||
@@ -227,7 +278,7 @@ try {
|
||||
let relative = (
|
||||
await readFile(`${directory}/plugins/MineGeo/latest-export.txt`, "utf8")
|
||||
).trim();
|
||||
const exportDir = `${directory}/plugins/MineGeo/${relative}`;
|
||||
exportDir = `${directory}/plugins/MineGeo/${relative}`;
|
||||
const local = datasetSchema.parse(
|
||||
JSON.parse(await readFile(`${exportDir}/manifest.json`, "utf8")),
|
||||
);
|
||||
@@ -243,44 +294,46 @@ try {
|
||||
(p) => p.x >= -32 && p.x < 32 && p.z >= -32 && p.z < 32,
|
||||
),
|
||||
);
|
||||
await command("minegeo upload", /Upload failed: Upload paused/);
|
||||
await command("minegeo upload", /Upload complete\. Open/);
|
||||
await upload(null, /Upload paused/, true);
|
||||
await upload(null, /queueing panorama processing|Already uploaded/);
|
||||
assert.equal(creates, 1);
|
||||
assert.equal(finalizes, 1);
|
||||
assert.equal(heads, 1);
|
||||
await command("minegeo upload", /Upload complete\. Open/);
|
||||
await upload(null, /queueing panorama processing|Already uploaded/);
|
||||
assert.equal(finalizes, 1);
|
||||
received = Buffer.alloc(0);
|
||||
expectedServer = "wrong.example.com";
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/Wrong capture server/,
|
||||
true,
|
||||
);
|
||||
assert.equal(creates, 1, "Mismatch must stop before creating a draft");
|
||||
expectedServer = "PLAY.REGRESSION.EXAMPLE.COM.:25565";
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
/Upload complete\. Open/,
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/queueing panorama processing|Already uploaded/,
|
||||
);
|
||||
assert.equal(manifest.contributionTo, "00000000-0000-4000-8000-000000000024");
|
||||
assert.equal(manifest.viewpoints.length, 12);
|
||||
assert.equal(creates, 2);
|
||||
expectedServer = "wrong.example.com";
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/Wrong capture server/,
|
||||
true,
|
||||
);
|
||||
assert.equal(creates, 2, "A cached selection must recheck the server");
|
||||
expectedServer = null;
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
/Upload complete\. Open/,
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/queueing panorama processing|Already uploaded/,
|
||||
);
|
||||
assert.equal(creates, 2, "Cleared server permits an idempotent retry");
|
||||
assert.equal(finalizes, 2);
|
||||
await command(
|
||||
"minegeo contribute 00000000-0000-4000-8000-000000000024",
|
||||
/Upload complete\. Open/,
|
||||
await upload(
|
||||
"00000000-0000-4000-8000-000000000024",
|
||||
/queueing panorama processing|Already uploaded/,
|
||||
);
|
||||
assert.equal(creates, 2);
|
||||
await command("minegeo export Random Positions", /Export complete:/);
|
||||
@@ -321,7 +374,7 @@ try {
|
||||
"repeated upload is idempotent",
|
||||
"cancellation preserves completed export",
|
||||
"24 random positions change on the next export",
|
||||
"contribution command caps at 12 and retries idempotently",
|
||||
"shared upload client caps contributions at 12 and retries idempotently",
|
||||
"wrong server blocked before upload, including cached retry",
|
||||
"owner override/default port and cleared address supported",
|
||||
],
|
||||
|
||||
@@ -7,7 +7,8 @@ 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";
|
||||
let minecraftVersion = process.argv[3] || "1.21.11";
|
||||
const output = `.cache/renderer-regression-${minecraftVersion}`;
|
||||
const snapshot = {
|
||||
bounds: { minX: -24, minZ: -24, maxX: 24, maxZ: 24 },
|
||||
coverage: [],
|
||||
@@ -69,11 +70,12 @@ let points = [
|
||||
[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]) {
|
||||
if (process.argv[2] && process.argv[2] !== "--fixture") {
|
||||
const directory = process.argv[2];
|
||||
const manifest = JSON.parse(
|
||||
await readFile(`${directory}/manifest.json`, "utf8"),
|
||||
);
|
||||
minecraftVersion = manifest.minecraftVersion;
|
||||
terrain = JSON.parse(
|
||||
gunzipSync(await readFile(`${directory}/terrain.json.gz`)).toString(),
|
||||
);
|
||||
@@ -81,7 +83,10 @@ if (process.argv[2]) {
|
||||
source = "cached Paper export (no server started)";
|
||||
}
|
||||
await mkdir(output, { recursive: true });
|
||||
const report = await renderSnapshot(terrain, points, output, { size: 256 });
|
||||
const report = await renderSnapshot(terrain, points, output, {
|
||||
size: 256,
|
||||
minecraftVersion,
|
||||
});
|
||||
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");
|
||||
@@ -106,12 +111,16 @@ assert.ok(
|
||||
"blocks without explicit default properties must appear on the aerial map",
|
||||
);
|
||||
const mapPixel = (x, z) => {
|
||||
const pixelX = Math.floor(((x - snapshot.bounds.minX + 0.5) / 48) * mapInfo.width);
|
||||
const pixelY = Math.floor(((z - snapshot.bounds.minZ + 0.5) / 48) * mapInfo.height);
|
||||
const pixelX = Math.floor(
|
||||
((x - snapshot.bounds.minX + 0.5) / 48) * mapInfo.width,
|
||||
);
|
||||
const pixelY = Math.floor(
|
||||
((z - snapshot.bounds.minZ + 0.5) / 48) * mapInfo.height,
|
||||
);
|
||||
const offset = (pixelY * mapInfo.width + pixelX) * mapInfo.channels;
|
||||
return mapPixels.subarray(offset, offset + 3);
|
||||
};
|
||||
if (!process.argv[2]) {
|
||||
if (!process.argv[2] || process.argv[2] === "--fixture") {
|
||||
const shadedGrass = mapPixel(10, 9);
|
||||
const sunlitGrass = mapPixel(10, -9);
|
||||
assert.ok(
|
||||
|
||||
Reference in New Issue
Block a user