Files
SVSimServer/SVSim.BattleNode/Protocol/BattleResult.cs
gamer147 1685b509c3 fix(battle-node): TurnEndFinal pushes LifeWin=101 (player perspective), not LifeLose=102
End-to-end trace of FinishBattleEffect proved my prior direction was
backwards. The path is:

  RESULT_CODE → JudgeResultReceive switch (NetworkBattleManagerBase:1439-1459)
              → SettingResultUI_SpecialResultTypeText
              → _finishEffectType = battleResult
  → eventually FinishBattleEffect(:1267-1316):
      bool isPlayer = false;
      switch (_finishEffectType) {
        case WIN:  isPlayer = true;  break;
        case LOSE: isPlayer = false; break;
      }
      InitiateGameEndSequence(!isPlayer);  // NEGATED
  → BattleManagerBase.InitiateGameEndSequence(hasWon):
      hasWon=true → WIN screen; hasWon=false → LOSE screen.

So LifeWin=101 (player perspective: "I won by life") → _finishEffectType=LOSE
→ isPlayer=false → hasWon=true → WIN UI. And LifeLose=102 ("I lost") → LOSE UI.

My prior misread treated the inner switch's BATTLE_RESULT_TYPE param as
the final UI render — but that param only feeds the secondary "by retire
/ by disconnect" text, not the primary WIN/LOSE. The real flip happens at
FinishBattleEffect:1315's !isPlayer negation.

User's live repro (bot HP to 0 → LOSS screen) confirmed the inversion.

The prior prod TK2 capture interpretation was also corrected: line 274
`result:102` was a LOSS capture (player lost to the opponent's attack on
line 271), not a win as I claimed earlier.

Changes:
- BattleResult.cs: docstring rewritten with the full FinishBattleEffect
  trace. Members reordered (LifeWin first since it's used by Scripted).
- BattleSession.cs:267: Scripted TurnEndFinal arm pushes LifeWin instead
  of LifeLose.
- Test updated to assert LifeWin=101 + describe the inversion lesson so
  the next reader sees the prior bug context.

177 battle-node tests passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 18:11:24 -04:00

69 lines
3.4 KiB
C#

namespace SVSim.BattleNode.Protocol;
/// <summary>
/// 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>. Names are <b>from the player's perspective</b>:
/// <c>LifeWin</c> = "I won by life", <c>LifeLose</c> = "I lost by life". Verified
/// end-to-end via the path
/// <c>RESULT_CODE</c> → <c>JudgeResultReceive</c> switch → <c>_finishEffectType</c> →
/// <c>FinishBattleEffect</c> → <c>InitiateGameEndSequence(hasWon)</c>:
/// </para>
/// <list type="bullet">
/// <item><c>LifeWin = 101</c> → <c>_finishEffectType = LOSE</c> →
/// <c>InitiateGameEndSequence(hasWon: true)</c> → PLAYER WIN UI</item>
/// <item><c>LifeLose = 102</c> → <c>_finishEffectType = WIN</c> →
/// <c>InitiateGameEndSequence(hasWon: false)</c> → PLAYER LOSE UI</item>
/// </list>
/// <para>
/// The <c>SettingResultUI_SpecialResultTypeText</c> switch passes the OPPONENT's
/// outcome to set the secondary "by retire/by disconnect/etc." text — that's why
/// the inner switch direction looks inverted (LifeWin → LOSE param, LifeLose → WIN
/// param). The actual WIN/LOSE rendering happens in <c>FinishBattleEffect</c> via
/// the <c>!isPlayer</c> flip at line 1315.
/// </para>
/// <para>
/// Prior docstrings on this enum had the direction backwards (claimed LifeLose
/// → WIN UI from a misread of the inner switch); see
/// <c>docs/audits/battle-node-sio-events-2026-06-02.md</c> Addendum for the live
/// reproduction that exposed the inversion.
/// </para>
/// <para>
/// The pre-2026-06-02 <c>Lose = 0</c> / <c>Win = 1</c> / <c>Consistency = 2</c>
/// values are kept for the existing Retire/Kill Scripted flow (which ships
/// <c>Win = 1</c>, parsed client-side as <c>RESULT_CODE.NoContest</c> and
/// rendered as "battle ended in no contest" — works in that the battle
/// terminates, but shows the wrong text). To be removed once that flow is
/// rewritten to use the proper retire codes.
/// </para>
/// <para>
/// Always serialize as the int value, not the name; see the
/// <c>JsonNumberEnumConverter</c> on <see cref="Bodies.BattleFinishBody.Result"/>.
/// </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>Player won by reducing opponent's life to 0. Pushed by Scripted mode
/// on the player's <c>TurnEndFinal</c> emit. Routes through the client switch to
/// <c>InitiateGameEndSequence(hasWon: true)</c>.</summary>
LifeWin = 101,
/// <summary>Player lost by their own life dropping to 0. Not currently emitted
/// by Scripted mode — the bot can't kill the player — but listed for completeness
/// and for the prod TK2 capture at
/// <c>data_dumps/captures/battle-traffic_tk2_regular.ndjson:274</c> (a loss the
/// player suffered to a real opponent).</summary>
LifeLose = 102,
}