From 1063d39de805a83169fc9ba1f841c1239be45da8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 9 Jan 2024 21:15:56 +0900
Subject: [PATCH] enhnace(frontend): tweak game

---
 locales/index.d.ts                            |   3 +
 locales/ja-JP.yml                             |   3 +
 .../assets/drop-and-fusion/gameover.mp3       | Bin 0 -> 31346 bytes
 packages/frontend/package.json                |   1 +
 .../frontend/src/pages/drop-and-fusion.vue    | 131 ++++++++++++--
 .../src/scripts/drop-and-fusion-engine.ts     | 163 +++++++++++++++---
 pnpm-lock.yaml                                |   5 +-
 7 files changed, 267 insertions(+), 39 deletions(-)
 create mode 100644 packages/frontend/assets/drop-and-fusion/gameover.mp3

diff --git a/locales/index.d.ts b/locales/index.d.ts
index 96bc9099dd..df84412473 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1195,6 +1195,9 @@ export interface Locale {
     "bubbleGame": string;
     "sfx": string;
     "soundWillBePlayed": string;
+    "showReplay": string;
+    "replay": string;
+    "replaying": string;
     "_announcement": {
         "forExistingUsers": string;
         "forExistingUsersDescription": string;
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index c28fde56cb..997ddf9c6e 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1192,6 +1192,9 @@ enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
 bubbleGame: "バブルゲーム"
 sfx: "効果音"
 soundWillBePlayed: "サウンドが再生されます"
+showReplay: "リプレイを見る"
+replay: "リプレイ"
+replaying: "リプレイ中"
 
 _announcement:
   forExistingUsers: "既存ユーザーのみ"
diff --git a/packages/frontend/assets/drop-and-fusion/gameover.mp3 b/packages/frontend/assets/drop-and-fusion/gameover.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..23b41c56995a8adc0033d46b946f967876073d1b
GIT binary patch
literal 31346
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

literal 0
HcmV?d00001

diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 9ef18a56a7..895aa47419 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -58,6 +58,7 @@
 		"rollup": "4.9.1",
 		"sanitize-html": "2.11.0",
 		"sass": "1.69.5",
+		"seedrandom": "^3.0.5",
 		"shiki": "0.14.7",
 		"strict-event-emitter-types": "2.0.0",
 		"textarea-caret": "3.1.0",
diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue
index b8d3d8bf04..974daf35e4 100644
--- a/packages/frontend/src/pages/drop-and-fusion.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.vue
@@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</div>
 				</div>
 			</div>
-			<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: gameOver }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
+			<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
 				<img v-if="defaultStore.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
 				<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/>
 				<canvas ref="canvasEl" :class="$style.canvas"/>
@@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				>
 					<div v-show="combo > 1" :class="$style.combo" :style="{ fontSize: `${100 + ((comboPrev - 2) * 15)}%` }">{{ comboPrev }} Chain!</div>
 				</Transition>
-				<div :class="$style.dropperContainer" :style="{ left: dropperX + 'px' }">
+				<div v-if="!isGameOver && !replaying" :class="$style.dropperContainer" :style="{ left: dropperX + 'px' }">
 					<!--<img v-if="currentPick" src="/client-assets/drop-and-fusion/dropper.png" :class="$style.dropper" :style="{ left: dropperX + 'px' }"/>-->
 					<Transition
 						:enterActiveClass="$style.transition_picked_enterActive"
@@ -91,15 +91,29 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<div :class="$style.dropGuide"/>
 					</template>
 				</div>
-				<div v-if="gameOver" :class="$style.gameOverLabel">
+				<div v-if="isGameOver && !replaying" :class="$style.gameOverLabel">
 					<div class="_gaps_s">
 						<img src="/client-assets/drop-and-fusion/gameover.png" style="width: 200px; max-width: 100%; display: block; margin: auto; margin-bottom: -5px;"/>
 						<div>SCORE: <MkNumber :value="score"/></div>
 						<div>MAX CHAIN: <MkNumber :value="maxCombo"/></div>
-						<div class="_buttonsCenter">
-							<MkButton primary rounded @click="restart">Restart</MkButton>
-							<MkButton primary rounded @click="share">Share</MkButton>
-						</div>
+					</div>
+				</div>
+				<div v-if="replaying" :class="$style.replayIndicator"><span :class="$style.replayIndicatorText"><i class="ti ti-player-play"></i> {{ i18n.ts.replaying }}</span></div>
+			</div>
+			<div v-if="replaying" style="display: flex;">
+				<div :class="$style.frame" style="flex: 1; margin-right: 10px;">
+					<div :class="$style.frameInner">
+						<MkButton @click="endReplay"><i class="ti ti-player-stop"></i> END REPLAY</MkButton>
+					</div>
+				</div>
+			</div>
+			<div v-if="isGameOver" :class="$style.frame">
+				<div :class="$style.frameInner">
+					<div class="_buttonsCenter">
+						<MkButton primary rounded @click="end">{{ i18n.ts.done }}</MkButton>
+						<MkButton primary rounded @click="replay">{{ i18n.ts.showReplay }}</MkButton>
+						<MkButton primary rounded @click="share">{{ i18n.ts.share }}</MkButton>
+						<MkButton rounded @click="exportLog">Copy replay data</MkButton>
 					</div>
 				</div>
 			</div>
@@ -139,7 +153,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 			<div :class="$style.frame">
 				<div :class="$style.frameInner">
-					<MkButton @click="restart">Restart</MkButton>
+					<MkButton danger @click="surrender">Retry</MkButton>
 				</div>
 			</div>
 		</div>
@@ -168,6 +182,7 @@ import { DropAndFusionGame, Mono } from '@/scripts/drop-and-fusion-engine.js';
 import * as sound from '@/scripts/sound.js';
 import MkRange from '@/components/MkRange.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
+import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 
 const NORMAL_BASE_SIZE = 30;
 const NORAML_MONOS: Mono[] = [{
@@ -401,6 +416,8 @@ const GAME_HEIGHT = 600;
 let viewScale = 1;
 let game: DropAndFusionGame;
 let containerElRect: DOMRect | null = null;
+let seed: string;
+let logs: ReturnType<DropAndFusionGame['getLogs']> | null = null;
 
 const containerEl = shallowRef<HTMLElement>();
 const canvasEl = shallowRef<HTMLCanvasElement>();
@@ -414,22 +431,25 @@ const comboPrev = ref(0);
 const maxCombo = ref(0);
 const dropReady = ref(true);
 const gameMode = ref<'normal' | 'square'>('normal');
-const gameOver = ref(false);
+const isGameOver = ref(false);
 const gameStarted = ref(false);
 const highScore = ref<number | null>(null);
 const showConfig = ref(false);
+const replaying = ref(false);
 const mute = ref(false);
 const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
 const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
 
 function onClick(ev: MouseEvent) {
 	if (!containerElRect) return;
+	if (replaying.value) return;
 	const x = (ev.clientX - containerElRect.left) / viewScale;
 	game.drop(x);
 }
 
 function onTouchend(ev: TouchEvent) {
 	if (!containerElRect) return;
+	if (replaying.value) return;
 	const x = (ev.changedTouches[0].clientX - containerElRect.left) / viewScale;
 	game.drop(x);
 }
@@ -454,9 +474,18 @@ function hold() {
 	game.hold();
 }
 
-function restart() {
+async function surrender() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.ts.areYouSure,
+	});
+	if (canceled) return;
+	game.surrender();
+}
+
+function end() {
 	game.dispose();
-	gameOver.value = false;
+	isGameOver.value = false;
 	currentPick.value = null;
 	dropReady.value = true;
 	stock.value = [];
@@ -467,6 +496,45 @@ function restart() {
 	gameStarted.value = false;
 }
 
+function replay() {
+	replaying.value = true;
+	game.dispose();
+	game = new DropAndFusionGame({
+		width: GAME_WIDTH,
+		height: GAME_HEIGHT,
+		canvas: canvasEl.value!,
+		seed: seed,
+		sfxVolume: mute.value ? 0 : sfxVolume.value,
+		...(
+			gameMode.value === 'normal' ? {
+				monoDefinitions: NORAML_MONOS,
+			} : {
+				monoDefinitions: SQUARE_MONOS,
+			}
+		),
+	});
+	attachGameEvents();
+	os.promiseDialog(game.load(), async () => {
+		game.start(logs!);
+	});
+}
+
+function endReplay() {
+	replaying.value = false;
+	game.dispose();
+}
+
+function exportLog() {
+	if (!logs) return;
+	const data = JSON.stringify({
+		seed: seed,
+		date: new Date().toISOString(),
+		logs: logs,
+	});
+	copyToClipboard(data);
+	os.success();
+}
+
 function attachGameEvents() {
 	game.addListener('changeScore', value => {
 		score.value = value;
@@ -492,9 +560,11 @@ function attachGameEvents() {
 	});
 
 	game.addListener('dropped', () => {
+		if (replaying.value) return;
+
 		dropReady.value = false;
 		window.setTimeout(() => {
-			if (!gameOver.value) {
+			if (!isGameOver.value) {
 				dropReady.value = true;
 			}
 		}, game.DROP_INTERVAL);
@@ -511,6 +581,8 @@ function attachGameEvents() {
 	});
 
 	game.addListener('monoAdded', (mono) => {
+		if (replaying.value) return;
+
 		// 実績関連
 		if (mono.level === 10) {
 			claimAchievement('bubbleGameExplodingHead');
@@ -523,9 +595,15 @@ function attachGameEvents() {
 	});
 
 	game.addListener('gameOver', () => {
+		if (replaying.value) {
+			endReplay();
+			return;
+		}
+
+		logs = game.getLogs();
 		currentPick.value = null;
 		dropReady.value = false;
-		gameOver.value = true;
+		isGameOver.value = true;
 
 		if (score.value > (highScore.value ?? 0)) {
 			highScore.value = score.value;
@@ -551,10 +629,13 @@ async function start() {
 		highScore.value = null;
 	}
 
+	seed = Date.now().toString();
+
 	game = new DropAndFusionGame({
 		width: GAME_WIDTH,
 		height: GAME_HEIGHT,
 		canvas: canvasEl.value!,
+		seed: seed,
 		sfxVolume: mute.value ? 0 : sfxVolume.value,
 		...(
 			gameMode.value === 'normal' ? {
@@ -690,7 +771,7 @@ useInterval(() => {
 }, 1000, { immediate: false, afterMounted: true });
 
 onDeactivated(() => {
-	restart();
+	end();
 });
 
 definePageMetadata({
@@ -922,6 +1003,28 @@ definePageMetadata({
 	}
 }
 
+.replayIndicator {
+	position: absolute;
+	z-index: 10;
+	left: 10px;
+	bottom: 10px;
+	padding: 6px 8px;
+	color: #f00;
+	background: #0008;
+	border-radius: 6px;
+	pointer-events: none;
+}
+
+.replayIndicatorText {
+	animation: replayIndicator-blink 2s infinite;
+}
+
+@keyframes replayIndicator-blink {
+	0% { opacity: 1; }
+	50% { opacity: 0; }
+	100% { opacity: 1; }
+}
+
 @keyframes currentMonoArrow {
 	0% { transform: translateY(0); }
 	25% { transform: translateY(-8px); }
diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts
index f71f3a668e..9db93d1534 100644
--- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts
+++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts
@@ -5,6 +5,7 @@
 
 import { EventEmitter } from 'eventemitter3';
 import * as Matter from 'matter-js';
+import seedrandom from 'seedrandom';
 import * as sound from '@/scripts/sound.js';
 
 export type Mono = {
@@ -20,6 +21,18 @@ export type Mono = {
 	spriteScale: number;
 };
 
+type Log = {
+	frame: number;
+	operation: 'drop';
+	x: number;
+} | {
+	frame: number;
+	operation: 'hold';
+} | {
+	frame: number;
+	operation: 'surrender';
+};
+
 export class DropAndFusionGame extends EventEmitter<{
 	changeScore: (newScore: number) => void;
 	changeCombo: (newCombo: number) => void;
@@ -35,18 +48,23 @@ export class DropAndFusionGame extends EventEmitter<{
 	public readonly DROP_INTERVAL = 500;
 	public readonly PLAYAREA_MARGIN = 25;
 	private STOCK_MAX = 4;
+	private TICK_DELTA = 1000 / 60; // 60fps
 	private loaded = false;
+	private frame = 0;
 	private engine: Matter.Engine;
 	private render: Matter.Render;
-	private runner: Matter.Runner;
+	private tickRaf: ReturnType<typeof requestAnimationFrame> | null = null;
+	private tickCallbackQueue: { frame: number; callback: () => void; }[] = [];
 	private overflowCollider: Matter.Body;
 	private isGameOver = false;
-
 	private gameWidth: number;
 	private gameHeight: number;
 	private monoDefinitions: Mono[] = [];
 	private monoTextures: Record<string, Blob> = {};
 	private monoTextureUrls: Record<string, string> = {};
+	private rng: () => number;
+	private logs: Log[] = [];
+	private replaying = false;
 
 	private sfxVolume = 1;
 
@@ -87,13 +105,17 @@ export class DropAndFusionGame extends EventEmitter<{
 		width: number;
 		height: number;
 		monoDefinitions: Mono[];
+		seed: string;
 		sfxVolume?: number;
 	}) {
 		super();
 
+		this.tick = this.tick.bind(this);
+
 		this.gameWidth = opts.width;
 		this.gameHeight = opts.height;
 		this.monoDefinitions = opts.monoDefinitions;
+		this.rng = seedrandom(opts.seed);
 
 		if (opts.sfxVolume) {
 			this.sfxVolume = opts.sfxVolume;
@@ -129,9 +151,6 @@ export class DropAndFusionGame extends EventEmitter<{
 
 		Matter.Render.run(this.render);
 
-		this.runner = Matter.Runner.create();
-		Matter.Runner.run(this.runner, this.engine);
-
 		this.engine.world.bodies = [];
 
 		//#region walls
@@ -223,9 +242,12 @@ export class DropAndFusionGame extends EventEmitter<{
 			Matter.Composite.add(this.engine.world, body);
 
 			// 連鎖してfusionした場合の分かりやすさのため少し間を置いてからfusion対象になるようにする
-			window.setTimeout(() => {
-				this.activeBodyIds.push(body.id);
-			}, 100);
+			this.tickCallbackQueue.push({
+				frame: this.frame + 6,
+				callback: () => {
+					this.activeBodyIds.push(body.id);
+				},
+			});
 
 			const comboBonus = 1 + ((this.combo - 1) / 5);
 			const additionalScore = Math.round(currentMono.score * comboBonus);
@@ -244,7 +266,7 @@ export class DropAndFusionGame extends EventEmitter<{
 		} else {
 			//const VELOCITY = 30;
 			//for (let i = 0; i < 10; i++) {
-			//	const body = createBody(FRUITS.find(x => x.level === (1 + Math.floor(Math.random() * 3)))!, x + ((Math.random() * VELOCITY) - (VELOCITY / 2)), y + ((Math.random() * VELOCITY) - (VELOCITY / 2)));
+			//	const body = createBody(FRUITS.find(x => x.level === (1 + Math.floor(this.rng() * 3)))!, x + ((this.rng() * VELOCITY) - (VELOCITY / 2)), y + ((this.rng() * VELOCITY) - (VELOCITY / 2)));
 			//	Matter.Composite.add(world, body);
 			//	bodies.push(body);
 			//}
@@ -255,10 +277,25 @@ export class DropAndFusionGame extends EventEmitter<{
 		}
 	}
 
+	public surrender() {
+		this.logs.push({
+			frame: this.frame,
+			operation: 'surrender',
+		});
+
+		this.gameOver();
+	}
+
 	private gameOver() {
 		this.isGameOver = true;
-		Matter.Runner.stop(this.runner);
+		if (this.tickRaf) window.cancelAnimationFrame(this.tickRaf);
+		this.tickRaf = null;
 		this.emit('gameOver');
+
+		// TODO: 効果音再生はコンポーネント側の責務なので移動する
+		sound.playUrl('/client-assets/drop-and-fusion/gameover.mp3', {
+			volume: this.sfxVolume,
+		});
 	}
 
 	/** テクスチャをすべてキャッシュする */
@@ -292,13 +329,14 @@ export class DropAndFusionGame extends EventEmitter<{
 		return Promise.all(this.monoDefinitions.map(x => loadSingleMonoTexture(x, this)));
 	}
 
-	public start() {
+	public start(logs?: Log[]) {
 		if (!this.loaded) throw new Error('game is not loaded yet');
+		if (logs) this.replaying = true;
 
 		for (let i = 0; i < this.STOCK_MAX; i++) {
 			this.stock.push({
-				id: Math.random().toString(),
-				mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
+				id: this.rng().toString(),
+				mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
 			});
 		}
 		this.emit('changeStock', this.stock);
@@ -327,10 +365,13 @@ export class DropAndFusionGame extends EventEmitter<{
 						this.fusion(bodyA, bodyB);
 					} else {
 						fusionReservedPairs.push({ bodyA, bodyB });
-						window.setTimeout(() => {
-							fusionReservedPairs = fusionReservedPairs.filter(x => x.bodyA.id !== bodyA.id && x.bodyB.id !== bodyB.id);
-							this.fusion(bodyA, bodyB);
-						}, 100);
+						this.tickCallbackQueue.push({
+							frame: this.frame + 6,
+							callback: () => {
+								fusionReservedPairs = fusionReservedPairs.filter(x => x.bodyA.id !== bodyA.id && x.bodyB.id !== bodyB.id);
+								this.fusion(bodyA, bodyB);
+							},
+						});
 					}
 				} else {
 					const energy = pairs.collision.depth;
@@ -354,6 +395,69 @@ export class DropAndFusionGame extends EventEmitter<{
 				this.combo = 0;
 			}
 		}, 500);
+
+		if (logs) {
+			const playTick = () => {
+				this.frame++;
+				const log = logs.find(x => x.frame === this.frame - 1);
+				if (log) {
+					switch (log.operation) {
+						case 'drop': {
+							this.drop(log.x);
+							break;
+						}
+						case 'hold': {
+							this.hold();
+							break;
+						}
+						case 'surrender': {
+							this.surrender();
+							break;
+						}
+						default:
+							break;
+					}
+				}
+				this.tickCallbackQueue = this.tickCallbackQueue.filter(x => {
+					if (x.frame === this.frame) {
+						x.callback();
+						return false;
+					} else {
+						return true;
+					}
+				});
+
+				Matter.Engine.update(this.engine, this.TICK_DELTA);
+
+				if (!this.isGameOver) {
+					this.tickRaf = window.requestAnimationFrame(playTick);
+				}
+			};
+
+			playTick();
+		} else {
+			this.tick();
+		}
+	}
+
+	public getLogs() {
+		return this.logs;
+	}
+
+	private tick() {
+		this.frame++;
+		this.tickCallbackQueue = this.tickCallbackQueue.filter(x => {
+			if (x.frame === this.frame) {
+				x.callback();
+				return false;
+			} else {
+				return true;
+			}
+		});
+		Matter.Engine.update(this.engine, this.TICK_DELTA);
+		if (!this.isGameOver) {
+			this.tickRaf = window.requestAnimationFrame(this.tick);
+		}
 	}
 
 	public async load() {
@@ -387,17 +491,22 @@ export class DropAndFusionGame extends EventEmitter<{
 
 	public drop(_x: number) {
 		if (this.isGameOver) return;
-		if (Date.now() - this.latestDroppedAt < this.DROP_INTERVAL) return;
+		if (!this.replaying && (Date.now() - this.latestDroppedAt < this.DROP_INTERVAL)) return;
 
 		const head = this.stock.shift()!;
 		this.stock.push({
-			id: Math.random().toString(),
-			mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
+			id: this.rng().toString(),
+			mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
 		});
 		this.emit('changeStock', this.stock);
 
-		const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), _x));
+		const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), Math.round(_x)));
 		const body = this.createBody(head.mono, x, 50 + head.mono.size / 2);
+		this.logs.push({
+			frame: this.frame,
+			operation: 'drop',
+			x,
+		});
 		Matter.Composite.add(this.engine.world, body);
 		this.activeBodyIds.push(body.id);
 		this.latestDroppedBodyId = body.id;
@@ -416,6 +525,11 @@ export class DropAndFusionGame extends EventEmitter<{
 	public hold() {
 		if (this.isGameOver) return;
 
+		this.logs.push({
+			frame: this.frame,
+			operation: 'hold',
+		});
+
 		if (this.holding) {
 			const head = this.stock.shift()!;
 			this.stock.unshift(this.holding);
@@ -426,8 +540,8 @@ export class DropAndFusionGame extends EventEmitter<{
 			const head = this.stock.shift()!;
 			this.holding = head;
 			this.stock.push({
-				id: Math.random().toString(),
-				mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
+				id: this.rng().toString(),
+				mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
 			});
 			this.emit('changeHolding', this.holding);
 			this.emit('changeStock', this.stock);
@@ -440,8 +554,9 @@ export class DropAndFusionGame extends EventEmitter<{
 
 	public dispose() {
 		if (this.comboIntervalId) window.clearInterval(this.comboIntervalId);
+		if (this.tickRaf) window.cancelAnimationFrame(this.tickRaf);
+		this.tickRaf = null;
 		Matter.Render.stop(this.render);
-		Matter.Runner.stop(this.runner);
 		Matter.World.clear(this.engine.world, false);
 		Matter.Engine.clear(this.engine);
 	}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d0f74de843..9d98224822 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -787,6 +787,9 @@ importers:
       sass:
         specifier: 1.69.5
         version: 1.69.5
+      seedrandom:
+        specifier: ^3.0.5
+        version: 3.0.5
       shiki:
         specifier: 0.14.7
         version: 0.14.7
@@ -7401,7 +7404,7 @@ packages:
     hasBin: true
     peerDependencies:
       '@swc/core': ^1.2.66
-      chokidar: ^3.5.1
+      chokidar: 3.5.3
     peerDependenciesMeta:
       chokidar:
         optional: true