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();