Inventory and windows
How bedboo keeps mineflayer's Java-layout inventory API (bot.inventory, bot.currentWindow, slot numbers,
clickWindow, transfer, deposit / withdraw, events) on top of Bedrock's server-authoritative inventory, and the
few visible consequences. Observed on BDS 1.26.51.1 (protocol 2193).
clickWindow is emulated with item stack requests
- Affects:
bot.clickWindow, and everything built on it (transfer,putAway,moveSlotItem,putSelectedItemRange,toss,tossStack,equip,unequip,simpleClick, windowdeposit/withdraw) - Java: the client sends
window_click(slot, button, mode); the server runs the click and syncs the window. - Bedrock: there is no click packet. The client sends
item_stack_requestactions (take,place,swap,drop,destroy,craft_creative, …) between explicit container slots (hotbar,inventory,armor,offhand,cursor,container,barrel,shulker, furnace / brewing / station ui slots) and the server answers with anitem_stack_response(ok + resulting counts / stack ids, or an error code). It does not resend the inventory after a request. Cursor and cross-container moves andcraft_creativeare only accepted while a screen is open (status 7 / 50 otherwise). - bedboo:
lib/bedrock/click.jsplans the Java (vanilla 1.21doClick/quickMoveStack) result of each click mode as stack request steps:- mode 0 (left / right, outside = drop), mode 1 (shift, with Java's per-menu rules including armor → armor slot; the station rules — furnace fuel vs ingredient, brewing bottles / ingredient / fuel, enchanting lapis, loom, cartography, smithing, beacon — come from the window specs and use item-name lists, as there is no smelting / brewing recipe data), mode 2 (number keys, 40 = off-hand), mode 3 (creative clone) and mode 4 (drop key).
- The model predicts the result (one
updateSlotper changed slot, like Java 1.17+), sends one request per click and waits for the response, which updates counts and stack ids (an answer that arrives after the 5 s timeout is still applied). Clicks may overlap: see overlapping requests. - A rejected request is rolled back and
clickWindowthrows mineflayer's own messageServer rejected transaction for clicking on slot <s>, on window with id <id>.(no response:Server didn't respond to transaction …). - Clicks on
bot.inventoryopen the player's own inventory screen (interact open_inventory) and close it 100 ms after the last click when the cursor is empty. - Modes 5 (drag) and 6 (double click) throw "not implemented", as prismarine-windows does. Clicking a crafting /
trade result slot needs the craft request of
bot.craft(crafting) and throws. - Java's server silently ignores an invalid placement (e.g. dirt into an armor slot); the planner applies Java's
Slot.mayPlacefor armor slots and result slots, and anything else BDS refuses surfaces as the rejection error.
Overlapping item stack requests
- Affects:
bot.clickWindowand everything built on it when calls overlap (clicks not awaited, armor-manager / tool / auto-eat equips racing user code,transferwhile tossing), windowdeposit/withdraw - Java: the client applies each click to its window at once (1.17+ prediction) and the server resyncs a wrong
slot with
set_slot/window_items, so overlapping clicks self-heal. - Bedrock: each click is an
item_stack_request; a request planned on an earlier request's prediction names the pending request id as the slot's stack id; BDS answers in send order; a rejected request (e.g. status 55 dirt into the off-hand, then 49 for the request that named it) gets only the error response — nothing is resent. - bedboo: the inventory model keeps, per slot, the requests in flight that changed it.
- A settled request hands its result for the slot to the next request on that slot (the server's count and stack id, or its own starting state when rejected) instead of overwriting that request's prediction; only the last request on a slot writes the slot.
- Chained rejections therefore end at the state before the first request — or at what the server last sent for the
slot while they were in flight (a pickup,
/give). A request planned on such a server update keeps it as its base, and a resend of the very state the requests were planned on leaves their predictions shown (inventory resync). - Late (timed-out) answers are applied in order. Answers for a container window that was closed meanwhile no longer touch the next window.
- A click's prediction is committed synchronously when the inventory screen is open (and re-planned after waiting for the screen otherwise), so clicks issued without awaiting the previous one build on it, like Java's client-side click.
A click raced by a server-side change of its slots is planned again
- Affects:
bot.clickWindowand everything built on it (transfer,deposit/withdraw,moveSlotItem,equip,toss/tossStack,putAway, crafting) while drops are picked up,/giveruns, or a hopper or another player changes a slot the request touches - Java:
window_clickis applied by the server to its own current state (and resynced); it never fails. - Bedrock: an item stack request names each slot's stack id as the client knows it; when the server changed the
slot meanwhile (a pickup:
inventory_transactionnormal with a new stack id) BDS rejects the whole request (status 49 stack id mismatch / 50). Measured: a chest deposit / withdraw loop with a console/giveevery 400 ms gave 6 rejections in 88 operations ("Server rejected transaction for clicking on slot 27 …"). - bedboo: a server packet that sets a slot of a request in flight to another stack than the one the request was
planned on marks it raced (
lib/bedrock/inventory_model.js); a raced request that BDS rejects is planned again once on the server's state and sent again (item stack requests are atomic: the rejected one changed nothing). Requests chained on it (planned on its prediction) are retried the same way. A rejection without such a change still throws mineflayer's message.
Closing a window puts the carried item and station inputs back
- Affects:
bot.closeWindow, windowclose() - Java: on close the server returns the carried item (and crafting grid / anvil / enchanting inputs) to the
player inventory (
placeItemBackInInventory). - Bedrock: BDS keeps them: the carried item stays in the
cursor(ui slot 0), station inputs stay in their ui slots (observed aftercontainer_close). Java < 1.17 followsclose_windowwith a no-op click; Bedrock has neither. - bedboo: like upstream,
closeWindowclearsbot.currentWindowand emitswindowCloseat once. It then moves those items with one stack request following Java's rule (selected hotbar slot, off-hand and stacks with room first, then the first empty slot, the rest is dropped) and sendscontainer_close { window_id, window_type, server: false }(with nothing to put back, onlycontainer_close). The returned promise resolves on the server's echo, and openers wait for it plus 100 ms (2 ticks) before activating the next container: BDS silently drops a container activation that arrives in the tick its previous container closed (nocontainer_open, no resync). Block entities (chests, furnaces, brewing stands, …) keep their contents as on Java.bot.closeWindow(bot.inventory)closes the inventory screen if the bot opened it.
Opening a window while another one is open
- Affects:
bot.openBlock,bot.openEntity,bot.openContainer/openChest/openDispenser, the station openers (openFurnace,openAnvil,openEnchantmentTable,openVillager,craftwith a table),bot.activateBlock/bot.activateEntity/bot.activateEntityAt,windowClose - Java:
ServerPlayer.openMenucloses the current menu first (close_window→ mineflayer emitswindowClose), then opens the new one; code that never closes a chest (mineflayer-collectblock'semptyInventory) works. - Bedrock: BDS ignores an activation that would open a container while the player has one open — no
container_open, nocontainer_close— soopenBlockwould wait foronce()'s 20 s and throw (verified live: chest then barrel, and the same chest twice). Other activations (a lever) are processed with a container open and the container stays open, like Java. - bedboo:
openBlock/openEntity, the station openers, andactivateBlock/activateEntity(At)of a container or station block (chests, barrels, shulker boxes, furnaces, crafting / enchanting / smithing tables, anvils, hoppers, dispensers, droppers, brewing stands, beacons, grindstones, stonecutters, looms, cartography tables, crafters, lecterns) or of a trading / storage entity (villagers, wandering traders, chest / hopper minecarts, chest boats) first close the open container window withbot.closeWindow(windowCloseat once, carried and station items put back,container_close, the server's echo), then wait BDS's 100 ms reopen delay and activate. Other activations leave the window open.
A container whose entity or block is gone
- Affects:
windowClose,bot.currentWindow, trading (bot.openVillager,bot.trade) and station windows (anvil, enchanting table, furnace, …) whose trader dies or despawns or whose block is broken - Java: the server closes the menu (
stillValidfails) and returns the items in the input slots. - Bedrock: BDS sends only
remove_entity(or the block update): nocontainer_close, nothing returned. Items in the trade / station input slots are in the player'suicontainer; BDS refuses to move them out of that dead screen (status 49, invalid source slot) and keeps them there — the next window using those slots shows them. - bedboo: the inventory plugin treats the container's entity going away (
entityGoneof the window's entity) or its block becoming another block (a lit furnace is still a furnace, a chipped anvil still an anvil) like a server close:windowClose,currentWindownull, thencontainer_close(no doomed return request; the next opener waits for its echo). Moving out of range does not close a window (BDS sends nothing; not handled).openVillagerreturns items it finds in the trade input slots at once (a fresh Java trade window is empty), so a dead trader's payment comes back with the next trade window.update_trade.villager_unique_idis a zigzag varint that bedrock-protocol 3.60.1 reads unsigned; it is decoded ininventory.js.
Window types: Java types for Bedrock containers
- Affects:
windowOpen,bot.currentWindow,bot.openBlock,bot.openEntity,bot.openContainer(openChest,openDispenser), the station openers - Java:
open_windowcarries the Java window type;window_itemsthe slots. - Bedrock:
container_open { window_id, window_type, coordinates, runtime_entity_id }+inventory_contentof that window id; every chest-like block (chest, trapped / ender / copper chest, barrel, shulker box) iswindow_type: 'container'and is told apart by the block atcoordinates; station inputs (crafting grid, anvil, enchanting, grindstone, stonecutter, loom, cartography, smithing, beacon, trading) live in theuicontainer. Bedrock sends a window's contents with its own window id right aftercontainer_open, and syncs the player inventory as its own container (inventory). - bedboo: the registry
bot._bedrock.windowsmaps every container type to the Java window type and slot layout of Java 1.21 (minecraft:generic_9x3/9x6/3x3,hopper,shulker_box,crafting,furnace,blast_furnace,smoker,brewing_stand,enchantment,anvil,merchant,beacon,grindstone,stonecutter,loom,cartography,smithing(1.20+ template / base / addition / result),crafter_3x3,lectern,HorseWindow), soopenContainer's and the station openers' type checks pass unchanged. The player part of a window mirrorsbot.inventory. An early content for a reused window id is buffered and used, and a trailing update after a close always reachesbot.inventory.openContaineralso accepts the Bedrock block namesundyed_shulker_boxand the copper chests. The horse and lectern windows are not verified on BDS yet. Opening a window while a container window is open closes that one first, like Java's server (window switching).
updateSlot fires on changes only
- Affects: window
updateSlot/updateSlot:<n>events,heldItemChanged - Java: every
set_slot/window_itemscallsupdateSlot(even for unchanged slots). - Bedrock: there is no
set_slot. BDS resends every inventory window (inventory_content× 4) after each command, gamemode change and rejected transaction. - bedboo: a resend that only changes stack ids replaces the Item silently;
updateSlot(and thusheldItemChanged) fires when type, count, metadata or NBT change.inventory_slot/inventory_contentof the held slot emitheldItemChanged.
No window resync request like Java's
- Affects:
bot._syncWindow(internal) - Java (1.17.1+): an impossible click makes the server resend the window.
- Bedrock: BDS does not resend anything after a rejected item stack request (verified live: only the error response arrives), and every request is answered.
- bedboo:
_syncWindowresolves at once; the model rolls the rejected prediction back itself (see overlapping requests). A slot found out of date is healed by an inventory resync.
Inventory resync (inventory_mismatch)
- Affects:
bot.inventoryafter a desync,clickWindow(and everything built on it), the Bedrock extrabot.bedrock.resyncInventory()(bot.bedrock) - Java: the server resends a window whenever the client's view is stale (state ids).
- Bedrock (measured on BDS 1.26.51.1): a client
inventory_transaction { transaction_type: 'inventory_mismatch' }makes BDS resend the player's whole inventory —inventory_contentof the inventory, armor, ui (cursor) and off-hand windows, thenplayer_hotbar— within a tick, with no other effect. Nothing else does: not (re)opening the inventory screen, not a rejected request (status 49 for a slot whose stack id the server does not have, and the slot stays rejected for every later request), not/clear <player> <item> -1 0. - bedboo: server packets always win (
inventory_content,inventory_slot, server transactions are applied as they come).- When the model finds a player slot out of date it asks for the server's copy (automatic resyncs at most once per
second — one asked for sooner goes out when the second is over). Triggers:
- a request rejected with status 49 that no server packet explains (not raced, not planned on another request that
was rejected). When the resent copy differs from what the request was planned on, a planned request
(
clickWindow,returnItems, craft) is planned again once on it, else the rejection stands (a request that cannot be planned again rejects at once, the resync runs meanwhile); - a server
inventory_transaction(pickup,/give) whoseold_itemis neither the local item nor the new one, for a slot no request is changing; - a response that counts items in a slot the model holds nothing in.
- a request rejected with status 49 that no server packet explains (not raced, not planned on another request that
was rejected). When the resent copy differs from what the request was planned on, a planned request
(
- A resync settles on the answer's
player_hotbar { select_slot: true }(a command's resend ends withselect_slot: false).bot.bedrock.resyncInventory()→Promise<boolean>asks explicitly (true once applied, false without an answer within 2 s); diagnostics inbot._bedrock.inventoryModel.resyncs. - A resend that arrives while requests are in flight and only repeats the server's known copy of a slot (stack id, type, count, metadata, NBT) does not replace their predictions: the server applies them to that state, so a click planned meanwhile builds on the prediction and the responses complete the predicted items (otherwise a swap answered after such a resend would show the old item with the new count).
- When the model finds a player slot out of date it asks for the server's copy (automatic resyncs at most once per
second — one asked for sooner goes out when the second is over). Triggers:
- Pickups: pickups arrive as
take_item_entity+ a serverinventory_transaction(normal: the container slot's old and new item, then aworld_interactionaction), never asinventory_slot. Item counts stay right for plain digging and pickups (merging into hotbar and main-inventory stacks, a full inventory, a chest open, mineflayer-collectblock). A desync (about 1 round in 40) came from a coal stack moved back and forth by overlappingmoveSlotItems while pickups merged into it: a pickup landed on the stack while a take from it and a click planned on that take were in flight; BDS rejected both (49: the stack changed / the request it named was rejected) and the rollbacks wrote the pre-pickup stack back —bot.inventoryshowed 28 coal (stack 236) while the server had 29 (stack 237), and every later click on that stack was rejected. The per-slot chains (overlapping requests) fix this: a rejected request goes back to what the server last sent for the slot, and a request planned on a server update keeps it as its base. The resync above heals any desync that remains.
The inventory screen after a nether portal trip
- Affects:
bot.equip(armor, off-hand),bot.toss, crafting from the inventory grid,bot.moveSlotItem— every request that needs the player's inventory screen (lib/bedrock/inventory_model.jsscreen.open()) - Java: inventory clicks need no screen.
- Bedrock: item stack requests on the player's inventory need the inventory screen open (
interact open_inventory→container_open window_type inventory). Measured on BDS 1.26.51.1: while the player stands in a nether portal, and for about 1–2 s after it stepped out of the arrival portal, BDS silently dropsopen_inventory(nocontainer_open, nothing else). A command teleport (/execute in nether run tp) does not do this. - bedboo:
screen.open()repeats theopen_inventoryrequest every 1.5 s until the screen opens (timeout 8 s). Without this the first equip after a portal trip failed with "the server did not open the inventory screen". Standing inside a portal still fails (vanilla behaviour): step out of the arrival portal first.
Held item selection
- Affects:
bot.quickBarSlot,bot.setQuickBarSlot,heldItemChanged - Java:
held_item_slotboth ways. - Bedrock: the server selects with
player_hotbar { selected_slot, select_slot }(select_slot: falseis only informative); the client announces its selection withmob_equipmentcarrying the exact held item. - bedboo: as described;
bot._bedrock.heldNetworkItem()returns the exact wire item of the held slot from the inventory model (used by everyinventory_transaction).
A server hotbar select can be older than the bot's own selection
- Affects:
bot.quickBarSlot,bot.heldItem,heldItemChangedright aftersetQuickBarSlot - Java:
held_item_slotfrom the server and the client'sheld_item_changedo not cross in practice. - Bedrock: BDS answers console commands (
/clear,/replaceitem,/give) withplayer_hotbar { select_slot: true }carrying its selected slot. When such a command runs before BDS has read the bot's latestmob_equipmentselection, the select is stale: following it switches the bot back to the previous slot while BDS then applies the bot's selection — client and server stay out of sync. - bedboo: a server select (
player_hotbaror the bot's ownmob_equipment) that contradicts a selection the bot sent less than 1 s ago is ignored and the bot's selection sent again (the window is not extended by the resend).
Off-hand: only off-hand items
- Affects:
bot.equip(item, 'off-hand'),bot.clickWindowon slot 45, theinventoryexample - Java: any item can be put into the off-hand slot.
- Bedrock: the off-hand only takes items with the off-hand component (shield, totem, arrows, firework rockets,
maps, nautilus shell, …); BDS rejects anything else (status 50). On the wire the off-hand slot is
offhandslot 1 for stack requests (slot 0 is rejected) whileinventory_content/inventory_slotuse index 0. - bedboo: slot 45 maps to
offhand1; equipping a non-off-hand item rejects with the click error. Withexamples/inventory.js,equip off-hand dirtis therefore refused;equip off-hand arrowworks (the item lists then end witharrow x 16, as slot 45 is listed last).
Creative inventory: craft_creative instead of set_creative_slot
- Affects:
bot.creative.setInventorySlot,bot.creative.clearSlot,bot.creative.clearInventory - Java:
set_creative_slotwrites any item (with any NBT) into a player slot. - Bedrock: a creative player creates items with an item stack request in the inventory screen:
craft_creative { item_id: <creative_content entry id> }(+placefromcreative_outputslot 50 with the request id as stack id);destroydeletes. Only the items of the server's creative inventory (creative_content: every base item plus the listed NBT variants, e.g. enchanted books) can be created — arbitrary NBT (custom names, other enchantments, damage) cannot; a legacy creativeinventory_transactionis rejected by BDS (it resends the inventory).craft_creativeis refused while a container is open (status 7). - bedboo: same signature and guards as upstream (same item → no-op, a second concurrent call on the slot →
Setting slot <n> cancelled …). Replacing an occupied slot isdestroy+craft_creative+placein one request; slot 45 (off-hand) is accepted too; an item not in the creative inventory, slot 0 (crafting result) or an open window reject with an explanatory error.waitTimeoutis unused (the server always answers). The creative index isbot._bedrock.creativeItems. Plain items whose creative entries all carry NBT: see creative defaults.
Plain items from the creative inventory
- Affects:
bot.creative.setInventorySlotwith a plainfirework_rocket,firework_starorbanner - Java: any item, any NBT.
- Bedrock: the creative inventory lists these only with NBT (rockets
Fireworks {Flight: 1, Explosions: []}, bannersType: 0, stars per colour). - bedboo: an item without NBT that has no exact entry gets the entry with the fewest NBT tags when exactly one has the fewest (the default variant); the slot then holds that NBT. Enchanted books (all variants alike) stay refused.
Item use, eating and the shield
- Affects:
bot.activateItem,bot.deactivateItem,bot.consume,bot.usingHeldItem - Java:
use_item(hand, sequence, rotation) /block_digstatus 5; eating ends withentity_status9. - Bedrock:
inventory_transaction item_use click_air(held item = the server's exact copy; no rotation field — the direction is the yaw / pitch of the same tick'splayer_auth_input),item_release release; one click_air eats: after 32 ticks the server sendscompleted_using_item { use_method: 'eat' }and the shrunk stack. Shields have no use action: a Bedrock player blocks while sneaking with a shield in either hand. On peaceful the food level never drops (a hunger effect only empties the saturation). - bedboo:
activateItem(offHand)sends click_air (hand: off_hand+ the off-hand item foroffHand); with a shield it holds sneak instead (deactivateItemreleases it). That sneak is the shield's own:clearControlStates()or a sneak-control release — mineflayer-pathfinder does both under mineflayer-pvp — leave it, as they leave a raised shield on Java;getControlState('sneak')reports it.completed_using_item(or a held-stack change, like upstream) finishesconsume()and clearsusingHeldItem. Java'sset_cooldownhandling has no counterpart: Bedrock item cooldowns are client side (no server packet in protocol 2193).
Buckets stack
- Affects: bucket handling (picking up lava / water),
bot.heldItemafterbot.activateBlockwith a bucket - Java: a bucket stack of 1; using an empty bucket on a source turns the held item into the filled bucket.
- Bedrock: empty buckets stack (16).
bot.activateBlock(<lava source>)with a stack of 3 buckets in the hand leaves 2 empty buckets in the hand and puts thelava_bucketinto another slot. - bedboo: nothing changes in the library (the server's inventory update is mirrored); count
lava_bucket/water_bucketitems instead of checking the held item.