BELENUS Web参加のローカル試作
起動コマンド、ディレクトリー構成、ゲーム・スマホの操作、停止とバックアップは 利用ガイド にまとめています。このREADMEでは実装と検証の詳細を扱います。
スマホからはサーバーの /guide(利用ガイド)と /technical-guide(この技術資料)を開けます。Markdownを編集したら export-guide.ps1 でHTMLを生成し直し、サーバーを再起動してください。
ゲーム内の ポーズメニュー → QUEST に参加用QRを表示し、ゲームを持っていない人がスマホのブラウザから依頼や支援を届ける機能です。ゲーム側は既存のクエスト・素材台帳を使います。
起動と操作
Node.js 24以降を使います。初回の依存関係はこの作業でインストール済みです。別の環境では Tools/CompanionBridge で npm ci を実行してください。
プロジェクトのルートから起動します。このターミナルを開いている間、サービスが動作します。
& .\Tools\CompanionBridge\start.ps1
- ビルド済みのEditorでゲームをプレイし、ポーズメニューの QUEST を開きます。
- サービスに接続するとQRが表示されます。R/QRを表示 で再取得、Shift+R/更新 で以前の参加コードを失効できます。招待の有効期間は24時間です。
- PCと同じWi-Fiに接続したスマホで読み取り、呼び名を入力して参加します。Epicアカウント・ゲームの購入・専用アプリは不要です。
- 定型依頼を送ると、ゲーム側の「進行中」「すべて」に 受注待ち として届きます。選択して決定すると受注します。
- 目標達成時に癒しの欠片を1個受け取ります。スマホの「届けた便り」は約5秒ごとに結果を更新します。
- 「ハーブを贈る」は、ゲームに接続した際に静心のハーブ1個を INVENTORY → 素材 に加えます。ゲーム終了中の送信もSQLiteに保管します。
- 写真を頼む場合は 「旅先の景色を見せて」 を選び、ひとことに撮ってほしい内容を書きます。プレイヤーは依頼を受注し、好きな場所で QUEST → 対象の依頼 → F/撮影 を選びます。メニューを一時的に閉じてゲーム画面を撮影し、戻った画面で写真を確認できます。
- T/この写真を納品 で初めて送信を許可します。撮影しただけでは送信しません。Webへの保存に成功し、ゲームの達成・報酬も保存できたら依頼が完了します。依頼者の「届けた便り」に写真が表示され、タップして拡大できます。コントローラーは左フェイスボタンで撮影、上フェイスボタンで納品です。
写真の保存とGCPへの移行
- ゲーム側の下書き・送信待ち:
Saved/CompanionBridge/Outbox/<接続キーとセーブ名から作る識別子>/<依頼UUID>.pngと.json。 - Web側の納品画像:
Saved/CompanionBridge/photos/<ハッシュ先頭2文字>/<SHA-256>.png。SQLiteのphotosテーブルに依頼ID・オブジェクトキー・寸法・ハッシュを保存します。 - PNGは長辺2048pxまでに縮小、最大8MiB。圧縮・画像のファイル書き込み・アップロード前のファイル読み込みはバックグラウンドで実行します。撮影時の元画像は最大約1600万画素までです。
- 納品ボタンを押した後の送信待ちは再起動しても復元します。同じ画像の再送は同じ納品記録を返し、別の写真で納品済み画像を上書きしません。送信待ちになると撮り直しはできません。
- 写真が届いたことを達成条件にします。依頼文の風景と一致するかの自動判定や、スマホ側での承認・差し戻しは今回の範囲に含めません。
- 写真は依頼者のゲスト認証と有効な招待を確認して配信します。画像フォルダーを静的公開せず、別の参加者には見せません。
photo-store.mjs の LocalPhotoStore が保存先の境界です。将来は createBridge({ photoStore }) に、同じ put(bytes, metadata) -> objectKey と get(objectKey) -> bytes を提供するGCP Cloud Storageアダプターを渡します。バケットを非公開にし、認証は現在のAPI側で確認する構成を維持できます。DBにはローカル絶対パスを保存しないため、移行時には画像オブジェクトを同じキーで転送します。実際のGCP接続・転送はまだ行っていません。
Web停止時のゲーム側の動作
Webがなくても通常の移動・操作・既存クエストを続けられます。通信は非同期で、Webの応答待ちをゲームの進行条件にはしません。既に受注した写真依頼はオフラインでも撮影・保存・納品予約できます。写真依頼の達成は納品が成功するまで保留されます。
- 通信の同時実行は最大4件、写真送信は1件ずつ。JSON応答の受信バッファは最大256KiBです。
- 通常リクエストは8秒、写真送信は15秒でタイムアウトします。別の監視でも未完了リクエストを回収し、再試行できる状態へ戻します。
- 失敗時は3~60秒の間隔で再接続します。過大な応答、不正JSON、型が違う項目は無視・拒否し、未検証の値でゲームオブジェクトや任意の報酬を作りません。
- Subsystem終了時は所属するリクエストと撮影コールバックを解除します。撮影には5秒の期限があり、バックグラウンド処理が終了後に戻っても、弱参照と終了フラグで破棄済みオブジェクトへのアクセスを防ぎます。
- Web・保存先が停止したときに、未送信の画像を削除したり、納品済みとして扱ったりしません。画像が壊れている場合もゲームを止めず、保存先の確認を案内します。
LANアドレスを明示する場合:
& .\Tools\CompanionBridge\start.ps1 -PublicUrl 'http://192.168.2.100:8787' -DisplayName '旅人'
IPが複数あるPCでは、起動時に表示されるアドレスを確認してください。スマホから接続できない場合は、同じWi-Fi、ゲストWi-Fiの端末間隔離、WindowsのプライベートネットワークでNode.jsへの通信許可を確認します。サービスはTCP 8787を使用します。外部公開やファイアウォール設定の変更は自動では行いません。
定型依頼とゲームイベント
| 依頼 | 達成条件 | 接続先 |
|---|---|---|
| 少し遠くの景色へ | 受注後に100m移動 | ローカルプレイヤーの水平移動を計測。10m単位で保存。ポーズ・シーン遷移・大きな瞬間移動は除外 |
| 旅の素材を集めよう | 受注後にアイテム3個入手 | UJourneyInventorySubsystem::OnItemAcquired |
| ひとつ、手作りのお土産 | 受注後にアイテム1個合成 | 既存の合成処理から companion.craft を通知 |
| 旅先の景色を見せて | 撮影後に確認し、写真を納品 | 依頼UUID固有の companion.photo.<UUID>。他の写真依頼を一緒に完了させません |
クエスト定義はサーバーとゲーム双方の許可リストにあります。自由文は依頼のメッセージであり、任意のコード・報酬・ゲームイベントは実行しません。同じ条件の依頼を複数受注した場合は、同じ行動でそれぞれ進行します。移動の10m未満の端数はポーズ・再起動時に破棄されます。
保存と重複防止
- SQLite:
Saved/CompanionBridge/companion.sqlite。WAL、外部キー、トランザクションを使用します。 - ローカルゲーム接続キー:
Saved/CompanionBridge/local-client.json。Webでは配信せず、キーはQRにも含めません。ゲームの接続先はループバックに限定します。 - Webの送信IDをDBの一意制約で照合します。同じ送信の再試行は同じ依頼を返します。
- ゲームではWeb依頼の定義と進行状況を
JourneyQuestsにまとめて保存します。 - ハーブは既存の素材台帳と
webgift_<UUID>の受取記録を同じセーブに保存してから受取完了を返します。保存失敗時は付与を取り消し、次の接続で再試行します。 - Web参加者ごとに依頼・支援を各UTC日3回まで、受取人全体で1日50件・未処理20件まで。Cookie削除による同一人物の再参加を完全には防ぎません。ゲーム内に保存するWeb依頼は500件までです。
- 手動で過去のゲームセーブに戻す操作は、サービス側の処理済み履歴を巻き戻しません。初版は1つのPC・進行データ用です。複数セーブスロットやNew Gameごとの独立した宛先は、プレイヤーIDに進行データIDを加える段階で対応します。
変更前の既存ソースは Saved/CompanionBridge/Backups/ に保存しています。マップ・地形・既存アセットは編集していません。
Tursoへの切り替え
db.mjs が保存層を分離し、SQLiteとTursoに同じ非同期SQL・トランザクションのインターフェースを提供します。ローカルではNode標準の node:sqlite、リモートでは @libsql/client/web を使います。
移行時にサービスを停止し、SQLiteをチェックポイント/バックアップして、Tursoへスキーマとデータを取り込みます。その後、起動するPowerShellに環境変数を設定します。接続先の変更だけでは既存データを移送しません。
$env:TURSO_DATABASE_URL = 'libsql://YOUR-DATABASE.turso.io'
$env:TURSO_AUTH_TOKEN = 'YOUR-TOKEN'
& .\Tools\CompanionBridge\start.ps1
Tursoの認証情報はブラウザやゲームに渡しません。実際のTursoデータベースへの接続は未検証です。Tursoに切り替えても、スマホ参加ページとAPIのホスティングは別途必要です。
EOSと公開運用
今回の試作にはEOS認証をまだ組み込んでいません。 ローカルで生成したゲーム側キーと、Web側ゲストCookieを使います。将来、EOS Connectの本人確認で得たプレイヤー識別子にDBの players を対応付け、ゲーム側キーの認証部分を置き換えます。EOSの管理者用秘密情報をWebへ埋め込む構成にはしません。
公開運用ではHTTPS、EOS等で検証したゲーム側の本人確認、Web参加者の継続的なアカウント識別、報酬・達成のサーバー検証、プレイヤー側の拒否・受付停止操作を追加します。現在はゲームクライアントの達成報告を信用するローカル試作です。
検証の実行
Set-Location .\Tools\CompanionBridge
npm test
node verify-engine.mjs
node verify-capture.mjs
npm test は分離したSQLiteでAPI往復、同時再送、保存のロールバック、認証、招待失効、受付上限、サービス再起動を検証します。
verify-engine.mjs は別のSQLite・別のセーブスロットを用意し、UEの実HTTPクライアントで依頼と支援を受信、受注、達成報告、Web側結果を照合します。画像の縮小・ローカル保存、納品予約後のSubsystem再起動、保存サービスの503後の再送を確認します。接続拒否・HTTP 500・不正JSON・過大応答・切断・遅延・不正な項目型・応答待ち中の終了も注入し、通常クエストの進行を確認します。出力は Saved/CompanionBridge/Verification/<日時>/result.json とUEのテストレポートです。
verify-capture.mjs は -UserDir で既存のセーブから分離したゲームを起動します。Entryワールドに検証用の一時的な立方体とライトを生成し、実際のゲームビューポートから撮影、写真の確認画面のPNG保存、納品、クエスト完了まで確認します。マップやアセットは保存しません。出力は Saved/CompanionBridge/RenderVerification/<日時>/ です。
UEの場所が異なる場合は UE_EDITOR_CMD 環境変数で UnrealEditor-Cmd.exe、撮影検証は UE_EDITOR で UnrealEditor.exe を指定します。テストは製品の既存セーブを書き換えません。ゲーム全体のCook・パッケージや、スマホ実機からのWi-Fi接続は別途確認が必要です。
公式資料: Node SQLite、Tursoクライアント、EOS Connect。