diff --git "a/docs/05_TECH/TECH_01_\351\200\232\344\277\241\346\234\200\351\201\251\345\214\226.txt" "b/docs/05_TECH/TECH_01_\351\200\232\344\277\241\346\234\200\351\201\251\345\214\226.txt" new file mode 100644 index 0000000..157eabb --- /dev/null +++ "b/docs/05_TECH/TECH_01_\351\200\232\344\277\241\346\234\200\351\201\251\345\214\226.txt" @@ -0,0 +1,166 @@ +======================================================================== +通信最適化 (Network Optimization) +======================================================================== + + +1. 概要 (Overview) +------------------------------------------------------------------------ + +1-1. 目的 + 最大100人が同時接続するリアルタイム対戦ゲームにおいて,サーバー・クライアント間の通信帯域と + メッセージ数を最小化しつつ,低遅延な状態同期を実現する. + +1-2. 主要な最適化手法 + ・AOI(関心領域)フィルタリング: プレイヤーの周辺データのみ送信する + ・ティックバッチ送信: 50ms間隔で差分をまとめて1回送信する + ・座標量子化: 浮動小数点座標を丸めて帯域を削減する + ・差分同期: 前回送信値から変化した分のみ送信する + ・マップセルのグループ化: セル更新をチームIDでまとめて送信する + + +2. AOI(関心領域)フィルタリング (Area of Interest) +------------------------------------------------------------------------ + +2-1. 仕組み + フィールド全体をAOIセル(3×3グリッド単位)に分割し,各プレイヤーの周辺ウィンドウ + (横5×縦3 AOIセル = 15×9グリッド)に含まれるデータのみを送信する. + +2-2. AOIグリッド設定 + ・AOIセルサイズ: 3グリッド単位 + ・AOIウィンドウ: 横5セル × 縦3セル + +2-3. フィルタリング対象 + ・プレイヤー位置更新(UPDATE_PLAYERS) + ・ボム設置通知(BOMB_PLACED) + ・ハリケーン更新(UPDATE_HURRICANES) + +2-4. AOIセルキャッシュ + ・各ソケットごとに前回のAOIセル座標を保持する + ・プレイヤーのAOIセルが変化した場合のみウィンドウ再計算を行う + ・同一セル内の移動ではフィルタリング処理をスキップする + +2-5. プレイヤー可視性管理 + 各ソケットごとに以下のSetを保持し,AOI境界の出入りを検知する. + ・visiblePlayerIds: 現在可視のプレイヤーIDセット + ・visibleBombIds: 現在可視のボムIDセット + ・visibleHurricaneIds: 現在可視のハリケーンIDセット + + ■ 出入り検知 + ・前回不可視 → 今回可視: NEW_PLAYERイベントを発行する + ・前回可視 → 今回不可視: REMOVE_PLAYERイベントを発行する + ・継続可視のプレイヤーのみUPDATE_PLAYERSに含める + + +3. 20Hzティック&バッチ送信 (Tick-Based Batching) +------------------------------------------------------------------------ + +3-1. ティックレート + ・サーバーゲームループ: 20Hz(50ms間隔) + ・スケジュール方式: setTimeoutで次回ティック時刻を正確に計算し,setIntervalのドリフトを回避する + +3-2. バッチ処理 + 1ティックで以下の処理をまとめて実行し,結果を1回で送信する. + 1. ハリケーンの位置更新と衝突判定 + 2. Bot AIの行動決定と位置更新 + 3. マップの塗り処理と競合判定 + 4. ボムの爆発判定(Bot向け) + 5. 差分データの組み立て + +3-3. 送信メッセージ(1ティックあたり最大) + ・UPDATE_PLAYERS: 位置が変化したプレイヤーのみ + ・UPDATE_MAP_CELLS: 塗り状態が変化したセルのみ + ・UPDATE_HURRICANES: 状態が変化したハリケーンのみ + ・変化がない種別のメッセージは送信しない + + +4. 座標量子化 (Position Quantization) +------------------------------------------------------------------------ + +4-1. プレイヤー座標 + ・量子化スケール: 100(小数第2位で丸める) + ・計算式: Math.round(value × 100) / 100 + ・例: 3.14159 → 3.14 + +4-2. ハリケーン座標 + ・位置の量子化スケール: 10(小数第1位で丸める) + ・回転の量子化スケール: 4(0.25刻み) + ・プレイヤーより粗い精度で十分なため,帯域をさらに削減する + +4-3. 3層の量子化パイプライン(プレイヤー位置) + 1. クライアント送信時: 量子化後の座標が前回と同じなら送信をスキップする + 2. サーバー受信時: 受信座標を再量子化して精度を統一する + 3. サーバー送信時: 量子化後の座標で差分比較し,変化分のみ送信する + +4-4. 異常値ガード + ・NaN/Infinityの場合は0を返す + ・量子化スケールが無効(0以下)の場合は元の値をそのまま返す + + +5. 差分同期 (Delta Synchronization) +------------------------------------------------------------------------ + +5-1. 汎用デルタ抽出ユーティリティ + ・collectSyncDeltaEntries(): IDベースのスナップショット比較を行う汎用関数 + ・前回送信したスナップショットをMapで保持し,変化したエントリのみを返す + ・プレイヤー位置同期とハリケーン同期の両方で共通利用する + +5-2. プレイヤー位置の差分 + ・ソケットごとにlastSentPositionByPlayerIdを保持する + ・量子化後の座標が前回と同じプレイヤーはUPDATE_PLAYERSから除外する + ・切断プレイヤーのキャッシュは自動クリーンアップする + +5-3. UPDATE_PLAYERSのteamId省略 + ・初回接続時のCURRENT_PLAYERSでteamIdを含む完全なPlayerDataを送信する + ・以降のUPDATE_PLAYERSではid, x, yのみ送信し,teamIdを省略する + ・毎ティックのペイロードサイズを削減する + + +6. マップセルのグループ化 (Grouped Cell Updates) +------------------------------------------------------------------------ + +6-1. データ形式 + セル更新を個別のオブジェクト配列ではなく,チームIDをキーとしたグループ形式で送信する. + + ■ 変換前(個別形式) + [{ index: 5, teamId: 2 }, { index: 7, teamId: 2 }, { index: 10, teamId: 1 }] + + ■ 変換後(グループ形式: GroupedCellUpdates) + { "2": [5, 7], "1": [10] } + +6-2. 効果 + ・teamIdキーの重複を排除し,ペイロードサイズを削減する + ・セル数が多いほど圧縮効率が高くなる + +6-3. 差分のみ送信 + ・MapStoreのpendingUpdatesキューに変化セルのみを蓄積する + ・既に同じチームで塗られているセルはキューに追加しない + ・getAndClearUpdates()でキューの参照をゼロコピーで差し替える + + +7. クライアント側の送信最適化 (Client-Side Send Optimization) +------------------------------------------------------------------------ + +7-1. 位置送信の間引き + ・PlayerMoveSenderが量子化後の座標を前回送信値と比較する + ・変化がない場合は送信をスキップする + ・停止時にはforce: trueオプションで最終座標を確実に送信する + +7-2. 送信間隔 + ・PLAYER_POSITION_UPDATE_MS: 50ms間隔でゲームループから呼び出される + ・ティックレートと同期し,サーバーが処理可能な頻度に制限する + + +8. パフォーマンス計測 (Performance Monitoring) +------------------------------------------------------------------------ + +8-1. 計測項目(1秒ウィンドウ単位) + ・tickCount: ウィンドウ内のティック数 + ・avgTickMs: ティックあたりの平均処理時間 + ・maxTickMs: ピーク処理時間 + ・cpuUsagePct: ゲームロジックのCPU使用率(= totalTickMs / windowMs × 100) + ・avgPayloadBytesPerTick: ティックあたりの平均ペイロードサイズ + ・outboundBytesPerSec: 推定送信帯域(= avgPayload × playerCount × tickCount) + +8-2. 活用 + ・1秒ごとにログ出力し,ボトルネックの検知に利用する + ・ティック処理が50msを超えた場合はキャッチアップ機構が作動する diff --git "a/docs/05_TECH/TECH_02_\346\231\202\345\210\273\345\220\214\346\234\237_\343\203\251\343\202\260\345\257\276\347\255\226.txt" "b/docs/05_TECH/TECH_02_\346\231\202\345\210\273\345\220\214\346\234\237_\343\203\251\343\202\260\345\257\276\347\255\226.txt" new file mode 100644 index 0000000..cf740fd --- /dev/null +++ "b/docs/05_TECH/TECH_02_\346\231\202\345\210\273\345\220\214\346\234\237_\343\203\251\343\202\260\345\257\276\347\255\226.txt" @@ -0,0 +1,172 @@ +======================================================================== +時刻同期・ラグ対策 (Clock Synchronization & Lag Mitigation) +======================================================================== + + +1. 概要 (Overview) +------------------------------------------------------------------------ + +1-1. 目的 + 通信遅延が存在する環境で,全クライアントとサーバーが同じゲーム内時刻を共有し, + ボムの爆発タイミングなどの時間依存イベントを全端末で同時に発生させる. + +1-2. 主要な手法 + ・Ping/Pongクロック同期: RTT計測によるクライアント・サーバー間の時刻差推定 + ・EWMA平滑化: 外れ値を除去し安定したオフセットを維持する + ・適応的同期間隔: ネットワーク品質に応じて同期頻度を自動調整する + ・ボムの爆発時刻指定: 経過時間ベースでラグに依存しない爆発判定を行う + ・クライアントサイド予測: ボム設置をローカルで即座に反映する + ・ティックキャッチアップ: サーバー遅延時の連続ティック処理と暴走防止 + + +2. Ping/Pongクロック同期 (Clock Synchronization) +------------------------------------------------------------------------ + +2-1. プロトコル + 1. クライアントがPINGイベントで現在のクライアント時刻(clientTime)を送信する + 2. サーバーがPONGイベントでclientTimeとサーバー時刻(serverTime)を返す + 3. クライアントがPONG受信時刻からRTTとオフセットを算出する + +2-2. RTT・オフセット算出 + ・RTT計測: measuredRttMs = PONG受信時刻 - clientTime + ・片道遅延推定: estimatedOneWayMs = measuredRttMs / 2 + ・オフセット算出: offsetMs = serverTime - (clientTime + estimatedOneWayMs) + ・このオフセットを加算することで,クライアント時刻をサーバー時刻に変換できる + +2-3. RTT外れ値フィルタ + ・負のRTT: 棄却する(時計の巻き戻りや異常パケット) + ・1000ms超のRTT: 棄却する(ネットワーク異常として無視) + ・バリデーション失敗時はnullを返し,オフセット更新をスキップする + + +3. EWMA平滑化 (Exponential Weighted Moving Average) +------------------------------------------------------------------------ + +3-1. オフセットの平滑化 + ・アルファ値: 0.12(約8サンプル分の移動平均に相当) + ・計算式: smoothed = current × (1 - 0.12) + measured × 0.12 + ・効果: 単一のノイジーなサンプルの影響を緩和し,安定したオフセットを維持する + +3-2. RTTの平滑化 + ・アルファ値: 0.25(約4サンプル分の移動平均に相当) + ・オフセットより高いアルファ値で,ネットワーク状態の変化に素早く追従する + +3-3. ジャンプ検出 + ・しきい値: 250ms + ・前回のオフセットとの差が250msを超えるサンプルは棄却する + ・Wi-Fi切り替えやネットワーク経路変更による急激な変動を吸収する + + +4. 適応的同期間隔 (Adaptive Sync Interval) +------------------------------------------------------------------------ + +4-1. RTTに応じた同期頻度 + ・RTT ≤ 80ms(低遅延): 5000ms間隔で同期する + ・RTT ≤ 180ms(中遅延): 3000ms間隔で同期する + ・RTT > 180ms(高遅延): 2000ms間隔で同期する + ・RTT未取得(初期状態): 3000ms間隔で同期する + +4-2. 可変間隔ループ + ・setTimeoutベースで毎回次の同期間隔を再計算する + ・ネットワーク品質の変化にリアルタイムで適応する + ・低遅延環境では同期頻度を下げて不要なトラフィックを削減する + + +5. ボムの爆発時刻指定方式 (Elapsed-Time-Based Bomb Detonation) +------------------------------------------------------------------------ + +5-1. 仕組み + ボムの爆発タイミングをサーバー時刻ではなく,ゲーム開始からの経過時間で指定する. + + ■ 設置時の流れ + 1. クライアントが設置時の経過時間にBOMB_FUSE_MS(1000ms)を加算する + explodeAtElapsedMs = 現在の経過時間 + 1000 + 2. PlaceBombPayloadにexplodeAtElapsedMsを含めてサーバーに送信する + 3. サーバーがActiveBombRegistryに登録し,全クライアントにブロードキャストする + + ■ 爆発判定 + ・クライアント: 各フレームでelapsedMs >= explodeAtElapsedMsならば爆発状態に遷移する + ・サーバー: 各ティックで同条件を判定し,Bot向けの被弾判定を実行する + +5-2. ラグ耐性 + ・全端末が同じ「経過時間」で判定するため,個別の通信遅延に影響されない + ・サーバーACKを待たずにクライアントが独立して爆発を判定できる + ・Ping/Pongクロック同期により経過時間の基準が揃っている + +5-3. ボムの状態遷移 + ・armed(設置中): 設置〜爆発まで + ・exploded(爆発中): 爆発〜250ms後まで(爆発エフェクト表示期間) + ・finished(完了): エフェクト終了後,描画から除去する + + +6. ボム重複排除 (Bomb Deduplication) +------------------------------------------------------------------------ + +6-1. 重複排除キー + ・形式: "ownerPlayerId:requestId" + ・クライアントが生成するrequestIdとソケットIDの組み合わせで一意性を保証する + +6-2. TTL管理 + ・有効期間: BOMB_FUSE_MS + BOMB_DEDUP_EXTRA_TTL_MS = 1000 + 1000 = 2000ms + ・チェック時に期限切れエントリを自動クリーンアップする + ・同一キーが存在する場合はブロードキャストをスキップする + +6-3. 防止する問題 + ・パケット再送による重複設置 + ・同一ボムの複数回ブロードキャスト + ・爆発後の遅延リクエスト処理 + + +7. クライアントサイド予測 (Client-Side Prediction) +------------------------------------------------------------------------ + +7-1. ボム設置の即時反映 + サーバーの応答を待たずにボムをローカルに描画し,体感遅延を解消する. + + ■ フェーズ1: 楽観的設置 + 1. BombIdRegistryが一時ID(temp:requestId)を発行する + 2. 一時IDでボムをBombRepositoryに登録し,即座に描画する + 3. サーバーにPlaceBombPayloadを送信する + + ■ フェーズ2: ACK後の差し替え + 1. サーバーからBOMB_PLACED_ACK(bombId, requestId)を受信する + 2. requestIdから一時IDを逆引きする + 3. 一時IDのボムを削除し,正式なbombIdで再登録する + +7-2. 効果 + ・ボム設置のレスポンスが通信遅延(RTT/2)分だけ早く感じられる + ・サーバーが拒否した場合は一時ボムを削除するだけで整合性を保てる + + +8. ティックキャッチアップ (Tick Catch-Up) +------------------------------------------------------------------------ + +8-1. 遅延回復機構 + サーバーのティック処理が50msを超えた場合,遅れを回復するために連続ティックを実行する. + + ■ キャッチアップルール + ・1サイクルあたり最大3ティックまで連続処理する + ・3ティック処理後も遅延が残る場合は,次回ティック時刻を現在時刻にリセットする + ・これにより「キャッチアップスパイラル」(遅延が蓄積し続ける状態)を防止する + +8-2. スケジュール方式 + ・setTimeoutで次回ティックの正確な時刻を計算する + ・setIntervalのドリフト(累積誤差)を回避する + ・各サイクル開始時にnowMsを取得し,nextTickAtMsとの差分で判定する + + +9. マップの競合判定 (Map Contest Resolution) +------------------------------------------------------------------------ + +9-1. 問題 + 同一セルに複数チームのプレイヤーが同時に存在する場合,どのチームが塗るかが不定になる. + +9-2. 解決策 + ・各ティックで全プレイヤーのグリッド座標を1パスで収集する + ・cellTeamMap(Map)を構築する + ・同一セルに異なるチームIDが検出された場合,CONTESTED_CELL(-2)を設定する + ・CONTESTED_CELLのセルは塗り処理をスキップする + +9-3. 効果 + ・不公平な陣地獲得を防止する + ・1パスで処理が完了し,計算コストが低い diff --git "a/docs/05_TECH/TECH_03_\346\217\217\347\224\273\346\234\200\351\201\251\345\214\226.txt" "b/docs/05_TECH/TECH_03_\346\217\217\347\224\273\346\234\200\351\201\251\345\214\226.txt" new file mode 100644 index 0000000..b20a389 --- /dev/null +++ "b/docs/05_TECH/TECH_03_\346\217\217\347\224\273\346\234\200\351\201\251\345\214\226.txt" @@ -0,0 +1,210 @@ +======================================================================== +描画最適化 (Rendering Optimization) +======================================================================== + + +1. 概要 (Overview) +------------------------------------------------------------------------ + +1-1. 目的 + PixiJSによるリアルタイム描画において,大人数・大マップでも60fpsを維持するための + クライアント側の描画最適化手法をまとめる. + +1-2. 主要な最適化手法 + ・ビューポートカリング: 画面外のエンティティの描画をスキップする + ・画面外プレイヤーの間引き更新: 不可視プレイヤーのtick頻度を削減する + ・プレイヤー補間(LERP): リモートプレイヤーの位置を滑らかに補間する + ・フレームデルタクランプ: 異常なデルタタイムを制限する + ・テクスチャキャッシュ: 同一アセットの重複ロードを防止する + ・マップの差分描画: 変化セルのみを再描画する + ・Bot JITウォームアップ: ゲーム開始前にV8 JITコンパイルを促進する + + +2. ビューポートカリング (Viewport Culling) +------------------------------------------------------------------------ + +2-1. 仕組み + カメラの表示範囲外にあるエンティティのvisibleプロパティをfalseに設定し, + PixiJSの描画パイプラインから除外する. + +2-2. カリング対象 + ・プレイヤー(PlayerView) + ・ボム(BombView) + ・ハリケーン(HurricaneOverlayController) + +2-3. 判定方法 + ・円-矩形交差判定(isCircleIntersectingViewport)を使用する + ・エンティティの半径円とビューポート矩形の交差を判定する + +2-4. カリングマージン + ・ビューポートをGRID_CELL_SIZE分だけ拡張する(expandWorldViewport) + ・マージンにより画面端でのエンティティのポップイン(突然の出現)を防止する + +2-5. 遅延描画 + ・ハリケーンは可視状態に変化した瞬間のみrenderDisplayFromState()を呼び出す + ・不可視→可視の遷移時のみ再描画し,可視継続中は通常のtick更新のみ行う + + +3. 画面外プレイヤーの間引き更新 (Offscreen Update Throttling) +------------------------------------------------------------------------ + +3-1. 仕組み + 画面外のリモートプレイヤーのtick処理を6フレームに1回に間引く. + +3-2. 実装 + ・フレームカウンタ: frameCount % 6 === 0 のフレームのみ画面外プレイヤーをtickする + ・デルタタイム補正: 間引き時は deltaSeconds × 6 を渡し,位置精度を維持する + ・画面内プレイヤー: 毎フレーム通常のdeltaSecondsでtickする + +3-3. 効果 + ・画面外プレイヤーの更新コストを約1/6に削減する + ・デルタタイム補正により,画面内に戻った際の位置ずれを防止する + + +4. プレイヤー補間 (Player Interpolation) +------------------------------------------------------------------------ + +4-1. リモートプレイヤーのLERP + サーバーから受信した目標座標に向けて,フレームごとに滑らかに補間移動する. + + ■ 計算式 + newPos = currentPos + (targetPos - currentPos) × LERP_SMOOTHNESS × deltaTime + + ・LERP_SMOOTHNESS: 設定値で補間速度を制御する + ・deltaTimeを乗算し,フレームレートに依存しない一定速度の補間を実現する + +4-2. スナップ閾値 + ・現在位置と目標位置の距離がPLAYER_LERP_SNAP_THRESHOLDを下回る場合,即座に目標位置に移動する + ・微小な揺れ(ジッター)を防止する + +4-3. 効果 + ・20Hz(50ms間隔)の位置更新でも60fpsで滑らかに見える + ・サーバーからの位置が急に変わった場合もスムーズに追従する + + +5. フレームデルタクランプ (Frame Delta Clamping) +------------------------------------------------------------------------ + +5-1. 問題 + フレーム落ちやタブ非表示からの復帰時に,deltaTimeが異常に大きくなり + 物理計算の暴走(プレイヤーが瞬間移動する等)が発生する. + +5-2. 解決策 + ・resolveFrameDelta()関数でdeltaTimeの上下限を制限する + ・下限: 0(負の値やNaNを防止する) + ・上限: FRAME_DELTA_MAX_MS(設定された最大値) + +5-3. 適用範囲 + ・プレイヤーの移動計算 + ・LERP補間 + ・ボム・ハリケーンの状態更新 + ・全ての時間依存処理がこのクランプ済みデルタを使用する + + +6. テクスチャキャッシュ (Texture Caching) +------------------------------------------------------------------------ + +6-1. モジュールレベルキャッシュ + SVGアセットのテクスチャをモジュールスコープの変数にキャッシュし, + 複数のインスタンスから参照しても1回だけロードする. + + ■ キャッシュパターン + ・cachedTexture: ロード完了後のTexture参照を保持する + ・texturePromise: ロード中のPromise参照を保持する + ・初回呼び出し: Assets.load()でPromiseを生成し,texturePromiseに保存する + ・2回目以降: 既存のPromiseをawaitする(追加のフェッチは発生しない) + ・ロード完了後: cachedTextureから即座に返す + +6-2. キャッシュ対象 + ・爆発エフェクト(bakuhatueffe.svg): RespawnEffectTextureCache + ・ハリケーン(hurricane.svg): HurricaneTextureCache + ・ボム(Bomb.svg): BombViewの静的テクスチャ + +6-3. アセットプリロード + ・preloadGameStartAssets()をゲーム開始前に呼び出す + ・重いSVGアセットを事前にキャッシュし,初回出現時のフレーム落ちを防止する + ・void(fire-and-forget)で呼び出し,ゲーム開始をブロックしない + + +7. マップの差分描画 (Differential Map Rendering) +------------------------------------------------------------------------ + +7-1. セル単位の更新 + ・GameMapViewはセルごとにGraphicsオブジェクトをコンストラクタで事前確保する + ・renderCell(index, color)で変化したセルのみを再描画する + ・全セルの再描画は行わない + +7-2. 描画手順 + 1. 対象セルのGraphics.clear()で前回の描画を消去する + 2. 新しい色でGraphics.fill()を適用する + 3. 未塗装(-1)のセルは既定の背景色を使用する + +7-3. リビジョン管理 + ・GameMapModelがapplyUpdates()のたびにリビジョン番号をインクリメントする + ・ミニマップ等のUI要素はリビジョン変化時のみ再描画する + + +8. 状態変化キャッシュ (State Change Caching) +------------------------------------------------------------------------ + +8-1. ボムの描画スキップ + ・BombViewが前回描画した状態(state, color, radiusGrid)を保持する + ・状態が変化していない場合はrender()をスキップする + ・armed状態のボムは毎フレーム位置更新のみ,外観は変化しない + +8-2. ハリケーンの描画スキップ + ・半径の変化が0.0001以下の場合はスプライトサイズの更新をスキップする + ・画面外→画面内の遷移時のみrenderDisplayFromState()を実行する + +8-3. ボムの重複チェック + ・BombRepositoryがupsertBomb()時にisSameRenderPayload()で全フィールドを比較する + ・同一ペイロードの場合はオブジェクトの破棄・再生成をスキップする + + +9. ゲームループのステップ分離 (Game Loop Step Architecture) +------------------------------------------------------------------------ + +9-1. ステップ構成 + GameLoopが毎フレーム以下の4ステップを順に実行する. + + 1. InputStep: ジョイスティック入力をPlayerControllerに適用する + 2. SimulationStep: 自プレイヤーの移動 + リモートプレイヤーの補間 + カリング判定 + 3. BombStep: ボムの状態更新 + 爆発判定 + カリング適用 + 4. CameraStep: ワールドコンテナの位置を自プレイヤーに追従させる + +9-2. 設計上の利点 + ・各ステップが独立しており,個別に最適化できる + ・共有のLoopFrameContext(deltaSeconds等)で一貫した時間管理を行う + ・LoopFrameEffectsで移動状態をステップ間で受け渡す + + +10. リソース管理 (Resource Management) +------------------------------------------------------------------------ + +10-1. DisposableRegistry + ・ゲームシーンのリソース(イベントリスナー,PixiJSオブジェクト等)を登録する + ・disposeAll()で登録の逆順に破棄し,依存関係のクラッシュを防止する + ・シーン遷移時にメモリリークを防止する + +10-2. プレイヤーの遅延テクスチャロード + ・初期状態ではTexture.WHITE(1×1ピクセル)で描画を開始する + ・非同期でプレイヤー画像をロードし,完了後にスプライトのテクスチャを差し替える + ・ゲーム画面の初期表示をブロックしない + + +11. Bot JITウォームアップ (Bot JIT Warmup) +------------------------------------------------------------------------ + +11-1. 問題 + ゲーム開始直後の最初のティックでBot全員のdecide()が初めて呼ばれると, + V8エンジンのJITコンパイルが集中し,ティック処理時間がスパイクする. + +11-2. 解決策 + ・ゲーム開始前の待機時間(GAME_START_DELAY_MS: 5000ms)を利用する + ・warmUp()メソッドで全BotのbotTurnOrchestrator.decide()を1回ずつ空実行する + ・V8がホットパスを事前にコンパイルし,初回ティックのスパイクを軽減する + +11-3. ターゲット初期化の分散 + ・各Botのインデックスに応じてchooseNextTarget()の呼び出し回数を分散する + ・chainCount = (botIndex % 4) + 1 で1〜4回の初期化を割り当てる + ・全Botが同一ティックで重い計算を行うことを回避する