Skip to content

Fix piece freezing at spawn after returning from background - #49

Open
nilsauf wants to merge 2 commits into
brandonp2412:mainfrom
nilsauf:main
Open

Fix piece freezing at spawn after returning from background#49
nilsauf wants to merge 2 commits into
brandonp2412:mainfrom
nilsauf:main

Conversation

@nilsauf

@nilsauf nilsauf commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Hej,

I experienced this bug while playing on android: backgrounding the app mid-game (switching to the launcher and back) could leave the current piece permanently stuck — no gravity, no input response — forcing a restart and losing all progress.

Root cause: GameLogic.pauseGame() cancelled gracePeriodTimer, clearAnimationTimer, and trailAnimationTimer on backgrounding, but never reset the fields back to null. resumeGame() only restarts each timer when its field is null:

if (isNewPieceGracePeriod && gracePeriodTimer == null) {
  _startNewPieceGracePeriod();
}

Since the field still held the cancelled (dead) Timer object, this guard was always false, so the timer never restarted. If the app was backgrounded during the ~200ms spawn grace window or a line-clear animation, isNewPieceGracePeriod / isAnimatingClear stayed true forever, and movePieceDown() starts with:

if (isAnimatingClear || isNewPieceGracePeriod) return;

so every gravity tick became a no-op — matching the reported "stuck at the top, won't fall" symptom. Because it only triggers when backgrounding lands inside one of those short windows, it reproduced intermittently rather than every time.

The Fix:

  • pauseGame() now nulls out gameTimer, clearAnimationTimer, trailAnimationTimer, and gracePeriodTimer after cancelling them, so resumeGame()'s guards correctly restart them.
  • Added a _lockDelayPaused flag so a grounded piece whose lock delay deadline expired entirely while backgrounded locks immediately on resume, instead of silently doing nothing.
  • Music now resumes on AppLifecycleState.resumed independent of the game's pause state, rather than being gated behind it.

Added test/lifecycle_timer_restart_test.dart: 7 tests covering spawn grace period, hard drop, gravity, line-clear animation, trail animation, and lock delay all across a pause/resume cycle. Confirmed each test fails against the old code and passes with the fix.

nilsauf and others added 2 commits September 14, 2026 20:45
pauseGame() cancelled the grace-period/clear/trail timers but left the fields non-null, so resumeGame()'s timer == null guards skipped restarting them — the piece could freeze permanently after switching apps. Also locks a grounded piece immediately on resume if its lock delay expired while backgrounded, and resumes music independent of pause state.
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.

2 participants