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 test from 'node:test'; import {build} from 'esbuild'; const require = createRequire(import.meta.url); const installed = createRequire(require.resolve('@wp2508/mineflayer')); const registry = installed('@wp2508/minecraft-data')('1.21.4'); const compiled = await build({ stdin: {contents: `export * from './bot/forward/stream.ts'; export * from './utils/forward_protocol.ts';`, resolveDir: fileURLToPath(new URL('../', import.meta.url)), loader: 'ts'}, bundle: true, platform: 'node', format: 'cjs', write: false, plugins: [{name: 'isolated-world', setup(build) { build.onResolve({filter: /(?:instance\/index\.ts|console_print\.ts|path_utils\.ts)$/}, args => ({path: args.path.includes('instance/') ? 'registry' : 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 = () => () => {};', console: 'export const console_print = {success: x => x, log() {}};', path: 'export const get_project_root_path = () => {throw Error("Unexpected filesystem access")};', }[path]})); }}], }); const module = {exports: {}}; new Function('require', 'module', 'exports', compiled.outputFiles[0].text)(specifier => { assert.ok(isBuiltin(specifier)); return require(specifier); }, module, module.exports); const {BotStream, validate_rpc} = module.exports; const code = expected => error => error.code === expected; const state = (name, properties = {}) => { const block = registry.blocksByName[name]; return block.minStateId + block.states.reduce((offset, property) => { const value = properties[property.name]; const index = property.type === 'bool' ? value === true ? 0 : 1 : property.values.indexOf(String(value)); assert.ok(index >= 0, `${name}.${property.name}`); return offset * property.num_values + index; }, 0); }; function fixture(t, {minY = -4, height = 32} = {}) { let now = 100, reads = 0; const columns = new Map(); const bot = Object.assign(new EventEmitter(), { chat() {}, version: '1.21.4', game: {minY, height, dimension: 'overworld'}, registry, world: {getColumn(x, z) {return columns.get(`${x},${z}`);}}, entity: {position: {x: -0.25, y: 4, z: -16.25}, yaw: 0}, }); const context = Object.assign(new EventEmitter(), {bot_status: 'online', bot_instance: bot}); let current = context; const stream = new BotStream(1, {source: {get: () => current, observe: () => () => {}}, now: () => now}); t.after(() => stream.dispose()); const column = (get = () => registry.blocksByName.stone.defaultState) => ({getBlockStateId(p) { assert.ok(p.x >= 0 && p.x < 16 && p.z >= 0 && p.z < 16); assert.ok(p.y >= bot.game.minY && p.y < bot.game.minY + bot.game.height); reads++; return get(p.x, p.y, p.z); }}); columns.set('-1,-2', column()); return {bot, context, stream, columns, column, get reads() {return reads;}, time(value) {now = value;}, set_context(value) {current = value;}, snapshot(input = {}) {return stream.world_snapshot({radius: 1, known: [], ...input}, now + 2000);}, chunk(snapshot, input = {}, check = () => {}) { return stream.world_chunk({x: -1, z: -2, world_generation: snapshot.world_generation, watch_id: snapshot.watch_id, ...input}, now + 2000, check); }, }; } function decode(chunk) { assert.deepEqual(Object.keys(chunk).sort(), ['x', 'z', 'revision', 'world_generation', 'min_y', 'height', 'palette', 'sections'].sort()); assert.ok(chunk.palette.length >= 1 && chunk.palette.length <= 8192); let previous = -1; const sections = new Map(); for (const section of chunk.sections) { assert.ok(section.y > previous && section.y < Math.ceil(chunk.height / 16)); previous = section.y; const bytes = Buffer.from(section.blocks, 'base64'); assert.equal(bytes.length, 8192); assert.equal(bytes.toString('base64'), section.blocks); for (let i = 0; i < 4096; i++) assert.ok(bytes.readUInt16LE(i * 2) < chunk.palette.length); sections.set(section.y, bytes); } return (x, y, z) => { const offset = y - chunk.min_y, bytes = sections.get(Math.floor(offset / 16)); return bytes ? chunk.palette[bytes.readUInt16LE(((offset % 16) * 256 + z * 16 + x) * 2)] : {name: 'air'}; }; } test('world.chunk validates exact integer coordinates and UUIDs without invoking accessors', () => { const payload = {x: -1, z: 2, world_generation: randomUUID(), watch_id: randomUUID()}; const request = value => ({method: 'world.chunk', payload: value, request_id: randomUUID(), correlation_id: randomUUID(), forward_session_id: randomUUID(), deadline_at: 1000}); assert.equal(validate_rpc(request(payload)).payload, payload); for (const invalid of [{}, {...payload, x: 1.5}, {...payload, z: Infinity}, {...payload, x: '1'}, {...payload, watch_id: undefined}, {...payload, world_generation: 'bad'}, {...payload, radius: 3}]) assert.throws(() => validate_rpc(request(invalid)), code('INVALID_REQUEST')); for (const key of Object.keys(payload)) { let reads = 0; const invalid = Object.defineProperty({...payload}, key, {enumerable: true, get() {reads++; return payload[key];}}); assert.throws(() => validate_rpc(request(invalid)), code('INVALID_REQUEST')); assert.equal(reads, 0); } }); test('real installed chunks retain cave floors, interiors, roofs and light below the surface', async t => { const app = fixture(t, {minY: -64, height: 384}); const Chunk = installed('prismarine-chunk')('1.21.4'); const World = installed('prismarine-world')('1.21.4'); const column = new Chunk({minY: -64, worldHeight: 384}); const world = new World(null, null, {savingInterval: 0}); app.bot.world = world.sync; app.bot.world.setColumn(-1, -2, column, false); for (const [x, y, z, name] of [[0, -64, 0, 'bedrock'], [0, -40, 0, 'diamond_ore'], [0, 80, 0, 'stone'], [15, 79, 15, 'glass'], [1, -39, 2, 'cave_air'], [1, -38, 2, 'void_air']]) column.setBlockStateId({x, y, z}, registry.blocksByName[name].defaultState); column.setBlockStateId({x: 2, y: -20, z: 3}, state('light', {level: 9, waterlogged: false})); const snapshot = await app.snapshot(), chunk = await app.chunk(snapshot), cell = decode(chunk); assert.equal(chunk.revision, snapshot.loaded[0].revision); assert.deepEqual(chunk.sections.map(s => s.y), [0, 1, 2, 8, 9]); assert.equal(cell(0, -64, 0).name, 'bedrock'); assert.equal(cell(0, -40, 0).name, 'diamond_ore'); assert.equal(cell(0, -39, 0).name, 'air'); assert.equal(cell(0, 80, 0).name, 'stone'); assert.equal(cell(15, 79, 15).name, 'glass'); assert.equal(cell(1, -39, 2).name, 'cave_air'); assert.equal(cell(1, -38, 2).name, 'void_air'); assert.deepEqual(cell(2, -20, 3).properties, {waterlogged: false, level: 9}); const unchanged = await app.snapshot({world_generation: snapshot.world_generation, known: snapshot.loaded, watch_id: snapshot.watch_id}); assert.deepEqual(unchanged.chunks, []); }); test('palette preserves generic typed registry properties, deduplicates state IDs and excludes partial occluders', async t => { const app = fixture(t); const samples = [ ['wheat', {age: 7}], ['pink_petals', {facing: 'south', flower_amount: 4}], ['snow', {layers: 6}], ['pointed_dripstone', {thickness: 'frustum', vertical_direction: 'down', waterlogged: true}], ['lever', {face: 'ceiling', facing: 'east', powered: true}], ['cake', {bites: 5}], ['beehive', {facing: 'west', honey_level: 4}], ['oak_slab', {type: 'bottom', waterlogged: false}], ['oak_leaves', {distance: 3, persistent: true, waterlogged: false}], ['glass', {}], ['torch', {}], ['water', {level: 8}], ['stone', {}], ['oak_slab', {type: 'top', waterlogged: false}], ]; const states = samples.map(([name, properties]) => state(name, properties)); app.columns.set('-1,-2', app.column((x, y, z) => y === -3 && z === 3 && x < states.length ? states[x] : y === 27 && z === 3 && x === 0 ? states[0] : registry.blocksByName.air.defaultState)); const chunk = await app.chunk(await app.snapshot()), cell = decode(chunk); for (let x = 0; x < samples.length; x++) { const [name, properties] = samples[x], result = cell(x, -3, 3); assert.equal(result.name, name); assert.deepEqual(result.properties, properties); assert.equal(typeof result.color, 'number'); assert.equal(result.occluding, name === 'stone' || name === 'beehive'); } assert.equal(chunk.palette.filter(p => p.name === 'wheat').length, 1); assert.equal(chunk.palette.filter(p => p.name === 'oak_slab').length, 2); assert.equal(cell(0, 27, 3).properties.age, 7); }); test('sections use offsets from nonaligned minimum, pad with air and omit only air variants', async t => { const app = fixture(t, {minY: -3, height: 33}); app.columns.set('-1,-2', app.column((x, y, z) => y === 29 && x === 15 && z === 14 ? state('light', {level: 0, waterlogged: true}) : [registry.blocksByName.air.defaultState, registry.blocksByName.cave_air.defaultState, registry.blocksByName.void_air.defaultState][(x + z) % 3])); const chunk = await app.chunk(await app.snapshot()); assert.deepEqual(chunk.sections.map(s => s.y), [2]); assert.equal(decode(chunk)(15, 29, 14).name, 'light'); const last = Buffer.from(chunk.sections[0].blocks, 'base64'); for (let i = 256; i < 4096; i++) assert.equal(chunk.palette[last.readUInt16LE(i * 2)].name, 'air'); app.bot.emit('blockUpdate', null, {position: {x: -1, z: -17}}); app.columns.set('-1,-2', app.column(() => registry.blocksByName.cave_air.defaultState)); const empty = await app.chunk(await app.snapshot()); assert.equal(empty.palette.length, 1); assert.deepEqual(empty.sections, []); }); test('chunk requires a live watch in its generation and radius, and never renews its lease', async t => { const app = fixture(t), snapshot = await app.snapshot(); await assert.rejects(app.chunk(snapshot, {watch_id: randomUUID()}), code('CANCELLED')); await assert.rejects(app.chunk(snapshot, {world_generation: randomUUID()}), code('CANCELLED')); const before = app.reads; await assert.rejects(app.chunk(snapshot, {x: 1}), code('FORBIDDEN')); assert.equal(app.reads, before); app.time(44_000); await app.chunk(snapshot); app.time(45_101); await assert.rejects(app.chunk(snapshot), code('CANCELLED')); assert.equal(app.bot.listenerCount('blockUpdate'), 0); const fresh = await app.snapshot(); app.bot.game.dimension = 'the_nether'; await assert.rejects(app.chunk(fresh), code('CANCELLED')); }); test('voxel reads cancel after at most 2048 reads on deadline, transport and changing loaded entry', async t => { for (const kind of ['deadline', 'transport', 'unload', 'replace', 'block', 'respawn', 'move', 'context', 'offline', 'unwatch']) { await t.test(kind, async t => { const app = fixture(t, {height: 64}), snapshot = await app.snapshot(); const before = app.reads; let disconnected = false; setImmediate(() => { if (kind === 'deadline') app.time(5000); if (kind === 'transport') disconnected = true; if (kind === 'unload') app.columns.delete('-1,-2'); if (kind === 'replace') app.columns.set('-1,-2', app.column()); if (kind === 'block') app.bot.emit('blockUpdate', null, {position: {x: -1, z: -17}}); if (kind === 'respawn') app.bot.emit('respawn'); if (kind === 'move') app.bot.entity.position.x = 32; if (kind === 'context') app.set_context(undefined); if (kind === 'offline') app.context.bot_status = 'offline'; if (kind === 'unwatch') app.stream.world_unwatch({watch_id: snapshot.watch_id}, 2100); }); await assert.rejects(app.chunk(snapshot, {}, () => { if (disconnected) throw Object.assign(new Error(), {code: 'SESSION_STALE'}); }), code(kind === 'deadline' ? 'TIMEOUT' : kind === 'transport' ? 'SESSION_STALE' : kind === 'offline' ? 'BOT_NOT_ONLINE' : 'CANCELLED')); assert.equal(app.reads - before, 2048); }); } }); test('two voxel requests may run independently of four surface requests', async t => { const app = fixture(t, {height: 64}), snapshot = await app.snapshot(); const pending = [app.chunk(snapshot), app.chunk(snapshot)]; await assert.rejects(app.chunk(snapshot), code('RATE_LIMITED')); assert.equal((await Promise.all(pending)).length, 2); await app.chunk(snapshot); }); test('cache shares surface revisions, returns detached data and invalidates on block events, unload and idle', async t => { const app = fixture(t), snapshot = await app.snapshot(); const first = await app.chunk(snapshot), before = app.reads; first.palette[0].name = 'mutated'; first.sections.length = 0; const second = await app.chunk(snapshot); assert.equal(app.reads, before); assert.equal(second.palette[0].name, 'stone'); assert.equal(second.sections.length, 2); app.bot.emit('blockUpdate', null, {position: {x: -1, z: -17}}); const third = await app.chunk(snapshot); assert.notEqual(third.revision, second.revision); const updated = await app.snapshot({world_generation: snapshot.world_generation, known: snapshot.loaded, watch_id: snapshot.watch_id}); assert.equal(updated.loaded[0].revision, third.revision); app.columns.delete('-1,-2'); await assert.rejects(app.chunk(updated), code('CANCELLED')); app.columns.set('-1,-2', app.column()); const reloaded = await app.chunk(updated); assert.notEqual(reloaded.revision, third.revision); app.stream.world_unwatch({watch_id: snapshot.watch_id}, 2100); await assert.rejects(app.chunk(snapshot), code('CANCELLED')); }); test('voxel LRU retains at most eight columns', async t => { const app = fixture(t, {height: 1}); for (let z = -3; z <= -1; z++) for (let x = -2; x <= 0; x++) app.columns.set(`${x},${z}`, app.column()); const snapshot = await app.snapshot(); const coordinates = snapshot.loaded; for (const {x, z} of coordinates) await app.chunk(snapshot, {x, z}); const before_cached = app.reads; await app.chunk(snapshot, {x: coordinates[8].x, z: coordinates[8].z}); assert.equal(app.reads, before_cached); await app.chunk(snapshot, {x: coordinates[0].x, z: coordinates[0].z}); assert.equal(app.reads - before_cached, 256); }); test('unknown interior states and excessive palettes fail without caching partial data', async t => { const app = fixture(t, {minY: 0, height: 64}); let unknown = true; app.columns.set('-1,-2', app.column((_x, y) => y === 0 && unknown ? 999_999 : registry.blocksByName.stone.defaultState)); const snapshot = await app.snapshot(); await assert.rejects(app.chunk(snapshot), code('INTERNAL_ERROR')); unknown = false; assert.equal((await app.chunk(snapshot)).sections.length, 4); app.bot.registry = {blocksByStateId: Object.fromEntries(Array.from({length: 8193}, (_, i) => [i, {name: `block_${i}`}]))}; app.columns.set('-1,-2', app.column((x, y, z) => Math.min(8192, y * 256 + z * 16 + x))); await assert.rejects(app.chunk(snapshot), code('INTERNAL_ERROR')); app.bot.registry.blocksByStateId[8192] = app.bot.registry.blocksByStateId[0]; app.columns.set('-1,-2', app.column((x, y, z) => (y * 256 + z * 16 + x) % 8192)); assert.equal((await app.chunk(snapshot)).palette.length, 8192); }); test('invalid generic registry properties are rejected and maximum height reads every cell', async t => { const app = fixture(t, {minY: -4096, height: 4096}), snapshot = await app.snapshot(); const before = app.reads, chunk = await app.chunk(snapshot); assert.equal(app.reads - before, 4096 * 256); assert.equal(chunk.sections.length, 256); assert.equal(chunk.sections[255].y, 255); assert.equal(decode(chunk)(15, -1, 15).name, 'stone'); for (const states of [Array.from({length: 33}, (_, i) => ({name: `p${i}`, type: 'bool', num_values: 2})), [{name: 'age', type: 'int', num_values: 1, values: ['NaN']}], [{name: 'shape', type: 'enum', num_values: 1, values: ['x'.repeat(65)]}]]) { app.bot.registry = {blocksByStateId: {0: {name: 'bad', minStateId: 0, states}}}; app.columns.set('-1,-2', app.column(() => 0)); await assert.rejects(app.chunk(snapshot), code('INTERNAL_ERROR')); } });