Mac Freeze when food consumption overlaps Mega Bomb detonation (v1.6.15, Mac, vanilla, code analysis + proposed fix)

Tavuco

Newcomer
Game version: v1.6.15 build 24356
Platform: Mac (Steam), Apple Silicon
Language: English
Mode: Singleplayer
Mods: None

Summary

The game enters a temporarily frozen state (up to ~20 s wall-clock; occasionally persists until the 2 AM forced pass-out) when the player triggers food consumption at the same instant a Mega Bomb detonates. Cross-platform forum reports confirm the bug exists on Switch, Xbox, and PC (Steam/GOG) — see references at bottom.

Reproduction (observed 3× organically; not yet forced)
  1. Be in Skull Cavern or the Mines with Danger in the Deep active. Locations with high breakable-tile/ore density make repro likelier.
  2. Place a Mega Bomb.
  3. As the fuse reaches detonation, switch to a Gold-quality Cheese.
  4. Use the cheese on the same frame (or the immediately adjacent frames) as the explosion resolves.
Observed symptoms
  • World freezes (visuals stop updating, time stops). Input loop remains partially alive, sometimes hotbar cycling still works.
  • Recovery: usually within ~20 s wall-clock. Occasionally, if the player was near 2 AM at trigger time, the freeze appears to persist until the forced pass-out warps the player to bed.
Root cause (decompile analysis, file/line references against 1.6.15 build 24356)

Farmer.eatObject() (Farmer.cs:9008–9045) sets freezePause = 20000 as a safety upper bound on the eat-state freeze. The bound is intended to be cleared early by Farmer.doneEating() (Farmer.cs:8765), which is called from the animation-completion path in FarmerSprite.animateOnce(GameTime) at FarmerSprite.cs:867–870 — but only conditionally:

doneWithAnimation();
if (owner.isEating)
{
owner.doneEating();
}

Two sub-paths cause this conditional to fail to fire doneEating():
  1. animateOnce silently rejects the eat animation.The eat path uses the 3-arg FarmerSprite.animateOnce(216, 80f, 8) (Farmer.cs:9127), which chains to the 7-arg overload at FarmerSprite.cs:665. That overload returns early at line 671:

    if (PauseForSingleAnimation || freezeUntilDialogueIsOver)
    return;

    If PauseForSingleAnimation is true at the moment performEatAnimation is polled — which can happen when the bomb's heavy synchronous workload in GameLocation.explode() (GameLocation.cs:13180+) interleaves with Farmer net-event polling — the animation never starts, so it never reaches the completion path that fires doneEating(). But isEating = true and freezePause = 20000 were already set in eatObject.
  2. isEating cleared by an intervening completelyStopAnimatingOrDoingAction() before the animation ends. That method (Farmer.cs:8910–8941) sets isEating = false and calls Sprite.StopAnimation(). If something fires it mid-animation, the animation may continue but owner.isEating is now false, so the doneEating() guard at FarmerSprite.cs:867 skips. Again, freezePause = 20000 is left set.
The Mega Bomb explosion provides the precipitating workload: it broadcasts hundreds of TemporaryAnimatedSprite instances via Game1.multiplayer.broadcastSprites() (even in singleplayer), calls obj.onExplosion(who) and destroyObject() synchronously across up to ~200 tiles in radius 7, and queues damagePlayersEvent via the netcode pipeline. The high density of breakable rocks/ores in Skull Cavern / Dangerous Mines makes the race window widest there. Regular Bomb (radius 5) and Cherry Bomb (radius 3) are likely also vulnerable at lower probability.

Time-pause behavior during eating is correct (Game1.shouldTimePass() at Game1.cs:8806–8841 returns player.forceTimePass, which is never assigned true anywhere in the codebase, only false at Farmer.cs:3464 and 7017). This is what stops the bomb fuse from advancing during the eat animation (TemporaryAnimatedSprite.cs:1711) — so the bug is not in the time-pause logic itself but in the eat-state cleanup path.

What I'd propose as a possible fix:

Smallest viable patch: change Farmer.cs:9042 from freezePause = 20000; to freezePause = 1000;. The eat animation is only 640 ms (8 frames × 80 ms), so a 1-second safety upper bound is sufficient with margin, and bounds the worst-case freeze to ~1 s even if doneEating() is missed entirely. This is a one-line change with no behavioral implications for the normal completion path.

Robust patch (addressing root cause but needs further validation): in performEatAnimation (Farmer.cs:9117–9129) and performDrinkAnimation (Farmer.cs:9054–9084), use the 4-arg FarmerSprite.animateOnce(int, float, int, endOfAnimationBehavior) overload (FarmerSprite.cs:625), passing static (Farmer f) => f.doneEating() (or equivalent) as the callback. This routes through the unconditional endOfAnimationFunction branch at FarmerSprite.cs:855–864, guaranteeing doneEating() is invoked exactly once at animation end regardless of isEating flag state.

Defensive addition: in Farmer.update, if isEating == true for > ~1500 ms (well above the 640 ms animation duration), force-call doneEating().

Related existing reports (same underlying bug, different surface symptoms):
 

CandyNim

Tiller
One method I have found to consistently trigger the 20 second freeze (when attempting to figure out the bug myself) was by entering the "yes/no" dialogue prompt, picking up the first artifact found on the save file, and then clicking yes. Attached is a save file in which you can reproduce it; go to the chest, drop the artifact out of it, move to the other side of the house, drop one of the paintings and start eating. This triggers Farmer.holdUpItemThenMessage which calls Farmer.completelyStopAnimatingOrDoingAction, which is one of the mentioned code paths I believe?
 

Attachments

Top