11// BoomNetwork VampireSurvivors Demo — Network Manager
22//
3- // DESIGN PRINCIPLE: All GameState mutations happen through exactly two
4- // deterministic paths, both driven by FrameData from the server:
5- //
3+ // DESIGN PRINCIPLE 1 — Deterministic paths (GameState mutation):
4+ // All GameState mutations go through exactly two paths driven by FrameData:
65// 1. Frame events (OnPlayerJoined/Left) — embedded in FrameData,
76// dispatched BEFORE OnFrame, same frame on all clients.
87// 2. OnFrame → Tick → ApplyInputs — processes player inputs,
98// auto-inits players on first input appearance.
9+ // OnFrameSyncStart only sets up the deterministic seed and Dt.
10+ // No InitPlayer, no direct GameState mutation outside frame processing.
1011//
11- // OnFrameSyncStart only sets up the deterministic seed and Dt.
12- // No InitPlayer, no direct GameState mutation outside frame processing.
12+ // DESIGN PRINCIPLE 2 — Level-Triggered Pause Convergence:
13+ // Game-pause state is managed via Level-Triggered State Convergence,
14+ // NOT edge-triggered delta tracking. On every OnFrame, we compare:
15+ // - wantsPause (game logic: IsAnyPlayerUpgrading)
16+ // - isPaused (network state: IsGamePaused)
17+ // and drive toward convergence. This pattern is self-correcting and
18+ // handles same-tick consecutive state changes without any local memory.
19+ // The Update() path provides the deadlock-breaker (RequestGameResume)
20+ // for when the server is paused and OnFrame never fires.
1321
1422using UnityEngine ;
1523using BoomNetwork . Client . FrameSync ;
@@ -34,7 +42,6 @@ public class VSNetworkManager : MonoBehaviour
3442 uint _desyncFrame ;
3543 byte _pendingUpgradeChoice ;
3644 bool _firstInputSent ;
37- bool _wasUpgrading ;
3845
3946 // Cached GUIStyles
4047 bool _stylesCached ;
@@ -99,12 +106,11 @@ void Update()
99106 VSInput . Encode ( _inputBuf , h , v , ability ) ;
100107 _network . SendInput ( _inputBuf ) ;
101108
102- // 升级选择发出后,立即请求恢复帧推送(打破死锁: OnFrame 需要服务器推帧才触发)
103- // 同时重置 _wasUpgrading,确保下一帧能重新检测到新的升级状态(同帧可能再次触发升级)
109+ // Deadlock-breaker: while server is paused, OnFrame never fires.
110+ // RequestGameResume unblocks frame delivery after upgrade choice is sent.
104111 if ( ability != 0 )
105112 {
106113 Debug . Log ( $ "[VS] Upgrade choice sent: ability={ ability } , IsGamePaused={ _network . Client . IsGamePaused } ") ;
107- _wasUpgrading = false ;
108114 _network . Client . RequestGameResume ( ) ;
109115 }
110116 }
@@ -192,13 +198,15 @@ void OnFrame(FrameData frame)
192198 uint hash = _sim . State . ComputeHash ( ) ;
193199 _network . Client . SendFrameHash ( frame . FrameNumber , hash ) ;
194200
195- // 检测升级暂停状态变化 → 请求服务器暂停/恢复帧推送
196- bool upgrading = _sim . IsAnyPlayerUpgrading ( ) ;
197- if ( upgrading && ! _wasUpgrading )
201+ // Level-Triggered Pause Convergence (see DESIGN PRINCIPLE 2 at top of file):
202+ // Compare game's desired pause state vs network's actual pause state,
203+ // and drive toward convergence every frame. No local memory needed.
204+ // IsGamePaused is the authoritative source of truth for network state.
205+ bool wantsPause = _sim . IsAnyPlayerUpgrading ( ) ;
206+ if ( wantsPause && ! _network . Client . IsGamePaused )
198207 _network . Client . RequestGamePause ( ) ;
199- else if ( ! upgrading && _wasUpgrading )
208+ else if ( ! wantsPause && _network . Client . IsGamePaused )
200209 _network . Client . RequestGameResume ( ) ;
201- _wasUpgrading = upgrading ;
202210 }
203211
204212 void OnDesync ( FrameHashMismatch mismatch )
0 commit comments