diff --git a/SVSim.BattleNode/Sessions/BattleSession.cs b/SVSim.BattleNode/Sessions/BattleSession.cs index c849257..6a04b60 100644 --- a/SVSim.BattleNode/Sessions/BattleSession.cs +++ b/SVSim.BattleNode/Sessions/BattleSession.cs @@ -51,7 +51,7 @@ public sealed class BattleSession [NetworkBattleUri.Judge] = new JudgeHandler(), [NetworkBattleUri.PlayActions] = new PlayActionsHandler(), [NetworkBattleUri.Echo] = new EchoHandler(), - [NetworkBattleUri.TurnEndActions] = forwardWhenReady, + [NetworkBattleUri.TurnEndActions] = new TurnEndActionsHandler(), [NetworkBattleUri.JudgeResult] = forwardWhenReady, }; } diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndActionsHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndActionsHandler.cs new file mode 100644 index 0000000..96d95c1 --- /dev/null +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/TurnEndActionsHandler.cs @@ -0,0 +1,19 @@ +using SVSim.BattleNode.Protocol; + +namespace SVSim.BattleNode.Sessions.Dispatch.Handlers; + +/// PvP TurnEndActions: the sender's orderList is dropped; the opponent receives an +/// empty body (it only flips _sendEcho + runs the opponent's end-of-turn triggers via the +/// opponent's own engine). Scripted/Bot drop. +internal sealed class TurnEndActionsHandler : IFrameHandler +{ + public IReadOnlyList Handle(FrameDispatchContext ctx) + { + if (ctx.Type == BattleType.Pvp && ctx.BothAfterReady()) + { + var frame = ctx.Env with { Body = new RawBody(new Dictionary()) }; + return new[] { new DispatchRoute(ctx.Other, frame, false) }; + } + return Array.Empty(); + } +} diff --git a/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs b/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs index 68c8c2f..0e449e4 100644 --- a/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs +++ b/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs @@ -508,16 +508,19 @@ public class BattleSessionDispatchTests } [Test] - public void Pvp_TurnEndActions_from_A_in_BothAfterReady_forwards_to_B() + public void Pvp_TurnEndActions_from_A_emits_empty_body_to_B() { var (s, a, b) = NewPvpSession(); DriveToAfterReady(s, a); DriveToAfterReady(s, b); - var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnEndActions)); + var body = MoveOrderList(3, 20, 30); // a non-empty orderList that must be dropped + var routes = s.ComputeFrames(a, EnvWith(NetworkBattleUri.TurnEndActions, body)); Assert.That(routes.Count, Is.EqualTo(1)); Assert.That(routes[0].Target, Is.SameAs(b)); + Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnEndActions)); + Assert.That(((RawBody)routes[0].Frame.Body).Entries, Is.Empty, "orderList is dropped; body is empty."); } [Test]