Claude Fable 5 が再公開されたので、また何か作らせてみることにしました。前回は回路図エディタでしたが、今回はこのサイトに置いてある3Dドライブゲーム(/drive3d_mobile)にオンライン対戦を足してもらいました。スマホ2台で、4桁のルームコードを伝え合うだけで対戦できます。
このサイトは自宅のRaspberry Piで動いています。ゲームサーバとして毎秒何十回も位置情報を中継するような仕事をさせたら、他のページごと死にます。
そこでClaudeに出した条件は「できればスマホ端末同士で直接通信してほしい」。返ってきた設計はこうでした。
create / join / answer の3本だけルーム情報はFlaskプロセスのメモリ上のdictに置くだけ(10分で自動破棄)。SDP交換が済んだら即削除されるので、サーバに残るものは何もありません。Piの負荷は実測でほぼゼロです。
P2P対戦で面倒なのは、遅延がある中で「どっちの画面を信じるか」です。Claudeの採った方針はシンプルでした。
これなら遅延があっても「自分は避けたのに死んだ」という理不尽が起きにくい。判定が両端末で完全一致はしませんが、落ちたら負けというルール上、勝敗判定(自分の落下)は各自が自分の分だけ宣言するので矛盾しません。
その後の改善で、DataChannelは2本に分けました。位置更新は順序なし・再送なしのチャンネル(古いパケットはシーケンス番号で捨てる)、弾・爆発・勝敗などのイベントは信頼性ありのチャンネル。1本の信頼性チャンネルに全部載せると、パケットロス時に位置更新まで巻き添えで遅延する(head-of-line blocking)ためです。
最初のP2P対戦が動いたあとは、思いつくまま注文を投げて育てていきました。
気に入っているのは縮む足場の実装です。足場の縮小は「ラウンド開始からの経過時間」だけから決定的に計算されるので、縮小のための同期通信が1バイトも要らない。P2Pゲームのギミックはこういう作り方をするのかと感心しました。
Jinjaテンプレートと ES6 テンプレートリテラルの衝突。 ゲームのHTMLはFlask(Jinja)経由で配信されているので、JavaScriptに ${{...}} のようなコードを書くと、Jinjaが {{ }} を自分の構文として解釈してサイトごと500エラーになります。一度これでゲームページが丸ごと落ちました。しかもClaude自身が「JinjaだからJSに {{ は書けない」という注意コメントをコードに残そうとして、そのコメント内の {{ でまた500を出すというオチ付き。
見えないCPU車が弾を食っていた。 対戦モードではソロ用のCPU車を非表示にしていたのですが、当たり判定は生きたままでした。ローカルの画面分割対戦では「たまに弾が消える」程度で誰も気づかなかったのが、オンライン化でCPU車の配置乱数が端末ごとに違うため「自分の画面では当たったのに相手の画面では外れている」という形で顕在化。オンライン化は隠れバグの検出器にもなります。
STUNだけではつながらない相手がいる。 TURN中継サーバを立てればほぼ確実につながりますが、中継トラフィックがPiを通るなら本末転倒なのでSTUNのみにしました。キャリア回線同士(対称NAT)だと稀に接続に失敗します。同じWi-Fiなら確実につながるので、個人サイトのゲームとしてはこれで十分と判断。
iPhoneのSafariは全画面にできない。 実機で遊ぶと、Safariの下のツールバーが消えずゲームが全画面になりません。調べてもらったところ、これは実装の問題ではなくiPhoneのSafariがページからのFullscreen APIを提供していないという仕様でした(動画だけは全画面にできる。iPadやAndroidのChromeでは使える)。つまりSafariのタブ内で完全全画面は、どんなサイトでも原理的に不可能。
対策は3段構えになりました。
apple-mobile-web-app-capable など)を足し、Safariの共有メニューから「ホーム画面に追加」→そのアイコンから起動すると、Safariの表示が一切ない完全全画面(standalone)で立ち上がる。実機で確認済みで、ちゃんと全画面になりました。オンライン対戦もそのまま動きます100dvh でツールバーを除いた実表示域に高さを合わせ、viewport-fit=cover + env(safe-area-inset-*) でスティックやボタンがノッチ・ホームバーに被らないよう調整「全画面にして」という雑な注文に対して、「iPhoneではAPIが存在しないので、ホーム画面追加へ誘導する案内をiPhoneのSafariで開いたときだけ表示する」という落とし所まで含めて返ってくるのは、制約の説明込みで任せられて楽でした。
対戦相手がいる方はぜひ /drive3d_mobile からどうぞ。