マガジン

昨日動いたゲームが今日動かない:環境・依存・キャッシュを切り分ける実験の設計書

遊びをつくる

執筆にClaudeを使用しています。

小さな迷路の壁を一か所取り外し、通り道を確かめる模型。
記事のテーマをもとに制作したAI生成イメージ。実際のゲーム画面ではありません。

昨日の夜、ブラウザで開いてきちんと遊べたゲームが、今朝は真っ白な画面のままです。コードには触れていないつもりです。ところが「触っていない」と「変わっていない」は別の話です。ブラウザは自動で更新されます。外部から読み込むライブラリは、同じURLでも中身が変わることがあります。ブラウザは古いファイルを覚えていることもあります。 この記事は、原因を当てずっぽうで探す代わりに、原因を「コード」「環境」「依存」「キャッシュ」の4つに仕分ける実験の設計書です。結果は書きません。読者が自分のゲームで手順を行えば、どこに原因があったかが記録として残る形にします。 核になるのは、一度に一つだけ変えて、変えた後の様子を同じ書式で残すことです。慌てると、リロードして、ブラウザを替えて、ライブラリを入れ直して、と同時に何か所も触りがちです。その結果、直っても何が効いたのか分からなくなります。

この記事の目次
  1. 先に4つの容疑者を定義する
  2. 準備:症状を一文にして、記録票を作る
  3. 実験A:キャッシュを疑う(いちばん安い順に)
  4. 実験B:環境を疑う
  5. 実験C:依存を疑う
  6. 実験D:コードを疑う(最後に、二分探索で)
  7. 起こりやすい誤り
  8. 次の行動

先に4つの容疑者を定義する

切り分けを始める前に、言葉を揃えておきます。

  • コード:自分が書いたファイルの中身です。昨日から差分があれば、それが容疑者です。
  • 環境:コードの外側にある、動かす側の条件です。ブラウザの種類とバージョン、OS、画面サイズ、端末の時刻、言語設定、「動きを抑える」などのアクセシビリティ設定が含まれます。
  • 依存:自分のコードが頼っている他人の成果物です。外部ライブラリ、CDN(配信用の外部サーバー)から読む素材、フォント、同梱しているライブラリのバージョンが該当します。
  • キャッシュ:ブラウザが再利用のために手元に残している、古いファイルや保存データです。HTTPキャッシュ(ファイルの使い回し)と、localStorageなどの保存データは別物です。この記事では後者も「状態の持ち越し」としてここで扱います。

この4つは重なります。たとえば「ライブラリが更新された」は依存の変化ですが、「古いライブラリがキャッシュに残っていて、新しい自作コードと組み合わさった」ならキャッシュと依存の合わせ技です。まず単純な形から潰していきます。

準備:症状を一文にして、記録票を作る

最初に、症状を一文に固定します。「動かない」では広すぎます。「起動直後に画面が真っ白になり、コンソールに Uncaught TypeError が出る」「ボタンを押しても反応しない」「音だけ鳴らない」のように、観察できる事実だけを書きます。原因の推測は混ぜません。

次に、開発者ツールを開きます。確認する場所は二つです。

  • コンソール:赤いエラー文と、その発生行
  • ネットワーク:読み込みに失敗した(赤い)ファイルと、その状態コード

この二つのスクリーンショットかテキストを、実験前の状態として保存します。

そのうえで、実験ごとに1行ずつ記録票を作ります。項目は次のとおりです。

  • 実験名、書く内容:例:シークレットウィンドウで開く
  • 変えたこと、書く内容:一つだけ
  • 変えなかったこと、書く内容:念のため列挙
  • 結果、書く内容:症状が「再現した/しなかった/形が変わった」
  • 回数、書く内容:同じ操作を何回行ったか
  • 気づき、書く内容:想定外の点

結果の欄は「直った/直らない」ではなく、「再現した/しなかった」で書きます。目的は直すことではなく、症状が出る条件を絞ることだからです。回数は最低2回にします。1回目だけ動かなかった、という揺れを拾うためです。

ページ自身に状態を語らせる小さなメモ

実験のたびに「今開いているのは本当に最新のファイルか」を確かめる必要があります。そこで、ゲームの隅やテスト用ページに、環境と読み込み状況を表示させます。60行に収まる最小構成を示します。

html
<!doctype html>
<meta charset="utf-8">
<title>切り分けメモ</title>
<pre id="out"></pre>
<script>
const BUILD = "2026-10-22-a"; // 修正のたびに手で書き換える
const L = [];
L.push("build: " + BUILD);
L.push("opened: " + new Date().toString());
L.push("ua: " + navigator.userAgent);
L.push("lang: " + navigator.language);
L.push("window: " + innerWidth + "x" + innerHeight + " @" + devicePixelRatio);
L.push("reduceMotion: " +
  matchMedia("(prefers-reduced-motion: reduce)").matches);
try {
  L.push("storage keys: " + localStorage.length);
} catch (e) {
  L.push("storage: 使えません (" + e.name + ")");
}
// script や img を足すと、読み込みの一覧に出ます
for (const r of performance.getEntriesByType("resource")) {
  L.push((r.transferSize === 0 ? "[キャッシュ?] " : "[取得] ") + r.name);
}
document.getElementById("out").textContent = L.join("\n");
</script>

BUILD の文字列は、ファイルを直すたびに手で書き換えます。画面に出た文字が古ければ、そのファイルは古いものが表示されています。これだけでキャッシュの疑いが半分以上決まります。

実験A:キャッシュを疑う(いちばん安い順に)

切り分けは、費用が小さい順に進めます。最初はキャッシュです。

手順

  1. 通常のウィンドウで症状を確認し、画面の build を記録します。
  2. 同じページを、シークレット(プライベート)ウィンドウで開きます。開き直すと保存データも空の状態から始まるので、HTTPキャッシュと保存データを同時に外せます。
  3. 通常のウィンドウに戻り、開発者ツールのネットワークで「キャッシュを無効化」にチェックを入れて再読み込みします。これはHTTPキャッシュだけを外す操作です。
  4. 最後に、保存データ(localStorageなど)だけを消して再読み込みします。

3と4は別々にやります。2は両方を同時に外すので、ここで差が出たら3と4で内訳を割ります。

結果の読み方

  • シークレットで再現しない、かつ3でも再現しない:古いファイルを使っていた可能性が高いです。画面の build が古い値だったなら、ほぼ確実です。
  • シークレットで再現しない、3では再現する、4で再現しない:原因は保存データです。典型例は、昨日の保存形式を今日のコードが読めない場合です。公式作品でも「古い保存(version1)は新しい形式(version2)へ移行する」という考え方で、保存データに版を持たせています。自作でも保存データに version を入れておくと、この種の事故は原因の特定が一気に楽になります。
  • どこで開いても再現する:キャッシュは外れます。次へ進みます。

アソバースへのアップロード作品では、サービスワーカー(ブラウザ内に常駐してキャッシュを細かく制御する仕組み)は対象外です。そのため、投稿物でサービスワーカー由来の「消えないキャッシュ」を心配する必要はありません。ただし通常のHTTPキャッシュと保存データは、ほかのウェブページと同じく考える必要があります。

実験B:環境を疑う

キャッシュが外れたら、次は環境です。昨日と今日で違うものを、動かす側から探します。

手順

次の項目を、一つずつ変えて症状を確認します。変えたら元に戻してから、次の項目に進みます。

  1. 別のブラウザで開く(自動更新の影響を受けていない別系統のものが望ましいです)。
  2. 別のプロファイルで開く。拡張機能が入っていない、まっさらな状態です。拡張機能が原因のケースは、シークレットでも拡張機能を許可していると外せません。
  3. ウィンドウサイズを変える。横幅を狭める、広げる、拡大率を変えるだけで出る症状があります。
  4. 端末の時刻に依存していないかを調べる。コードに Date.now() や日付での分岐があるなら、日をまたいだことが原因になりえます。日付の分岐を一時的に固定値へ置き換えて、症状が変わるか確認します。
  5. 動きを抑える設定や言語設定を切り替える。表示や処理が分岐する作りのゲームでは、設定の違いで通る経路が変わります。

結果の読み方

  • 別のブラウザやプロファイルでは再現しない:普段使いのブラウザの設定、拡張機能、保存データ、またはそのブラウザのバージョンが原因です。ブラウザのバージョンを記録に残し、他のプロファイルとの差を絞ります。
  • すべてのブラウザで再現する:環境ではなく、コードか依存の側にあります。
  • 時刻を固定すると症状が消える:日付や時刻に依存した処理です。こういうバグは、いつまでも「昨日は動いた」という記憶と結びつくので、見落としやすい種類です。

実験C:依存を疑う

環境でも絞れなければ、他人の成果物を疑います。

手順

  1. ネットワークのタブで、自分のドメイン以外から読み込んでいるファイルを洗い出します。ライブラリ、フォント、画像、音源などです。
  2. それぞれについて、バージョンが固定されているかを確認します。URLに latest が入っていたり、バージョン番号がなかったりするものは、中身がいつの間にか変わる可能性があります。
  3. 外部ファイルを手元にダウンロードして同じフォルダに置き、相対パスで読むように書き換えます。この変更は一つのライブラリにつき一回ずつ行います。
  4. 書き換えるたびに症状を確認します。

アソバースでZIPを投稿する場合は、素材を相対パスで読むことが前提です。外部サーバーに頼らない作りは、公開後に「動かなくなった理由」を減らす点でも、この実験の結果を再現可能にする点でも都合がよい設計です。

結果の読み方

  • 手元に固定したら再現しなくなった:外部の配布物が変わったか、一時的に取得できなかった可能性があります。以後は固定版を使い続けるのが安全です。
  • 固定しても再現する:依存の更新ではなく、自分のコードか、同梱ライブラリと自作コードの相性です。

実験D:コードを疑う(最後に、二分探索で)

ここまでで外部要因が絞り込めたら、最後にコードの差分を調べます。コードを最後に回すのは、自分のコードが原因だと決めつけると、ほかの可能性を見ずに修正を繰り返して時間を失いやすいからです。

手順

  1. バージョン管理(Gitなど)を使っていれば、昨日動いていた時点の状態を別フォルダに取り出します。使っていなければ、昨日のバックアップを使います。
  2. 昨日のコードを、今日の同じブラウザで開きます。
  3. 動くなら、昨日と今日の間の変更を半分に分けて試します。半分戻して動くなら、残りの半分に原因があります。これを繰り返す二分探索で、変更箇所を絞り込みます。

結果の読み方

ここが記事全体の判定の山場です。昨日のコードと今日のコードを、同じ環境で動かした結果を並べて読みます。

  • 動く、今日のコード:動かない、読み方:コードの差分が原因です。二分探索で絞ります
  • 動かない、今日のコード:動かない、読み方:コードの外が原因です。環境・依存・キャッシュに戻ります
  • 動く、今日のコード:動く、読み方:状態の持ち越しか、再現が不安定です。回数を増やします

2行目が出たときは、昨日の自分の記憶が当てにならなくなっています。「昨日は動いた」は、昨日の環境で動いたのであって、今日の環境で動くとは限らない、と記録に書き残します。

起こりやすい誤り

  • 同時に複数を変える:リロードしながら設定も変えると、効いたものが分かりません。一つずつ、元に戻してから次へ進みます。
  • 直った時点で終わりにする:原因が不明なまま直ると、同じ症状がまた出ます。直ったら、直した操作を巻き戻して再現するかを確かめ、原因を確定させます。
  • 外部ファイルのキャッシュ判定を過信する:先ほどのメモで出した [キャッシュ?] の表示は、別のドメインから読んだファイルでは、ブラウザの制限で転送サイズが0と表示されることがあります。0だから必ずキャッシュ、とは読めません。別オリジンのファイルでは、ネットワークタブの表示を優先します。
  • 保存データをキャッシュと混同する:ハードリロードで保存データは消えません。症状が残るなら、保存データを個別に消します。
  • 記憶で書く:「昨日は確かに動いた」ではなく、記録票には昨日の build の値と日付を書きます。書けないなら、今日から書き始める合図です。

次の行動

まず、手持ちのゲームに先ほどの切り分けメモを加えて、画面で build が見える状態にしてください。次に、動いているうちに記録票の1行目を「正常時の状態」として埋めておきます。症状が出た日に、この記録と並べるだけで、実験A〜Dのどこから始めればよいかが見えます。公開前には、シークレットウィンドウと別のブラウザで一度ずつ開き、同じ結果になるかを確かめておくと、公開後の「動かない」を減らせます。