import test from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { loadPhysics, LocalPrediction, samplePhysicsWorld, } from "../player-physics.js"; const binary = await readFile(new URL("../physics.wasm", import.meta.url)); const fixture = JSON.parse( await readFile( new URL( "../../crates/shacraft-physics/tests/fixtures/java26.2.json", import.meta.url, ), "utf8", ), ); const originalFetch = globalThis.fetch; let step; try { // Exercise the actual browser ABI and its non-streaming MIME fallback. globalThis.fetch = async () => new Response(binary, { headers: { "Content-Type": "application/octet-stream" }, }); step = await loadPhysics("http://local-test/physics.wasm"); } finally { globalThis.fetch = originalFetch; } const bounds = { min: [-64, -32, -64], max: [63, 63, 63] }; function floor(surface = "stone") { const blocks = new Map(); for (let x = -4; x <= 4; x++) for (let z = -55; z <= 5; z++) blocks.set(`${x},-1,${z}`, 1); const materials = new Map([ [ 1, { state: `minecraft:${surface}`, collision: [{ min: [0, 0, 0], max: [1, 1, 1] }], }, ], ]); return (body) => samplePhysicsWorld(body, blocks, materials, bounds); } const settings = { allow_flight: true }; test("the shipped browser WebAssembly follows independently measured Java 26.2 trajectories", () => { let checked = 0; for (const [name, trajectory] of Object.entries( fixture.travel_kernel_trajectories, )) { if (trajectory.medium !== "air") continue; const getWorld = floor(trajectory.surface); let body = { position: trajectory.initial_position, velocity: trajectory.initial_velocity, flying: trajectory.flying, on_ground: !trajectory.flying, }; for (const sample of trajectory.samples) { const input = { forward: sample.tick <= trajectory.input_ticks ? 1 : 0, sprint: trajectory.sprint, jump: sample.tick === 1 && trajectory.jump_first_tick, }; body = step({ body, input, world: getWorld(body), settings }); for (const field of ["position", "velocity"]) for (let axis = 0; axis < 3; axis++) { const expected = sample[field][axis] * (axis === 2 ? -1 : 1); assert.ok( Math.abs(body[field][axis] - expected) < 2e-6, `${name} tick ${sample.tick} ${field}[${axis}]: ${body[field][axis]} != ${expected}`, ); } assert.equal( body.on_ground, sample.on_ground, `${name} tick ${sample.tick} grounded`, ); checked++; } } assert.ok(checked >= 200); }); test("real WebAssembly prediction replays delayed authority without introducing trajectory drift", () => { const getWorld = floor(); const prediction = new LocalPrediction(step, getWorld); let authority = { position: [0, 0, 0], velocity: [0, -0.0784000015258789, 0], on_ground: true, }; prediction.receive({ body: authority, ack: 0, tick: 0, epoch: 1, settings }); const packets = []; for (let tick = 1; tick <= 80; tick++) { const input = { forward: tick < 55 ? 1 : 0, sprint: tick > 20 && tick < 50, jump: tick === 25, sneak: tick >= 55, }; prediction.push(tick, input); authority = step({ body: authority, input, world: getWorld(authority), settings, }); packets.push({ body: authority, ack: tick, tick, epoch: 1, settings }); // Variable 150–250 ms inbound latency; authority still acknowledges order. if (packets.length > (tick % 3) + 3) prediction.receive(packets.shift()); for (let axis = 0; axis < 3; axis++) { assert.ok( Math.abs(prediction.body.position[axis] - authority.position[axis]) < 1e-11, `position drift at tick ${tick}`, ); assert.ok( Math.abs(prediction.body.velocity[axis] - authority.velocity[axis]) < 1e-11, `velocity drift at tick ${tick}`, ); } } for (const packet of packets) prediction.receive(packet); assert.equal(prediction.pending.length, 0); assert.deepEqual(prediction.body, authority); });