Skip to content

Test scaffolding: client-side Lunge saturation prediction - #456

Open
MrPlankton611 wants to merge 8 commits into
squeek502:1.21.11-fabricfrom
MrPlankton611:lunge-prediction-test
Open

Test scaffolding: client-side Lunge saturation prediction#456
MrPlankton611 wants to merge 8 commits into
squeek502:1.21.11-fabricfrom
MrPlankton611:lunge-prediction-test

Conversation

@MrPlankton611

Copy link
Copy Markdown

Summary

Experimental client-only prediction of Lunge's saturation cost (and food's saturation restore), so the HUD can show a reasonable saturation estimate on servers that don't run AppleSkin server-side.

This is test scaffolding, not a finished feature - explicitly marked for removal/rework once validated:

  • Renders a second saturation row for comparison against the real, server-synced value
  • Predicts Lunge's cost via MinecraftClientMixin hooking the piercing-weapon attack dispatch (works on swing, not just landed hits)
  • Predicts food's saturation restore via ItemStackMixin hooking finishUsing, since vanilla's actual restore (ConsumableComponent#finishConsumption) is gated server-only
  • Includes temporary diagnostic logging ([LungeTest] prefix) added while chasing a bug where the eating-prediction hook doesn't appear to fire in practice - still open

Known issues

  • The eating-prediction hook (ItemStackMixin#onFinishUsing) has not been observed firing in real testing despite the call chain checking out by inspection; root cause still under investigation
  • Debug logging should be removed before this is feature-complete

Test plan

  • Builds cleanly, boots via runClient with no mixin errors
  • Confirm eating-prediction hook actually fires
  • Validate prediction accuracy against real server behavior over an extended play session
  • Strip test-only logging/scaffolding once validated

…r tick

Syncing was driven by a mixin injected at the head of ServerPlayerEntity#tick,
which other mods (e.g. spear lunge attacks, attribute swaps) can skip by
cancelling that same tick method to freeze movement during their own logic.
Drive the sync from ServerTickEvents.END_WORLD_TICK instead, which always
fires directly from the server tick loop regardless of what happens in any
individual player's tick method.
Renders a second saturation bar predicting the cost of a Lunge jab from
client-local state alone, to compare against the real server-synced bar
before attempting anything like this on servers that don't run AppleSkin.
Not a real feature yet - remove once validated.
AttackEntityCallback only fires when the attack connects with an entity,
but piercing weapons (spears) dispatch their jab through
ClientPlayerInteractionManager#attackWithPiercingWeapon unconditionally,
whether or not it hits anything. Movement-only lunges (no target) never
triggered the old hook, which is why the predicted bar never moved in
practice on a PvP server where lunges are mostly used for repositioning.
Hook the piercing-attack dispatch directly instead via MinecraftClientMixin.
doAttack() returns boolean, and Mixin requires CallbackInfoReturnable for
injections into non-void methods - plain CallbackInfo fails mixin apply
with "CallbackInfoReturnable is required", which is fatal since this mixin
isn't optional and crashed the client on boot. Verified with a runClient
smoke test that mixin application now succeeds.
Two compounding bugs: the grace-window design fell back to displaying the
real saturation once it expired, but on a server without AppleSkin the
real value is stale (only corrected by vanilla's combined health/food
packet, which fires on damage, not on Lunge use). And each lunge computed
its cost from the real value rather than the shadow, so it could never
accumulate - it just dipped from whatever stale number "real" currently was
and snapped back.

Now the shadow value only resyncs when the real value actually changes,
and each lunge subtracts from the shadow itself, so repeated lunges without
an intervening correction now accumulate correctly.
The shadow value only ever predicted losses from Lunge, so eating a golden
apple never credited it - it just stayed at whatever the last lunge left it
at. Turns out food's actual restore is server-only too: ConsumableComponent
#finishConsumption gates its onConsumeEffects behind !world.isClient(), so
there's no client-side HungerManager.eat() call to piggyback on either.

Hook ItemStack#finishUsing (which does run on the client) to detect when
consumption completes, and predict the restore ourselves using the same
FoodHelper query already used for the held-food preview overlay, mirroring
HungerManager#addInternal's exact clamp order (food level first, then
saturation clamped against the new food level).
Traced through the eating-doesn't-update-when-full report by hand and
couldn't find the bug by inspection - every code path checks out on paper.
Logging swing/eat/resync events with their actual values so the next
reproduction can be diagnosed from real evidence instead of more guessing.
Remove alongside the rest of this test scaffolding.
@MrPlankton611

MrPlankton611 commented Jul 26, 2026

Copy link
Copy Markdown
Author

This is all claude code btw, I just wanted to play around with it and it seems to be working. Just wanted your opinion on this.

Root cause of "ate" never logging: LivingEntity#tickItemStackUsage only
calls consumeItem() (which leads to ItemStack#finishUsing) when
!world.isClient() - the natural "finished eating" completion is entirely
server-only. ItemStack#finishUsing is simply never invoked on the client
for a normal timed eat, so that mixin was targeting a dead end.

itemUseTimeLeft ticks down identically on both sides though, so hook
tickItemStackUsage directly and treat it hitting 0 for the local player as
the completion signal. Removes the now-dead ItemStackMixin#onFinishUsing.
@MrPlankton611

Copy link
Copy Markdown
Author

I added a "prediction" saturation bar since that is the info that we don't get from the server above the actual hunger bar itself. It seems pretty accurate to me. I played a couple of spearmace games with it, and when that prediction bar runs out, the actual hunger bar starts depleting. I would love it if you could double-check it as well, but it seems good to me.

@MrPlankton611

Copy link
Copy Markdown
Author

If the prediction bar seems good to you, ill move that logic into the actual hunger bar itself, that is just for testing and debugging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant