Repository navigation
fix(entity): interpolate actors over ticks like the game's lerpTo - #65
Merged
Merged
Conversation
Remote actors jumped straight to every move_entity / move_entity_delta, and the attack ray and click were tested against that newest position. The game moves them toward it over 3 ticks (or the packet's ticks), so its hit position sits between the positions the server has seen acknowledged; precise hitbox checks flagged ours by 0.3 to 0.5 block on moving targets. The renderer now draws between the last two ticks instead of its own ~100 ms sample buffer, so what is drawn is what the hit is tested against.
notchyves
marked this pull request as ready for review
November 6, 2026 21:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Once #62 fixed the click position, a precise hitbox check kept flagging melee hits in PvP (
hitbox.b). That check takes the attack'sclickPosition(InventoryTransaction→UseItemOnEntity, action Attack) and flags it when it lands more than 0.0085 block outside the target's padded hitbox, using the target's last two ACK-confirmed positions.H(blocks)A moving player covers 0.28 (sprint) to 0.4 (sprint-jump) block per tick, so these deviations are one to two ticks of target movement.
Cause
EntityTracker::Move/MoveDeltamoved a remote actor straight to everymove_entity/move_entity_delta/move_player.UpdateAimraycasts against that position, so the click was tested against the newest position, often before the client has acknowledged it. Meanwhile the renderer drew the actor from its own sample buffer, about 100 ms behind. What you saw, what Perlin hit and what the server expected were three different positions.What the vanilla client does
The behaviour below comes from IDA on the Windows client 1.26.60.5:
0x1422C88B0→0x1422C8D20. It callsActor::lerpTo(pos, rot, steps, forceCompletion)(0x142C74350) withsteps = tickswhenticks >= 4, else 3. It places the actor at once (0x1422C8ED0) instead when the packet is a teleport moving 0.5 block or more (dist² >= 0.25), or when the destination block is not loaded.Actor::lerpTowritesMovementInterpolatorComponent: target position and rotation, remaining/total steps, and theforceCompletionflag. If an interpolation started withforceCompletionis still running, a new target instead goes toQueuedMovementInterpolationComponent.MovementInterpolatorSystem::handleQueuedMovementInterpolation(0x143634AE0): while the forced interpolation runs, the queued target loses a step each tick (max(steps, 2) - 1). After that it becomes the active interpolation.MovementInterpolatorSystem::tickClient(0x143630D70): once per tick,pos += (target - pos) / remaining, landing exactly on the target on the last step, thenremaining--.So the client's
HitResult(and the attack click, see #62) is tested against an actor position that trails the newest server position by up to the step count. That keeps it between the positions the server has seen acknowledged.Change
EntityTrackerkeeps the actor's tick position inposand its previous tick inprevPos/prev*. Received poses become a lerp target with the rules above (MoveKind::Lerp/Teleport;Placestays for our own predicted vehicles).move_entity_deltafills missing lanes from the last received pose, and spawning resets the interpolation.EntityTracker::TickInterpolation()runs the queued and tick steps for every actor. It runs once per client tick inLocalPlayer::Update, just before the local player's tick, and on its own 20 Hz clock while a replay plays (there is no local tick then).forceMove/ticks/forceCompletion(move_entity_delta), flag bit 1 (move_entity), and Teleport/Reset modes (move_player).EntityRendererdraws remote actors betweenprevPosandposwith the client partial tick, instead of the sample buffer and its adaptive delay. What is drawn is now exactly what the attack ray is tested against. Projectiles and our own player keep their existing paths.tests/actor_lerp_selftest.cppcovers the 3-step curve, replacement,ticksabove 3, delta lanes, theforceCompletionqueue, near and far teleports, and moves before the first tick.Not done
TODOinReceive).Testing
actor_lerp_selftestare running; results will follow on this PR.hitbox.bno longer fires; replays still look smooth.