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>
This commit is contained in:
gamer147
2026-06-02 18:11:24 -04:00
parent ee23985055
commit 1685b509c3
3 changed files with 53 additions and 33 deletions

View File

@@ -83,12 +83,18 @@ public class BattleSessionDispatchTests
}
[Test]
public void Scripted_TurnEndFinal_pushes_BattleFinish_LifeLose_to_player_and_terminates()
public void Scripted_TurnEndFinal_pushes_BattleFinish_LifeWin_to_player_and_terminates()
{
// Regression for the BattleFinishToOpponentDisConnectChecker softlock — when the
// player declares their final winning turn (TurnEndFinal), the server must push a
// wire BattleFinish so the client doesn't park on "waiting for opponent" for 128s.
// LifeLose=102 = "opponent's life ran out" → client UI shows player WIN.
//
// Wire-result direction: RESULT_CODE names are FROM THE PLAYER'S PERSPECTIVE.
// LifeWin=101 = "I won by life". The client's FinishBattleEffect:1289-1315 then
// routes this through InitiateGameEndSequence(hasWon: true) → WIN UI. (An earlier
// version of this test asserted LifeLose=102 — that pushed LOSE UI in live test,
// matching the inversion documented in
// docs/audits/battle-node-sio-events-2026-06-02.md Addendum.)
var (s, a, b) = NewSession();
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
@@ -105,8 +111,8 @@ public class BattleSessionDispatchTests
"BattleFinish is a no-stock control push (matches the Retire/Kill arm).");
Assert.That(routes[0].Frame.Body, Is.InstanceOf<SVSim.BattleNode.Protocol.Bodies.BattleFinishBody>());
var body = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[0].Frame.Body;
Assert.That(body.Result, Is.EqualTo(BattleResult.LifeLose),
"Wire result must be RESULT_CODE.LifeLose (102); the client maps that to player WIN.");
Assert.That(body.Result, Is.EqualTo(BattleResult.LifeWin),
"Wire result must be RESULT_CODE.LifeWin (101) — names are player-perspective; client routes this to WIN UI.");
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal),
"Session must transition to Terminal so the RunAsync cascade doesn't synthesize a second BattleFinish.");
}