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:
gamer147
2026-06-01 02:08:04 -04:00
parent 0b859f1c8e
commit e06d97ef6f
4 changed files with 50 additions and 18 deletions

View File

@@ -20,22 +20,34 @@ public class BattleSessionDispatchTests
PubSeq: null, PlaySeq: null, Body: new Dictionary<string, object?>());
[Test]
public void InitNetwork_PushesAckThenMatchedThenBattleStart()
public void InitNetwork_PushesAckOnly_TransitionsToAwaitingInitBattle()
{
var s = NewSession();
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork));
Assert.That(responses.Select(r => r.Envelope.Uri),
Is.EqualTo(new[] { NetworkBattleUri.InitNetwork, NetworkBattleUri.Matched, NetworkBattleUri.BattleStart }));
Is.EqualTo(new[] { NetworkBattleUri.InitNetwork }));
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.AwaitingInitBattle));
}
[Test]
public void InitBattle_PushesMatched_TransitionsToAwaitingLoaded()
{
var s = NewSession();
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork));
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitBattle));
Assert.That(responses.Single().Envelope.Uri, Is.EqualTo(NetworkBattleUri.Matched));
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded));
}
[Test]
public void Loaded_PushesDeal_TransitionsToAwaitingSwap()
public void Loaded_PushesBattleStartThenDeal_TransitionsToAwaitingSwap()
{
var s = NewSession();
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork)); // advance phase
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork));
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitBattle));
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.Loaded));
Assert.That(responses.Single().Envelope.Uri, Is.EqualTo(NetworkBattleUri.Deal));
Assert.That(responses.Select(r => r.Envelope.Uri),
Is.EqualTo(new[] { NetworkBattleUri.BattleStart, NetworkBattleUri.Deal }));
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.AwaitingSwap));
}
@@ -44,6 +56,7 @@ public class BattleSessionDispatchTests
{
var s = NewSession();
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork));
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitBattle));
s.ComputeResponses(NewEnvelope(NetworkBattleUri.Loaded));
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.Swap));
Assert.That(responses.Select(r => r.Envelope.Uri),