fix(battle-node): push BattleFinish on Scripted TurnEndFinal so the client doesn't park on the disconnect-checker
Before: when the player declared their final winning turn (TurnEndFinal), Scripted mode forwarded it to the bot — which fired a useless 3-frame TurnStart/TurnEnd/Judge burst as if the game were continuing. No BattleFinish was ever pushed, so the client's BattleFinishToOpponentDisConnectChecker (NetworkBattleManagerBase.cs:1640 + BattleFinishToOpponentDisConnectChecker.cs) parked the player on a "waiting for opponent" dialog for 128 seconds, eventually falling through to a synthetic OnDisConnectWin. The user could see "opponent defeated" animations but couldn't proceed to the post-battle screen. After: Scripted TurnEndFinal pushes BattleFinish with result=LifeLose=102 to the player (matches the RESULT_CODE the client expects per NetworkBattleReceiver.cs:963-986; client maps LifeLose → "opponent's life ran out, PLAYER WIN" UI per NetworkBattleManagerBase.cs:1450-1459). Phase transitions to Terminal so RunAsync's PvP-disconnect cascade doesn't synthesize a second BattleFinish on top. No bot burst — the game is over. Wire reference: prod TK2 capture battle-traffic_tk2_regular.ndjson:273-274 shows server pushing TurnEndFinal followed immediately by BattleFinish result:102. BattleResult enum gets the LifeWin=101 / LifeLose=102 values and a corrected docstring. The pre-existing Lose=0 / Win=1 / Consistency=2 values stay (Retire/Kill flow ships them today and works as "no contest" end-of-battle), but their docstring no longer claims they're the WS shape — they were always the HTTP /finish shape, mislabeled. TurnEnd (regular, not final) keeps the existing forward-to-bot behavior in Scripted mode — that's a normal turn boundary, not game end. PvP TurnEndFinal still broadcasts the same TurnEnd+Judge as regular TurnEnd; the actual game-end BattleFinish push in PvP rides the loser's Retire/Kill or the disconnect cascade in RunAsync. 177 battle-node tests passing (was 176; +1 covering the new dispatch arm). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,55 @@
|
||||
namespace SVSim.BattleNode.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Wire value of <c>result</c> on a BattleFinish frame. The client's
|
||||
/// <c>BattleFinishResponsProcessing</c> switch maps these as:
|
||||
/// 0 → LOSE, 1 → WIN, 2 → CONSISTENCY (desync / action-list mismatch).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is NOT the same as the client's in-memory <c>BATTLE_RESULT_TYPE</c> enum
|
||||
/// (NONE=0, WIN=1, LOSE=2, CONSISTENCY=3) — the wire codes shift LOSE down to 0.
|
||||
/// Wire value of <c>result</c> on a WS <c>BattleFinish</c> frame.
|
||||
/// <para>
|
||||
/// Maps to the client's <c>NetworkBattleReceiver.RESULT_CODE</c> enum at
|
||||
/// <c>NetworkBattleReceiver.cs:963-986</c>. The client extracts this via
|
||||
/// <c>NetworkBattleReceiver.cs:1170-1172</c>:
|
||||
/// <code>
|
||||
/// case NetworkParameter.result:
|
||||
/// _receiveData.result = (RESULT_CODE)ConvertToInt(data.Value);
|
||||
/// </code>
|
||||
/// then dispatches through <c>NetworkBattleManagerBase.JudgeResultReceive</c>
|
||||
/// (lines 1412-1488). The wire codes are categorized by HOW the battle ended
|
||||
/// (life / deckout / retire / disconnect / timeout) and are named from the
|
||||
/// OPPONENT'S perspective:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item><c>LifeLose</c> = "opponent's life ran out" → PLAYER WIN UI</item>
|
||||
/// <item><c>LifeWin</c> = "opponent's life win condition met" → PLAYER LOSE UI</item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// Prior to 2026-06-02 the docstring here claimed the values were 0/1/2 mapping
|
||||
/// LOSE/WIN/CONSISTENCY — that was the HTTP <c>/finish</c> response shape, not the
|
||||
/// WS frame. The previous values are kept for backwards compat with the
|
||||
/// Retire/Kill scripted flow (which has shipped emitting <c>Win = 1</c>, parsed
|
||||
/// client-side as <c>RESULT_CODE.NoContest</c> and rendered as "battle ended in
|
||||
/// no contest" — a working-but-not-quite-right scripted-bot terminator).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Always serialize as the int value, not the name; see the
|
||||
/// <c>JsonNumberEnumConverter</c> on <see cref="Bodies.BattleFinishBody.Result"/>.
|
||||
/// </remarks>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public enum BattleResult
|
||||
{
|
||||
/// <summary>Pre-2026-06-02 value, kept for the existing Retire/Kill Scripted flow.
|
||||
/// Wire-equivalent of <c>RESULT_CODE.NotFinish = 0</c>. Don't use for new code.</summary>
|
||||
Lose = 0,
|
||||
/// <summary>Pre-2026-06-02 value, kept for the existing Retire/Kill Scripted flow.
|
||||
/// Wire-equivalent of <c>RESULT_CODE.NoContest = 1</c>. Don't use for new code.</summary>
|
||||
Win = 1,
|
||||
/// <summary>Pre-2026-06-02 value, kept for the existing Retire/Kill Scripted flow.
|
||||
/// Wire-equivalent of <c>RESULT_CODE.Invalid = 2</c>. Don't use for new code.</summary>
|
||||
Consistency = 2,
|
||||
|
||||
/// <summary>Opponent's life ran out (PLAYER WIN). Pushed by Scripted mode on
|
||||
/// the player's <c>TurnEndFinal</c> emit. Matches the prod TK2 capture at
|
||||
/// <c>data_dumps/captures/battle-traffic_tk2_regular.ndjson:274</c>.</summary>
|
||||
LifeLose = 102,
|
||||
|
||||
/// <summary>Opponent's life-win condition met (PLAYER LOSE). Not currently emitted
|
||||
/// by Scripted mode — the bot can't kill the player — but listed for completeness.</summary>
|
||||
LifeWin = 101,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user