import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { harness, Client, delay, options } from "./server-harness.mjs"; import { raycast, direction, key } from "../client/math.js"; const opts = options(), clients = [], checks = []; let h = await harness(opts); const createClient = () => { const c = new Client(h.url); clients.push(c); return c; }; try { const manifest = await h.get("/api/manifest"); assert.equal(manifest.protocol, 1); for (const pack of manifest.packages) for (const file of pack.files) { assert.notEqual(file.scope, "server"); const r = await fetch(h.url + file.url); assert.equal(r.status, 200); const bytes = Buffer.from(await r.arrayBuffer()); assert.equal(bytes.length, file.size); assert.equal( createHash("sha256").update(bytes).digest("hex"), file.sha256, ); } const denied = await fetch(h.url + "/api/control", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ method: "world.list" }), }); assert.equal(denied.status, 401); const secret = await fetch( h.url + "/packages/shacraft.trampoline/1.0.0/server/trampoline.wasm", ); assert.equal(secret.status, 404); checks.push( "Manifest resource SHA-256/size, admin token required, server WASM not public", ); const bad = createClient(); await bad.open; bad.send({ type: "join", protocol: 1, manifest_hash: "incorrect", world: "lobby", name: "BadManifest", }); assert.match((await bad.next("error")).message, /manifest|package/i); await bad.close(); checks.push("Incompatible manifest rejected"); const catalog = await h.control("catalog.search", { query: "minecraft:stone", limit: 64, }), stone = catalog.items.find((m) => m.state === "minecraft:stone"); assert.ok(stone); await h.control("world.create", { world: "test_e2e_base" }); const plan = await h.control("build.plan", { world: "test_e2e_base", expected_revision: 0, operations: [ { type: "box", min: [-8, -1, -8], max: [8, 0, 12], block: stone.id }, ], }); await h.control("build.commit", { plan_id: plan.plan_id, operation_id: "test-floor", }); for (const world of ["test_e2e_a", "test_e2e_b"]) await h.control("world.create", { world, template: "test_e2e_base" }); let a = createClient(); const b = createClient(); const wa = await a.join(manifest, "test_e2e_a", "TestAlice"); await b.join(manifest, "test_e2e_a", "TestBob"); assert.equal(wa.revision, 0); assert.ok(wa.blocks.length > 500); await a.state((p, m) => m.players.length === 2); checks.push("Two clients receive shared snapshot and presence"); const initial = (await b.state((p) => p.position[1] < 1.15)).players.find( (p) => p.id === b.id, ).position; b.input({ forward: 1, position: [1000, 1000, 1000] }); const moved = ( await b.state((p) => p.position[2] < initial[2] - 0.5) ).players.find((p) => p.id === b.id).position; b.input(); assert.ok(moved.every((v) => Math.abs(v) < 20)); assert.ok(moved[1] < 2); checks.push( "Authoritative movement and collision; client position spoof ignored", ); a.send({ type: "break", pos: [1000, 0, 1000] }); assert.match((await a.next("error")).message, /reach|sight|ray|target/i); checks.push("Forged out-of-reach block break rejected"); const p = (await a.state((p) => p.position[1] < 1.15)).players.find( (p) => p.id === a.id, ).position; const materials = new Map(wa.materials.map((m) => [m.id, m])); const blocks = new Map(wa.blocks.map((b) => [key(b.pos), b.block])); const hit = raycast( [p[0], p[1] + 1.62, p[2]], direction(0.2, -1.3), blocks, materials, ); assert.ok(hit); const seq = a.input({ yaw: 0.2, pitch: -1.3 }); await a.state((_p, m) => m.ack >= seq); await delay(150); a.send({ type: "break", pos: hit.pos }); const removed = await a.next("blocks", (m) => m.changes.some((c) => key(c.pos) === key(hit.pos) && c.block === 0), ); await b.next("blocks", (m) => m.revision === removed.revision); assert.equal(removed.revision, 1); await delay(160); a.send({ type: "place", pos: hit.pos, block: stone.id }); const restored = await a.next("blocks", (m) => m.revision === 2); await b.next("blocks", (m) => m.revision === 2); assert.equal(restored.changes[0].block, stone.id); checks.push("Reachable break/place, server revision ordering and broadcast"); const late = createClient(); const lateSnapshot = await late.join(manifest, "test_e2e_a", "TestLate"); assert.equal(lateSnapshot.revision, 2); assert.equal( lateSnapshot.blocks.find((c) => key(c.pos) === key(hit.pos)).block, stone.id, ); await late.close(); checks.push("Late join receives current geometry and revision"); await h.control("world.edit", { world: "test_e2e_a", expected_revision: 2, operation_id: "test-isolation", changes: [{ pos: [3, 1, 3], block: stone.id }], }); await a.next("blocks", (m) => m.revision === 3); for (const world of ["test_e2e_base", "test_e2e_b"]) { const r = await h.control("world.read", { world, min: [3, 1, 3], max: [3, 1, 3], }); assert.equal(r.blocks.length, 0); } checks.push("Pinned template and independent clone isolation"); a.send({ type: "chat", text: "local integration check" }); const chat = await b.next( "chat", (m) => m.text === "local integration check", ); assert.equal(chat.name, "TestAlice"); checks.push("Scoped game chat delivered to peer"); const entity = await h.control("entity.spawn", { world: "test_e2e_a", kind: "minecraft:pig", position: [3, 2, 3], properties: { name: "TestPig" }, }); await a.next("entities", (m) => m.entities.some((e) => e.id === entity.entity.id), ); await b.next("entities", (m) => m.entities.some((e) => e.id === entity.entity.id), ); await h.control("entity.delete", { id: entity.entity.id }); checks.push("Persisted entity events reach both clients"); await a.close(); a = createClient(); const reconnect = await a.join(manifest, "test_e2e_a", "TestAlice"); assert.equal(reconnect.revision, 3); assert.ok(reconnect.blocks.some((c) => key(c.pos) === "3,1,3")); checks.push("Reconnect restores authoritative world snapshot"); await h.control("world.create", { world: "test_e2e_arena", template: "spleef_template", }); await delay(1150); for (const c of [a, b]) { c.send({ type: "switch_world", world: "test_e2e_arena" }); await c.next("snapshot", (m) => m.world === "test_e2e_arena"); } a.send({ type: "start_match" }); await a.state((_p, m) => m.match?.phase === "countdown"); const active = await a.state((_p, m) => m.match?.phase === "active", 7000); const victim = active.players.find((p) => p.id === a.id), at = victim.position.map(Math.floor); const floor = []; for (let x = at[0] - 1; x <= at[0] + 1; x++) for (let z = at[2] - 1; z <= at[2] + 1; z++) floor.push({ pos: [x, 0, z], block: 0 }); const arena = (await h.control("world.list")).find( (w) => w.name === "test_e2e_arena", ); await h.control("world.edit", { world: "test_e2e_arena", expected_revision: arena.revision, operation_id: "test-eliminate", changes: floor, }); const finished = await b.state( (_p, m) => m.match?.phase === "finished", 7000, ); assert.equal(finished.match.winner, "TestBob"); await b.state( (_p, m) => m.tick > finished.tick && m.match?.phase === "waiting", 8000, ); const reset = await h.control("world.read", { world: "test_e2e_arena", min: [at[0], 0, at[2]], max: [at[0], 0, at[2]], }); assert.equal(reset.blocks.length, 1); checks.push( "Full two-player Spleef countdown, elimination, winner and automatic floor reset", ); for (const c of clients) await c.close(); let metrics; for (let n = 0; n < 30; n++) { metrics = await h.get("/api/metrics"); if (metrics.players === 0) break; await delay(100); } assert.equal(metrics.players, 0); assert.equal(metrics.active_worlds, 0); checks.push("Disconnect removes players and active worlds"); const durableEdit = await h.control("world.edit", { world: "test_e2e_a", expected_revision: 3, operation_id: "test-durable-edit", changes: [{ pos: [4, 2, 4], block: stone.id }], }); const durableEntity = await h.control("entity.spawn", { world: "test_e2e_a", kind: "minecraft:pig", position: [5, 2, 5], properties: { name: "DurablePig", marker: "before-sigkill" }, }); await h.control("arena.configure", { world: "test_e2e_a", mode: "creative", spawn: [1, 3, 1], countdown_seconds: 2, round_seconds: 120, elimination_y: -5, }); const durableData = h.data; await h.kill("SIGKILL"); h = await harness({ ...opts, data: durableData }); const afterCrash = await h.control("world.read", { world: "test_e2e_a", min: [4, 2, 4], max: [4, 2, 4], }); assert.equal(afterCrash.revision, durableEdit.revision); assert.equal(afterCrash.blocks[0]?.block, stone.id); const persisted = await h.control("entity.list", { world: "test_e2e_a" }); const pig = persisted.items.find( (entity) => entity.id === durableEntity.entity.id, ); assert.equal(pig?.properties.marker, "before-sigkill"); // No fields supplied: the server returns and resaves the loaded configuration. const config = await h.control("arena.configure", { world: "test_e2e_a" }); assert.equal(config.countdown_seconds, 2); assert.equal(config.round_seconds, 120); assert.equal(config.elimination_y, -5); assert.deepEqual(config.spawn, [1, 3, 1]); const afterRestart = createClient(); const restartedWelcome = await afterRestart.join( await h.get("/api/manifest"), "test_e2e_a", "RestartProbe", ); assert.deepEqual(restartedWelcome.spawn, [1, 3, 1]); assert.ok( restartedWelcome.blocks.some( (cell) => key(cell.pos) === "4,2,4" && cell.block === stone.id, ), ); await afterRestart.close(); checks.push( "Real SIGKILL/restart preserves committed blocks, entity properties and arena configuration", ); metrics = await h.get("/api/metrics"); await h.report({ passed: true, checks, metrics, binary: h.binary }); } catch (error) { await h.report({ passed: false, checks, error: error.stack, server_logs: h.logs(), client_errors: clients.map((c) => c.queue.filter((m) => m.type === "error"), ), }); throw error; } finally { await Promise.allSettled(clients.map((c) => c.close())); await h.stop(); }