diff --git a/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs b/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs index 9faf1c6..97d38f1 100644 --- a/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs +++ b/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs @@ -12,46 +12,6 @@ namespace SVSim.UnitTests.BattleNode.Sessions; [TestFixture] public class BattleSessionDispatchTests { - [Test] - public void InitNetwork_acks_to_sender_transitions_to_AwaitingInitBattle() - { - var (s, a, b) = NewSession(); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork)); - - 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(routes[0].NoStock, Is.True); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingInitBattle)); - } - - [Test] - public void InitBattle_pushes_Matched_to_sender_only() - { - var (s, a, b) = NewSession(); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork)); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle)); - - Assert.That(routes.Count, Is.EqualTo(1)); - Assert.That(routes[0].Target, Is.SameAs(a)); - Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.Matched)); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded)); - } - - [Test] - public void Loaded_pushes_BattleStart_then_Deal_to_sender() - { - var (s, a, b) = NewSession(); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle)); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded)); - - Assert.That(routes.Select(r => r.Frame.Uri), - Is.EqualTo(new[] { NetworkBattleUri.BattleStart, NetworkBattleUri.Deal })); - Assert.That(routes.All(r => ReferenceEquals(r.Target, a)), Is.True); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingSwap)); - } - [Test] public void Pvp_Loaded_from_A_assigns_turnState_0() { @@ -76,92 +36,6 @@ public class BattleSessionDispatchTests Assert.That(bs.TurnState, Is.EqualTo(1), "B (second arriver) goes second."); } - [Test] - public void Scripted_Loaded_from_player_assigns_turnState_0() - { - // Real player is constructed as A in scripted sessions, so it always goes first. - var (s, a, _) = NewSession(); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle)); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded)); - - var bs = (BattleStartBody)routes[0].Frame.Body; - Assert.That(bs.TurnState, Is.EqualTo(0)); - } - - [Test] - public void Swap_pushes_SwapResponse_then_Ready_to_sender() - { - // Opponent stub is not IHasHandshakePhase → not a barrier swapper → Ready releases immediately. - var (s, a, b) = NewSession(); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded)); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap)); - - Assert.That(routes.Select(r => r.Frame.Uri), - Is.EqualTo(new[] { NetworkBattleUri.Swap, NetworkBattleUri.Ready })); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AfterReady)); - } - - [Test] - public void TurnEnd_from_real_forwards_to_other_participant() - { - var (s, a, b) = NewSession(); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap)); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnEnd)); - - Assert.That(routes.Count, Is.EqualTo(1)); - Assert.That(routes[0].Target, Is.SameAs(b)); - Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnEnd)); - Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AfterReady)); - } - - [Test] - public void Scripted_TurnEndFinal_forwards_envelope_and_pushes_paired_BattleFinish() - { - // Unified TurnEndFinal handling: forward the envelope to other (matches prod - // capture battle-traffic_tk2_regular.ndjson:273) + push BattleFinish per-side - // with player-perspective codes (LifeWin to winner, LifeLose to loser). - // In Scripted mode the "loser" is a ScriptedBotParticipant; the loser-side - // BattleFinish push is harmless (bot swallows non-TurnEnd URIs). - var (s, a, b) = NewSession(); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap)); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnEndFinal)); - - Assert.That(routes.Count, Is.EqualTo(3), - "TurnEndFinal must produce: forwarded envelope + BattleFinish(LifeWin) to from + BattleFinish(LifeLose) to other."); - - // Route 0: forwarded TurnEndFinal envelope to other. - Assert.That(routes[0].Target, Is.SameAs(b)); - Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnEndFinal)); - - // Route 1: BattleFinish(LifeWin) to from (the winner who declared the final turn). - Assert.That(routes[1].Target, Is.SameAs(a)); - Assert.That(routes[1].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish)); - Assert.That(routes[1].NoStock, Is.True); - var winBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[1].Frame.Body; - Assert.That(winBody.Result, Is.EqualTo(BattleResult.LifeWin), - "Winner gets LifeWin (101) — player-perspective: 'I won by life' → WIN UI."); - - // Route 2: BattleFinish(LifeLose) to other (the loser). - Assert.That(routes[2].Target, Is.SameAs(b)); - Assert.That(routes[2].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish)); - Assert.That(routes[2].NoStock, Is.True); - var loseBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[2].Frame.Body; - Assert.That(loseBody.Result, Is.EqualTo(BattleResult.LifeLose), - "Loser gets LifeLose (102) — player-perspective: 'I lost by life' → LOSE UI."); - - Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal), - "Session must transition to Terminal so the RunAsync cascade doesn't synthesize a second BattleFinish."); - } - [Test] public void Handshake_dispatch_reads_per_participant_Phase_not_session_Phase() { @@ -185,102 +59,10 @@ public class BattleSessionDispatchTests Assert.That(b.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded)); } - [Test] - public void ScriptedBot_emitted_OpponentTurnStart_forwards_to_real() - { - var (s, a, b) = NewSession(); - // Bot emits a TurnStart frame (carrying ViewerId == FakeOpponentViewerId per the - // ScriptedBotParticipant impl). Session should route it to the real participant. - var botFrame = ScriptedLifecycle.BuildOpponentTurnStart(); - var routes = s.ComputeFrames(b, botFrame); - - Assert.That(routes.Count, Is.EqualTo(1)); - Assert.That(routes[0].Target, Is.SameAs(a)); - Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnStart)); - } - - [Test] - public void ScriptedBot_emitted_Judge_forwards_to_real() - { - var (s, a, b) = NewSession(); - var botFrame = ScriptedLifecycle.BuildOpponentJudge(); - var routes = s.ComputeFrames(b, botFrame); - - Assert.That(routes.Count, Is.EqualTo(1)); - Assert.That(routes[0].Target, Is.SameAs(a)); - Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.Judge)); - } - - [Test] - public void ScriptedBot_emitted_TurnEnd_forwards_to_real() - { - // TurnEnd from the bot is also one of the burst frames. The case is handled - // by the TurnEnd-from-scripted arm (bot ViewerId matches FakeOpponentViewerId). - var (s, a, b) = NewSession(); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded)); - s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap)); - // Drive player TurnEnd first (transitions Phase if needed) — but TurnEnd from bot - // arrives in Phase AfterReady too. The bot's TurnEnd is routed via the dispatch - // arm that forwards any frame from the FakeOpponentViewerId participant. - - var botFrame = ScriptedLifecycle.BuildOpponentTurnEnd(); - var routes = s.ComputeFrames(b, botFrame); - - Assert.That(routes.Count, Is.EqualTo(1)); - Assert.That(routes[0].Target, Is.SameAs(a)); - Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnEnd)); - } - - [Test] - public void Retire_pushes_paired_BattleFinish_RetireLose_to_from_and_RetireWin_to_other() - { - var (s, a, b) = NewSession(); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Retire)); - - Assert.That(routes.Count, Is.EqualTo(2)); - Assert.That(routes[0].Target, Is.SameAs(a)); - Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish)); - Assert.That(routes[0].NoStock, Is.True); - var loseBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[0].Frame.Body; - Assert.That(loseBody.Result, Is.EqualTo(BattleResult.RetireLose), - "Retirer gets RetireLose=106 — player-perspective: 'I lost by retire'."); - - Assert.That(routes[1].Target, Is.SameAs(b)); - Assert.That(routes[1].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish)); - Assert.That(routes[1].NoStock, Is.True); - var winBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[1].Frame.Body; - Assert.That(winBody.Result, Is.EqualTo(BattleResult.RetireWin), - "Survivor gets RetireWin=105. In Scripted mode the bot swallows it; in PvP the opponent renders 'opponent retired'."); - - Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal)); - } - - [Test] - public void Kill_pushes_paired_BattleFinish_RetireLose_to_from_and_RetireWin_to_other() - { - var (s, a, b) = NewSession(); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Kill)); - - Assert.That(routes.Count, Is.EqualTo(2)); - Assert.That(routes[0].Target, Is.SameAs(a)); - Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish)); - var loseBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[0].Frame.Body; - Assert.That(loseBody.Result, Is.EqualTo(BattleResult.RetireLose)); - - Assert.That(routes[1].Target, Is.SameAs(b)); - Assert.That(routes[1].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish)); - var winBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[1].Frame.Body; - Assert.That(winBody.Result, Is.EqualTo(BattleResult.RetireWin)); - - Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal)); - } - [Test] public void OutOfOrder_dispatch_returns_empty_and_does_not_advance_phase() { - var (s, a, _) = NewSession(); + var (s, a, _) = NewPvpSession(); var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap)); Assert.That(routes, Is.Empty); @@ -627,23 +409,6 @@ public class BattleSessionDispatchTests Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal)); } - [Test] - public void Scripted_Retire_pushes_RetireLose_to_player_and_RetireWin_to_bot() - { - // Unified with PvP — paired BattleFinish per-side. In Scripted mode the "loser" - // is a ScriptedBotParticipant; its loser-side push is swallowed (it only reacts - // to TurnEnd). The wire-correct codes are still emitted in case future work - // wants to inspect them or run a real two-real-participant session. - var (s, a, b) = NewSession(); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Retire)); - - Assert.That(routes.Count, Is.EqualTo(2)); - Assert.That(routes[0].Target, Is.SameAs(a)); - Assert.That(((BattleFinishBody)routes[0].Frame.Body).Result, Is.EqualTo(BattleResult.RetireLose)); - Assert.That(routes[1].Target, Is.SameAs(b)); - Assert.That(((BattleFinishBody)routes[1].Frame.Body).Result, Is.EqualTo(BattleResult.RetireWin)); - } - private static (BattleSession, FakeRealParticipant, FakeParticipant) NewBotSession() { var a = new FakeRealParticipant(viewerId: 1001, PlayerACtx()); @@ -822,14 +587,6 @@ public class BattleSessionDispatchTests EmblemId: "701441022", DegreeId: "300004", FieldId: 44, IsOfficial: 0, BattleType: 11); - private static (BattleSession, FakeRealParticipant, FakeParticipant) NewSession() - { - var a = new FakeRealParticipant(viewerId: 1, FixtureCtx()); - var b = new FakeParticipant(viewerId: ScriptedLifecycle.FakeOpponentViewerId, ScriptedBotContext()); - var s = new BattleSession("bid-1", BattleType.Scripted, a, b, NullLogger.Instance); - return (s, a, b); - } - private static MatchContext FixtureCtx() => new( SelfDeckCardIds: Enumerable.Range(1, 30).Select(_ => 100_011_010L).ToList(), ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015", @@ -837,13 +594,6 @@ public class BattleSessionDispatchTests EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0, BattleType: 11); - private static MatchContext ScriptedBotContext() => new( - SelfDeckCardIds: Array.Empty(), - ClassId: "0", CharaId: "0", CardMasterName: "card_master_node_10015", - CountryCode: "JPN", UserName: "Opponent", SleeveId: "704141010", - EmblemId: "400001100", DegreeId: "120027", FieldId: 5, IsOfficial: 0, - BattleType: 0); - private static MsgEnvelope NewEnvelope(NetworkBattleUri uri) => new(uri, ViewerId: 1, Uuid: "u", Bid: null, Try: 0, Cat: EmitCategory.Battle, PubSeq: null, PlaySeq: null,