Driving a real client (BeDD)
bedboo normally is the client: it logs in to Bedrock Dedicated Server over RakNet or NetherNet and sends every
packet itself. With transport: 'bedd' it drives a real Minecraft Bedrock 1.26.51.01 client instead, hosted by
Be.D.D. (Bedrock Deep Dive, a LeviLamina client mod). The same script then runs
against the game window on your desk: you watch the local player walk, look, dig, place and chat as the script says.
const bedboo = require('bedboo')
const bot = bedboo.createBot({ transport: 'bedd', bedd: { pipe: '\\\\.\\pipe\\BeDD' } })
bot.once('spawn', () => bot.chat('hello from bedboo'))Choosing between the modes is only transport; direct BDS mode (raknet, nethernet) is unchanged. The differences
of this mode, entry by entry: BeDD mode.
Setup
Windows only (BeDD's pipes are Windows named pipes); Node 24 or later.
-
Install BeDD into a Minecraft Bedrock 1.26.51.01 instance (see the BeDD docs;
clone-instance.ps1makes a separate instance,Start-BeDDGame.ps1 -Instance <name>starts it). -
Turn the bedboo features on in the instance's
mods\BeDD\config\config.json. Every one of them is off by default:key default what it enables bedboo needs it for pipeNameBeDDthe control pipe \\.\pipe\<pipeName>; the tap pipe is\\.\pipe\<pipeName>-tapbedd.pipe(the tap name is derived)tapfalsethe tap pipe: every clientbound packet, an echo of every serverbound packet, markers, a join journal everything (without it bedboo cannot attach) tapLoopbacktruetapping singleplayer / LAN-host worlds (loopback link, re-serialized packets) singleplayer tapJournalMiB128size cap of the join journal replayed to a late attach attaching mid-session ( complete:falsepast the cap)gameHooksfalsethe world,lookandinputops (hooks in the game's own input pipeline)precise movement and looking (without it: virtual keys and mouse) allowSendfalsethe sendop for the allowlisted packet ids (sendAllow, default[9, 77]= text, command_request)bot.chat, whispers, commandsallowStackfalsethe stackop: item_stack_request submitted through the client's own ItemStackNetManagerinventory: clickWindow, transfer, craft, deposit/withdraw, equip from the main inventory, creative pipeInstances4how many control-pipe clients may be connected at once bedboo next to BeDD.psm1 and other harnesses A config for bedboo:
json{ "pipeName": "BeDD-1.26.51.01-hi20", "tap": true, "tapLoopback": true, "gameHooks": true, "allowSend": true, "allowStack": true, "pipeInstances": 4, "loopbackOnly": true }loopbackOnly(BeDD's own key) keeps the client on servers of the same PC (127.0.0.1). The client joins BDS 1.26.51 over NetherNet, so start the server withtransport=nethernet; NetherNet joins go through Minecraft's online signalling service, so no other Minecraft game on the PC may start while the client joins and plays (a later sign-in knocks the session out: "Multiplayer Connection Failed … NetherNet"). -
Start the game and get the player into a world (a server, a LAN world or singleplayer): bedboo attaches to a client that is already playing, it does not log in.
-
createBot({ transport: 'bedd', bedd: { pipe: '\\\\.\\pipe\\BeDD-1.26.51.01-hi20' } }).spawnfires once the join journal has been replayed; from then onbot.*is the client's state.
The bedd_follow.js example makes the client follow the nearest other player:
node examples/bedd_follow.js --pipe \\.\pipe\BeDD-1.26.51.01-hi20How it works
The real client stays authoritative for everything it sends: login, resource packs, the loading screen, chunk
requests, player_auth_input every tick (physics included), block actions, inventory transactions and item stack
requests. bedboo never forges those packets. Its whole stack still runs, and what it would have sent becomes an
intent for the client:
script → mineflayer API → plugins → _client.queue → router ──(JSON ops)──────→ \\.\pipe\<pipeName> → the game's input pipeline
bot.* state ← plugins ← decoder ← tap reader ←──(binary frames)──── \\.\pipe\<pipeName>-tap ← every packet the client gets / sends- State in: the tap pipe carries every clientbound packet the client receives, an echo of every serverbound packet
it sends, and JSON markers (
HELLO,LINK_UP/LINK_DOWN,REPLAY_BEGIN/REPLAY_END,DROPPED). bedboo decodes them with bedrock-protocol exactly as in direct mode, sobot.entities, the world, inventory, chat and every event come from the real session. A join journal (sincestart_game, compacted: the latest chunk per position, the latest attributes per entity, removed entities pruned) is replayed to every new tap client, so bedboo can attach mid-session. If bedboo falls more than 32 MiB behind, BeDD sendsDROPPEDand closes; bedboo reconnects and gets a full replay. - Self state:
bot.entityposition, velocity, yaw, pitch and onGround follow the echoedplayer_auth_input(eye − 1.62).physicsTickandmovefire once per client tick; bedboo's own physics engine does not step. - Intents out: a router diffs what bedboo would have sent and calls BeDD's control ops:
input(move vector, jump, sneak, sprint, with a dead-man TTL),look(absolute yaw/pitch),slot,button(attack/use/pick, released on block change for digging),action(drop, inventory, …),send(allowlisted text/command_request bytes) andstack(item_stack_request through the client's ItemStackNetManager). Input is applied only while the game has the mouse and shows no menu. - Negotiation: at attach, bedboo pings the control pipe and probes each op with no arguments:
unknown op→ absent,code:'disabled'→ present but turned off in config.json,ok→ present.bot.bedrock.bedd.capsholds the result andbot.bedrock.bedd.supports(name)answers per mineflayer API.
Authority table
| class | packets | handling |
|---|---|---|
| clientOwned | login, client_to_server_handshake, request_network_settings, resource_pack_client_response, client_cache_status, client_cache_blob_status, request_chunk_radius, subchunk_request, serverbound_loading_screen, set_local_player_as_initialized, network_stack_latency, tick_sync, player_skin, emote_list, respawn, player_action (dimension_change_ack / start_item_use_on / stop_item_use_on), animate, interact mouse_over_entity | dropped by the router (the client sends its own); counted |
| translated | player_auth_input (move_vector and flags → input; yaw/pitch → look; block_actions start_break / abort_break / stop_break → button attack), mob_equipment (→ slot), inventory_transaction (item_use click_block / click_air, item_release, item_use_on_entity → look + button), interact open_inventory (→ action inventory), container_close (→ ESC), item_stack_request (→ stack) | translated into control ops |
| shared | text, command_request | sent as bytes through send (the command uuid is kept, so command_output correlates); fallback: T / SLASH key, text, ENTER |
| unsupported | book_edit, block_pick_request, crafting_event, set_default_game_type, everything else | BeddUnsupportedError (code: 'ERR_BEDD_UNSUPPORTED') |
Coverage
"Caps" names the BeDD ops the API needs; without them bot.bedrock.bedd.supports(name) is false and the call throws
or rejects with ERR_BEDD_UNSUPPORTED (or uses the stated fallback).
| API | works | caps (fallback) |
|---|---|---|
spawn, login, game events, bot.game, time, weather, health, food, experience | yes, from the tap | tap |
bot.entities, bot.players, blocks and world (blockAt, findBlocks, raytrace, waitForChunksToLoad) | yes, from the tap | tap |
| chat / message / messagestr events, title, bossbar, scoreboard, tablist, sound, particle, explosion | yes, from the tap | tap |
bot.inventory, heldItem (reads) | yes | tap |
bot.entity position / velocity / yaw / pitch / onGround | yes, from the echoed player_auth_input (1-2 ticks behind) | tap |
setControlState, getControlState, clearControlStates | yes | input (virtual W/A/S/D/SPACE/SHIFT/CTRL keys) |
| mineflayer-pathfinder, collectblock, pvp | yes, through controls, look and attack | input, look |
look, lookAt | yes; resolve when the echo converges (< 0.5°) | look (closed-loop raw mouse) |
setQuickBarSlot, equip of a hotbar item | yes | slot (keys 1-9) |
chat, whisper, commands | yes, uuid preserved | send (T/SLASH + text + ENTER, uuid lost) |
dig, stopDigging | yes; crack animation on the client, completes on update_block → air | look, button (mouse) |
placeBlock, activateBlock, activateItem, deactivateItem, consume, bows | yes (look, then use) | look, button |
attack, activateEntity, mount, dismount | yes | look, button, input |
toss of the held item | yes | action (drop / dropAll) |
openContainer, openChest, openFurnace, closeWindow | yes (close = ESC) | look, button |
clickWindow, moveSlotItem, transfer, deposit, withdraw, putAway, craft, equip from the main inventory / armor / off-hand, unequip, tossStack, creative.setInventorySlot, anvil / enchanting / villager / smithing | yes, through the client's ItemStackNetManager | stack (none: ERR_BEDD_UNSUPPORTED) |
writeBook, updateSign, respawn, bed UI sleep / wake | no (screen-level) | — |
bot.end, quit | detach only; the client stays in its world | — |
auth, username, host, port, viewDistance options | ignored with a warning (the client owns them) | — |
Notes: activateItem(true) (off-hand) uses the main hand, the client has no off-hand use button; activateItem of an
item that is not used by holding (throwables, a fishing rod) is one click; bot.respawn() is a no-op with a warning
(the user clicks Respawn, so respawn: true never throws from the health listener); bot.craft keeps upstream's
new Error(err) wrapping, so check err.message there when the surface did not refuse it first.
Degraded: pathfinder sees 1-2 ticks of lag (prediction is overwritten by the echo); the client's own raycast decides
what a click hits, so an action can land on an adjacent block or entity (bedboo sees it on update_block / entity
events); flight via a double-tap jump; input pauses while a screen is open; a late attach depends on the journal
(complete:false → chunks missing until re-entered); a world change on the client ends the bot (LINK_DOWN).
Singleplayer and LAN worlds
With tapLoopback (on by default once tap is), BeDD also taps a world the client hosts itself: singleplayer or a
LAN world. Those packets never hit the network, so BeDD re-serializes them (Packet::write, frame flag
reserialized) and filters them to the local player, so LAN guests' traffic is not mixed in. Everything in the
coverage table works the same; the HELLO marker says link.type: 'local'.
Security
- The tap exposes the session. A tap client reads everything the logged-in player sees and does: chat, positions,
inventory, the world. The tap pipe accepts one local client of the current Windows user (
PIPE_REJECT_REMOTE_CLIENTS, current-user DACL), and it only exists whentapis on. sendandstackact as the account.sendposts chat and commands as the logged-in player,stackmoves its items. Both are off by default (allowSend,allowStack);sendis limited to thesendAllowids ([9, 77]), and login,player_auth_input, item stack and similar ids (1, 30, 144, 147) are always refused.- Turn these on only in instances you use for automation, and never let a manager or remote tool toggle them.
Options and bot.bedrock.bedd
createBot({ transport: 'bedd', bedd: { pipe, tapPipe?, connect?, timeoutMs?, modules? } }):
| option | |
|---|---|
bedd.pipe | the control pipe, \\.\pipe\<pipeName> (default $BEDBOO_BEDD_PIPE, else \\.\pipe\BeDD) |
bedd.tapPipe | the tap pipe (default <pipe>-tap) |
bedd.connect | (path) => Duplex, to connect over something else (e.g. a unix socket) |
bedd.timeoutMs | control request timeout (default 5000 ms; an op's own timeoutMs gets 2 s more) |
bedd.modules | the lib/bedd modules to attach (default, in this order: authority, router, router_actions, follower, surface, inventory) |
bot._bedrock.mode === 'bedd'.bot.bedrock.bedd.caps:{ [op]: 'present' | 'disabled' | 'absent' }fortap,world,look,input,slot,button,action,send,stack(and the protocol-1 opskey,text,mouse,raw, … oncepinganswers). Without a control pipe every op isabsentand the bot only observes.bot.bedrock.bedd.supports(name): whether a mineflayer API ('dig','craft','creative.setInventorySlot') or a BeDD op works with these caps.bot.bedrock.bedd.observed:{ yaw, pitch, tick }of the last echoedplayer_auth_input,nullbefore the first.- Errors:
BeddUnsupportedError(code: 'ERR_BEDD_UNSUPPORTED',bedd: true,feature,missing); an unsupported packet also emits'bedd_unsupported_packet'{ name, feature }onbot, once per name. - Client events besides the usual ones:
'serverbound'{ name, params, reserialized }(what the client sent),'bedd_marker'(tap markers) and'warning'(a packet that did not decode; the stream goes on).
Roadmap
- A drive / puppet mode (suppress, inject and pin the pose), once pinning is shown to survive client reconciliation.
- Screen-level ops: the respawn button, signs and books.
- A
connectop so bedboo can make the client join a given server. - A second split-screen subclient.