Movement and physics
How player movement, server corrections, looking, sprinting, flight, elytra gliding and vehicle control differ
between Java and Bedrock, and how bedboo's physics engine reproduces the Bedrock rules. Measured on BDS 1.26.51.1
(protocol 2193, bedrock-protocol 3.60.1): the bot recorded every player_auth_input it sent and every
correct_player_move_prediction; on a server started with player-position-acceptance-threshold=0.001 and every
sent position offset by +5 blocks the server never adopts the client position, so each correction (about every
6 ticks) carries the server's own simulation state. The engine was fitted until it reproduces those states offline.
Movement engine
- Affects:
bot.physics(and every field below),bot.physics.simulatePlayer,bot.entity.velocity, mineflayer-pathfinder's simulations,bot.physics.stepHeight,negligeableVelocity,soulsandSpeed,honeyblockSpeed,honeyblockJumpSpeed,ladderMaxSpeed,playerHeight(sneaking),slowFalling,sprintingUUID. - Java: prismarine-physics (Java 1.8–1.21 rules), client-authoritative position packets.
- Bedrock: movement is server-authoritative with rewind: the client sends one
player_auth_inputper tick (eye-level position, move vector, yaw, input flags,delta), BDS simulates the same inputs and corrects positions that move further than its own result (or by more than the acceptance threshold). Measured rules that differ from Java:- float32 positions/velocities; the server takes the yaw from the float32 degrees on the wire and uses precise sine/cosine (no 65536-entry table): a diagonal walk only matches with precise trigonometry;
- the move vector is normalized (a diagonal is (0.7071, 0.7071)), × 0.3 while sneaking, × 0.98;
- order of one tick: speed + friction from the previous tick's ground state → move vector → jump → climb → stuck
blocks → sneak edge avoidance → collide (Y, X, Z) with auto-step → step-on slowdown / bounce → gravity + drag →
friction; the reported
deltais the velocity at the end of the tick (standing still: (0, −0.0784, 0)); - friction comes from the block at floor(pos − 0.5), else the block supporting the box, else 0.6 — standing on an edge or walking off a ledge never uses air's 0.9;
- auto-step 0.5625 (Java 0.6);
negligeableVelocityis 1e-6 (the velocity is only zeroed when |v|² < 1e-12); - jump: vy = max(vy, 0.42 + 0.1 × jump boost), sprint-jump +0.2, 10-tick delay while held; honey jump × 0.6 (Java 0.5);
- sneak edge avoidance shrinks the box by 0.025, probes 1.01 × step height down and clips the velocity itself; the
sneaking box is 1.49 high (measured;
SNEAK_HEIGHTinlib/bedrock/constants.js, also used for theheightof crouching players); - climbing: falls at most 0.2/tick (Java 0.15); holding jump climbs 0.2/tick (then gravity: delta 0.1176); pushing against the climbable keeps 0.2 as the end-of-tick velocity (delta 0.2); no horizontal clamp; sneaking holds on;
- collisions treat penetrations below 1e-4 as contact (float32 positions end up slightly inside the faces they rest against);
- water gravity 0.005 (gravity / 16), lava 0.02; bubble columns act per block with the Java values;
- soul sand: ground speed × 0.544 (Java: velocity × 0.4); honey and slime: × (0.4 + 0.2 × |vy|) on the step (Java honey: velocity × 0.4); slime bounces only when landing from the air;
- slow falling only while already falling (vy < 0) — the first tick from rest still uses 0.08;
- using an item: the impulse is clamped and sprinting stops; a raised shield is a plain sneak (see item use slowdown);
- BDS does not simulate the first input it processes after a teleport (see corrections);
- creative flight: see creative flight.
- bedboo:
lib/physics/engine.js(a prismarine-physics 1.11.1 fork) implements these rules with the upstream module shape:bot.physicskeeps the 31 upstream field names, with Bedrock values:stepHeight0.5625,negligeableVelocity1e-6,soulsandSpeed0.544,honeyblockSpeed0.4 (the base of the step-on slowdown),honeyblockJumpSpeed0.6,ladderMaxSpeed0.2,waterGravity0.005,sprintingUUID= Bedrock's "Sprinting speed boost" modifier;simulatePlayer(state, world)accepts prismarine-physicsPlayerStates (pathfinder) and bedboo's own (require('bedboo/lib/physics').PlayerState);require('minecraft-data')(bot.version)resolves to the Bedrock data (upstream API contract) so prismarine-physics'PlayerStateworks;- measured on 60 s walking laps: 0 corrections at the default acceptance threshold, at most 1 per 100 ticks at 0.1.
Server corrections, teleports, knockback, effects
- Affects:
forcedMove,move,bot.entity.position/velocity. - Java: the server sends absolute/relative
positionpackets; the client confirms the teleport. - Bedrock:
move_player(teleport/reset) for teleports,correct_player_move_prediction(position at eye level,delta= end-of-tick velocity,on_ground,tickof the corrected input) for mispredictions,set_entity_motionfor knockback. There are no relative teleports. Measured:move_player.tick,set_entity_motion.tick,mob_effect.tickandupdate_attributes.tickare the last input the server had processed; it applies the change from the next input on, and it does not simulate the first input it receives after a teleport. - bedboo:
- keeps a history of the last
rewind_history_size(+20) inputs; a correction rewinds to its tick, takes the server state and replays the inputs since (forcedMoveis emitted); - teleports: position at the target, velocity 0,
handled_teleportin the next input; inputs already in flight are re-simulated from the target, else the next input is not simulated; - knockback / effects / movement speed with a
tickrewrite the inputs already sent from that tick on and re-simulate them (the server still sends one correction for the input in flight; its state equals bedboo's); - velocity "relative" teleports do not exist;
set_entity_motionsets the velocity.
- keeps a history of the last
Input stream and suspension
- Affects:
physicsTick, movement packets while no chunk is loaded or during loading screens. - Java: position packets only when something changed (or once a second); no packets during the configuration phase; no physics without a chunk.
- Bedrock: one
player_auth_inputper server tick, always (the server drives the player from it); no configuration phase — loading screens, dimension changes and death suspend the stream (bot._bedrock.input.suspend). - bedboo: without a loaded column the bot neither simulates nor emits
physicsTickbut keeps sending its unchanged position; while the input queue is suspended nothing is sent.
Looking
- Affects:
bot.look,bot.lookAt,bot.physics.yawSpeed,bot.physics.pitchSpeed. - Java: the yaw sent to the server turns by at most
yawSpeedper tick;lookresolves once the server got the final rotation. - Bedrock: the rotation is part of every
player_auth_input(yaw, pitch, head yaw, interact rotation and camera orientation); the server moves the player with that yaw. - bedboo: same sensitivity rounding and
forcesemantics as upstream; the new yaw/pitch goes out entirely in the next input (no gradual turn — the movement of that tick already uses it, exactly as the server does);lookresolves once that input was sent (≤ 1 tick), withforceat once.yawSpeed/pitchSpeedkeep their upstream values but are unused.
Sneak / sprint before a click
- Affects:
bot.setControlState('sneak' | 'sprint')followed at once by a click (placeBlock/activateBlockagainst an interactable block,attackwith sprint knockback,useOn);bot._bedrock.physics.controlsSent(). - Java: mineflayer writes
entity_action/player_inputinsidesetControlState, so the next click is handled with the new state. - Bedrock: sneaking and sprinting only exist as
player_auth_inputflags (start_sneaking,sneaking, …), sent once per tick; BDS handles a transaction queued before that input with the old state (verified on BDS 1.26.51.1: a sneak-click on a chest opened it instead of placing the held block). - bedboo: clicks that depend on the state wait for the input that carries it:
useItemOn/useOnEntity(lib/bedrock/transactions.js) awaitbot._bedrock.physics.controlsSent()when a sneak / sprint change is pending; the synchronousbot.attack/useOn/mount(lib/plugins/entities.js) send throughafterControlsSent(fn)(see attacking and using entities). The click is at most one tick (50 ms) later than upstream's; with nothing pending it is queued synchronously. If the input is suspended first (loading screen, dimension change) the click goes out at once; afterendit is dropped (controlsSent()rejects). So a sneak followed at once byuseItemOnagainst a chest places the block instead of opening the chest.
A double-tapped forward sprints
- Affects:
bot.setControlState('forward', …)toggled quickly (mineflayer-pathfinder's short moves, a tap then a hold),bot.entity.position/ corrections while walking. - Java: double-tap sprinting is a client feature; mineflayer never sprints unless the
sprintcontrol is set. - Bedrock: BDS judges the double tap itself from the input's
upkey flag: forward pressed again within 6 ticks of its previous press makes the player sprint (no sprint flag needed) until forward is released (measured on 1.26.51.1: press-to-press 6 ticks sprints, 7 does not; without predicting it, tap 3 ticks → 1 tick off → hold gave 3 corrections in 3 s). Theupflag also drives the movement, so it cannot be held back. - bedboo: the physics plugin predicts it like the vanilla client (which sprints on a double tap on its own): such
a second press sprints (
sprinting/start_sprintingin the input, no sprint key) while forward is held and sprinting is possible (not flying or riding, not sneaking or backing, no item in use, food above 6, not stopped by a wall; a teleport or death ends it). Like any Bedrock sprint it is sticky:setControlState('sprint', false)does not end it while forward stays held — BDS keeps the sprint speed (predicting a walk there costs a correction about every 27 ticks, e.g. under mineflayer-pathfinder, which releases the sprint control every tick while it walks). Every toggle pattern tried had no correction and the server's position equalled the bot's.getControlState('sprint')stays false. CreativeflyToavoids the double tap instead (creative flight).
A sprint lasts until forward is released
- Affects:
bot.setControlState('sprint', false)whileforwardstays held (mineflayer-pathfinder's walking branches, user code that sprints for a while), the bot's speed. - Java: releasing the sprint control sends
stop_sprinting; the player walks from the next tick on. - Bedrock: a sprint is sticky (measured on 1.26.51.1): once the player sprints — sprint flag or a double tap —
BDS keeps the sprint speed while forward is held, also after a
stop_sprinting(it clears thesprintingactor flag but re-sends the movement attribute with its "Sprinting speed boost" until forward is released). The vanilla client behaves the same way (letting go of the sprint key does not stop a sprint). Walking at 0.1 then costs a correction about every 27 ticks. - bedboo: the physics plugin keeps sprinting (and reporting
sprinting, nostop_sprinting) after the sprint control is released, until forward is released or another end condition applies: sneaking, backing, a wall, food ≤ 6 in survival, an item in use, flying, riding, a teleport or a death.getControlState('sprint')reports the control (false). To walk on at walking speed, releaseforward(for at least 7 ticks if the previous press was within 6 ticks, or the re-press is a double tap).
Item use slowdown and the raised shield
- Affects: movement while
bot.usingHeldItem(eating, drawing a bow, a raised shield). - Java: mineflayer's physics does not slow item use down at all.
- Bedrock: BDS clamps the movement impulse to ±0.1225 and drops the sprint modifier while the player uses an
item — which it enters only on a
click_airtransaction; the held sprint key sprints again afterwards. A shield is raised by sneaking (no item use), so BDS simulates a plain sneak (measured: 3.81 blocks per 60 ticks for both). - bedboo: the engine applies the item-use clamp only while
bot.usingHeldItemis set and aclick_airwas sent for that use (observed on the outgoing transactions;item_release/completed_using_itemend it). A raised shield (activateItem(true)) moves exactly like a sneak, without corrections. See also item use.
Creative flight and flyTo
- Affects:
bot.creative.flyTo/startFlying/stopFlying,bot.physics.gravity,bot._bedrock.physics.setFlying. - Java: the client toggles
abilities.flyingitself;startFlyingsetsbot.physics.gravity = 0andstopFlyingrestores it; upstreamflyTomovesbot.entity.positionin 0.5-block steps per tick by setting it (no physics). - Bedrock: flight is a server-side movement state. BDS starts/stops flight on
request_ability flying(the auth-inputstart_flyingflag alone is ignored) and reports it withupdate_abilities; flight is simulated server-side:- +0.15 / −0.22 vertical per tick of jump / sneak, vertical drag 0.6, horizontal 0.91, no sprint bonus, and an extra × 0.375 damping without input (flight stops within a few ticks);
- double-tapping jump (two presses a few ticks apart) toggles flight on the server too;
- a double-tapped forward makes BDS sprint-fly (about twice the speed), and BDS stops the flight when the player touches the ground.
- bedboo:
bot.creative.startFlying/stopFlyingcallbot._bedrock.physics.setFlying(on), which sendsrequest_abilityright before the next input (+start_flying/stop_flying), and leavebot.physics.gravityuntouched (a gravity of 0 would outlive a flight the server refused or ended — game mode change, death — and break every later movement and pathfinder's simulations). Without the physics plugin's flight hook, upstream's gravity handling applies;- the engine follows
bot.abilities(parsed fromupdate_abilities, which also derive game mode changes); server-side changes send no request back (see abilities); bot.creative.flyTo(destination)delegates tobot._bedrock.physics.flyTo(upstream's step loop without it) and flies with real inputs: every tick it picks forward-toward-the-target × jump / sneak / nothing by simulating each choice (then idle until stopped) with the engine; it never re-presses jump within 12 ticks (a guard kept across consecutiveflyTocalls), never re-presses forward within 10 ticks of releasing it (no double-tap sprint flight), and never lands short of a destination above the landing spot; it resolves once stopped within 0.15 block (upstream: exactly at the destination) and rejects when flight stops, on a teleport, or when blocked for 60 ticks;- measured: a 15 s hover and
flyToflights with zero corrections. Real flight is slower than upstream's teleport steps: the 11 short legs of mineflayer's bee example take about 15.5 s (Java: about 3 s).
Elytra
- Affects:
bot.elytraFly,bot.entity.elytraFlying, firework boosts while gliding. - Java:
entity_action start_elytra_flying. - Bedrock:
start_glidinginplayer_auth_input; BDS only accepts it together with a jump press (jump_pressed_raw,jumping,start_jumping, …) and confirms with theglidingactor flag; it can end the glide (flag cleared). The glide itself follows the Java formula. During continuous rocket use BDS fires about one rocket per 2 ticks (20 uses → about 10 rockets) and corrects a boosted glide about every 6 ticks. - bedboo: same checks and errors as upstream (the chest slot is read from
bot.inventory, or from the raw armor window without the inventory plugin); gliding is predicted from the next input on and dropped when the server clears the flag (rewinding from that tick) or has not confirmed it after 20 ticks. A boost's start is recorded in the tick's input and replayed, so a correction does not erase it. Rocket detection (usedFirework,bot.fireworkRocketDuration): see firework boosts; a plainfirework_rocketfrom the creative inventory gets its defaultFireworks {Flight: 1}variant (creative defaults).
Vehicles
- Affects:
bot.moveVehicle. - Java:
steer_vehicle/player_input. - Bedrock: the rider's
player_auth_inputcarries the move vector,client_predicted_vehiclewith the vehicle's unique id and rotation, and paddle flags for boats. - bedboo:
moveVehicle(left, forward)keeps the input until changed (upstream semantics) and the provider sends it every tick whilebot.vehicleis set; no player physics while riding. Mounting and links: vehicles.
Riding control
- Affects:
bot.moveVehiclewithbot.mount/bot.dismount. - Java: the client simulates the ridden vehicle and sends
vehicle_move/steer_vehicle. - Bedrock: the rider's
player_auth_inputcarries the move vector (+ paddle flags for boats) and the predicted vehicle id / rotation; BDS simulates the boat / horse from it (a boat about 7 blocks/s on water, turning with one paddle; a horse follows the rider's yaw). The client does not predict vehicle physics, and BDS reconciles the vehicle with acorrect_player_move_prediction { prediction_type: 'vehicle' }every 6 ticks; the rider gets no player correction. Dismounting a horse teleports the rider next to it; for a boat BDS may keep the position the rider reported. Measured: boat 20.8 blocks in 3 s, horse 25–29 blocks in 3 s, 0 player corrections, vehicle corrections exactly 6 ticks apart. - bedboo: like upstream,
physicsTickdoes not fire while mounted. The physics plugin reports the rider at the vehicle's seat in everyplayer_auth_inputand movesbot.entity.positionwith it, so a boat rider gets off next to the boat: see the rider's position, the rider's seat and getting off.
Known residuals
- Approximate (a correction is possible; the server state is then taken over and replayed): landing on honey while jumping, slime bounce heights (within 0.01 block), bubble column surfaces, powder snow (< 2 mm per 6 ticks), taking off from the ground in creative flight (one 3 cm correction at the default threshold; the strict probe agrees).
- Not modelled: swimming (sprinting in water does not start swimming), crawling / gliding poses (box height), being
pushed out of blocks (BDS pushes 0.1/tick), scaffolding, entity collisions (boats, shulkers), double-tap-jump flight
toggles in creative (the server's
update_abilitiesis followed), float32 precision far from the origin. - Bubble columns: BDS forms a column when the soul sand / magma goes under existing water; replacing the base later did not convert it.
bot._bedrock.input(input_queue.js): the clock is not re-anchored by corrections — tick numbers only identify inputs for the server's rewind, and one engine step runs per sent tick, so a skipped (catch-up) tick is skipped on both sides.