fix(battle-node): Bot mode must push Matched + BattleStart (client state-machine triggers)
Phase 3 shipped a Bot dispatch table that ack'd InitBattle without pushing Matched and stayed silent on Loaded, per the architecture spec's inference that "the client uses AIBattleStart HTTP data instead of Matched in Bot mode." That inference was wrong. The client's matching state machine (Matching.ReactionReceiveUri, Matching.cs:400) gates StartBattleLoad() on the Matched envelope, and BattleStart at Matching.cs:417 triggers GotoBattle. Without those envelopes the client never transitions out of MatchingStatus.Connect — which renders as the "Waiting for opponent" hang on the loading screen. AIBattleStart HTTP only provides opponent cosmetics, not state-machine triggers. Fix: drop the Bot-specific InitBattle ack-only and Loaded silent arms; let Bot fall through to the existing handshake arms that push Matched and BattleStart + Deal. Only TurnEnd stays Bot-specific (Judge to sender, not broadcast — there's no real other side to broadcast to). Tests updated to match the corrected contract. ai-passive.md doc amended with a correction note. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -135,28 +135,20 @@ public sealed class BattleSession
|
||||
phaseFrom!.Phase = BattleSessionPhase.AwaitingInitBattle;
|
||||
break;
|
||||
|
||||
// --- Phase 3 Bot arms — placed BEFORE the existing handshake arms so they
|
||||
// win pattern matching on Type == Bot. Bot mode: ack handshake, silent Loaded,
|
||||
// Judge-to-sender on TurnEnd. The rest reuse Scripted's arms (Retire/Kill ->
|
||||
// BattleFinishNoContest, Swap -> per-sender response, default -> drop).
|
||||
// --- Phase 3 Bot arms — placed BEFORE the existing handshake arms so the
|
||||
// Bot-specific TurnEnd wins pattern matching on Type == Bot. InitBattle and
|
||||
// Loaded fall through to the regular handshake arms below (which push Matched
|
||||
// and BattleStart+Deal) — the architecture spec's "no Matched in Bot mode"
|
||||
// claim was wrong: the client's Matching.ReactionReceiveUri (Matching.cs:400)
|
||||
// gates StartBattleLoad on receiving Matched, and BattleStart triggers
|
||||
// GotoBattle. Without those envelopes the client hangs in matching status
|
||||
// Connect ("Waiting for opponent"). AIBattleStart HTTP only provides
|
||||
// opponent cosmetics, not the state-machine triggers.
|
||||
//
|
||||
// Bot's Swap arm reuses the existing Scripted Swap arm (per-sender
|
||||
// SwapResponse + Ready), and Retire/Kill reuses BattleFinishNoContest.
|
||||
// Reference: docs/api-spec/in-battle/ai-passive.md.
|
||||
|
||||
case NetworkBattleUri.InitBattle
|
||||
when Type == BattleType.Bot && phaseFrom?.Phase == BattleSessionPhase.AwaitingInitBattle:
|
||||
// Ack only — NO Matched push. Client uses AIBattleStart HTTP data for
|
||||
// the lobby plates instead of the Matched envelope.
|
||||
result.Add((from, BuildAck(NetworkBattleUri.InitBattle), true));
|
||||
phaseFrom!.Phase = BattleSessionPhase.AwaitingLoaded;
|
||||
break;
|
||||
|
||||
case NetworkBattleUri.Loaded
|
||||
when Type == BattleType.Bot && phaseFrom?.Phase == BattleSessionPhase.AwaitingLoaded:
|
||||
// Silent — no BattleStart, no Deal. Client's AINetworkBattleManager
|
||||
// short-circuits to its local AI flow after Loaded; the server has
|
||||
// nothing to contribute.
|
||||
phaseFrom!.Phase = BattleSessionPhase.AwaitingSwap;
|
||||
break;
|
||||
|
||||
case NetworkBattleUri.TurnEnd
|
||||
when Type == BattleType.Bot && phaseFrom?.Phase == BattleSessionPhase.AfterReady:
|
||||
case NetworkBattleUri.TurnEndFinal
|
||||
|
||||
Reference in New Issue
Block a user