Design: travel and move hooks
Proposal for #1009, not an implemented API. This is a clean-room Horizon design from vanilla 26.3 and the requested use cases. There is no NeoForge counterpart. No other project's source informs this proposal. Code follows agreement on this note.
From the modding book of arismoko/horizonbfbda6a
On this page
Moments and typed contract
Declare exactly two catalog moments: living_travel and entity_move. Both use HostSide with typed ServerHost or ClientHost access, never an unchecked side flag granting server writes. Entity reads and authoritative writes belong on typed entity/player handles; do not add entity-id movement methods to Host.
Travel fires once at the LivingEntity.travel boundary, after ordinary input and attribute resolution and player swimming preparation, before fluid/air/glide selection. Its owned snapshot contains a living-entity handle, optional player handle, level/entity epoch, input vector, velocity, orientation, grounded/fluid/ flying/gliding state, resolved attributes, simulation tick and action identity. The output is Continue, Cancel, or Replace { input, velocity }. Replacement supplies the branch's starting values; it does not replace the physics algorithm. Cancel skips this travel invocation, including its nested moves and flying velocity restoration, leaving its entry position and velocity intact. Earlier input/jump processing is not undone. Speed, jump, gravity and step remain attributes; neither hook adds multipliers or a parallel attribute system.
Move fires once per physical Entity.move invocation, including external pushes, before no-physics handling, piston limiting, stuck multipliers, edge backoff and collision. Snapshot includes entity handle, starting position/AABB, requested delta, velocity, typed cause (Self, Piston, ShulkerBox, Shulker or Vehicle), epoch and identity (connection epoch, tick, action sequence, invocation ordinal). Output is Continue, Cancel, or ReplaceDelta(Vec3). Cancel skips that invocation without changing position, velocity, collision flags or fall state and emits no displacement notice. Replacement still passes through all ordinary restrictions. Collision, step height, grounding, fall damage, restitution and movement emissions use the actual resulting displacement in vanilla order. Travel and move are distinct moments: a travel replacement can still encounter a move veto. No second hook is added for individual branches or replay.
Vectors use owned finite f64 components. Travel input components must lie in [-1,1]; replacement velocity and move delta components in [-64,64] blocks per tick or call. These are proposed mod-output budgets, not limits applied to vanilla Continue. Reject the entire malformed result (NaN, infinity, out-of-range, including negative magnitude budgets); signed negative vector components within range are valid. Reject rather than clamp, retain the preceding valid proposal, and diagnose once per listener/tick. Dependency-ordered listeners see the preceding valid proposal; Cancel is terminal. Panic or wasm trap rolls back that listener's writes, discards its result and disables it for the session; continue from the preceding proposal. Native abort cannot be recovered. Native and wasm otherwise share validation.
Ownership and route map
References verified at worktree HEAD 9556bd72a8; paths below are relative to crates/horizon-client/src/. These are current owners, not claims that hooks exist.
Player travel / air / glide
- Current file:line
motion.rs:1487,:1167,:2187- Required integration
- One travel decision before branch selection; water, lava, air, glide and creative flying covered
Physical player movement
- Current file:line
motion.rs:2297, trait adapter:3039- Required integration
- Shared move operation, retain cause through adapters
Shulker box push / piston limit
- Current file:line
motion.rs:2292,:2492- Required integration
- Same move moment before external-motion restrictions
Controlled window simulation
- Current file:line
game/network/ remote/ mod.rs:7129 - Required integration
- Predict with client-side context
Local tick / replay
- Current file:line
motion.rs:1367,lan/prediction.rs:900 - Required integration
- Same deterministic decision/apply path; replay is not a new action
Integrated, LAN and dedicated guest turns
- Current file:line
game/,network/ lan_ actions/ turns.rs:6 :18,:45- Required integration
- Server context bound to acting guest, never host player identity
Action service and scoped fake actors
- Current file:line
game/,modding/ native_ server.rs:538 :549- Required integration
- Enter existing player action validation; physical motion subsequently uses shared hooks
Normal living mobs
- Current file:line
mobs/,living.rs:1451 :1544- Required integration
- Lift decision to game adapter; include mob air and fluid/glide equivalents, not just player motion
Shared air equations
- Current file:line
../horizon-world/ src/ living_ air.rs:134 - Required integration
- Keep physics below game independent of Host
The last path is relative to the crates directory instead of the client source root: crates/horizon-world/src/living_air.rs:134 is the shared owner. Vehicle movement must use the entity move boundary for the vehicle itself; passenger positioning is not a second travel or physical move. Shulker pushes and other external physical displacement preserve their cause and invoke move even without travel. Teleports, packet corrections, remote interpolation and passenger seat assignment are position assignments, excluded from both hooks. Remote entities are not predicted locally; their authoritative server invokes the hooks. An action service request is not itself travel: do not manufacture movement on attack/use. Implementation must inventory vehicle and nonliving collision adapters before code lands and route their actual Entity.move equivalents through this boundary.
Transaction, prediction and replay
Use decide/fire/apply: lower physics prepares a bounded immutable decision, the game dispatches with Host, and apply revalidates entity existence, level epoch, position, velocity, attributes and collision/environment revision in the same transaction as listener writes. A changed precondition refuses the decision and rolls back that transaction; never recompute against changed state mid-callback. No listeners takes the existing path without snapshots, allocations or mod scans.
Integrate with the client action pipeline, preserving its input/action ordering and server validation. Negotiate movement-rule identity and deterministic configuration before enabling prediction. Server validates intent, attachment state, reach and environment independently; it never trusts a client's replacement delta. Server simulation is the sole authoritative result. Unmatched client rules disable custom prediction and await authoritative movement.
Record input, tick/action identity, mod rule version and deterministic attachment state with each prediction checkpoint. Corrections restore those values and rerun the same rules against corrected environment snapshots. Do not reuse outputs from a divergent environment. Predicted callbacks may update only rollback-owned state; server writes remain server-only. Sound, particles, packets, damage and other external effects commit through an identity-deduplicated effect journal, never inside replay callbacks. Server damage uses the authoritative collision result.
A grapple stores an anchor on the entity and contributes bounded velocity before travel; server checks anchor reach and obstruction. A glider replaces travel input and starting velocity while vanilla chooses the glide branch and applies gravity. Wall-running uses a recorded wall normal to remove velocity into the wall, then ordinary collision prevents penetration. Loss of anchor/wall invalidates the rule; client and server return Continue from the same rule rather than inventing a teleport. Each example uses attributes for speed/jump/gravity/step adjustments.
Acceptance matrix
These are implementation requirements, not tests claimed to have run. Geometric cases isolate move from travel acceleration and gravity.
| Fixture | Concrete expected result |
|---|---|
| Empty world, start (0,64,0), delta (1,0,0), Continue | Position (1,64,0), one committed displacement notice |
| Same, Cancel | Position (0,64,0), zero displacement notices |
| Same, ReplaceDelta (0,0,1) | Position (0,64,1), one notice |
| AABB x extent [-0.3,0.3], solid wall plane x=1, delta (2,0,0), no step opening | Center x=0.7, right face x=1, zero penetration |
| Replacement NaN, infinity, 65 or -65 on one axis | Entire result rejected; original delta (1,0,0) still yields (1,64,0) |
| Replacement (-1,0,0) | Position (-1,64,0) |
| Listener panics after attachment write | Write rolled back; original delta applied; listener disabled |
| Callback changes level epoch or starting position | Stale decision refused; zero movement from that decision |
| Two identical corrected replays of fixed input sequence | Final position/velocity and attachment bytes equal fresh authoritative simulation; exactly one external effect per action identity |
| Host window, LAN guest, dedicated player; native and wasm | Each geometric case above has identical values and correct acting-player identity |
| No listeners: air, water, lava, glide, flying, piston, vehicle | Exact position/velocity/collision/fall-state equality with vanilla 26.3 oracle fixtures for the same inputs |
| Teleport/correction/interpolation/seat assignment | Zero travel/move hook calls for the assignment itself |
Vanilla was inspected with python3 tools/rea_vanilla.py method net.minecraft.world.entity.LivingEntity travel and the corresponding Entity move query using the prepared jars under /workspace/horizon. The former selects fluid, glide then air; the latter limits piston movement before collision and applies fall and emission behavior afterward. No vanilla implementation is copied here.
Open questions
- Exact vehicle/nonliving adapter census is implementation work still needed. Recommend requiring a file:line route inventory and oracle fixtures before the hook implementation is approved; never ship player-only coverage under Entity.move.
- Prediction checkpoint transport for mod attachment state is not specified by the existing action note. Recommend a negotiated, versioned bounded schema (64 KiB per entity checkpoint, reject overflow without prediction), with server snapshots and rule identity; agree its wire ownership before implementing prediction support.
Edit this page on GitHub · Spotted something wrong? Open an issue .