fix(battle-node): revert Bot Matched/BattleStart push (corrupts OppoBattleStartInfo)
Previous commit 51e9dd2 changed Bot's InitBattle to push Matched and
Loaded to push BattleStart+Deal, on the theory that the architecture
spec's "no Matched in Bot mode" claim was wrong. That theory was based
on misreading Matching.cs:400 (the Matched handler) as a required
state-machine trigger.
End-to-end trace of the AI client flow shows:
1. _initNetworkSuccess (set when the client receives uri=InitNetwork,
i.e., our ack) is the actual trigger — MatchingNetworkConnectChecker
phase 3 sees it and calls MatchingInitBattle.
2. MatchingInitBattle (Matching.cs:298) for IsAINetwork IMMEDIATELY
calls StartBattleLoad + GotoBattle right after emitting InitBattle.
It does NOT wait for any wire envelope.
3. The Matched handler at Matching.cs:400 is gated on
status == Connect and is already past Prepared by the time the
wire round-trip completes — sending Matched is harmless but
unnecessary.
4. The BattleStart handler at Matching.cs:417 runs UNCONDITIONALLY and
SetNetworkInfo at RealTimeNetworkAgent.cs:1562 overwrites
OppoBattleStartInfo with the wire envelope's oppoInfo. Our oppoInfo
comes from NoOpBotParticipant.Context placeholders (classId/emblemId
etc. = 0), corrupting the good values the client set from the HTTP
AIBattleStart response.
The "Waiting for opponent" hang was caused by SBattleLoad.LoadOpponentAssets
trying to fetch emblemId=0, degreeId=0, etc. after BattleStart corrupted
OppoBattleStartInfo. The asset group load silently hangs on missing
assets, no error logged.
Restored the spec's original Bot arms: InitBattle ack-only, Loaded silent,
TurnEnd Judge-to-sender. ai-passive.md updated with the corrected reasoning
and a discovery-history note.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -458,40 +458,38 @@ public class BattleSessionDispatchTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Bot_InitBattle_pushes_Matched_to_sender()
|
||||
public void Bot_InitBattle_acks_to_sender_with_no_Matched_push()
|
||||
{
|
||||
var (s, a, _) = NewBotSession();
|
||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
||||
|
||||
// Bot InitBattle reuses the regular handshake arm — pushes Matched. The
|
||||
// client's Matching.ReactionReceiveUri gates StartBattleLoad on receiving
|
||||
// Matched (Matching.cs:400), so without it the client hangs in
|
||||
// "Waiting for opponent." Cosmetics in the Matched body come from
|
||||
// NoOpBotParticipant.Context placeholders; the client renders opponent
|
||||
// cosmetics from AIBattleStart HTTP data, not from Matched, so the
|
||||
// placeholders are harmless here.
|
||||
// Bot InitBattle is ack-only — NO Matched push. Matched would be ignored
|
||||
// by the client anyway (gated on status == Connect, which is already
|
||||
// past by the time the wire round-trip completes), but the spec is to
|
||||
// not send it for clarity.
|
||||
Assert.That(routes.Count, Is.EqualTo(1));
|
||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.Matched));
|
||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.InitBattle),
|
||||
"Expected an ack envelope for InitBattle, NOT a Matched envelope.");
|
||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Bot_Loaded_pushes_BattleStart_then_Deal_to_sender()
|
||||
public void Bot_Loaded_produces_no_routes_but_advances_phase()
|
||||
{
|
||||
var (s, a, _) = NewBotSession();
|
||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded));
|
||||
|
||||
// Bot Loaded reuses the regular handshake arm — pushes BattleStart + Deal.
|
||||
// BattleStart triggers GotoBattle on the client (Matching.cs:417); without
|
||||
// it the client hangs.
|
||||
Assert.That(routes.Select(r => r.Frame.Uri),
|
||||
Is.EqualTo(new[] { NetworkBattleUri.BattleStart, NetworkBattleUri.Deal }));
|
||||
Assert.That(routes.All(r => ReferenceEquals(r.Target, a)), Is.True);
|
||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingSwap));
|
||||
// Bot Loaded is silent — no BattleStart, no Deal. Pushing BattleStart
|
||||
// would actively CORRUPT OppoBattleStartInfo on the client (the wire
|
||||
// handler at Matching.cs:417 → SetNetworkInfo overwrites it with our
|
||||
// placeholder NoOpBotParticipant.Context zeros).
|
||||
Assert.That(routes, Is.Empty, "Bot Loaded is silent.");
|
||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingSwap),
|
||||
"Phase still advances even though there are no outbound routes.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
Reference in New Issue
Block a user