Overview New
On a Source game a single usercmd carries the input of a tick from the client to the server. Bedrock keeps the same
input in two places, and xiao.cmd gives you both with the same cmd object.
The two layers
Which one to use
Rubberband
A movement change in authinput is seen by the server only. The client keeps simulating its own input, so the server
position drifts away and the server corrects you. Change movement in createmove. In authinput, only rotate the
movement that is already there (rotate_movement, lock_angles) so that the server simulates the same path as the
client.
One tick, step by step
client game thread, 20 ticks per second
1. ClientInputUpdateSystem the game reads keyboard, mouse and gamepad into MoveInputComponent
2. xiao.cmd createmove forced states of xiao.game.set_state, then your callbacks; changes are written to
MoveInputComponent, rotation and camera
3. movement simulation SetMoveSystemClient, RotateAndSetVelocitySystem, JumpInputSystem ...
4. SendPlayerInputPacketSystem builds PlayerAuthInputPacket from MoveInputComponent, rotation and position
5. send of the packet native aim, then set_state buttons, then silent angles of lock_angles, then
xiao.cmd authinput, then xiao.packet.hook on PlayerAuthInput, then the wire
next tick: the MoveInputComponent values written at step 2 are restored before step 1
Step 2 runs in the client tick hook used by the pathfinder (after its own input, so a script sees and can override the pathfinder). The restore before the next step 1 means the game never sees your input as a key it pressed itself: a callback that stops writing gives the keyboard back at once.
Threads and time budget
Both callbacks run outside the render thread, inside the same VM as the rest of the script, with the rules of packet hooks (see Execution contexts):
Time constraint
-
createmovewaits at most 2 ms for the VM,authinputat most 1 ms. When the render thread is still running the script (on_frame, receivers, paint), the tick passes unchanged and counts instats().<layer>.busy. -
All callbacks of a tick share a budget of 2 ms, checked every 1000 Lua instructions (callbacks are never JIT compiled). In
authinputthe 2 ms start when the packet is intercepted, so they include the wait for the VM and are shared with thexiao.packet.hookcallbacks of that packet. -
Over budget, every change of that tick is dropped and counts in
overruns. After 3 overruns in a row the callback is removed and the console printscmd hook 'id' (createmove) removed: over the 2000 us budget 3 times in a row. -
A callback that raises an error is removed (
cmd hook 'id' (authinput) removed: <error>), the next callbacks still run; the changes it made before the error are kept.
Restricted inside a cmd callback
-
xiao.config,xiao.workspace,xiao.notifyandxiao.timer.spawnraise... is not available inside a packet hook or a cmd hook. -
xiao.paintis inert: shapes are skipped,text.bake,text.unbakeandimage.bakereturnfalse,text.get_sizereturns zero sizes. -
xiao.gamereads return nothing (there is no frame): read what you need inthinkand keep it in an upvalue. -
print,xiao.packet.*,xiao.cmd.*,xiao.json,xiao.timer.after,every,canceland pure Lua work.
Measured in lua_runtime_test and actors_test (Release, one callback):
A packet that nothing changed is never encoded again: the game sends its own bytes.
Forced states New
xiao.game.set_state uses these two layers without any callback: a forced sprinting,
sneaking or jumping is written into the client input of every tick before the createmove callbacks, and the
start_*, stop_* and held buttons of every state go into the packet before the authinput callbacks. A callback
sees the forced state and keeps the last word for its tick. xiao.cmd.stats() counts it in state_client and
state_server.
Priority with the native aim
The client aim features (kill aura, aim assist toward the server) rewrite the angles of the outgoing packet in the network layer. The order is fixed:
- native aim writes its angles and rotates the movement to match;
-
New
the buttons of
xiao.game.set_stateare applied (buttons only, angles untouched); - the silent angles of a
createmovelock_anglesare applied; authinputcallbacks run and see the result (cmd:native_aim()istrue);xiao.packet.hookcallbacks onPlayerAuthInputsee the cmd result.
The script has the last word. When the angles that leave are not the ones the native aim wrote, the native aim is told
its rotation was not delivered, so the aura waits for a packet that carries its own angles before it attacks. To
leave the native aim alone, test cmd:native_aim() and do not touch the angles.
Silent angles
lock_angles() makes the angle changes of the cmd silent: the server receives them, the camera and the player view
do not move.
-
In
createmove, the camera and the rotation are left untouched and the angles are carried to the nextPlayerAuthInputPacket(it is sent in the same tick, the angles expire after 100 ms). The movement in that packet was simulated with the real view, so it is rotated to the silent yaw: the server moves you along the same path. -
In
authinput, angles are always packet only;lock_angles()adds the same movement rotation at the end of the callbacks, so you do not needrotate_movement.
See angles and the silent aim example.
Lifecycle
-
A new callback takes effect at the next frame; removing the last one stops the layer at the next frame.
-
Clear VM and unload stop both layers: no callback runs after the VM is closed, pending silent angles are dropped and the client input is restored at the next tick.
-
When a layer is not installed in the current game build (
xiao.cmd.available(event)isfalse), its callbacks never run and the other layer keeps working.