import assert from 'node:assert/strict'; import {randomUUID} from 'node:crypto'; import {EventEmitter} from 'node:events'; import {createRequire, isBuiltin} from 'node:module'; import {fileURLToPath} from 'node:url'; import {setImmediate as tick, setTimeout as delay} from 'node:timers/promises'; import test from 'node:test'; import {build} from 'esbuild'; const root = fileURLToPath(new URL('../', import.meta.url)); const compiled = await build({ stdin: {contents: `export * from './bot/forward/stream.ts'; export * from './bot/forward/manager.ts'; export * from './utils/forward_protocol.ts';`, resolveDir: root, loader: 'ts'}, bundle: true, platform: 'node', format: 'cjs', write: false, metafile: true, plugins: [{name: 'isolated-node', setup(build) { build.onResolve({filter: /(?:instance\/index\.ts|^socket\.io-client$|console_print\.ts|path_utils\.ts)$/}, args => ({path: args.path.includes('instance/') ? 'registry' : args.path === 'socket.io-client' ? 'socket' : args.path.endsWith('path_utils.ts') ? 'path' : 'console', namespace: 'stub'})); build.onLoad({filter: /.*/, namespace: 'stub'}, ({path}) => ({loader: 'js', contents: { registry: 'export const get_bot_context = () => undefined; export const observe_bot_context = () => () => {};', socket: 'export const io = () => {throw Error("Unexpected real socket")};', console: 'export const console_print = {success: x => x, log() {}};', path: 'export const get_project_root_path = () => {throw Error("Unexpected filesystem access")};', }[path]})); }}], }); assert.ok(Object.keys(compiled.metafile.inputs).every(path => !path.includes('forward_server'))); const require = createRequire(import.meta.url); const module = {exports: {}}; new Function('require', 'module', 'exports', compiled.outputFiles[0].text)(specifier => { assert.ok(isBuiltin(specifier), `Unexpected external import: ${specifier}`); return require(specifier); }, module, module.exports); const {BotStream, NodeForwardManager, validate_rpc} = module.exports; const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const code = expected => error => error.code === expected; const request = (method, payload, forward_session_id = randomUUID(), deadline_at = Date.now() + 2000) => ({ request_id: randomUUID(), correlation_id: randomUUID(), forward_session_id, deadline_at, method, payload, }); function fixture(t, {minY = -4, height = 16, direct = false} = {}) { let now = 100; let reads = 0; const columns = new Map(); const bot = Object.assign(new EventEmitter(), { chat() {}, version: '1.21.4', game: {minY, height, dimension: 'overworld'}, registry: {blocksByStateId: {7: {name: 'air'}, 8: {name: 'cave_air'}, 9: {name: 'void_air'}, 101: {name: 'grass_block'}, 202: {name: 'water'}, 303: {name: 'stone'}, 404: {name: 'oak_leaves'}, 505: {name: 'modded_marble'}}}, entity: {position: {x: -0.25, y: 3.5, z: -16.25}, yaw: Math.PI / 3}, }); const sync = {getColumn(x, z) { return columns.get(`${x},${z}`); }}; bot.world = direct ? sync : {sync}; const context = Object.assign(new EventEmitter(), {bot_status: 'online', bot_instance: bot}); let current = context; const observers = new Set(); const source = {get: () => current, observe(_id, callback) { observers.add(callback); return () => observers.delete(callback); }}; const stream = new BotStream(1, {source, now: () => now}); t.after(() => stream.dispose()); const column = (surface = () => 7) => ({getBlockStateId(position) { assert.equal(typeof position, 'object'); assert.ok(position.x >= 0 && position.x < 16 && position.z >= 0 && position.z < 16); assert.ok(position.y >= minY && position.y < minY + height); reads++; return surface(position.x, position.y, position.z); }}); return {bot, context, stream, columns, column, observers, get reads() { return reads; }, snapshot(input = {}, check = () => {}) { return stream.world_snapshot({radius: 1, known: [], ...input}, now + 2000, check); }, time(value) { now = value; }, set(value) { current = value; for (const observer of observers) observer(value); }, }; } function decoded(chunk, layer = chunk) { for (const name of ['heights', 'blocks', ...('levels' in layer ? ['levels'] : [])]) { assert.equal(layer[name].length, 684); assert.equal(Buffer.from(layer[name], 'base64').length, 512); assert.equal(Buffer.from(layer[name], 'base64').toString('base64'), layer[name]); } const heights = Buffer.from(layer.heights, 'base64'), blocks = Buffer.from(layer.blocks, 'base64'); const levels = layer.levels && Buffer.from(layer.levels, 'base64'); const result = []; for (let index = 0; index < 256; index++) { const palette_index = blocks.readUInt16LE(index * 2); assert.ok(palette_index < chunk.palette.length); const cell = {height: heights.readUInt16LE(index * 2), ...chunk.palette[palette_index]}; if (levels) { cell.level = levels.readUInt16LE(index * 2); assert.ok(cell.level >= 0 && cell.level <= 15); if (cell.height) assert.ok(['water', 'lava'].includes(cell.name)); else assert.equal(cell.level, 0); } result.push(cell); } assert.ok(chunk.palette.length >= 1 && chunk.palette.length <= 512); for (const entry of chunk.palette) { assert.deepEqual(Object.keys(entry).sort(), ['color', 'name']); assert.ok(entry.name.length <= 128); assert.ok(Number.isInteger(entry.color) && entry.color >= 0 && entry.color <= 0xffffff); } return result; } test('surface feature sampling retains torch, sign, hanging and waterlogged states above their foundation', async t => { const h = fixture(t), mf = createRequire(require.resolve('@wp2508/mineflayer')); const data = mf('minecraft-data')('1.21.4'); const id = (name, properties) => { const b = data.blocksByName[name]; let offset = 0; for (const p of b.states) offset = offset * p.num_values + (p.type === 'bool' ? properties[p.name] === true ? 0 : 1 : p.values.indexOf(String(properties[p.name]))); const value = b.minStateId + offset; h.bot.registry.blocksByStateId[value] = b; return value; }; const states = [id('wall_torch', {facing: 'east'}), id('oak_sign', {rotation: 7, waterlogged: false}), id('oak_hanging_sign', {rotation: 3, attached: true, waterlogged: true}), id('oak_wall_hanging_sign', {facing: 'north', waterlogged: false}), id('oak_slab', {type: 'bottom', waterlogged: true}), id('oak_stairs', {facing: 'south', half: 'top', shape: 'inner_left', waterlogged: true})]; h.columns.set('-1,-2', h.column((x,y,z) => z === 0 && x < states.length ? y === 3 ? states[x] : y === 2 ? 303 : 7 : y === 2 ? 303 : 7)); const result = await h.snapshot(), chunk = result.chunks[0]; assert.equal(chunk.features.entries.length, states.length); const entries = chunk.features.entries; assert.deepEqual(entries[0].properties, {facing: 'east'}); assert.equal(entries[1].properties.rotation, 7); assert.equal(entries[1].properties.waterlogged, false); assert.equal(entries[2].properties.attached, true); assert.equal(entries[2].properties.waterlogged, true); assert.equal(entries[4].properties.type, 'bottom'); assert.equal(entries[5].properties.shape, 'inner_left'); assert.ok(entries.every(e => e.height === 8)); assert.ok(decoded(chunk, chunk.features).slice(0, states.length).every(c => c.height === 7 && c.name === 'stone')); }); test('feature sampling retains short runs and falls back without holes when a column exceeds its bound', async t => { const h = fixture(t, {minY: 0, height: 24}); h.bot.registry.blocksByStateId[700] = {name: 'oak_sign', minStateId: 700, states: [{name: 'rotation', num_values: 16, values: Array.from({length: 16}, (_, i) => String(i))}]}; h.columns.set('-1,-2', h.column((x,y,z) => { if (z !== 0 || x > 1) return y === 1 ? 303 : 7; if (x === 0) return y === 3 || y === 2 ? 700 : y === 1 ? 303 : 7; return y >= 2 && y <= 18 ? 700 : y === 1 ? 303 : 7; })); const chunk = (await h.snapshot()).chunks[0]; assert.equal(chunk.features.entries.length, 2); assert.ok(chunk.features.entries.every(e => e.index === 0)); const bases = decoded(chunk, chunk.features); assert.equal(bases[0].height, 2); assert.equal(bases[1].height, 19); assert.equal(bases[1].name, 'oak_sign'); }); test('world protocol validates exact bounded requests, revisions and descriptors', () => { const valid = {radius: 3, known: [{x: -1, z: -2, revision: randomUUID()}], world_generation: randomUUID(), watch_id: randomUUID()}; assert.equal(validate_rpc(request('world.snapshot', valid)).payload, valid); assert.equal(validate_rpc(request('world.unwatch', {watch_id: valid.watch_id})).method, 'world.unwatch'); for (const payload of [null, {}, [], {radius: 1}, {known: []}, {...valid, extra: true}, ...[0, 4, -1, 1.5, NaN, Infinity, '1', null].map(radius => ({...valid, radius})), {...valid, known: Array.from({length: 50}, (_, x) => ({x, z: 0, revision: randomUUID()}))}, {...valid, known: Array(1)}, {...valid, known: [{x: 0, z: 0}]}, {...valid, known: [{x: 1.2, z: 0, revision: randomUUID()}]}, {...valid, known: [{x: 0, z: Infinity, revision: randomUUID()}]}, {...valid, known: [{x: 0, z: 0, revision: 'bad'}]}, {...valid, known: [{x: 0, z: 0, revision: randomUUID(), extra: true}]}, {...valid, known: [valid.known[0], valid.known[0]]}, {...valid, world_generation: undefined}, {...valid, watch_id: 'bad'}]) assert.throws(() => validate_rpc(request('world.snapshot', payload)), code('INVALID_REQUEST')); for (const payload of [{}, {watch_id: 'bad'}, {watch_id: valid.watch_id, extra: 1}, {watch_id: null}]) assert.throws(() => validate_rpc(request('world.unwatch', payload)), code('INVALID_REQUEST')); for (const name of ['radius', 'known', 'watch_id', 'world_generation']) { let reads = 0; const payload = Object.defineProperty({...valid}, name, {enumerable: true, get() { reads++; return valid[name]; }}); assert.throws(() => validate_rpc(request('world.snapshot', payload)), code('INVALID_REQUEST')); assert.equal(reads, 0); } let reads = 0; const known = []; Object.defineProperty(known, '0', {enumerable: true, get() { reads++; return valid.known[0]; }}); assert.throws(() => validate_rpc(request('world.snapshot', {...valid, known})), code('INVALID_REQUEST')); assert.equal(reads, 0); }); test('snapshot encodes negative chunk coordinates, z-major heights and runtime registry surface names', async t => { const app = fixture(t); app.columns.set('-1,-2', app.column((x, y, z) => { if (x === 1 && z === 0) return y === -1 ? 202 : 7; if (x === 2 && z === 0) return y === 10 ? 404 : 7; if (x === 3 && z === 0) return y === -4 ? 303 : 7; if (x === 4 && z === 0) return y === 2 ? 505 : 7; if (x === 0 && z === 1) return y > 4 ? (y % 2 ? 8 : 9) : y === 4 ? 101 : 7; return 7; })); const result = await app.snapshot(); assert.deepEqual(Object.keys(result).sort(), ['world_generation', 'context_generation', 'stream_id', 'watch_id', 'version', 'dimension', 'min_y', 'height', 'center', 'position', 'chunks', 'loaded', 'partial'].sort()); for (const key of ['world_generation', 'context_generation', 'stream_id', 'watch_id']) assert.match(result[key], uuid); assert.deepEqual(result.center, {x: -1, z: -2}); assert.deepEqual(result.position, {x: -0.25, y: 3.5, z: -16.25, yaw: Math.PI / 3}); assert.equal(result.min_y, -4); assert.equal(result.height, 16); assert.equal(result.version, app.bot.version); assert.equal(result.dimension, 'overworld'); assert.equal(result.partial, true); assert.equal(result.chunks.length, 1); assert.equal(result.loaded.length, 1); const chunk = result.chunks[0]; assert.deepEqual(result.loaded, [{x: -1, z: -2, revision: chunk.revision}]); const cells = decoded(chunk); assert.deepEqual(cells[0], {height: 0, name: 'air', color: 0}); assert.deepEqual(cells[1], {height: 4, name: 'water', color: 0x3f76e4}); assert.equal(cells[2].height, 15); assert.equal(cells[2].name, 'oak_leaves'); assert.equal(cells[3].height, 1); assert.equal(cells[3].name, 'stone'); assert.equal(cells[4].height, 7); assert.equal(cells[4].name, 'modded_marble'); assert.equal(cells[16].height, 9); assert.equal(cells[16].name, 'grass_block'); assert.notEqual(cells[1].color, cells[16].color); }); test('only loaded columns are returned, zero-chunk metadata remains valid and radius 3 is bounded at 49', async t => { const app = fixture(t, {direct: true}); const empty = await app.snapshot(); assert.deepEqual(empty.chunks, []); assert.deepEqual(empty.loaded, []); assert.equal(empty.partial, true); for (let z = -5; z <= 1; z++) for (let x = -4; x <= 2; x++) app.columns.set(`${x},${z}`, app.column(() => 303)); const full = await app.snapshot({radius: 3, watch_id: empty.watch_id}); assert.equal(full.chunks.length, 49); assert.equal(full.loaded.length, 49); assert.equal(full.partial, false); const cached = await app.snapshot({radius: 3, watch_id: full.watch_id, world_generation: full.world_generation, known: full.loaded}); assert.equal(cached.chunks.length, 0); assert.equal(cached.loaded.length, 49); assert.equal(cached.partial, false); }); test('matching generation omits unchanged chunks without reads, block events change revisions before hint', async t => { const app = fixture(t); let state = 101; app.columns.set('-1,-2', app.column((_x, y) => y === 0 ? state : 7)); app.columns.set('0,-2', app.column(() => 303)); const first = await app.snapshot(); const reads = app.reads; const input = {known: first.loaded, world_generation: first.world_generation, watch_id: first.watch_id}; const same = await app.snapshot(input); assert.equal(same.chunks.length, 0); assert.equal(same.watch_id, first.watch_id); assert.equal(app.reads, reads); state = 202; app.bot.emit('blockUpdate', null, {position: {x: -1, y: 0, z: -17}}); const changed = await app.snapshot(input); assert.equal(changed.world_generation, first.world_generation); assert.equal(changed.chunks.length, 1); assert.equal(decoded(changed.chunks[0])[0].name, 'water'); assert.notEqual(changed.chunks[0].revision, first.chunks[0].revision); const forced = await app.snapshot({...input, world_generation: randomUUID()}); assert.equal(forced.chunks.length, 2); first.chunks[0].palette[0].name = 'mutated'; const clone = await app.snapshot({watch_id: first.watch_id}); assert.notEqual(clone.chunks[0].palette[0].name, 'mutated'); }); test('column replacement and unload cannot reuse stale cached surfaces', async t => { const app = fixture(t); app.columns.set('-1,-2', app.column(() => 101)); const first = await app.snapshot(); const input = {known: first.loaded, world_generation: first.world_generation, watch_id: first.watch_id}; app.columns.set('-1,-2', app.column(() => 303)); const replacement = await app.snapshot(input); assert.notEqual(replacement.chunks[0].revision, first.loaded[0].revision); assert.equal(decoded(replacement.chunks[0])[0].name, 'stone'); app.columns.delete('-1,-2'); app.bot.emit('chunkColumnUnload', {x: -16, z: -32}); const unloaded = await app.snapshot(input); assert.deepEqual(unloaded.loaded, []); assert.deepEqual(unloaded.chunks, []); app.columns.set('-1,-2', app.column(() => 202)); app.bot.emit('chunkColumnLoad', {x: -16, z: -32}); const reloaded = await app.snapshot(input); assert.notEqual(reloaded.loaded[0].revision, first.loaded[0].revision); }); test('world object, dimension, version and explicit same-world respawn reset the generation', async t => { const app = fixture(t); app.columns.set('-1,-2', app.column(() => 303)); let last = await app.snapshot(); for (const reset of [() => { app.bot.world = {sync: app.bot.world.sync}; }, () => { app.bot.game.dimension = 'the_nether'; }, () => { app.bot.version = '1.20.4'; }, () => { app.bot.emit('respawn'); }]) { reset(); const next = await app.snapshot({watch_id: last.watch_id, world_generation: last.world_generation, known: last.loaded}); assert.notEqual(next.world_generation, last.world_generation); assert.equal(next.chunks.length, 1); assert.equal(next.watch_id, last.watch_id); last = next; } }); test('watch events coalesce at 200ms, filter by active radius and only invalidate on chunk movement', async t => { const app = fixture(t); const events = []; app.stream.subscribe(event => { if (event.name === 'info_update') events.push(event); }); assert.equal(app.bot.listenerCount('chunkColumnLoad'), 0); const first = await app.snapshot(); assert.equal(app.bot.listenerCount('chunkColumnLoad'), 1); for (let index = 0; index < 20; index++) app.bot.emit('blockUpdate', null, {position: {x: -1, z: -17}}); app.bot.emit('chunkColumnLoad', {x: 0, z: -32}); assert.equal(events.length, 0); await delay(230); assert.equal(events.length, 1); assert.deepEqual(events[0].payload, {name: 'world_preview', value: {world_generation: first.world_generation}}); app.bot.emit('chunkColumnLoad', {x: 1600, z: 1600}); app.bot.entity.position.x = -0.5; app.bot.emit('move'); await delay(230); assert.equal(events.length, 1); app.bot.entity.position.x = 0.1; app.bot.emit('move'); await delay(230); assert.equal(events.length, 2); assert.deepEqual(app.stream.world_unwatch({watch_id: first.watch_id}, 2000), {released: true}); assert.deepEqual(app.stream.world_unwatch({watch_id: first.watch_id}, 2000), {released: true}); for (const name of ['chunkColumnLoad', 'chunkColumnUnload', 'blockUpdate', 'move', 'respawn', 'end']) assert.equal(app.bot.listenerCount(name), 0); }); test('leases cap at 64, renew for 45 seconds and final unwatch or TTL removes caches, timers and listeners', async t => { const app = fixture(t); app.columns.set('-1,-2', app.column(() => 303)); const first = await app.snapshot(); const ids = [first.watch_id]; for (let index = 1; index < 64; index++) ids.push((await app.snapshot()).watch_id); await assert.rejects(app.snapshot(), code('RATE_LIMITED')); app.time(44_000); const renewed = await app.snapshot({watch_id: first.watch_id, known: first.loaded, world_generation: first.world_generation}); assert.equal(renewed.watch_id, first.watch_id); assert.equal(renewed.chunks.length, 0); app.time(45_101); for (const id of ids.slice(1)) app.stream.world_unwatch({watch_id: id}, 90_000); assert.equal(app.bot.listenerCount('blockUpdate'), 1); app.time(89_001); app.bot.emit('move'); assert.equal(app.bot.listenerCount('blockUpdate'), 0); const reads = app.reads; const fresh = await app.snapshot({watch_id: first.watch_id, known: first.loaded, world_generation: first.world_generation}); assert.notEqual(fresh.watch_id, first.watch_id); assert.notEqual(fresh.world_generation, first.world_generation); assert.ok(app.reads > reads); app.bot.emit('chunkColumnLoad', {x: -16, z: -32}); app.stream.world_unwatch({watch_id: fresh.watch_id}, 100_000); assert.equal(app.bot.listenerCount('blockUpdate'), 0); }); test('lease expiry timer detaches without further bot traffic and cancels a queued invalidation', async t => { t.mock.timers.enable({apis: ['setTimeout', 'Date'], now: 1000}); const app = fixture(t); app.time(1000); const events = []; app.stream.subscribe(event => { if (event.name === 'info_update') events.push(event); }); const first = await app.snapshot(); app.time(45_900); t.mock.timers.tick(44_900); app.bot.emit('chunkColumnLoad', {x: -16, z: -32}); app.time(46_000); t.mock.timers.tick(100); assert.equal(app.bot.listenerCount('blockUpdate'), 0); t.mock.timers.tick(1000); assert.equal(events.length, 0); assert.deepEqual(app.stream.world_unwatch({watch_id: first.watch_id}, 47_000), {released: true}); }); test('watch radii use the union of active leases and shrink after releasing the wider watch', async t => { t.mock.timers.enable({apis: ['setTimeout']}); const app = fixture(t); const events = []; app.stream.subscribe(event => { if (event.name === 'info_update') events.push(event); }); const narrow = await app.snapshot(); const wide = await app.snapshot({radius: 3}); app.bot.emit('chunkColumnLoad', {x: 32, z: -32}); t.mock.timers.tick(200); assert.equal(events.length, 1); app.stream.world_unwatch({watch_id: wide.watch_id}, 2000); app.bot.emit('chunkColumnLoad', {x: 32, z: -32}); t.mock.timers.tick(200); assert.equal(events.length, 1); app.bot.emit('respawn'); t.mock.timers.tick(200); assert.equal(events.length, 2); const reset = await app.snapshot({watch_id: narrow.watch_id}); assert.equal(reset.world_generation, events[1].payload.value.world_generation); }); test('caller mutation after dispatch cannot change radius or known revisions, and scans are bounded to four concurrent calls', async t => { const app = fixture(t); app.columns.set('-1,-2', app.column()); const input = {radius: 1, known: []}; const pending = app.stream.world_snapshot(input, 2100); input.radius = 3; input.known.push({x: 0, z: 0, revision: 'bad'}); const result = await pending; assert.equal(result.chunks.length, 1); app.bot.emit('blockUpdate', null, {position: {x: -1, z: -17}}); const calls = Array.from({length: 4}, () => app.snapshot()); await assert.rejects(app.snapshot(), code('RATE_LIMITED')); await Promise.all(calls); }); test('scalar column adapters and deterministic fallback colors remain compatible with the surface contract', async t => { const app = fixture(t); app.columns.set('-1,-2', {getBlockStateId(x, y, z) { assert.equal(typeof x, 'number'); assert.equal(typeof z, 'number'); return y === 3 ? 505 : 7; }}); const first = await app.snapshot(); const cells = decoded(first.chunks[0]); assert.equal(cells[0].name, 'modded_marble'); assert.equal(cells[0].height, 8); assert.equal(Object.hasOwn(first.chunks[0], 'terrain'), false); assert.equal(Object.hasOwn(first.chunks[0], 'fluids'), false); assert.equal(Object.hasOwn(first.chunks[0], 'spikes'), false); app.bot.emit('respawn'); const reset = await app.snapshot({watch_id: first.watch_id}); assert.equal(decoded(reset.chunks[0])[0].color, cells[0].color); }); test('height 4096 and a full 256-entry surface palette fit UInt16, and transient metadata cannot retain stale revisions', async t => { const app = fixture(t, {minY: -4096, height: 4096}); for (let index = 0; index < 256; index++) app.bot.registry.blocksByStateId[1000 + index] = {name: `surface_${index}`}; app.columns.set('-1,-2', app.column((x, _y, z) => 1000 + z * 16 + x)); const first = await app.snapshot(); const cells = decoded(first.chunks[0]); assert.equal(first.chunks[0].palette.length, 256); assert.equal(cells[255].name, 'surface_255'); assert.equal(cells[255].height, 4096); app.bot.entity.yaw = NaN; app.bot.emit('blockUpdate', null, {position: {x: -1, z: -17}}); app.bot.entity.yaw = 0; const next = await app.snapshot({known: first.loaded, world_generation: first.world_generation, watch_id: first.watch_id}); assert.equal(next.chunks.length, 1); assert.notEqual(next.loaded[0].revision, first.loaded[0].revision); }); test('surface reads yield and abort promptly on deadline or transport disconnection', async t => { for (const cause of ['deadline', 'transport']) await t.test(cause, async t => { const app = fixture(t, {minY: -64, height: 384}); app.columns.set('-1,-2', app.column()); let stale = false; setImmediate(() => { if (cause === 'deadline') app.time(5000); else stale = true; }); await assert.rejects(app.snapshot({}, () => { if (stale) throw Object.assign(new Error(), {code: 'SESSION_STALE'}); }), code(cause === 'deadline' ? 'TIMEOUT' : 'SESSION_STALE')); assert.ok(app.reads <= 4096, `scan must stop during first read batch: ${app.reads}`); assert.equal(app.bot.listenerCount('blockUpdate'), 0); }); }); test('in-flight reads reject context replacement, offline transition, disposal and column mutation', async t => { for (const cause of ['context', 'instance', 'offline', 'dispose', 'block', 'replacement', 'unload', 'respawn', 'world', 'move']) await t.test(cause, async t => { const app = fixture(t); app.columns.set('-1,-2', app.column()); setImmediate(() => { if (cause === 'context') app.set(Object.assign(new EventEmitter(), {bot_status: 'online', bot_instance: app.bot})); if (cause === 'instance') app.context.bot_instance = {...app.bot}; if (cause === 'offline') { app.context.bot_status = 'offline'; app.context.emit('status_update'); } if (cause === 'dispose') app.stream.dispose(); if (cause === 'block') app.bot.emit('blockUpdate', null, {position: {x: -1, z: -17}}); if (cause === 'replacement') app.columns.set('-1,-2', app.column(() => 303)); if (cause === 'unload') app.columns.delete('-1,-2'); if (cause === 'respawn') app.bot.emit('respawn'); if (cause === 'world') app.bot.world = {sync: app.bot.world.sync}; if (cause === 'move') { app.bot.entity.position.x = 20; app.bot.emit('move'); } }); await assert.rejects(app.snapshot(), error => ['CANCELLED', 'BOT_NOT_ONLINE'].includes(error.code)); app.stream.dispose(); assert.equal(app.bot.listenerCount('blockUpdate'), 0); }); }); test('offline, missing context, invalid metadata and unknown registry data never fabricate air', async t => { const app = fixture(t); await assert.rejects(app.stream.world_snapshot({radius: 0, known: []}, 200), code('INVALID_REQUEST')); await assert.rejects(app.stream.world_snapshot({radius: 1, known: []}, 100), code('TIMEOUT')); app.context.bot_status = 'offline'; await assert.rejects(app.snapshot(), code('BOT_NOT_ONLINE')); assert.deepEqual(app.stream.world_unwatch({watch_id: randomUUID()}, 200), {released: true}); app.context.bot_status = 'online'; app.bot.entity.yaw = NaN; await assert.rejects(app.snapshot(), code('INTERNAL_ERROR')); app.bot.entity.yaw = 0; app.columns.set('-1,-2', app.column(() => 999_999)); await assert.rejects(app.snapshot(), code('INTERNAL_ERROR')); assert.equal(app.bot.listenerCount('blockUpdate'), 0); app.set(undefined); await assert.rejects(app.snapshot(), code('NO_CONTEXT')); assert.deepEqual(app.stream.world_unwatch({watch_id: randomUUID()}, 200), {released: true}); app.stream.dispose(); app.stream.dispose(); await assert.rejects(app.snapshot(), code('CANCELLED')); assert.equal(app.observers.size, 0); }); test('fluid columns retain top surfaces and sample contiguous water plants down to actual terrain', async t => { const app = fixture(t); const plants = ['bubble_column', 'kelp', 'kelp_plant', 'seagrass', 'tall_seagrass']; plants.forEach((name, index) => { app.bot.registry.blocksByStateId[600 + index] = {name: `minecraft:${name}`}; }); app.bot.registry.blocksByStateId[700] = {name: 'minecraft:lava'}; app.columns.set('-1,-2', app.column((x, y, z) => { if (z) return 7; if (x === 0) return y >= -2 && y <= 2 ? 202 : y === -3 ? 303 : 7; if (x >= 1 && x <= 5) return y === 4 ? 599 + x : y === 3 ? 602 : y === 2 ? 202 : y === 1 ? 101 : 7; if (x === 6) return y === 6 || y === 5 ? 700 : y === 4 ? 505 : 7; if (x === 7) return y > 8 ? 7 : y === 8 ? 404 : 999_999; // Solid tops must never inspect lower cells. if (x === 8) return y === -4 ? 202 : 7; if (x === 9) return y === 4 ? 202 : y === -4 ? 303 : 7; if (x === 10 || x === 11) return y > 4 ? 7 : y === 4 ? (x === 10 ? 202 : 700) : y === 3 ? (x === 10 ? 700 : 202) : 999_999; return 7; })); const {chunks: [chunk]} = await app.snapshot(); const top = decoded(chunk), terrain = decoded(chunk, chunk.terrain), fluids = decoded(chunk, chunk.fluids); assert.deepEqual(top[0], {height: 7, name: 'water', color: 0x3f76e4}); assert.deepEqual(terrain[0], {height: 2, name: 'stone', color: 0x858585}); assert.deepEqual(fluids[0], {...top[0], level: 0}); // Missing metadata defaults to source. for (let x = 1; x <= 5; x++) { assert.deepEqual(top[x], {height: 9, name: 'water', color: 0x3f76e4}); assert.deepEqual(fluids[x], {...top[x], level: 0}); assert.equal(terrain[x].name, 'grass_block'); assert.equal(terrain[x].height, 6); } assert.equal(top[6].name, 'lava'); assert.equal(fluids[6].name, 'lava'); assert.equal(fluids[6].level, 0); assert.equal(fluids[6].height, 11); assert.equal(terrain[6].name, 'modded_marble'); assert.equal(terrain[6].height, 9); assert.deepEqual(terrain[7], top[7]); assert.equal(fluids[7].height, 0); assert.equal(fluids[8].height, 1); assert.equal(terrain[8].height, 0); assert.equal(fluids[9].height, 9); assert.equal(terrain[9].height, 0); // No floor across an air gap. for (const x of [10, 11]) { assert.equal(top[x].name, x === 10 ? 'water' : 'lava'); assert.deepEqual(fluids[x], {...top[x], level: 0}); assert.equal(terrain[x].height, 0); // A different fluid ends the column without becoming solid terrain. } assert.equal(top[12].height, 0); assert.equal(terrain[12].height, 0); assert.equal(fluids[12].height, 0); assert.ok(!chunk.palette.some(entry => plants.some(name => entry.name.endsWith(name)))); }); test('fluid level decoding respects registry property order and explicit level value order', async t => { const app = fixture(t, {minY: -10, height: 4}); const states = [{name: 'before', num_values: 2}, {name: 'level', num_values: 4, values: ['8', '0', '15', '3']}, {name: 'after', num_values: 3}]; const expected = [8, 0, 15, 3]; expected.forEach((_level, index) => { const state = 1000 + 12 + index * 3 + 2; app.bot.registry.blocksByStateId[state] = {name: 'water', minStateId: 1000, states}; }); app.columns.set('-1,-2', app.column((x, y) => y === -7 && x < 4 ? 1000 + 12 + x * 3 + 2 : 303)); const {chunks: [chunk]} = await app.snapshot(); assert.deepEqual(decoded(chunk, chunk.fluids).slice(0, 4).map(cell => cell.level), expected); assert.deepEqual(decoded(chunk, chunk.terrain).slice(0, 4).map(cell => cell.height), [3, 3, 3, 3]); }); test('one shared palette accommodates 256 distinct floors plus water and lava', async t => { const app = fixture(t, {minY: -64, height: 2}); app.bot.registry.blocksByStateId[600] = {name: 'lava'}; for (let index = 0; index < 256; index++) app.bot.registry.blocksByStateId[1000 + index] = {name: `floor_${index}`}; app.columns.set('-1,-2', app.column((x, y, z) => y === -63 ? (x % 2 ? 202 : 600) : 1000 + z * 16 + x)); const {chunks: [chunk]} = await app.snapshot(); const terrain = decoded(chunk, chunk.terrain), fluids = decoded(chunk, chunk.fluids); assert.equal(chunk.palette.length, 258); for (let index = 0; index < 256; index++) { assert.equal(terrain[index].name, `floor_${index}`); assert.equal(terrain[index].height, 1); assert.equal(fluids[index].name, index % 2 ? 'water' : 'lava'); assert.equal(fluids[index].height, 2); } }); test('fluid terrain scans reject unknown blocks and yield before cancellation without caching partial data', async t => { await t.test('unknown beneath fluid', async t => { const app = fixture(t); let unknown = true; app.columns.set('-1,-2', app.column((_x, y) => y === 11 ? 202 : unknown ? 999_999 : 303)); await assert.rejects(app.snapshot(), code('INTERNAL_ERROR')); assert.equal(app.bot.listenerCount('blockUpdate'), 0); unknown = false; const {chunks: [chunk]} = await app.snapshot(); assert.equal(decoded(chunk, chunk.terrain)[0].height, 15); }); await t.test('deep fluid cancellation', async t => { const app = fixture(t, {minY: -64, height: 4096}); app.columns.set('-1,-2', app.column(() => 202)); let stale = false; setImmediate(() => { stale = true; }); await assert.rejects(app.snapshot({}, () => { if (stale) throw Object.assign(new Error(), {code: 'SESSION_STALE'}); }), code('SESSION_STALE')); assert.equal(app.reads, 4096); assert.equal(app.bot.listenerCount('blockUpdate'), 0); app.columns.set('-1,-2', app.column(() => 303)); const {chunks: [chunk]} = await app.snapshot(); assert.equal(Object.hasOwn(chunk, 'terrain'), false); assert.equal(Object.hasOwn(chunk, 'fluids'), false); }); }); test('spike runs decode mixed-radix direction and every thickness while preserving original surfaces', async t => { const app = fixture(t); const thicknesses = ['base', 'middle', 'frustum', 'tip_merge', 'tip']; const states = [{name: 'before', num_values: 2}, {name: 'vertical_direction', num_values: 2, values: ['down', 'up']}, {name: 'thickness', num_values: 5, values: thicknesses}, {name: 'waterlogged', num_values: 2, values: ['true', 'false']}, {name: 'after', num_values: 3}]; const state_id = (direction, thickness) => 1000 + (((2 + direction) * 5 + thickness) * 2 + 1) * 3 + 2; for (let direction = 0; direction < 2; direction++) for (let thickness = 0; thickness < 5; thickness++) app.bot.registry.blocksByStateId[state_id(direction, thickness)] = {name: 'minecraft:pointed_dripstone', minStateId: 1000, states}; app.columns.set('-1,-2', app.column((x, y, z) => { if (z || x > 2) return y === 4 ? 101 : 7; if (x === 2) return y === 9 ? 404 : y < 9 ? 999_999 : 7; return y <= 7 && y >= 3 ? state_id(x, 7 - y) : y === 2 ? 303 : 7; })); const {chunks: [chunk]} = await app.snapshot(); const top = decoded(chunk), bases = decoded(chunk, chunk.spikes); assert.equal(Object.hasOwn(chunk, 'terrain'), false); assert.equal(Object.hasOwn(chunk, 'fluids'), false); for (const x of [0, 1]) { assert.equal(top[x].name, 'minecraft:pointed_dripstone'); assert.equal(top[x].height, 12); assert.equal(bases[x].name, 'stone'); assert.equal(bases[x].height, 7); } for (let index = 2; index < 256; index++) assert.deepEqual(bases[index], top[index]); assert.deepEqual(chunk.spikes.segments, [0, 1].flatMap(index => thicknesses.map((thickness, offset) => ({index, height: 12 - offset, direction: index ? 'up' : 'down', thickness})))); }); test('spike bases stop at air gaps and the world minimum, and absent metadata defaults to up/tip', async t => { const app = fixture(t); app.bot.registry.blocksByStateId[600] = {name: 'pointed_dripstone'}; app.columns.set('-1,-2', app.column((x, y, z) => { if (z) return 7; if (x === 0) return y === 4 || y === 3 ? 600 : y === -4 ? 303 : 7; if (x === 1) return y === -4 ? 600 : 7; if (x === 2) return y > 4 ? 7 : y === 4 ? 600 : y === 3 ? 8 : 999_999; if (x === 3) return y > 4 ? 7 : y === 4 ? 600 : y === 3 ? 9 : 999_999; return 7; })); const {chunks: [chunk]} = await app.snapshot(); const bases = decoded(chunk, chunk.spikes); for (const x of [0, 1, 2, 3]) assert.deepEqual(bases[x], {height: 0, name: 'air', color: 0}); assert.deepEqual(chunk.spikes.segments, [ {index: 0, height: 9, direction: 'up', thickness: 'tip'}, {index: 0, height: 8, direction: 'up', thickness: 'tip'}, {index: 1, height: 1, direction: 'up', thickness: 'tip'}, {index: 2, height: 9, direction: 'up', thickness: 'tip'}, {index: 3, height: 9, direction: 'up', thickness: 'tip'}, ]); }); test('underwater spikes override only spike bases and mirror terrain in every other cell', async t => { const app = fixture(t); app.bot.registry.blocksByStateId[600] = {name: 'pointed_dripstone'}; app.bot.registry.blocksByStateId[601] = {name: 'lava'}; app.columns.set('-1,-2', app.column((x, y, z) => { if (z) return y === 0 ? 101 : 7; if (x <= 1) return y === 8 || y === 7 ? 202 : y === 6 || y === 5 ? 600 : y === 4 && !x ? 303 : 7; if (x === 2) return y === 8 ? 202 : y === 7 ? 303 : 7; if (x === 3) return y > 8 ? 7 : y === 8 ? 202 : y === 7 ? 601 : 999_999; if (x === 4) return y > 8 ? 7 : y === 8 ? 404 : 999_999; return 7; })); const {chunks: [chunk]} = await app.snapshot(); const top = decoded(chunk), terrain = decoded(chunk, chunk.terrain), fluids = decoded(chunk, chunk.fluids); const bases = decoded(chunk, chunk.spikes); for (const x of [0, 1]) { assert.equal(top[x].name, 'water'); assert.equal(top[x].height, 13); assert.equal(fluids[x].height, 13); assert.equal(fluids[x].level, 0); assert.equal(terrain[x].name, 'pointed_dripstone'); assert.equal(terrain[x].height, 11); } assert.equal(bases[0].name, 'stone'); assert.equal(bases[0].height, 9); assert.equal(bases[1].height, 0); assert.equal(bases[1].name, 'air'); for (let index = 2; index < 256; index++) assert.deepEqual(bases[index], terrain[index]); assert.deepEqual(chunk.spikes.segments.map(({index, height}) => ({index, height})), [{index: 0, height: 11}, {index: 0, height: 10}, {index: 1, height: 11}, {index: 1, height: 10}]); }); test('spike caps commit complete columns and retain original columns when limits would be exceeded', async t => { await t.test('sixteen per column, oversized runs omitted', async t => { const app = fixture(t, {minY: 0, height: 20}); app.bot.registry.blocksByStateId[600] = {name: 'pointed_dripstone'}; app.columns.set('-1,-2', app.column((x, y, z) => !z && x < 2 ? (y >= (x ? 3 : 4) ? 600 : 303) : 7)); const {chunks: [chunk]} = await app.snapshot(); const top = decoded(chunk), bases = decoded(chunk, chunk.spikes); assert.equal(chunk.spikes.segments.length, 16); assert.ok(chunk.spikes.segments.every(segment => segment.index === 0)); assert.equal(bases[0].height, 4); assert.equal(bases[0].name, 'stone'); assert.deepEqual(bases[1], top[1]); }); await t.test('chunk budget does not commit partial columns and later smaller runs can fit', async t => { const app = fixture(t, {minY: 0, height: 20}); app.bot.registry.blocksByStateId[600] = {name: 'pointed_dripstone'}; app.columns.set('-1,-2', app.column((x, y, z) => { const index = z * 16 + x; const length = index < 31 ? 16 : index === 31 ? 15 : index === 32 ? 2 : 1; return y >= 20 - length ? 600 : 303; })); const {chunks: [chunk]} = await app.snapshot(); const top = decoded(chunk), bases = decoded(chunk, chunk.spikes); assert.equal(chunk.spikes.segments.length, 512); assert.equal(chunk.spikes.segments.filter(segment => segment.index === 31).length, 15); assert.equal(chunk.spikes.segments.filter(segment => segment.index === 32).length, 0); assert.equal(chunk.spikes.segments.filter(segment => segment.index === 33).length, 1); assert.equal(bases[33].height, 19); assert.equal(bases[33].name, 'stone'); for (const index of [32, ...Array.from({length: 222}, (_, offset) => offset + 34)]) assert.deepEqual(bases[index], top[index]); assert.ok(app.reads <= 256 + 512 + 256 + 3, `bounded spike scan: ${app.reads}`); }); await t.test('no successfully sampled spikes omits the feature', async t => { const app = fixture(t, {minY: 0, height: 20}); app.bot.registry.blocksByStateId[600] = {name: 'pointed_dripstone'}; app.columns.set('-1,-2', app.column(() => 600)); const {chunks: [chunk]} = await app.snapshot(); assert.equal(Object.hasOwn(chunk, 'spikes'), false); assert.ok(decoded(chunk).every(cell => cell.name === 'pointed_dripstone' && cell.height === 20)); assert.equal(app.reads, 256 * 17); }); await t.test('underwater oversized runs retain pointed terrain and fluid surfaces', async t => { const app = fixture(t, {minY: 0, height: 20}); app.bot.registry.blocksByStateId[600] = {name: 'pointed_dripstone'}; app.columns.set('-1,-2', app.column((x, y, z) => { if (z || x > 1) return 7; if (y === 19) return 202; return y >= (x ? 2 : 17) ? 600 : 303; })); const {chunks: [chunk]} = await app.snapshot(); const top = decoded(chunk), terrain = decoded(chunk, chunk.terrain), fluids = decoded(chunk, chunk.fluids); const bases = decoded(chunk, chunk.spikes); assert.equal(chunk.spikes.segments.length, 2); assert.ok(chunk.spikes.segments.every(segment => segment.index === 0)); assert.equal(bases[0].name, 'stone'); assert.equal(bases[0].height, 17); assert.deepEqual(bases[1], terrain[1]); assert.equal(terrain[1].name, 'pointed_dripstone'); assert.equal(terrain[1].height, 19); assert.equal(top[1].name, 'water'); assert.equal(top[1].height, 20); assert.deepEqual(fluids[1], {...top[1], level: 0}); }); }); test('spike scans reject unknown states and preserve yield/cancellation bounds without caching partial features', async t => { await t.test('unknown segment or foundation', async t => { const app = fixture(t); app.bot.registry.blocksByStateId[600] = {name: 'pointed_dripstone'}; let unknown = true; app.columns.set('-1,-2', app.column((_x, y) => y >= 9 ? 600 : unknown ? 999_999 : 303)); await assert.rejects(app.snapshot(), code('INTERNAL_ERROR')); assert.equal(app.bot.listenerCount('blockUpdate'), 0); unknown = false; const {chunks: [chunk]} = await app.snapshot(); assert.equal(chunk.spikes.segments.length, 510); assert.equal(decoded(chunk, chunk.spikes)[0].height, 13); }); await t.test('cancel at row yield', async t => { const app = fixture(t, {minY: 0, height: 20}); app.bot.registry.blocksByStateId[600] = {name: 'pointed_dripstone'}; app.columns.set('-1,-2', app.column((_x, y) => y >= 4 ? 600 : 303)); let stale = false; setImmediate(() => { stale = true; }); await assert.rejects(app.snapshot({}, () => { if (stale) throw Object.assign(new Error(), {code: 'SESSION_STALE'}); }), code('SESSION_STALE')); assert.equal(app.reads, 32 * 17); assert.equal(app.bot.listenerCount('blockUpdate'), 0); app.columns.set('-1,-2', app.column(() => 303)); const {chunks: [chunk]} = await app.snapshot(); assert.equal(Object.hasOwn(chunk, 'spikes'), false); }); }); test('real installed WorldSync and chunk registry produce the same surface wire format', async t => { const installed = createRequire(require.resolve('@wp2508/mineflayer')); const registry = installed('@wp2508/minecraft-data')('1.21.4'); const Chunk = installed('prismarine-chunk')('1.21.4'); const World = installed('prismarine-world')('1.21.4'); const chunk = new Chunk({minY: -64, worldHeight: 384}); chunk.setBlockStateId({x: 0, y: 67, z: 0}, registry.blocksByName.grass_block.minStateId); chunk.setBlockStateId({x: 1, y: 70, z: 0}, registry.blocksByName.water.minStateId); for (const [x, name, level] of [[3, 'water', 3], [4, 'water', 8], [5, 'water', 15], [6, 'lava', 0], [7, 'lava', 8], [8, 'lava', 15]]) { chunk.setBlockStateId({x, y: 70, z: 0}, registry.blocksByName[name].minStateId + level); chunk.setBlockStateId({x, y: 69, z: 0}, registry.blocksByName.stone.minStateId); } const dripstone = registry.blocksByName.pointed_dripstone; const spike_state = properties => dripstone.minStateId + dripstone.states.reduce((offset, property) => offset * property.num_values + (property.values ? property.values.indexOf(properties[property.name]) : properties[property.name] === 'false' ? 1 : 0), 0); const real_spikes = []; for (const direction of ['up', 'down']) for (const thickness of ['tip', 'tip_merge', 'frustum', 'middle', 'base']) { const index = 16 + real_spikes.length; const state = spike_state({vertical_direction: direction, thickness, waterlogged: 'false'}); chunk.setBlockStateId({x: index % 16, y: 70, z: 1}, state); chunk.setBlockStateId({x: index % 16, y: 69, z: 1}, registry.blocksByName.stone.minStateId); real_spikes.push({index, height: 135, direction, thickness}); } const app = fixture(t, {minY: -64, height: 384}); const world = new World(null, null, {savingInterval: 0}); app.bot.world = world.sync; app.bot.registry = registry; app.bot.world.setColumn(-1, -2, chunk, false); const result = await app.snapshot(); const cells = decoded(result.chunks[0]); assert.equal(cells[0].height, 132); assert.equal(cells[0].name, 'grass_block'); assert.equal(cells[1].height, 135); assert.equal(cells[1].name, 'water'); assert.equal(cells[2].height, 0); const fluids = decoded(result.chunks[0], result.chunks[0].fluids); const terrain = decoded(result.chunks[0], result.chunks[0].terrain); assert.equal(fluids[1].level, 0); assert.equal(terrain[1].height, 0); for (const [x, name, level] of [[3, 'water', 3], [4, 'water', 8], [5, 'water', 15], [6, 'lava', 0], [7, 'lava', 8], [8, 'lava', 15]]) { assert.equal(fluids[x].name, name); assert.equal(fluids[x].level, level); assert.equal(fluids[x].height, 135); assert.equal(terrain[x].name, 'stone'); assert.equal(terrain[x].height, 134); } assert.deepEqual(result.chunks[0].spikes.segments, real_spikes); const bases = decoded(result.chunks[0], result.chunks[0].spikes); for (const {index} of real_spikes) { assert.equal(terrain[index].name, 'pointed_dripstone'); assert.equal(terrain[index].height, 135); assert.equal(bases[index].name, 'stone'); assert.equal(bases[index].height, 134); } }); function transport_fixture(t) { const app = fixture(t); app.columns.set('-1,-2', app.column()); const command = {forward_id: 0, forward_session_id: randomUUID(), node_session_id: 'node-session', node_id: 1, bot_id: 1, url: 'http://127.0.0.1:1234', path: '/api/forward_server', namespace: '/', token: randomUUID(), expires_at: Date.now() + 30_000, deadline_at: Date.now() + 2000}; let socket; const manager = new NodeForwardManager(1, 'node-session', { stream_factory: () => app.stream, socket_factory() { socket = new EventEmitter(); socket.auth = {}; socket.connect = () => { socket.connected = true; queueMicrotask(() => socket.emit('connect')); }; socket.disconnect = () => { socket.connected = false; socket.emit('disconnect'); }; socket.io = {engine: {writeBuffer: []}}; socket.volatile = {emit() {}}; socket.timeout = () => ({emit(event, _payload, ack) { const {forward_id, forward_session_id, node_session_id, node_id, bot_id, expires_at} = command; queueMicrotask(() => ack(null, {status: 'success', data: event === 'forward_node_ready' ? {node_ready: true} : {forward_id, forward_session_id, node_session_id, node_id, bot_id, role: 'node', node_ready: false, expires_at}})); }}); return socket; }, }); t.after(() => manager.stop()); return {...app, manager, command, get socket() { return socket; }, rpc(method, payload) { return new Promise(resolve => socket.emit('relay_request', request(method, payload, command.forward_session_id), resolve)); }, }; } test('manager dispatches world reads through guarded RPC and rejects malformed requests and disconnect during yield', async t => { const app = transport_fixture(t); assert.equal((await app.manager.ensure(app.command)).status, 'success'); const snapshot = await app.rpc('world.snapshot', {radius: 1, known: []}); assert.equal(snapshot.status, 'success'); assert.match(snapshot.data.watch_id, uuid); assert.deepEqual(await app.rpc('world.unwatch', {watch_id: snapshot.data.watch_id}), {status: 'success', data: {released: true}}); assert.equal((await app.rpc('world.snapshot', {radius: 4, known: []})).data.code, 'INVALID_REQUEST'); const pending = app.rpc('world.snapshot', {radius: 1, known: []}); setImmediate(() => app.socket.disconnect()); assert.equal((await pending).data.code, 'SESSION_STALE'); assert.equal(app.bot.listenerCount('blockUpdate'), 0); });