Animation State Bleeding with GeckoLib Entities in FeatureRenderer

Hey peeps, my mod renders animated shoulder hamsters using GeckoLib. The shoulder hamsters work just fine in vanilla. The implementation uses a FeatureRenderer on the player model, which in turn renders several client-side "dummy" GeoEntity instances—one for each shoulder/head slot. Each dummy entity has its own AnimationController with a unique name based on its instance ID. The Issue: With Iris enabled, all shoulder pet dummies appear to share the same animation state, (but I think it's purely visual; watch the end of the video for a note on this) moving in perfect sync. Furthermore, their animations are hijacked by any in-world entity of the same type that is also being rendered. This animation bleeding does not occur without Iris. My guess is that Iris's batched entity rendering is causing GeckoLib to reuse animation data across these dummy instances, despite their unique controller names. Video: Here is a screen recording demonstrating the issue (vanilla vs. Iris comparison) Does anyone know what could be causing this? I'm happy to provide a minimal reproducible example .zip if needed. Thanks in advance! These attached files provide a complete picture of the rendering and state management pipeline for the shoulder pets. 1. HamsterShoulderFeatureRenderer.java: The core class that manages the dummy entities and initiates their rendering. This is the most important file. 2. ClientShoulderHamsterData.java: Shows how the animation state for each shoulder pet is managed and ticked independently on the client thread. 3. ShoulderHamsterState.java: The state machine class that determines which animation should be playing for a single shoulder pet. 4. HamsterEntity.java: Specifically the registerControllers method, so they can see how the GeckoLib animation controller is set up. 5. ShoulderHamsterRenderer.java: The specialized GeoEntityRenderer used for the dummy entities.
Adorable Hamster Pets
YouTube
iris shaders visual glitch
Adorable Hamster Pets - 70+ Variants, Shoulder Launching, Diamond Sniffing, Cheek Pouch Inventories, Unique Personalities, 30+ Animations, and much more! https://youtu.be/2KuEjC1ZkwE Download here: https://modrinth.com/mod/adorable-hamster-pets https://www.curseforge.com/minecraft/mc-mods/adorable-hamster-pets Join "The Cheek Pouch" Discord h...
Solution:
SOLVED! Ok so I knew the key clue was that the animations appeared correct on the pause screen. This confirmed that my underlying animation data and state machines were working perfectly, so the problem had to be purely a render-time issue. The problem was specifically within GeckoLib's AnimatableManager. My guess is that when Iris's batch rendering processes multiple GeoEntity instances in the same frame, the manager for the first entity would get its lastUpdateTime set. When the renderer immediately moved to the next entity in the batch, the time delta since the last update was near-zero, causing GeckoLib to (correctly, from its perspective) skip the animation tick for that entity. This resulted in it being rendered with the stale bone data from the previously rendered entity....
Jump to solution
15 Replies
The Scarlet Fox
The Scarlet FoxOP2w ago
@Moderators anyone have any ideas what could be going wrong here?
Rain
Rain2w ago
Please don't ping moderators for help unless it's help moderating the server. @Support Team is who you want. But otherwise I'll have a look Actually I have no idea if this is BER (Batched Entity Rendering), I'll ping @IMS and he'll respond at some point :)
The Scarlet Fox
The Scarlet FoxOP2w ago
Ahh thank you— my bad. I actually just logged back on to see what the roles were when I saw your comment. I appreciate anyone who wants to get eyes on this! I’m willing to go to pretty much whatever lengths I need to go to to fix this aside from removing Geckolib, if I can be done from my end! Hamsters gotta have shaders, know what I’m sayin? Lol
IMS
IMS2w ago
hi I… have no clue what could possibly be wrong here BER is just fundamentally broken on 1.21, and is being removed with 1.21.9 the only thing I can think of is making the state a map tied to the Entity? (Can’t read all the code right now, so sorry if that’s already the case)
The Scarlet Fox
The Scarlet FoxOP2w ago
I’m guessing you read the part where I said everything works fine until I install Iris?
IMS
IMS2w ago
yeah ber meaning batched entity rendering, to be clear Iris's feature
The Scarlet Fox
The Scarlet FoxOP7d ago
Any other @Support Team have any ideas? I realllly want the hamsters to be shader-compatible
hardester
hardester7d ago
Can't help, I don't have enough knowledge in graphics programming. :yes:
The Scarlet Fox
The Scarlet FoxOP7d ago
Ahh ok. Are there any other support team who maybe aren't online yet? Just wondering if I should try and reach out to.... like the fabric discord or something idk
hardester
hardester7d ago
Only a small few of the support teams are devs. Guess you have to wait for them to provide input.
The Scarlet Fox
The Scarlet FoxOP7d ago
ok, so I guess the person I'm looking for is @Head Developer? Hmm yes that's exactly the approach I'm currently using. On the client, each PlayerEntity has a custom data object (ClientShoulderHamsterData). This object holds a Map<ShoulderLocation, ShoulderHamsterState>, where each ShoulderHamsterState is an independent state machine that gets ticked every frame. The FeatureRenderer then applies the unique state from that map to the corresponding dummy HamsterEntity for each shoulder slot right before rendering it. So, the state is indeed managed independently per-instance on my end. The issue seems to be that during Iris's batched rendering (maybe?), something ends up using the animation data from just one of those instances for all of them, despite each having a unique controller name. But here's the weirdest and most intriguing part: if I pause the game, while it's paused I can see in the blurred background that they immediately flick back to whatever animation state they are actually in, rather than the shared identical animations. In other words, as long as the game is paused, it seems to be showing a snapshot of everything working just fine. It's super weird. Check out the this part of the video to see what I'm talking about.
Solution
The Scarlet Fox
SOLVED! Ok so I knew the key clue was that the animations appeared correct on the pause screen. This confirmed that my underlying animation data and state machines were working perfectly, so the problem had to be purely a render-time issue. The problem was specifically within GeckoLib's AnimatableManager. My guess is that when Iris's batch rendering processes multiple GeoEntity instances in the same frame, the manager for the first entity would get its lastUpdateTime set. When the renderer immediately moved to the next entity in the batch, the time delta since the last update was near-zero, causing GeckoLib to (correctly, from its perspective) skip the animation tick for that entity. This resulted in it being rendered with the stale bone data from the previously rendered entity. The fix was to manually force GeckoLib's AnimatableManager to re-evaluate its state for each dummy entity right before its render call. By setting its lastUpdateTime to 0, I trick the manager into thinking a very long time has passed, guaranteeing it runs a full animation update and ignores any polluted state from other entities in the batch.
The Scarlet Fox
The Scarlet FoxOP7d ago
Here's the snippet that fixed it, placed inside my FeatureRenderer's render logic for each shoulder pet:
// Inside the FeatureRenderer's render method for each dummy entity

// THE FIX: Manually reset the animation manager's update timer.
// This tricks GeckoLib into thinking a long time has passed, forcing
// it to run a full animation update and ignore any polluted state
// from previously rendered entities in the same batch.
AnimatableInstanceCache cache = dummyHamster.getAnimatableInstanceCache();
if (cache != null) {
AnimatableManager<?> manager = cache.getManagerForId(dummyHamster.getId());
if (manager != null) {
manager.updatedAt(0);
}
}

// ...then proceed with the normal render call for the dummy entity.
hamsterRenderer.render(dummyHamster, renderYaw, tickDelta, matrices, vertexConsumers, light);
// Inside the FeatureRenderer's render method for each dummy entity

// THE FIX: Manually reset the animation manager's update timer.
// This tricks GeckoLib into thinking a long time has passed, forcing
// it to run a full animation update and ignore any polluted state
// from previously rendered entities in the same batch.
AnimatableInstanceCache cache = dummyHamster.getAnimatableInstanceCache();
if (cache != null) {
AnimatableManager<?> manager = cache.getManagerForId(dummyHamster.getId());
if (manager != null) {
manager.updatedAt(0);
}
}

// ...then proceed with the normal render call for the dummy entity.
hamsterRenderer.render(dummyHamster, renderYaw, tickDelta, matrices, vertexConsumers, light);
Thanks again for the help!
hardester
hardester7d ago
Congrats on finding a solution. :haha_yes:
The Scarlet Fox
The Scarlet FoxOP7d ago
This one was a doozy

Did you find this page helpful?