マガジン

version1からversion2へ:古いセーブはどう読み替えられるか

遊びを見つける

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

プレイヤーの駒と小さなゲームの世界をつなぐ橋、上に浮かぶ雲形の器
記事のテーマをもとに制作したAI生成イラスト。

しばらく遊んでいなかった「余白の庭」を開く場面を考えてみます。以前に置いた木や池は、同じ場所に残っているでしょうか。季節は覚えているでしょうか。それとも、何事もなかったように空の庭が出てくるのでしょうか。保存データの形式が新しくなるとき、古いデータが「新しい形式に読み替えられる」とは、実際にはこの問いに答える作業です。 確認できている事実は多くありません。13作品に共通する事項として、古い保存(version1)が新しい形式(version2)へ移行されることだけです。何を引き継ぎ、何を初期値に戻し、何を手放したのかは、現在の案内からは確かめられません。そのため、この記事は移行の中身を言い当てるのではなく、移行処理が一般に何をするのかを整理し、手元の保存で読者自身が確かめる手順を示します。 題材は、移行処理が守るものと捨てるものです。移行を「データが残るかどうか」の二択で見ると、見落としが出ます。残り方には種類があり、その種類を知っていれば、画面を見ただけで何が起きたかをかなり読み取れます。

この記事の目次
  1. 確認できること、できないこと
  2. 移行の結果は四つに分けられる
  3. 架空の例で四つの扱いを見る
  4. 三作品で「守られていてほしいもの」を先に書き出す
  5. 読者が自分で確かめる手順
  6. 判断基準:どの捨て方なら受け入れられるか
  7. 次の行動

確認できること、できないこと

まず線を引いておきます。

確認できていることは次のとおりです。

  • 13作品はすべて独立したHTMLで、外部通信なしに動きます。
  • 保存があるのは「余白の庭」「WAVE / WAVE」「かたちのあいだ」の3作品です。他の10作品の記録や録音は、画面を開いている間だけ残ります。
  • 古い保存(version1)は、新しい形式(version2)へ移行されます。

確認できていないことは次のとおりです。

  • version1に含まれていた項目の一覧
  • 移行で捨てられた項目があるかどうか
  • 移行が初回に開いたときに行われるのか、そのとき古い保存が残るのか

ここから読み取れることが一つあります。移行の重みが乗るのは、保存を持つ3作品だけです。庭は作った物、リズムは組んだパターン、パズルは途中の盤面や記録で、どれも利用者が時間をかけて育てたものです。10作品のように画面を閉じれば消える設計なら、形式が変わっても失うものはほとんどありません。移行処理が必要になったこと自体が、この3作品では保存が利用者の持ち物として扱われていることの表れと読めます。

なお、3作品すべてに実際に古い保存があったかどうかも、この記事からは断定できません。共通事項として書かれているため、どの作品で実際に移行が起きるかは、手元の状態で確かめることになります。

移行の結果は四つに分けられる

保存データの一項目が移行でたどる道は、一般に次の四つのどれかです。

  • そのまま引き継ぐ、意味:値も意味も同じ、画面での見え方:以前と同じに見える
  • 形を変えて引き継ぐ、意味:名前や単位、並びを変換する、画面での見え方:同じに見えるが、内部の表現は別物
  • 初期値で埋める、意味:新形式にあって旧形式にない項目を既定値にする、画面での見え方:新しい機能の設定が標準状態になる
  • 手放す、意味:新形式で使わない項目や、読めない項目を除く、画面での見え方:以前あったものが見当たらない

注意したいのは、「手放す」が必ずしも悪いわけではない点です。表示用に一時的に計算していた値や、新形式で作り直せる情報は、捨てても利用者は困りません。困るのは、利用者が自分で選んだり作ったりした値が、気づかないうちに初期値に置き換わる場合です。

もう一つの落とし穴が、「形を変えて引き継ぐ」の中にあります。単位や上限が変わると、値は残っても見た目が変わります。たとえば大きさの尺度が変わったのに数値だけ引き継がれると、置いた物が極端に大きく、または小さく見えます。

架空の例で四つの扱いを見る

四つの扱いを、短いコードで見てみます。これは架空の庭ゲームの例で、実際の13作品の実装ではありません。上限の3は説明用の小さな数です。

js
const MAX_ITEMS = 3;

function v1ToV2(old) {
  const things = Array.isArray(old.things) ? old.things : [];
  const items = things
    .filter(t => Number.isFinite(t.x) && Number.isFinite(t.y))
    .slice(0, MAX_ITEMS)
    .map(t => ({ kind: String(t.type), x: t.x, y: t.y, size: t.size ?? 1 }));
  return { version: 2, items, season: old.season ?? "spring" };
}

function migrate(save) {
  if (save && save.version === 2) return save;
  if (save && save.version === 1) return v1ToV2(save);
  return null; // 読めない形式は初期化せず、呼び出し側で元を残したまま扱う
}

const old = { version: 1, season: "autumn", cache: "x",
  things: [{ type: "tree", x: 1, y: 2 }, { type: "pond", x: 3, y: 4, size: 2 },
           { type: "stone", x: "?", y: 0 }, { type: "lamp", x: 5, y: 5 },
           { type: "flower", x: 6, y: 6 }] };
console.log(JSON.stringify(migrate(old)));

このコードでの扱いは次のとおりです。

  • x と y と season は、そのまま引き継がれます。
  • things は items に、type は kind に名前が変わります。これが「形を変えて引き継ぐ」です。
  • size を持たない物には、既定値の1が入ります。これが「初期値で埋める」です。
  • cache は新形式に持ち込まれません。座標が数値でない石も除かれ、上限を超えた最後の花も落ちます。これが「手放す」です。
  • 想定外のversionに対しては、空の庭で上書きせず null を返します。移行の失敗が元のデータの破壊につながらないようにするための工夫です。

最後の点は、移行処理の良し悪しを見分ける基準になります。読み替えに失敗したとき、元を残すのか、黙って空にするのかで、利用者が失うものはまるで変わります。

三作品で「守られていてほしいもの」を先に書き出す

移行後の画面を見る前に、守られていてほしいものを決めておきます。確認済みの機能から、見る価値のある項目を挙げます。これらが保存の対象だと断定するものではなく、画面で確かめられる項目の候補です。

  • 余白の庭:置いた木・石・草・花・池・灯りの種類と位置、大きさ、置いた個数(最大80個)、季節と昼夜の設定
  • WAVE / WAVE:4つの音を並べた16ステップのパターン、テンポ、スウィング、トラックごとのミュート、パッド演奏の録音
  • かたちのあいだ:3×3か4×4か(入門と2段階)、盤面の配置、手数、時計の状態

「かたちのあいだ」の手数は特に気にかけたい項目です。一手戻しても手数は減らない仕様なので、手数は単純な盤面の関数では導けず、記録として別に持つ必要があります。こうした「盤面から計算し直せない値」は、移行で落ちやすく、落ちると取り戻せません。同じ理由で、演奏から録音したパッドの内容も、パターンからは再現できない値です。

読者が自分で確かめる手順

古い保存が端末に残っている読者向けの手順です。古い保存がない場合は、移行後の挙動は比較できません。

  1. 保存を壊さない:ブラウザのサイトデータの削除や、別のブラウザ・プロファイルでの確認は避けます。古い保存は、消した時点で戻せません。
  2. 条件を記録する:端末、ブラウザ、ログインの有無を書き留めます。ゲストの進行は端末内に保存される一方、ログイン後のアカウントへ自動では混ざらないと案内されています。条件が違えば、見えるデータも違う可能性があります。
  3. 開く前の状態を残す:移行は開いたときに起きる可能性があるため、可能なら先に控えます。手元に以前のスクリーンショットや、作品の中身を書いたメモがあれば、それを「以前」の記録にします。すでに新しい版を開いてしまった場合は、記憶ではなく残っている資料だけを根拠にします。
  4. 項目ごとに判定する:前節の項目について、次の表を作ります。
  • 池の位置、以前:左下、開いた後:左下、判定:同じ
  • 季節、以前:秋、開いた後:春、判定:初期値に戻った

判定は「同じ」「見た目が変わった」「初期値に戻った」「見当たらない」の四つにします。前節の四つの扱いに対応しています。

  1. 二度開く:一度閉じて開き直し、判定が変わらないかを見ます。二度目に値が動いていれば、移行が二重にかかっているなど、読み替えに不具合がある可能性があります。

判断基準:どの捨て方なら受け入れられるか

判定がそろったら、次の順で読みます。

  • 「同じ」が並べば、守るべきものは守られています。
  • 「見た目が変わった」には、尺度の変換を疑います。大きさやテンポのように連続した値で起きやすい現象です。
  • 「初期値に戻った」が、利用者の選択(季節、テンポ、スウィングなど)にあれば、読み替えで上書きされた可能性があります。新機能の設定が初期値なのは自然ですが、以前から選んでいた設定が戻るのは別の話です。
  • 「見当たらない」が、作ったものや記録(庭の物、パターン、手数)にあれば、重い損失です。表示用の補助値なら、影響は小さいと考えられます。

気になる結果が出た場合は、作品を開いた状態の記録を添えて、現在の案内に沿って問い合わせるのが確実です。仕様の細部は、この記事ではなく案内で確認してください。

作る側にとっても、同じ四分類は設計の道具になります。保存に version を持たせ、読み替え関数を一本にまとめ、読めない形式は上書きせず残すという三点だけでも、更新後の事故はかなり減らせます。アソバースでは、アップロード作品のクラウドセーブは作品ごとにJSONで128KiBまでと案内されています。一方で、アップロード作品内のlocalStorageやsessionStorageは一時メモリーの互換処理とされているため、長く残したい進行をそこだけに置く設計は避けたほうが安全です。

次の行動

保存のある3作品のうち、遊んだ覚えのある作品を一つ選び、前節の表を作ってみてください。三つの判定のうち「初期値に戻った」と「見当たらない」が出た項目は、次に同じ作品を開くときも、同じ条件でもう一度確かめます。自分の作品に保存を付ける方は、上のコードの migrate を雛形に、読めない形式を上書きしない一行から取り入れてみてください。