fix: re-anchor effects clock when ticker drains (stale-clock freeze)
After Ticker.stop(), elapsed resets to zero on the next start(). _now was left frozen at the old elapsed, so effects added after a drain captured a stale start time and their progress() clamped to 0 forever — ticker never stopped, second batch broken. Fix: reset _now = Duration.zero on drain. Adds @visibleForTesting getters and a regression test that catches the stale value directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,11 +60,22 @@ class EffectsOverlayState extends State<EffectsOverlay>
|
|||||||
_ticker = createTicker(_tick);
|
_ticker = createTicker(_tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Ticker get ticker => _ticker;
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Duration get now => _now;
|
||||||
|
|
||||||
void _tick(Duration elapsed) {
|
void _tick(Duration elapsed) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_now = elapsed;
|
_now = elapsed;
|
||||||
_fx.removeWhere((e) => e.done(elapsed));
|
_fx.removeWhere((e) => e.done(elapsed));
|
||||||
if (_fx.isEmpty) _ticker.stop();
|
if (_fx.isEmpty) {
|
||||||
|
_ticker.stop();
|
||||||
|
// Ticker elapsed restarts from zero after stop(); re-anchor so
|
||||||
|
// effects added later don't inherit a stale clock.
|
||||||
|
_now = Duration.zero;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:block_seasons/game/engine/game_engine.dart';
|
||||||
|
import 'package:block_seasons/ui/widgets/effects_overlay.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
PlacementResult _clearResult() => const PlacementResult(
|
||||||
|
events: [],
|
||||||
|
pointsGained: 250,
|
||||||
|
linesCleared: 1,
|
||||||
|
gemsCleared: 0,
|
||||||
|
clearedRows: [3],
|
||||||
|
clearedCols: [],
|
||||||
|
comboStreak: 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('second batch after drain completes within its own duration',
|
||||||
|
(tester) async {
|
||||||
|
final key = GlobalKey<EffectsOverlayState>();
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home:
|
||||||
|
Stack(children: [Positioned.fill(child: EffectsOverlay(key: key))]),
|
||||||
|
));
|
||||||
|
|
||||||
|
const board = Rect.fromLTWH(0, 0, 320, 320);
|
||||||
|
|
||||||
|
// ── First batch ──────────────────────────────────────────────────────────
|
||||||
|
key.currentState!.onPlacement(_clearResult(), boardRect: board);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Ticker must be stopped after the first drain.
|
||||||
|
expect(
|
||||||
|
key.currentState!.ticker.isActive,
|
||||||
|
isFalse,
|
||||||
|
reason: 'ticker should be idle after first batch drains',
|
||||||
|
);
|
||||||
|
|
||||||
|
// _now must be Duration.zero after drain (regression for stale-clock bug).
|
||||||
|
// Without the fix, _now stays frozen at the elapsed value from the last
|
||||||
|
// tick of the first batch (~1000 ms).
|
||||||
|
expect(
|
||||||
|
key.currentState!.now,
|
||||||
|
Duration.zero,
|
||||||
|
reason:
|
||||||
|
'_now must be reset to zero when the list drains so the next '
|
||||||
|
'batch starts from a clean clock',
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Second batch ─────────────────────────────────────────────────────────
|
||||||
|
// With the stale-clock bug the effects get start: ~1000ms, but the
|
||||||
|
// restarted ticker delivers elapsed from 0, so progress stays 0 forever
|
||||||
|
// and they never drain. We verify the second batch completes via
|
||||||
|
// pumpAndSettle (which would time out if the ticker ran forever).
|
||||||
|
key.currentState!.onPlacement(_clearResult(), boardRect: board);
|
||||||
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tester.hasRunningAnimations,
|
||||||
|
isFalse,
|
||||||
|
reason:
|
||||||
|
'second batch must finish; stale clock would keep effects frozen '
|
||||||
|
'and the ticker running indefinitely',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user