ピンクのグラフィック、暴走するスコア、XRクラッシュ。一つの土曜日に起きた四つのバグ。
それぞれのユニットテストに合格する四つのサブシステムの四つのバグ——それが壊れたデモを出荷してしまう失敗モードだ。それを捕まえる規律こそが、RakuAI上で実際に動くサンプルを作り出す。
デモを出荷した経験がある人なら誰でも知っている、特有の種類の土曜日がある。デモは先週末の終わりには動いていた。今週の土曜の朝にそれを開いた。今は四つの異なる方法で壊れていて、しかもどの壊れ方も互いに関係がない。
それが今日だった。デモは、私たちが標準サンプルの一つとして出荷しているShooterゲームだ。バグたちは、誰も招待していないパーティーの客のように待ち構えていた。
何が壊れていたか
デモは起動した。それは良い知らせだった。その後——
グラフィックがピンクだった。 リアルタイムレンダラーで働いたことのある人なら誰でも「ピンク・オブ・ドゥーム」を知っている。これは「シェーダー欠落」を示すプレースホルダーカラー、ホットピンクで、レンダリングパイプラインのどこかが期待していたシェーダーを見つけられなかったことを叫んでいる。デモ内の手続き的に生成されたオブジェクトはすべてこの色でレンダリングされていた。船。小惑星。パーティクル。全部ピンク。至る所ピンク。
スコアが暴走した。 すべてのキルが二回記録された。キルカウンターはペアで増えていった。敵二十体を倒すと「スコア:四十」。敵四十体を倒すと「スコア:八十」。紙の上ではゲームが優しくなったように見えるが、実際にはリーダーボードが水増しされ、ハイスコアのロジックが完全に間違った閾値で発火していた。
XR起動がクラッシュした。 フラットスクリーンでのデモはきれいに起動した。ヘッドセットを接続してXRモードに切り替えた瞬間、ハードに終了した。バックトレースはランチャー経由で一切浮上しなかった。ランチャーがログを取得する前に、レンダラースレッドは消えていた。
カメラが船にロックオンしなかった。 標準的なサードパーソン・オービットカメラで、船をフレーム内に収め続けるはずだった。起動時、カメラはワールド原点にスポーンしたまま留まった。船は虚空の彼方に小さな点として見えていた。
四つのバグ。サブシステムの重複はゼロ。ワークフローを持っているかどうかを決める種類の土曜の朝だった。
それぞれの原因
一つ一つ順に話したい。それぞれが、エージェント駆動のワークフローでコードベースがどう成長し、どう壊れるかについて異なる教訓を教えてくれるからだ。
ピンクのグラフィック
根本原因:シェーダーハンドルがすべてのレンダリング呼び出しごとに文字列名で検索されており、休暇中のリファクタリングによってシェーダーキャッシュの初期化が起動シーケンスの別の地点に移動していた。手続き的ジェネレーターは、シェーダーキャッシュがウォームアップする前にレンダリングを行うようになっていた。ジェネレーターはヌルハンドルのフォールバックを受け取っており、レンダラーはそれを律儀にホットピンクとしてレンダリングしていた。
修正は、初回使用時に解決済みのシェーダー参照をキャッシュし、手続きジェネレーターが起動順序を気にせず呼び出せる同期的なGetOrCreateAPIを公開する、明示的なShaderCacheユーティリティを作ることだった。すべての手続きジェネレーターがこれを使うよう更新された。ピンクは消えた。
教訓:起動シーケンスに暗黙的に存在していた初期化順序の前提が、その前提にフラグを立てないリファクタリングによって壊された。暗黙的な順序は脆い。新しいキャッシュは順序を明示的かつ自己修復的にする。
スコアの暴走
根本原因:GameplayHUDがUI側でキルを集計しており、ScoreManagerもシミュレーション側でキルを集計していた。両方とも同じキルイベントシグナルをリッスンしていた。両方とも共有のスコアフィールドに加算していた。スコアはキルごとに二回インクリメントされていた。
修正は、スコアの正規の所有者を決めることだった。ScoreManagerが真実を所有する。GameplayHUDはScoreManagerから読み取ってレンダリングする。HUD内の重複インクリメントは削除された。
教訓:イベント駆動エンジンでは、同じイベントの二つのサブスクライバーは両方とも実行される。両方が同じ状態に書き込むなら、状態は書き込んだサブスクライバーの数に比例して間違ったものになる。修正は、誰が何の状態を所有するかについて明確な線を引くことだ。一度引かれれば、このバグは不可能になる。
XR起動クラッシュ
根本原因:XRサブシステムは、基盤となるグラフィックデバイスが準備できる前に初期化しようとしていた。フラットスクリーン起動では、ユーザーが「Start」を押す頃にはグラフィックデバイスが常に準備できていたため、順序はたまたまうまくいっていた。XR起動では、ヘッドセット検出がグラフィックデバイスの準備完了が確認される前にXR初期化パスをトリガーしていた。XRサブシステムはヌルのデバイスハンドルを参照外しした。クラッシュ。
修正は、起動のできるだけ早い段階でXRを検出し、グラフィックデバイスが準備完了を確認するまで実際のXR初期化を遅延させ、チェーン内の何かが不具合を報告した場合に優雅にシャットダウンするXRBootstrapだった。クラッシュは今やクリーンなエラーメッセージになり、ユーザーはフラットスクリーンへとフォールバックする。
教訓:ハードクラッシュは最悪の種類のエラーだ。なぜなら、何が起きたかを教えてくれたはずのロギングを道連れに殺してしまうから。障害をより早く検出し、クリーンに報告することは、エンジニアリングコストに見合う価値がある。
ロックしなかったカメラ
根本原因:オービットカメラは起動時にプレイヤーの船をターゲットにしようとする。船は手続きジェネレーターによって、カメラが初期化してから数フレーム後に非同期にスポーンされる。カメラは最初のフレームで船を探し、何も見つからず、その後二度と試さなかった。
修正は、オービットカメラにリトライ機構を与えることだった。カメラは、諦める前に限られたフレーム数だけ船を探し、一度ロックオンすればロックされたままになる。カメラは今、原点で一瞬スポーンした後、ゲームプレイの最初の一秒以内に船へとスナップする。
教訓:わずかに異なるスケジュールで初期化するシステム間の競合状態は、あなたを噛みつく。修正は小さなリトライだ。無限にループしないよう境界を設け、リトライが尽きた場合に有用なエラーを生成する明示的なタイムアウトを付ける。
Shooterデモについて具体的に学んだこと
三つ、どれも居心地の悪いものだ。
各サブシステムは単独では動いていた。統合が壊れていた。 レンダラーは動いていた。スコアシステムは動いていた。XRサブシステムは動いていた。カメラは動いていた。統合された体験としてのShooterデモは動いていなかった。これは、ユニットテストの性質上、常にすり抜ける種類の障害だ。ユニットテストはその性質上、物事を単独で運動させるからだ。
十二月末の休暇中のリファクタリングが、少なくともこれらのバグのうち二つの直接的な原因だった。 シェーダーキャッシュの再編とキルイベントのサブスクリプション分割は、どちらも休暇スプリントの期間中に着地した。どちらも単独では正しいPRだった。どちらも自明でない方法でデモを壊した。教訓は、統合デモをユニットテストだけでなくCIの一部として実行することだ。その作業は今朝、別のPRとして着地した。
XR起動パスには独自の起動ガーディアンが必要だった。 ハードクラッシュは診断を殺すため許容できない。自明でないハードウェアに触れるすべての起動パスにはガーディアンが必要だ。今はXRについてそれがある。空間オーディオやWi-Fi 7リンクについてはまだない。両方を追加することは、次の二回の土曜日のキューに入っている。
パートナーとビルダーがここから学ぶべきこと
初期化順序の依存関係を持つ複数のサブシステムを統合するものを構築しているなら、教訓はShooterデモがちょうど学んだものと同じだ。初期化順序を明示的にせよ。障害モードを優雅にせよ。統合デモをユニットテストだけでなくCIで実行せよ。
Rakuの採用を検討しているインディーチームなら、Shooterデモは私たちが毎リリースでテストする実際のサンプルだ。壊れれば、あなたはそれが壊れるのを見る。それは機能リストよりも重要な種類の公開規律のシグナルだ。
モデルをAR体験に組み込むことを考えているAIラボなら、XR起動パスには今や擁護可能なガーディアンがある。あなたのモデルは入り口でハードクラッシュを見ることはない。チェーン内の何かが失敗すればクリーンなエラーレポートを得られる。インフラは整っている。
四つのバグ。一つの土曜日。デモは今、きれいにビルドされる。パーティーの招かれざる客たちは退場させられた。
構築に戻る。
自らのサンプルを実行するランタイムの上に出荷する
RakuAIは統合CIと優雅なXR起動ガーディアンで、すべてのリリースに対して自らのデモをテストする。それは機能リストよりも重要な公開規律のシグナルだ。それを土台に構築を始めよう。