diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index ce3fd90a86c721d08eabf6a83c7b56023d569278..63eaded96812d7624c5141df582ac526bf2df158 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -53,7 +53,7 @@
 		"is-file-animated": "1.0.2",
 		"json5": "2.2.3",
 		"katex": "0.16.10",
-		"matter-js": "0.19.0",
+		"matter-js": "0.20.0",
 		"misskey-bubble-game": "workspace:*",
 		"misskey-js": "workspace:*",
 		"misskey-reversi": "workspace:*",
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index fb4d599c2856b7c447214033ee04ab9183a48fd1..8d369101affcaaa6ae25b9f886d4230090fd6b7a 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -557,7 +557,7 @@ let bgmNodes: ReturnType<typeof sound.createSourceNode> | null = null;
 let renderer: Matter.Render | null = null;
 let monoTextures: Record<string, Blob> = {};
 let monoTextureUrls: Record<string, string> = {};
-let tickRaf: number | null = null;
+let tickInterval: number | null = null;
 let game = new DropAndFusionGame({
 	seed: seed,
 	gameMode: props.gameMode,
@@ -663,13 +663,20 @@ function getTextureImageUrl(mono: Mono) {
 	}
 }
 
+function startTicking(tickFunction: () => void) {
+	tickInterval = window.setInterval(tickFunction, game.TICK_DELTA);
+}
+
+function stopTicking() {
+	if (tickInterval !== null) {
+		window.clearInterval(tickInterval);
+		tickInterval = null;
+	}
+}
+
 function tick() {
 	const hasNextTick = game.tick();
-	if (hasNextTick) {
-		tickRaf = window.requestAnimationFrame(tick);
-	} else {
-		tickRaf = null;
-	}
+	if (!hasNextTick) stopTicking();
 }
 
 function tickReplay() {
@@ -700,11 +707,7 @@ function tickReplay() {
 		if (!hasNextTick) break;
 	}
 
-	if (hasNextTick) {
-		tickRaf = window.requestAnimationFrame(tickReplay);
-	} else {
-		tickRaf = null;
-	}
+	if (!hasNextTick) stopTicking();
 }
 
 async function start() {
@@ -716,7 +719,7 @@ async function start() {
 	});
 	Matter.Render.run(renderer);
 	game.start();
-	window.requestAnimationFrame(tick);
+	startTicking(tick);
 
 	gameLoaded.value = true;
 
@@ -803,9 +806,7 @@ function reset() {
 function dispose() {
 	game.dispose();
 	if (renderer) Matter.Render.stop(renderer);
-	if (tickRaf) {
-		window.cancelAnimationFrame(tickRaf);
-	}
+	stopTicking();
 }
 
 function backToTitle() {
@@ -829,7 +830,7 @@ function replay() {
 		});
 		Matter.Render.run(renderer);
 		game.start();
-		window.requestAnimationFrame(tickReplay);
+		startTicking(tickReplay);
 	});
 }
 
diff --git a/packages/misskey-bubble-game/src/game.ts b/packages/misskey-bubble-game/src/game.ts
index 7f230e39cb8f511a9d02c1a31de598892025da0d..ff43488c7d0079aab98b784f3a899ab0f9128515 100644
--- a/packages/misskey-bubble-game/src/game.ts
+++ b/packages/misskey-bubble-game/src/game.ts
@@ -51,7 +51,7 @@ export class DropAndFusionGame extends EventEmitter<{
 	public readonly DROP_COOLTIME = 30; // frame
 	public readonly PLAYAREA_MARGIN = 25;
 	private STOCK_MAX = 4;
-	private TICK_DELTA = 1000 / 60; // 60fps
+	public readonly TICK_DELTA = 1000 / 60; // 60fps
 
 	public frame = 0;
 	public engine: Matter.Engine;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 39956522fcaed9699c6f2442f3c7420b078c005a..beb051af19a42df74f46ade865a5209f0f947084 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -824,8 +824,8 @@ importers:
         specifier: 0.16.10
         version: 0.16.10
       matter-js:
-        specifier: 0.19.0
-        version: 0.19.0
+        specifier: 0.20.0
+        version: 0.20.0
       misskey-bubble-game:
         specifier: workspace:*
         version: link:../misskey-bubble-game
@@ -2639,7 +2639,6 @@ packages:
   '@humanwhocodes/config-array@0.11.14':
     resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
     engines: {node: '>=10.10.0'}
-    deprecated: Use @eslint/config-array instead
 
   '@humanwhocodes/module-importer@1.0.1':
     resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
@@ -2651,7 +2650,6 @@ packages:
 
   '@humanwhocodes/object-schema@2.0.3':
     resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
-    deprecated: Use @eslint/object-schema instead
 
   '@humanwhocodes/retry@0.3.0':
     resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
@@ -4447,7 +4445,6 @@ packages:
 
   '@types/form-data@2.5.0':
     resolution: {integrity: sha512-23/wYiuckYYtFpL+4RPWiWmRQH2BjFuqCUi2+N3amB1a1Drv+i/byTrGvlLwRVLFNAZbwpbQ7JvTK+VCAPMbcg==}
-    deprecated: This is a stub types definition. form-data provides its own type definitions, so you do not need this installed.
 
   '@types/glob@7.2.0':
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
@@ -6500,7 +6497,6 @@ packages:
   eslint@8.57.0:
     resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
     hasBin: true
 
   eslint@9.14.0:
@@ -6992,12 +6988,10 @@ packages:
 
   glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
-    deprecated: Glob versions prior to v9 are no longer supported
 
   glob@8.1.0:
     resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
     engines: {node: '>=12'}
-    deprecated: Glob versions prior to v9 are no longer supported
 
   global-dirs@3.0.1:
     resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
@@ -7292,7 +7286,6 @@ packages:
 
   inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
-    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
 
   inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@@ -8116,6 +8109,9 @@ packages:
   matter-js@0.19.0:
     resolution: {integrity: sha512-v2huwvQGOHTGOkMqtHd2hercCG3f6QAObTisPPHg8TZqq2lz7eIY/5i/5YUV8Ibf3mEioFEmwibcPUF2/fnKKQ==}
 
+  matter-js@0.20.0:
+    resolution: {integrity: sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA==}
+
   mdast-util-find-and-replace@3.0.1:
     resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==}
 
@@ -9676,7 +9672,6 @@ packages:
 
   rimraf@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
-    deprecated: Rimraf versions prior to v4 are no longer supported
     hasBin: true
 
   rollup@4.26.0:
@@ -11819,7 +11814,7 @@ snapshots:
       '@babel/traverse': 7.23.5
       '@babel/types': 7.24.7
       convert-source-map: 2.0.0
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -11871,7 +11866,7 @@ snapshots:
 
   '@babel/helper-environment-visitor@7.24.7':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.25.7
 
   '@babel/helper-function-name@7.24.7':
     dependencies:
@@ -11884,7 +11879,7 @@ snapshots:
 
   '@babel/helper-module-imports@7.22.15':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.25.7
 
   '@babel/helper-module-imports@7.24.7':
     dependencies:
@@ -11917,7 +11912,7 @@ snapshots:
 
   '@babel/helper-simple-access@7.22.5':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.25.7
 
   '@babel/helper-simple-access@7.24.7':
     dependencies:
@@ -11928,7 +11923,7 @@ snapshots:
 
   '@babel/helper-split-export-declaration@7.24.7':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.25.7
 
   '@babel/helper-string-parser@7.24.7': {}
 
@@ -11946,7 +11941,7 @@ snapshots:
     dependencies:
       '@babel/template': 7.22.15
       '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/types': 7.25.7
     transitivePeerDependencies:
       - supports-color
 
@@ -12119,8 +12114,8 @@ snapshots:
   '@babel/template@7.22.15':
     dependencies:
       '@babel/code-frame': 7.24.7
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/parser': 7.25.7
+      '@babel/types': 7.25.7
 
   '@babel/template@7.24.0':
     dependencies:
@@ -12142,9 +12137,9 @@ snapshots:
       '@babel/helper-function-name': 7.24.7
       '@babel/helper-hoist-variables': 7.24.7
       '@babel/helper-split-export-declaration': 7.24.7
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
-      debug: 4.3.5(supports-color@5.5.0)
+      '@babel/parser': 7.25.7
+      '@babel/types': 7.25.7
+      debug: 4.3.7(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -12157,9 +12152,9 @@ snapshots:
       '@babel/helper-function-name': 7.24.7
       '@babel/helper-hoist-variables': 7.24.7
       '@babel/helper-split-export-declaration': 7.24.7
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
-      debug: 4.3.5(supports-color@5.5.0)
+      '@babel/parser': 7.25.7
+      '@babel/types': 7.25.7
+      debug: 4.3.7(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -19721,6 +19716,8 @@ snapshots:
 
   matter-js@0.19.0: {}
 
+  matter-js@0.20.0: {}
+
   mdast-util-find-and-replace@3.0.1:
     dependencies:
       '@types/mdast': 4.0.3
@@ -21858,7 +21855,7 @@ snapshots:
   socks-proxy-agent@8.0.2:
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       socks: 2.7.1
     transitivePeerDependencies:
       - supports-color