fix(battle-node): respond to InitBattle/Loaded, not InitNetwork
Pushing Matched in response to InitNetwork lands it before
MatchingInitBattle() finishes wiring up the OnReceivedEvent handler
and setting status=Connect. The client's Matched-case in
ReactionReceiveUri only transitions to StartLoad when status is
Connect at the moment of receipt; otherwise the frame is silently
dropped at the state machine and the matchmaking UI never advances.
The real connect-handshake sequence (per MatchingNetworkConnectChecker
+ Matching.cs):
1. WS opens.
2. Client emits InitNetwork (cat=general).
3. Server replies InitNetwork ack → _initNetworkSuccess = true.
4. MatchingInitBattle: status=Connect; emit InitBattle; subscribe
OnReceivedEvent matching handler.
5. Server replies Matched → status=StartLoad, StartBattleLoad.
6. Asset load done → client emits Loaded.
7. Server replies BattleStart + Deal → status=Prepared, GotoBattle.
Add AwaitingInitBattle phase, gate Matched on InitBattle receipt, and
gate BattleStart+Deal on Loaded receipt. Update dispatch and
integration tests to walk the new sequence; InitBattle's wire cat is
Matching(2), not Battle(1).
Caught during v1 smoke walkthrough — battle-traffic.ndjson showed the
client receiving Matched/BattleStart at sub-millisecond gaps after
InitNetwork ack, but never advancing past matchmaking.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -183,13 +183,28 @@ public sealed class BattleSession
|
||||
var result = new List<(MsgEnvelope Envelope, bool NoStock)>();
|
||||
switch (env.Uri)
|
||||
{
|
||||
// The real handshake sequence (MatchingNetworkConnectChecker + Matching.cs):
|
||||
// 1. WS opens.
|
||||
// 2. Client emits InitNetwork (cat=general).
|
||||
// 3. Server replies with InitNetwork ack → _initNetworkSuccess = true.
|
||||
// 4. MatchingInitBattle() runs: status=Connect, emits InitBattle, THEN subscribes
|
||||
// the OnReceivedEvent matching handler.
|
||||
// 5. Server replies with Matched → handler is subscribed, status=Connect →
|
||||
// transitions to StartLoad and StartBattleLoad() loads decks/scene.
|
||||
// 6. Asset load completes → client emits Loaded.
|
||||
// 7. Server replies with BattleStart + Deal → status=Prepared, GotoBattle().
|
||||
// Pushing Matched in response to InitNetwork (instead of InitBattle) drops it
|
||||
// before the handler is subscribed; the state machine never advances.
|
||||
case NetworkBattleUri.InitNetwork when Phase == BattleSessionPhase.AwaitingInitNetwork:
|
||||
result.Add((BuildAckedEnvelope(NetworkBattleUri.InitNetwork), NoStock: true));
|
||||
Phase = BattleSessionPhase.AwaitingInitBattle;
|
||||
break;
|
||||
case NetworkBattleUri.InitBattle when Phase == BattleSessionPhase.AwaitingInitBattle:
|
||||
result.Add((ScriptedLifecycle.BuildMatched(ViewerId, ScriptedLifecycle.FakeOpponentViewerId, BattleId), NoStock: false));
|
||||
result.Add((ScriptedLifecycle.BuildBattleStart(ViewerId), NoStock: false));
|
||||
Phase = BattleSessionPhase.AwaitingLoaded;
|
||||
break;
|
||||
case NetworkBattleUri.Loaded when Phase == BattleSessionPhase.AwaitingLoaded:
|
||||
result.Add((ScriptedLifecycle.BuildBattleStart(ViewerId), NoStock: false));
|
||||
result.Add((ScriptedLifecycle.BuildDeal(), NoStock: false));
|
||||
Phase = BattleSessionPhase.AwaitingSwap;
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user