D-20260802-02|GO 前全員 race-ready 屏障¶
背景與驅動力¶
正式 match 原本在各 client 完成本機 world/network 後各自開始倒數;較慢 peer 的場地、車資產或第一個真實 render 尚未完成時,較快 peer 已可能啟動 frame pump。這會讓同一 roster 在不同 wall-clock 時刻進入模擬,並把純載入失敗誤推進賽中斷線、棄賽或結算語意。
考慮過的選項¶
- 只等待 world 與 network:無法證明玩家實際看得到完整場地與車輛。
- 用任意 heartbeat 延長等待:惡意或故障 peer 可無限續命,且不能證明進度。
- 由 host 單點宣告 GO:host 無法可信觀測其他 client 的 presentation,亦會把賽內 mesh 再中心化。
- 全 roster 有限單調 milestone 屏障(採納):每端互相驗證進度,只有真進度可延長有限等待。
決定¶
- 每一回合建立綁定
matchId + roundIndex + roster的RaceReadyCoordinator;觀戰者不參與。 - milestone 固定為
world-built → network-ready → presentation-ready。只接受傳輸來源等於 sender、屬 roster 且嚴格遞進的訊息;重送/倒退不算進度。 presentation-ready只能由 viewport 在場地與全車資產成功完成、且完成後實際 render 至少一幀後回報;同賽道的下一回合也重開世代。- 最新本機 milestone 週期重送;無嚴格進度 30 秒或 GO 前
peer-left即取消。只有全 roster 到達最後 milestone 才能建立 countdown 與 frame pump。 - 30 秒 stall 與 2 秒 resend 都以注入的單調時鐘保存絕對 deadline;timer 只是喚醒器。瀏覽器由背景回前景時立即重評,逾期 stall 當下取消;只有 resend 逾期時補送一次,不追補成 burst。嚴格 milestone 前進才建立新的 stall deadline。
- GO 前取消不產生 settlement、forfeit、斷線或信譽後果。房主移除 missing 的非 host participant、保留 host,房間回 waiting、清舊 match、ready 歸零;在線 participant 回 canonical
/room/:roomId。再次 start 必須以新startedAt推導新matchId。
後果與影響¶
正式賽的模擬入口從「各端本機初始化完成」收斂為全 roster 的可驗證共同屏障;資產失敗會在 GO 前安全取消,不再污染賽中結果。代價是最慢健康 participant 決定起跑時間,且任一成員可拒絕進度阻止該場開始;30 秒有限 stall 窗與移除 missing 後重新 start 將擾亂限制在單一舊 matchId。