diff --git a/SVSim.BattleNode/Protocol/BattleResult.cs b/SVSim.BattleNode/Protocol/BattleResult.cs index 0c2d4b7..022f151 100644 --- a/SVSim.BattleNode/Protocol/BattleResult.cs +++ b/SVSim.BattleNode/Protocol/BattleResult.cs @@ -30,12 +30,15 @@ namespace SVSim.BattleNode.Protocol; /// reproduction that exposed the inversion. /// /// -/// The pre-2026-06-02 Lose = 0 / Win = 1 / Consistency = 2 -/// values are kept for the existing Retire/Kill Scripted flow (which ships -/// Win = 1, parsed client-side as RESULT_CODE.NoContest 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. +/// The legacy Lose = 0 / Win = 1 / Consistency = 2 values +/// (client RESULT_CODE.NotFinish / NoContest / Invalid) are +/// no longer emitted by any dispatch arm: the Retire/Kill flow now ships the +/// proper RetireWin / RetireLose codes, and the involuntary-drop +/// cascade ships DisconnectWin. They survive only as wire-constant +/// references in serialization tests and can be deleted once those are dropped. +/// (Historically Win = 1 was shipped on Retire and rendered client-side +/// as "battle ended in no contest" — the wrong text — which is why it was +/// replaced.) /// /// /// Always serialize as the int value, not the name; see the @@ -71,4 +74,11 @@ public enum BattleResult /// Player lost by retiring. Pushed to the retirer on Retire/Kill. RetireLose = 106, + + /// Survivor wins because the opponent's socket dropped without a graceful + /// Retire. Pushed to the survivor by the + /// RunAsync drop cascade. Client RESULT_CODE.DisconnectWin renders the + /// "opponent disconnected" result text → player WIN UI. Same player-perspective + /// convention as the Life/Retire codes. + DisconnectWin = 201, } diff --git a/SVSim.BattleNode/Sessions/BattleSession.cs b/SVSim.BattleNode/Sessions/BattleSession.cs index 38dfb08..a435db4 100644 --- a/SVSim.BattleNode/Sessions/BattleSession.cs +++ b/SVSim.BattleNode/Sessions/BattleSession.cs @@ -58,11 +58,13 @@ public sealed class BattleSession if (Phase != BattleSessionPhase.Terminal) { - // Involuntary drop (no graceful Retire): synthesize BattleFinish(Win) to survivor. + // Involuntary drop (no graceful Retire): synthesize BattleFinish(DisconnectWin) + // to survivor. DisconnectWin=201 → client renders "opponent disconnected" → + // WIN UI; the legacy Win=1 used here previously rendered "no contest". try { await survivor.PushAsync( - BuildBattleFinish(BattleResult.Win), noStock: true, cancellation) + BuildBattleFinish(BattleResult.DisconnectWin), noStock: true, cancellation) .ConfigureAwait(false); } catch (Exception ex) @@ -334,17 +336,6 @@ public sealed class BattleSession PlaySeq: null, Body: new ResultCodeOnlyBody()); - private MsgEnvelope BuildBattleFinishNoContest() => new( - NetworkBattleUri.BattleFinish, - ViewerId: ScriptedLifecycle.FakeOpponentViewerId, - Uuid: WireConstants.ServerUuid, - Bid: null, - Try: 0, - Cat: EmitCategory.Battle, - PubSeq: null, - PlaySeq: null, - Body: new BattleFinishBody(Result: BattleResult.Win)); - private MsgEnvelope BuildTurnEndBroadcast() => new( NetworkBattleUri.TurnEnd, ViewerId: ScriptedLifecycle.FakeOpponentViewerId, diff --git a/SVSim.UnitTests/BattleNode/Integration/BattleNodeFlowTests.cs b/SVSim.UnitTests/BattleNode/Integration/BattleNodeFlowTests.cs index d37c421..4f0d88c 100644 --- a/SVSim.UnitTests/BattleNode/Integration/BattleNodeFlowTests.cs +++ b/SVSim.UnitTests/BattleNode/Integration/BattleNodeFlowTests.cs @@ -320,11 +320,11 @@ public class BattleNodeFlowTests // Abruptly close A's WS (no Retire). await clientA.DisposeAsync(); - // B should receive BattleFinish(Win) within a few seconds. + // B should receive BattleFinish(DisconnectWin) within a few seconds. var bFinish = await clientB.ReceiveSynchronizeAsync(ct); Assert.That(bFinish.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish)); var bBody = (RawBody)bFinish.Body; - Assert.That((long)bBody.Entries["result"]!, Is.EqualTo((long)SVSim.BattleNode.Protocol.BattleResult.Win)); + Assert.That((long)bBody.Entries["result"]!, Is.EqualTo((long)SVSim.BattleNode.Protocol.BattleResult.DisconnectWin)); // PendingBattle should be evicted by the second arriver's RemovePending. var store = factory.Services.GetRequiredService();