diff --git a/SVSim.BattleNode/Sessions/BattleSession.cs b/SVSim.BattleNode/Sessions/BattleSession.cs index bb54819..cd2853c 100644 --- a/SVSim.BattleNode/Sessions/BattleSession.cs +++ b/SVSim.BattleNode/Sessions/BattleSession.cs @@ -38,7 +38,7 @@ public sealed class BattleSession public BattleType Type { get; } public IBattleParticipant A { get; } public IBattleParticipant B { get; } - public BattleSessionPhase Phase => _state.SessionPhase; + public SessionLifecycle Lifecycle => _state.Lifecycle; // Per-URI dispatch table. All 14 inbound URIs are registered (Tasks 5-14); unknown // URIs are dropped with a LogDebug in ComputeFrames. @@ -104,7 +104,7 @@ public sealed class BattleSession var first = await Task.WhenAny(aTask, bTask).ConfigureAwait(false); var survivor = first == aTask ? B : A; - if (Phase != BattleSessionPhase.Terminal) + if (Lifecycle != SessionLifecycle.Terminal) { // Involuntary drop (no graceful Retire): synthesize BattleFinish(DisconnectWin) // to survivor. DisconnectWin=201 → client renders "opponent disconnected" → @@ -121,7 +121,7 @@ public sealed class BattleSession "BattleSession {Bid}: failed to push BattleFinish to survivor (their WS may also be closed)", BattleId); } - _state.SessionPhase = BattleSessionPhase.Terminal; + _state.Lifecycle = SessionLifecycle.Terminal; } cts.Cancel(); // unblock the survivor's RunAsync read loop @@ -202,8 +202,8 @@ public sealed class BattleSession if (Handlers.TryGetValue(env.Uri, out var handler)) return handler.Handle(BuildContext(from, env)); - _log.LogDebug("BattleSession {Bid}: dropping uri={Uri} in phase={Phase} from vid={Vid}", - BattleId, env.Uri, Phase, from.ViewerId); + _log.LogDebug("BattleSession {Bid}: dropping uri={Uri} in lifecycle={Lifecycle} from vid={Vid}", + BattleId, env.Uri, Lifecycle, from.ViewerId); return Array.Empty(); } diff --git a/SVSim.BattleNode/Sessions/BattleSessionPhase.cs b/SVSim.BattleNode/Sessions/BattleSessionPhase.cs deleted file mode 100644 index 7375486..0000000 --- a/SVSim.BattleNode/Sessions/BattleSessionPhase.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace SVSim.BattleNode.Sessions; - -/// -/// Where we are in the v1 server-authored frame lifecycle. Drives which server-authored frames -/// the session pushes in response to inbound emits. -/// -public enum BattleSessionPhase -{ - AwaitingInitNetwork, - AwaitingInitBattle, - AwaitingLoaded, - AwaitingSwap, - AfterReady, - OpponentTurn, - Terminal, -} diff --git a/SVSim.BattleNode/Sessions/Dispatch/BattleSessionState.cs b/SVSim.BattleNode/Sessions/Dispatch/BattleSessionState.cs index 6ac9008..f3b9fcd 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/BattleSessionState.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/BattleSessionState.cs @@ -39,7 +39,7 @@ internal sealed class BattleSessionState return deck; } - public BattleSessionPhase SessionPhase { get; set; } = BattleSessionPhase.AwaitingInitNetwork; + public SessionLifecycle Lifecycle { get; set; } = SessionLifecycle.Active; public Dictionary PostSwapHands { get; } = new(); /// Per-side idx->cardId, seeded lazily from . diff --git a/SVSim.BattleNode/Sessions/Dispatch/FrameDispatchContext.cs b/SVSim.BattleNode/Sessions/Dispatch/FrameDispatchContext.cs index 83dd0ef..1d8a163 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/FrameDispatchContext.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/FrameDispatchContext.cs @@ -20,7 +20,7 @@ internal sealed class FrameDispatchContext /// The dispatching participant's handshake phase (null for a non-IHasHandshakePhase /// participant, e.g. NoOpBot). Setting it advances the sender. - internal BattleSessionPhase? SenderPhase + internal HandshakePhase? SenderPhase { get => (From as IHasHandshakePhase)?.Phase; set { if (From is IHasHandshakePhase p && value is { } v) p.Phase = v; } @@ -29,6 +29,6 @@ internal sealed class FrameDispatchContext /// Both participants have completed the handshake. Reads A/B (not From/Other) so the /// result is identical regardless of which side sent the frame — matches legacy BothAfterReady. internal bool BothAfterReady() => - (A as IHasHandshakePhase)?.Phase == BattleSessionPhase.AfterReady && - (B as IHasHandshakePhase)?.Phase == BattleSessionPhase.AfterReady; + (A as IHasHandshakePhase)?.Phase == HandshakePhase.AfterReady && + (B as IHasHandshakePhase)?.Phase == HandshakePhase.AfterReady; } diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/InitBattleHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/InitBattleHandler.cs index f27cc65..211abba 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/Handlers/InitBattleHandler.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/InitBattleHandler.cs @@ -8,18 +8,18 @@ internal sealed class InitBattleHandler : IFrameHandler public IReadOnlyList Handle(FrameDispatchContext ctx) { // case 2: Bot — ack only, NO Matched (Matched would corrupt client opponent info). - if (ctx.Type == BattleType.Bot && ctx.SenderPhase == BattleSessionPhase.AwaitingInitBattle) + if (ctx.Type == BattleType.Bot && ctx.SenderPhase == HandshakePhase.AwaitingInitBattle) { var r = new List { new(ctx.From, BattleFrames.BuildAck(NetworkBattleUri.InitBattle), Stock.Bypass), }; - ctx.SenderPhase = BattleSessionPhase.AwaitingLoaded; + ctx.SenderPhase = HandshakePhase.AwaitingLoaded; return r; } // case 5: general — push Matched (per-perspective) to the sender only. - if (ctx.SenderPhase == BattleSessionPhase.AwaitingInitBattle) + if (ctx.SenderPhase == HandshakePhase.AwaitingInitBattle) { var r = new List { @@ -28,7 +28,7 @@ internal sealed class InitBattleHandler : IFrameHandler ctx.BattleId, BattleSeeds.Stable(ctx.State.MasterSeed), ctx.State.GetShuffledDeck(ctx.From)), Stock.Normal), }; - ctx.SenderPhase = BattleSessionPhase.AwaitingLoaded; + ctx.SenderPhase = HandshakePhase.AwaitingLoaded; return r; } diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/InitNetworkHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/InitNetworkHandler.cs index 628d200..2b39803 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/Handlers/InitNetworkHandler.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/InitNetworkHandler.cs @@ -7,14 +7,14 @@ internal sealed class InitNetworkHandler : IFrameHandler { public IReadOnlyList Handle(FrameDispatchContext ctx) { - if (ctx.SenderPhase != BattleSessionPhase.AwaitingInitNetwork) + if (ctx.SenderPhase != HandshakePhase.AwaitingInitNetwork) return Array.Empty(); var routes = new List { new(ctx.From, BattleFrames.BuildAck(NetworkBattleUri.InitNetwork), Stock.Bypass), }; - ctx.SenderPhase = BattleSessionPhase.AwaitingInitBattle; + ctx.SenderPhase = HandshakePhase.AwaitingInitBattle; return routes; } } diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/LoadedHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/LoadedHandler.cs index 2999f0a..31c114e 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/Handlers/LoadedHandler.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/LoadedHandler.cs @@ -8,14 +8,14 @@ internal sealed class LoadedHandler : IFrameHandler public IReadOnlyList Handle(FrameDispatchContext ctx) { // case 3: Bot — silent (client populates opponent state from AIBattleStart HTTP data). - if (ctx.Type == BattleType.Bot && ctx.SenderPhase == BattleSessionPhase.AwaitingLoaded) + if (ctx.Type == BattleType.Bot && ctx.SenderPhase == HandshakePhase.AwaitingLoaded) { - ctx.SenderPhase = BattleSessionPhase.AwaitingSwap; + ctx.SenderPhase = HandshakePhase.AwaitingSwap; return Array.Empty(); } // case 6: general — BattleStart (per-perspective) + Deal to the sender. - if (ctx.SenderPhase == BattleSessionPhase.AwaitingLoaded) + if (ctx.SenderPhase == HandshakePhase.AwaitingLoaded) { // A goes first deterministically; B goes second. var turnState = ReferenceEquals(ctx.From, ctx.A) ? TurnState.First : TurnState.Second; @@ -25,7 +25,7 @@ internal sealed class LoadedHandler : IFrameHandler ctx.From.Context, ctx.Other.Context, ctx.From.ViewerId, turnState), Stock.Normal), new(ctx.From, ServerBattleFrames.BuildDeal(), Stock.Normal), }; - ctx.SenderPhase = BattleSessionPhase.AwaitingSwap; + ctx.SenderPhase = HandshakePhase.AwaitingSwap; return r; } diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/RetireKillHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/RetireKillHandler.cs index cbbfcc0..25617aa 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/Handlers/RetireKillHandler.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/RetireKillHandler.cs @@ -6,7 +6,7 @@ internal sealed class RetireKillHandler : IFrameHandler { public IReadOnlyList Handle(FrameDispatchContext ctx) { - ctx.State.SessionPhase = BattleSessionPhase.Terminal; + ctx.State.Lifecycle = SessionLifecycle.Terminal; // Polarity: the SENDER retired, so From LOSES / Other WINS. This is the OPPOSITE of // TurnEndFinalHandler (From WINS there — sender dealt the lethal). Intentional — do NOT // "consistency-fix" the two handlers to match; a swap here silently reverses every retire. diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/SwapHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/SwapHandler.cs index 3e06276..de83825 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/Handlers/SwapHandler.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/SwapHandler.cs @@ -8,7 +8,7 @@ internal sealed class SwapHandler : IFrameHandler { public IReadOnlyList Handle(FrameDispatchContext ctx) { - if (ctx.SenderPhase != BattleSessionPhase.AwaitingSwap) + if (ctx.SenderPhase != HandshakePhase.AwaitingSwap) return Array.Empty(); var routes = new List(); @@ -17,7 +17,7 @@ internal sealed class SwapHandler : IFrameHandler // SwapResponse is always immediate — completes the sender's own mulligan UI. routes.Add(new DispatchRoute(ctx.From, ServerBattleFrames.BuildSwapResponse(hand), Stock.Normal)); ctx.State.PostSwapHands[ctx.From] = hand; - ctx.SenderPhase = BattleSessionPhase.AfterReady; + ctx.SenderPhase = HandshakePhase.AfterReady; // Release Ready to every swapper once all handshake-driving participants have swapped. // IHasHandshakePhase membership IS the "participates in mulligan" set. diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndFinalHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndFinalHandler.cs index 5b9f42e..d02db46 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndFinalHandler.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndFinalHandler.cs @@ -7,13 +7,13 @@ internal sealed class TurnEndFinalHandler : IFrameHandler public IReadOnlyList Handle(FrameDispatchContext ctx) { // case 4: Bot — Judge to sender only. - if (ctx.Type == BattleType.Bot && ctx.SenderPhase == BattleSessionPhase.AfterReady) + if (ctx.Type == BattleType.Bot && ctx.SenderPhase == HandshakePhase.AfterReady) return new[] { new DispatchRoute(ctx.From, BattleFrames.BuildJudgeBroadcast(), Stock.Normal) }; // case 9: general — forward the envelope to other + paired BattleFinish + Terminal. - if (ctx.SenderPhase == BattleSessionPhase.AfterReady) + if (ctx.SenderPhase == HandshakePhase.AfterReady) { - ctx.State.SessionPhase = BattleSessionPhase.Terminal; + ctx.State.Lifecycle = SessionLifecycle.Terminal; // Polarity: the SENDER dealt the lethal, so From WINS / Other LOSES. This is the // OPPOSITE of RetireKillHandler (From LOSES there — retire is self-inflicted). // Intentional — do NOT "consistency-fix" the two handlers to match; a swap here diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndHandler.cs index c74bb83..3da849e 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndHandler.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndHandler.cs @@ -8,12 +8,12 @@ internal sealed class TurnEndHandler : IFrameHandler public IReadOnlyList Handle(FrameDispatchContext ctx) { // case 4: Bot — Judge to sender only (no real opponent; client flips back to its local AI). - if (ctx.Type == BattleType.Bot && ctx.SenderPhase == BattleSessionPhase.AfterReady) + if (ctx.Type == BattleType.Bot && ctx.SenderPhase == HandshakePhase.AfterReady) return new[] { new DispatchRoute(ctx.From, BattleFrames.BuildJudgeBroadcast(), Stock.Normal) }; // case 8: general AfterReady arm — PvP forwards a {turnState} TurnEnd to the opponent // (handover gate). Any non-Pvp non-Bot type that reaches AfterReady consumes the frame. - if (ctx.SenderPhase == BattleSessionPhase.AfterReady) + if (ctx.SenderPhase == HandshakePhase.AfterReady) { if (ctx.Type == BattleType.Pvp && ctx.BothAfterReady()) { diff --git a/SVSim.BattleNode/Sessions/HandshakePhase.cs b/SVSim.BattleNode/Sessions/HandshakePhase.cs new file mode 100644 index 0000000..a8c9ebe --- /dev/null +++ b/SVSim.BattleNode/Sessions/HandshakePhase.cs @@ -0,0 +1,18 @@ +namespace SVSim.BattleNode.Sessions; + +/// +/// Per-participant progression through the v1 server-authored setup handshake. Each side advances +/// InitNetwork → InitBattle → Loaded → Swap → AfterReady as the session acks its emits. Tracked +/// per participant via ; the session reads the +/// SENDER's phase () to gate which setup +/// frame to author next. Distinct from the session-global — this is +/// one axis per side, that is one axis per battle. +/// +public enum HandshakePhase +{ + AwaitingInitNetwork, + AwaitingInitBattle, + AwaitingLoaded, + AwaitingSwap, + AfterReady, +} diff --git a/SVSim.BattleNode/Sessions/Participants/RealParticipant.cs b/SVSim.BattleNode/Sessions/Participants/RealParticipant.cs index 28f0bd1..ed82666 100644 --- a/SVSim.BattleNode/Sessions/Participants/RealParticipant.cs +++ b/SVSim.BattleNode/Sessions/Participants/RealParticipant.cs @@ -19,7 +19,7 @@ namespace SVSim.BattleNode.Sessions.Participants; /// internal interface IHasHandshakePhase { - BattleSessionPhase Phase { get; set; } + HandshakePhase Phase { get; set; } } /// @@ -66,9 +66,9 @@ public sealed class RealParticipant : IBattleParticipant, IHasHandshakePhase /// because they never send the gating URIs. Also satisfies /// (the interface BattleSession uses to gate /// handshake dispatch without depending on the concrete RealParticipant type). - internal BattleSessionPhase Phase { get; set; } = BattleSessionPhase.AwaitingInitNetwork; + internal HandshakePhase Phase { get; set; } = HandshakePhase.AwaitingInitNetwork; - BattleSessionPhase IHasHandshakePhase.Phase + HandshakePhase IHasHandshakePhase.Phase { get => Phase; set => Phase = value; diff --git a/SVSim.BattleNode/Sessions/SessionLifecycle.cs b/SVSim.BattleNode/Sessions/SessionLifecycle.cs new file mode 100644 index 0000000..9afb298 --- /dev/null +++ b/SVSim.BattleNode/Sessions/SessionLifecycle.cs @@ -0,0 +1,15 @@ +namespace SVSim.BattleNode.Sessions; + +/// +/// Session-global lifecycle. A battle stays until a terminal event — a lethal +/// TurnEndFinal, a Retire/Kill, or the disconnect drop cascade — flips it to , +/// after which the drop cascade will not synthesize another BattleFinish. Distinct from the +/// per-participant (which side reached which setup step); this is one +/// axis per battle. Only these two states are load-bearing — the handshake progression lives on the +/// other enum. +/// +public enum SessionLifecycle +{ + Active, + Terminal, +} diff --git a/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchConcurrencyTests.cs b/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchConcurrencyTests.cs index d59f759..56363dc 100644 --- a/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchConcurrencyTests.cs +++ b/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchConcurrencyTests.cs @@ -103,7 +103,7 @@ public class BattleSessionDispatchConcurrencyTests private readonly ConcurrencyDetector _detector; public long ViewerId { get; } public MatchContext Context { get; } - public BattleSessionPhase Phase { get; set; } = BattleSessionPhase.AwaitingInitNetwork; + public HandshakePhase Phase { get; set; } = HandshakePhase.AwaitingInitNetwork; public event Func? FrameEmitted; public ProbeParticipant(long viewerId, MatchContext context, ConcurrencyDetector detector) diff --git a/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs b/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs index ac16298..7270204 100644 --- a/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs +++ b/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs @@ -44,19 +44,19 @@ public class BattleSessionDispatchTests var s = new BattleSession("bid-1", BattleType.Pvp, a, b, NullLogger.Instance); // A is AwaitingInitNetwork; B is AwaitingInitBattle (manually set). - b.Phase = BattleSessionPhase.AwaitingInitBattle; + b.Phase = HandshakePhase.AwaitingInitBattle; // A's InitNetwork should ack (matches A's phase). var routesA = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork)); Assert.That(routesA.Count, Is.EqualTo(1)); Assert.That(routesA[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.InitNetwork)); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingInitBattle)); + Assert.That(a.Phase, Is.EqualTo(HandshakePhase.AwaitingInitBattle)); // B's InitBattle should produce Matched (matches B's phase, set above). var routesB = s.ComputeFrames(b, NewEnvelope(NetworkBattleUri.InitBattle)); Assert.That(routesB.Count, Is.EqualTo(1)); Assert.That(routesB[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.Matched)); - Assert.That(b.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded)); + Assert.That(b.Phase, Is.EqualTo(HandshakePhase.AwaitingLoaded)); } [Test] @@ -66,7 +66,7 @@ public class BattleSessionDispatchTests var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap)); Assert.That(routes, Is.Empty); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingInitNetwork)); + Assert.That(a.Phase, Is.EqualTo(HandshakePhase.AwaitingInitNetwork)); } [Test] @@ -169,7 +169,7 @@ public class BattleSessionDispatchTests Assert.That(routes.Select(r => r.Frame.Uri), Is.EqualTo(new[] { NetworkBattleUri.Swap }), "Ready is withheld until BOTH sides have mulliganed."); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AfterReady), + Assert.That(a.Phase, Is.EqualTo(HandshakePhase.AfterReady), "Phase advances on Swap even though Ready is withheld."); } @@ -833,7 +833,7 @@ public class BattleSessionDispatchTests Assert.That(routes[2].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish)); Assert.That(((BattleFinishBody)routes[2].Frame.Body).Result, Is.EqualTo(BattleResult.LifeLose)); - Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal)); + Assert.That(s.Lifecycle, Is.EqualTo(SessionLifecycle.Terminal)); } [Test] @@ -866,7 +866,7 @@ public class BattleSessionDispatchTests Assert.That(((BattleFinishBody)bRoute.Frame.Body).Result, Is.EqualTo(BattleResult.RetireWin)); Assert.That(aRoute.Stock, Is.EqualTo(Stock.Bypass)); Assert.That(bRoute.Stock, Is.EqualTo(Stock.Bypass)); - Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal)); + Assert.That(s.Lifecycle, Is.EqualTo(SessionLifecycle.Terminal)); } [Test] @@ -879,7 +879,7 @@ public class BattleSessionDispatchTests var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Kill)); Assert.That(routes.Count, Is.EqualTo(2)); - Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal)); + Assert.That(s.Lifecycle, Is.EqualTo(SessionLifecycle.Terminal)); } private static (BattleSession, FakeRealParticipant, FakeParticipant) NewBotSession() @@ -905,7 +905,7 @@ public class BattleSessionDispatchTests Assert.That(routes.Count, Is.EqualTo(1)); Assert.That(routes[0].Target, Is.SameAs(a)); Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.InitNetwork)); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingInitBattle)); + Assert.That(a.Phase, Is.EqualTo(HandshakePhase.AwaitingInitBattle)); } [Test] @@ -923,7 +923,7 @@ public class BattleSessionDispatchTests Assert.That(routes[0].Target, Is.SameAs(a)); Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.InitBattle), "Expected an ack envelope for InitBattle, NOT a Matched envelope."); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded)); + Assert.That(a.Phase, Is.EqualTo(HandshakePhase.AwaitingLoaded)); } [Test] @@ -939,7 +939,7 @@ public class BattleSessionDispatchTests // handler at Matching.cs:417 → SetNetworkInfo overwrites it with our // placeholder NoOpBotParticipant.Context zeros). Assert.That(routes, Is.Empty, "Bot Loaded is silent."); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingSwap), + Assert.That(a.Phase, Is.EqualTo(HandshakePhase.AwaitingSwap), "Phase still advances even though there are no outbound routes."); } @@ -956,7 +956,7 @@ public class BattleSessionDispatchTests Assert.That(routes.Select(r => r.Frame.Uri), Is.EqualTo(new[] { NetworkBattleUri.Swap, NetworkBattleUri.Ready })); Assert.That(routes.All(r => ReferenceEquals(r.Target, a)), Is.True); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AfterReady)); + Assert.That(a.Phase, Is.EqualTo(HandshakePhase.AfterReady)); } [Test] @@ -1114,7 +1114,7 @@ public class BattleSessionDispatchTests { public long ViewerId { get; } public MatchContext Context { get; } - public BattleSessionPhase Phase { get; set; } = BattleSessionPhase.AwaitingInitNetwork; + public HandshakePhase Phase { get; set; } = HandshakePhase.AwaitingInitNetwork; public event Func? FrameEmitted; public FakeRealParticipant(long viewerId, MatchContext context) { ViewerId = viewerId; Context = context; } public Task PushAsync(MsgEnvelope env, Stock stock, CancellationToken ct) => Task.CompletedTask; diff --git a/SVSim.UnitTests/BattleNode/Sessions/Participants/RealParticipantTests.cs b/SVSim.UnitTests/BattleNode/Sessions/Participants/RealParticipantTests.cs index a3eb759..e7d3058 100644 --- a/SVSim.UnitTests/BattleNode/Sessions/Participants/RealParticipantTests.cs +++ b/SVSim.UnitTests/BattleNode/Sessions/Participants/RealParticipantTests.cs @@ -97,7 +97,7 @@ public class RealParticipantTests var p = new RealParticipant(ws, viewerId: 1, FixtureCtx(), NullLogger.Instance); - Assert.That(p.Phase, Is.EqualTo(SVSim.BattleNode.Sessions.BattleSessionPhase.AwaitingInitNetwork)); + Assert.That(p.Phase, Is.EqualTo(SVSim.BattleNode.Sessions.HandshakePhase.AwaitingInitNetwork)); } [Test] @@ -108,9 +108,9 @@ public class RealParticipantTests NullLogger.Instance); // Setter is `internal`; SVSim.UnitTests has InternalsVisibleTo on SVSim.BattleNode. - p.Phase = SVSim.BattleNode.Sessions.BattleSessionPhase.AfterReady; + p.Phase = SVSim.BattleNode.Sessions.HandshakePhase.AfterReady; - Assert.That(p.Phase, Is.EqualTo(SVSim.BattleNode.Sessions.BattleSessionPhase.AfterReady)); + Assert.That(p.Phase, Is.EqualTo(SVSim.BattleNode.Sessions.HandshakePhase.AfterReady)); } [Test] @@ -121,9 +121,9 @@ public class RealParticipantTests var a = new RealParticipant(wsA, viewerId: 1, FixtureCtx(), NullLogger.Instance); var b = new RealParticipant(wsB, viewerId: 2, FixtureCtx(), NullLogger.Instance); - a.Phase = SVSim.BattleNode.Sessions.BattleSessionPhase.AfterReady; + a.Phase = SVSim.BattleNode.Sessions.HandshakePhase.AfterReady; - Assert.That(b.Phase, Is.EqualTo(SVSim.BattleNode.Sessions.BattleSessionPhase.AwaitingInitNetwork), + Assert.That(b.Phase, Is.EqualTo(SVSim.BattleNode.Sessions.HandshakePhase.AwaitingInitNetwork), "B's Phase must not change when A's Phase is set."); }