refactor(battle-node): unify TurnEndFinal / Retire-Kill / gameplay-forwarder dispatch across types

Three dispatch arms had Type-based branching that was either wrong or
unnecessary. Unified per the audit doc's recommended order, grounded in
verified facts about each participant's PushAsync.

(1) TurnEndFinal — was branched: PvP broadcast TurnEnd+Judge (wrong on a
game-end signal); Scripted pushed BattleFinish(LifeWin). Unified:
  - forward the envelope to other (matches prod TK2 capture
    battle-traffic_tk2_regular.ndjson:273 — loser receives TurnEndFinal
    from server before BattleFinish)
  - push BattleFinish(LifeWin) to from (winner)
  - push BattleFinish(LifeLose) to other (loser)
  - Phase → Terminal

  Requires ScriptedBotParticipant.PushAsync to no longer fire its 3-frame
  burst on TurnEndFinal (previously it reacted to both TurnEnd and
  TurnEndFinal). The dispatch arm now owns TurnEndFinal's response; the
  bot reacting too would race with the BattleFinish push. Bot still
  fires on regular TurnEnd as before.

(2) Retire / Kill — was branched: PvP pushed Lose=0 (NotFinish) /
Win=1 (NoContest); Scripted pushed BuildBattleFinishNoContest() (Win=1).
Both shipped wrong RESULT_CODE values; the audit doc's outstanding item
documented this. Unified:
  - push BattleFinish(RetireLose=106) to from (the retirer)
  - push BattleFinish(RetireWin=105) to other (the survivor)
  - Phase → Terminal

  Added RetireWin=105 / RetireLose=106 to BattleResult enum with the
  same player-perspective convention.

(3) PvP gameplay forwarder (TurnStart / PlayActions / Echo /
TurnEndActions / JudgeResult) — had a redundant `Type == BattleType.Pvp`
guard. Verified that BothAfterReady() is naturally only true when both
participants are RealParticipant (ScriptedBot / NoOpBot don't implement
IHasHandshakePhase per RealParticipant.cs:20-23 / Participants/*.cs grep).
Dropped the redundant guard.

Bot type still has its dedicated InitBattle/Loaded/TurnEnd arms above
the unified ones, so Bot-specific behavior is unchanged.

Tests: 177 battle-node tests passing.
- Updated 9 tests to match the unified dispatch (paired BattleFinish
  pushes, correct RESULT_CODE values, forwarded TurnEndFinal envelope).
- ScriptedBotParticipantTests.PushAsync_TurnEndFinal_* rewritten to
  assert the bot does NOT fire on TurnEndFinal (was asserting it did).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-02 18:37:24 -04:00
parent 1685b509c3
commit 0a8a84b2cc
6 changed files with 152 additions and 116 deletions

View File

@@ -54,15 +54,21 @@ public enum BattleResult
/// 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
/// <summary>Player won by reducing opponent's life to 0. Pushed to the winner
/// on <c>TurnEndFinal</c>. 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>
/// <summary>Player lost by their own life dropping to 0. Pushed to the loser on
/// the opponent's <c>TurnEndFinal</c>. Prod TK2 capture at
/// <c>data_dumps/captures/battle-traffic_tk2_regular.ndjson:274</c> is a loss
/// shown to the player from a real opponent's lethal.</summary>
LifeLose = 102,
/// <summary>Player won because opponent retired. Pushed to the survivor on
/// <c>Retire</c>/<c>Kill</c>. Same player-perspective convention as the Life codes.</summary>
RetireWin = 105,
/// <summary>Player lost by retiring. Pushed to the retirer on <c>Retire</c>/<c>Kill</c>.</summary>
RetireLose = 106,
}