Repository navigation
Conversation
これまで観戦者は難易度選択画面 (カード未選択時) でしか参加できなかった。 ホストが直近の同期ポイント (難易度選択画面・キャラクター選択画面に入った時点) の 状態と乱数シード、およびそれ以降の入力を記録しておき、途中参加した観戦者には それらを送信して早送りで再生させることで、キャラクター選択中や対戦中でも 参加できるようにする。 - 観戦者の受け付けを画面に関わらず行う (F1 も対戦中に受け付ける) - 観戦者側はホストからの初期化情報を非ブロッキングで待つ - キャラクター選択画面から同期する場合は難易度を決定して画面遷移し、 到達時にカード選択状態と乱数シードを再現する - 対戦中に未処理の入力が溜まっている場合は早送りで追いつく Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
- sync_screen を bool を返す is_sync_point に変更 - SpectatorSelect の initializing_state を enum 化し、 キャラクター選択画面経由の同期でのみ到達時に乱数シードを設定するよう整理 - WaitingForPureP2pSpectator の ready を実態に合わせて show_hint に改名 - pushed_f1 の戻り値の変数名を f1_pushed に改名 - SyncPoint のメッセージ蓄積量についてコメントを追記 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
- 同期ポイントへの移動処理を SpectatorSelect から SpectatorSync 状態に切り出し、 SpectatorSelect を従来に近いシンプルな形に戻す - 実質到達しない pending_sessions と関連表示を削除し、 同期ポイントが無い場合は途中参加を拒否する - 同期ポイント以降の記録量に上限 (約 10 分) を設け、超えたら次の同期ポイントまで 途中参加を受け付けない - 対戦中の F1 受け付けをやめ、ready の改名も元に戻す - Screen::Game を削除して unimplemented!() によるパニック経路をなくす - buffered_len を副作用がわかる poll_buffered_len に改名 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
progre
left a comment
There was a problem hiding this comment.
可読性・保守性の観点で気になった箇所を5件コメントしました。特に spectator_host.rs の pending_sessions 周りは動作に影響しうる懸念です。
Generated by Claude Code
|
|
||
| fn recv_session(&mut self, pushed: bool, main_menu: Option<&MainMenu>, th19: &Th19) { | ||
| let Some(waiting) = &mut self.waiting else { | ||
| if self.sessions.is_empty() && self.pending_sessions.is_empty() { |
There was a problem hiding this comment.
委譲された観戦ホストセッションが pending_sessions に入ったまま切断すると検出できず、受け付け再開の判定が成立しないように見えます。
予約部屋で対戦者が最初の観戦者を観戦ホストに任命した直後、次の同期ポイント(キャラクター選択画面突入など)が来る前にそのホストが切断すると、セッションは pending_sessions に残ったままになります。pending_sessions は broadcast() で送信を試みられることがないため切断が検知されず、recv_session() の self.sessions.is_empty() && self.pending_sessions.is_empty() が真になりません。結果として対戦者は次の対戦(次の同期ポイント)が来るまで新しい観戦者を Pure P2P で一切受け付けられなくなります。PR本文にある「観戦ホストが離脱した後、対戦者が Pure P2P で受け付けを再開できるか」という未検証の懸念は、まさにこの経路に当たると思います。
Generated by Claude Code
There was a problem hiding this comment.
ご指摘の経路は実際には発生しないため、変更は見送ります。
pending_sessions に入るのは sync_point が None の間だけです。sync_point は一度作られると None に戻らず、以降の同期ポイントでは上書きされるだけです。そのため、2 回目以降の同期ポイント(キャラクター選択画面への突入など)の前に接続したセッションは、保留されずにその場で join され sessions に入ります。切断は broadcast() で検出されます。
pending_sessions に残る可能性があるのは、セッション開始後に一度も同期ポイントを通過していない間に接続した場合だけです。update() の初回呼び出しは難易度選択画面(カード未選択)またはキャラクター選択画面で行われます。どちらも同期ポイントであり、recv_session() より前に sync_point が作られます。したがって、この状態は実質的に起こりません。
Generated by Claude Code
| } | ||
| } | ||
|
|
||
| /// 予約部屋の場合、観戦者を受信した後は再度 `new` で待ち受けを作り直す必要がある |
There was a problem hiding this comment.
try_recv_session() は受信のみを行い、「予約部屋の場合は受信後に再度 new で待ち受けを作り直す必要がある」という義務をコメントのみで呼び出し側に転嫁しています。型で保護されていないため、現状唯一の呼び出し元 recv_session では正しく再生成されていますが、将来別の箇所から呼ばれてこの再生成を書き忘れると、WaitingForSpectatorInReservedRoom 内部の session_rx(oneshot::Receiver)は一度しか値を返せないため、以後その予約部屋は誰にも観戦者を受け付けなくなるにもかかわらずコンパイルは通ってしまいます。呼び出し側に契約を強制できる形(例えば受信と再生成をまとめて返す)にできないか検討の余地があると思います。
Generated by Claude Code
There was a problem hiding this comment.
再生成を呼び出し側に任せているのは意図的なため、現状のままとします。
対戦者は、最初の観戦者を受信したら予約部屋の待ち受けを作り直してはいけません。予約部屋の待ち受け(WaitingInRoom)は、破棄されると中断扱いになり、部屋を削除します。そのため、try_recv_session() 内で自動的に作り直すと、対戦者が委譲のためにそれを破棄した時点で、観戦ホストが引き継ぐはずの部屋が消えてしまいます。
このため、作り直すかどうかは状況を知っている呼び出し側(SpectatorHostState::recv_session)が判断し、rearm() にまとめています。6fc9740 で、委譲時に作り直さない理由を recv_session にもコメントとして残しました。
Generated by Claude Code
入力ログを記録・再生する方式をやめ、観戦者が接続した時点の状態を送る方式にする。 - キャラクター選択画面では、カーソル位置・カード・各プレイヤーの進行段階 (キャラクター選択/カード選択/準備完了) を送る。進行段階はメモリ上の値が 未解析のため、ホストが決定キーとキャンセルキーの押下から推定する - 観戦者は決定キーを自動で押して進行段階を再現してから、ホストの入力を再生する - 対戦中など状態を送れない間に接続した観戦者は、次に状態を送れるまで待機させる - 入力ログとその上限を削除 - 観戦用の接続のデータチャネルプロトコルを JUNOWEN/1.2 にし、 旧バージョンの観戦者を接続時に拒否する (対戦は JUNOWEN/1.1 のまま) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
キャラクター選択とカード選択は同時に進み、準備完了後もカードを変更できるため、 キャラクター選択画面の途中状態を送る方式をやめる。 - ホストは観戦者の接続時にプレイヤー名と設定を送り、試合開始時 (読み込み画面に 移ったとき) に難易度・キャラクター・カード・乱数シードを送る - 観戦者は難易度選択画面またはキャラクター選択画面で待機し、試合開始時の状態を 受け取ったら難易度・カーソル・カードを合わせ、一致を確認してから決定する - 選択画面の入力は観戦者に送らず、試合中の入力のみを送る - 対戦中に接続した観戦者は次の試合から観戦する - SpectatorSync / SpectatorSelect を SpectatorStandby にまとめる Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
- 古い方式を前提にしたコメントを直す - 試合開始時の状態が一致しない場合は観戦を終了し、状態を合わせられないまま 一定フレームを超えた場合も諦める - 観戦者の試合中に次の試合の開始を受け取ったら、今の試合を打ち切って 次の試合から同期し直す - 待機中もゲームパッドの PAUSE で中断できるようにする (自動で入力した PAUSE は除く) - try_recv_init_game の副作用をなくし、待機状態に入るときに明示的に破棄する Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
ホストは進行中の試合の開始時の状態と、それ以降のラウンド初期化情報・入力を 記録する (1試合分で、試合ごとに作り直す)。試合中に接続した観戦者には これらをまとめて送り、観戦者は試合の最初から早送りでホストに追いつく。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
- next_game_initial フィールドをやめ、試合中に受け取った InitGame は 受信バッファの先頭に戻す - 待機の上限を超えたときに RecvError を中断の合図として流用していることをコメントに明記 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
観戦者側で selection.card に書き込んでも、試合では常に初期位置のカードが 使われることがわかった。ゲームが実際に使うカードの値の場所を特定するため、 試合開始時にホストと観戦者の両方で候補の値をログに出力する。 原因がわかったら削除する。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
カードは入力で選ばせる方式にするため、診断ログは不要になった。 This reverts commit e7ef63c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
selection.card への書き込みはキャラクター選択画面の表示にのみ影響し、 試合で使われるカードには反映されなかった。通常の操作と同じく 「低速移動を押しながら左右」の入力でカードを切り替える。 - 観戦者側で現在のカードを把握する。タイトル画面を経由すると初期位置に戻り、 以降は前の試合の選択が引き継がれる - キャラクター選択画面に入ってから少し待ち、目標との差の分だけ入力してから決定する Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
画面遷移中などに左右を押すと入力が無視され、カードが切り替わらなかった。 押す前の selection.card を覚えておき、値が変わったら入力が受け付けられたとみなして 次へ進む。一定フレーム変わらなければ押し直す。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
selection.card は v1.10c で構造体の配置が変わった後、正しい値を指していない 可能性が高く、ホストが常に 0 を送っていたため観戦者はカードを切り替えなかった。 - ホストは前回の選択を保持している vs_mode のカードを送り、観戦者も同じ値で照合する - selection.card の変化で入力の受け付けを確認する方式は、値があてにならないため 回数を数えて入力する方式に戻し、開始前の待ち時間を 60 フレームに延ばす - 原因を確かめるため、試合開始時に両方の候補の値をログに出す (一時的) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
vs_mode のカードに 37 という、カードの番号としてありえない値が入っていたため、 正しい値の場所を改めて調べる。試合開始時にホストと観戦者の両方で、 selection・vs_mode のカード周辺・キャラクター選択カーソルの生のバイト列をログに出力する。 原因がわかったら削除する。 This reverts commit 718716e. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
v1.10c 対応で Player 構造体が大きくなった際、カードの位置が +90h のまま 残っており、常に別の値 (0 や 100) を読んでいた。診断ログで、カードの値が +94h にあり vs_mode のカードと一致することを確認した。 カードの値を正しく読み書きできるようになったため、観戦者のカードは 入力ではなくキャラクター選択画面で直接書き込む方式に戻す。 - selection.card の位置を +94h に修正 - ホストは selection.card を送り、観戦者はキャラクター選択画面で書き込んで照合してから決定する - 入力でカードを切り替える処理 (CardMover) と、観戦者側のカードの把握を削除 - 診断ログを削除 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
試合中に no wait を切り替えても反映されない可能性があり、読み込み画面で no wait を強制的にオフにしていたため、試合全体が通常速度で進んでいた。 読み込み画面でも溜まった入力の量で判定し、溜まっていれば早送りで試合を始める。 早送り中も入力が届くまで待つので、追いついた後はホストの速度で進む。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
試合中に観戦を始めて10秒以上遅れても早送りが発生しなかった。 溜まった入力の量が条件を満たしていないのか、no wait を設定しても ゲームに反映されていないのかを切り分けるため、約60フレームごとに 溜まった入力の数と no wait の設定前後の値をログに出力する。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
診断ログで、溜まった入力が 964 フレーム分あり no wait を有効にしても、 試合中は早送りされないことがわかった。試合中は表示の垂直同期待ちで 60fps に制限されていると考え、早送り中は Present を8回に1回だけ行う。 早送りの要求が途絶えたら2フレームで通常の表示に戻す。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qhChXocxUoUmMqt9cLXQa
protocol は SignalingSocket が決めて渡す値のため、PeerConnection は保持しない。 データチャネルの作成と相手との照合で使う箇所だけ引数で受け取る。 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
早送り中は表示を間引く対応を入れたため、原因調査用の一時的な診断ログを削除する。 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
6fc9740 to
3f5386a
Compare
Direct3D9 のラッパーは windows クレート以外に依存しないため、 features として app から分離する。 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
3f5386a to
f17a4c7
Compare
画面の種類は型引数で受け取る。 入力は MenuButtons で、効果音・キーボード・描画は MenuDevice と MenuRenderer で受け取る。 Th19 とのつなぎ込みは app 側の lobby/common_menu.rs に置く。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f17a4c7 to
10c377e
Compare
…ュー定義を移動 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bby へ移動 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10c377e to
016b34e
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
016b34e to
d3ad067
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
対戦者は最初の観戦者のみを受け付け、その観戦者を観戦ホストに任命して 以降の観戦者の受け付けと試合の進行の中継を委譲する。 - DelegateSpectatorHost メッセージで観戦ホストを任命し、専有ルームの場合は部屋名とキーを渡す - 観戦ホストは自分が処理した試合開始・ラウンド初期化・入力を、処理した順に配下の観戦者へ中継する - 観戦ホストが離脱した場合、配下の観戦者もすべて離脱し、対戦者は Pure P2P で受け付けを再開する - SpectatorHostState を state 直下に移し、受け付け状態・試合記録・表示文言を別ファイルに分割する Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
d3ad067 to
748dc12
Compare
概要
複数の観戦者を受け付けられるようにします。対戦者は最初の観戦者のみを受け付け、その観戦者を観戦ホストに任命します。以降の観戦者は観戦ホストが受け付け、入力を中継します。観戦ホストが離脱した場合、その配下の観戦者もすべて離脱します(仕様として許容)。
Note
このブランチは #23 の上に積んでいます。#23 のマージまではドラフトとし、差分には #23 の変更も含まれます。
このPR固有の差分: d329696...claude/fervent-bell-okj1ng
仕組み
DelegateSpectatorHostで観戦ホストを任命します。予約部屋の場合は部屋名とキーも渡し、観戦ホストが予約部屋で観戦者を待ち受けます。Pure P2P の場合は観戦ホストが F1 で受け付けますSpectatorHostStateを呼び出して配下に送ります。feat(spectator): 観戦者が任意のタイミングで途中参加できるようにする #23 の同期ポイント・途中参加の仕組みも観戦ホスト自身の画面遷移から作られるため、配下の観戦者もそのまま途中参加できる想定ですSpectatorHostState::updateはBattleSessionの代わりにSpectatorMatchInfo(プレイヤー名・ゲーム設定)を受け取るように変更しました注意点
確認状況
i686-pc-windows-msvcターゲットでcargo clippy/fmtが通ることを確認済み(C 依存部分はスタブで代用。新しい警告なし)🤖 Generated with Claude Code
https://claude.ai/code/session_01EpMNqyMexaKkm8jCjwz9vS
Generated by Claude Code