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

@@ -28,22 +28,20 @@ public class ScriptedBotParticipantTests
}
[Test]
public async Task PushAsync_TurnEndFinal_fires_three_FrameEmitted_in_order()
public async Task PushAsync_TurnEndFinal_does_NOT_fire_burst()
{
// Same burst as TurnEnd — TurnEndFinal is the game-ending variant but the
// bot's response shape is unchanged for v1.2 behaviour preservation.
// TurnEndFinal is the game-end signal — owned by BattleSession's TurnEndFinal
// dispatch arm, which pushes BattleFinish per-side. The bot no longer reacts to
// it; reacting would race the BattleFinish with the no-longer-needed 3-frame
// burst. Only regular TurnEnd triggers the burst.
var p = new ScriptedBotParticipant();
var emitted = new List<NetworkBattleUri>();
p.FrameEmitted += (env, _) => { emitted.Add(env.Uri); return Task.CompletedTask; };
var fired = 0;
p.FrameEmitted += (_, _) => { fired++; return Task.CompletedTask; };
await p.PushAsync(NewEnvelope(NetworkBattleUri.TurnEndFinal), noStock: false, CancellationToken.None);
Assert.That(emitted, Is.EqualTo(new[]
{
NetworkBattleUri.TurnStart,
NetworkBattleUri.TurnEnd,
NetworkBattleUri.Judge,
}));
Assert.That(fired, Is.EqualTo(0),
"TurnEndFinal must not trigger the bot's burst — the dispatch arm pushes BattleFinish directly.");
}
[Test]