可観測性とスナップショット (Observability & Snapshots)


目的

このページでは、Reactive Entity Sets (RES) が提供する「真の可観測性(True Observability)」について説明します。集合論的意味論に基づく理論的背景と、スナップショット、タイムトラベルデバッグ、自動テストといった実践的な応用について解説します。


理論的基礎 - 集合論的可観測性

RESがなぜこのようにデータを扱うのかを理解するには、「観測(Observation)」の本質に立ち返ることが大切です。

観測とは「フィルタリング」である

どのようなシステムにおいても、観測者が「生の真実」を直接見ることはありません。常に特定の視点を通してフィルタリングされた「部分集合」を見ています。

  • $T$ をシステム内の全データの全集合(Total Set)とします。
  • $S_n$ を観測者(Observer)(フィルタやビュー)とします。

$S_n$ によって観測されるデータは以下のようになります。

\[S_n(T) \subseteq T\]

たとえば、

  • $T$ — メモリ上の生のエンティティデータ
  • $S_{Inspector}$ — Unity Inspector用に整形・フィルタリングされたデータ
  • $S_{GameView}$ — 画面上のピクセルとしてレンダリングされたデータ
  • $S_{Logic}$ — 特定のAIアルゴリズムがアクセスするデータ

時間関数としてのデータ

データは静的ではなく、時間とともに変化します。$T$ は時間 $i$ の関数 $T_i$ として表されます。

\[\text{観測される状態} = S_n(T_i)\]

この式は、ゲーム開発における可観測性(Observability)の本質を明らかにします。

真の可観測性とは、任意の時点 $i$ における全状態 $T_i$ を決定論的にキャプチャし、後から任意の観測者 $S_n$ を適用して、完全に同じ結果を再現できる能力を指します。

従来アーキテクチャの問題点

従来のオブジェクト指向プログラミング(OOP)やシングルトンベースのアーキテクチャでは、$T_i$ はヒープ上に散らばり、privateフィールドに隠蔽され、オブジェクト参照で絡み合っています。

  • $T_i$ をキャプチャできない — ヒープ全体を瞬時にシリアライズすることは事実上不可能です。
  • $S_n(T_i)$ を再現できない — 正確な状態がなければ、バグを決定論的に再現することはできません。

RESの解決策 - 「真実」としてのデータ

Reactive Entity Setsは、データ ($T$)振る舞い ($S_n$) を厳密に分離することでこれを解決します。

1. 連続したメモリレイアウト

RESは unmanaged struct 制約を使用するため、10,000体のエンティティの状態 $T_i$ は、単なる連続したメモリブロック(NativeArray)として存在します。

2. 瞬時のスナップショット (Blitting)

$T_i$ のキャプチャは複雑なシリアライズ処理ではありません。生のメモリコピー(MemCpy/Blit)です。

  • 速度 — 10,000エンティティのコピーでも1ms未満です。
  • 正確性 — 状態のビット単位での完全なコピーです。

3. 決定論 (Determinism)

ロジックがデータから分離されている(純粋関数)ため、状態 $T_i$ に対してロジック $S_A$ を適用すれば、常に同じ結果が得られます。


実践的なパターン

1. タイムトラベルデバッグ

$T_i$ を瞬時にキャプチャ・復元できるため、ゲーム履歴の「シークバー」を実装できます。

// 履歴を保存
history.Add(entitySet.CreateSnapshot(Allocator.Persistent));

// 時間を巻き戻す(0.0msの切り替え)
public void SeekToFrame(int frame)
{
    // 復元は単なるメモリコピーです。
    // 世界の「真実」が瞬時に上書きされます。
    entitySet.RestoreSnapshot(history[frame]);
}

なぜ強力なのか 単に入力をリプレイしているのではなく、メモリ状態そのものを復元しています。一時停止し、値を検査し、さらには介入(状態変更)して、未来がどう変わるか(バタフライエフェクト)を観察することができます。

2. フライトレコーダー(バグレポート)

直近60秒分のスナップショットをリングバッファに保持します。エラーが発生した際、このバッファをファイルにダンプします。

シナリオ QA「敵が壁をすり抜けました。」

従来のアプローチ 曖昧な説明を頼りに再現を試みます(多くの場合不可能です)。

RESのアプローチ フライトレコーダーのダンプをロードします。手元には $T_{i-60s} \dots T_i$ の完全なシーケンスがあります。フレームごとにステップ実行し、物理エンジンがなぜその位置を計算したのかを完全に特定できます。

3. ホットスワップと状態注入

現在の状態 $T_{now}$ をバイナリファイルに保存し、外部で修正して、実行中のゲームに注入し直すことができます。

// 現在の状態を保存
var snapshot = entitySet.CreateSnapshot(Allocator.Temp);
File.WriteAllBytes("state.bin", ToBytes(snapshot));

// ... 外部ツールで state.bin を編集 ...

// 注入
var bytes = File.ReadAllBytes("state.bin");
var newSnapshot = FromBytes(bytes);
entitySet.RestoreSnapshot(newSnapshot);

4. Golden Snapshotによる並列テスト

ユニットテストのために複雑なセットアップコードを書く代わりに、スナップショットを使用できます。

  1. 記録 — ゲームを実行し、複雑な状況を作り、「Golden Snapshot(正解データ)」として保存します。
  2. テスト — テストコード内で、Golden Snapshotを新しい ReactiveEntitySet にロードします。
  3. 検証 — ロジックを実行し、出力をアサートします。

UnityのシーンやGameObjectに依存しないため、データ駆動テストを極めて高速に、かつ並列で実行できます。


まとめ

RESにおける可観測性とは、単にInspectorで値が見えることではありません。ロジックやViewから分離され、スナップショットとしてキャプチャでき、決定論的に復元できます——これが数学的な保証です。

ゲームの状態を $T_i$ の連続として扱うことで、「何が起きたか推測する」状態から「何が起きたか完全に知っている」状態へと移行できます。


次のステップ


This site uses Just the Docs, a documentation theme for Jekyll.