From 58994a53c94623a22b62bca6338d1260c5a933e9 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Wed, 3 Jun 2026 18:09:44 -0400 Subject: [PATCH] feat(battle-node): JudgeHandler emits {spin:0} to opponent in PvP Co-Authored-By: Claude Opus 4.8 --- .../Sessions/Dispatch/Handlers/JudgeHandler.cs | 12 +++++++++++- .../Sessions/BattleSessionDispatchTests.cs | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/JudgeHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/JudgeHandler.cs index 4ba8146..0e2b973 100644 --- a/SVSim.BattleNode/Sessions/Dispatch/Handlers/JudgeHandler.cs +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/JudgeHandler.cs @@ -1,4 +1,5 @@ using SVSim.BattleNode.Protocol; +using SVSim.BattleNode.Protocol.Bodies; namespace SVSim.BattleNode.Sessions.Dispatch.Handlers; @@ -6,9 +7,18 @@ internal sealed class JudgeHandler : IFrameHandler { public IReadOnlyList Handle(FrameDispatchContext ctx) { - // Only a scripted-bot Judge is forwarded. A real player's Judge has no routing rule (drops). + // Scripted-bot Judge (test stub): forward verbatim (carries the {spin} shape already). if (ctx.IsScriptedBot(ctx.From)) return new[] { new DispatchRoute(ctx.Other, ctx.Env, false) }; + + // PvP: active player's Judge{battleCode} -> opponent {spin} (RNG catch-up; spin=0 for the + // deterministic-turn slice). Receiving Judge starts the opponent's own turn. + if (ctx.Type == BattleType.Pvp && ctx.BothAfterReady()) + { + var frame = ctx.Env with { Body = new JudgeBody(Spin: 0) }; + 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 ed3be3a..68c8c2f 100644 --- a/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs +++ b/SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs @@ -399,6 +399,22 @@ public class BattleSessionDispatchTests Assert.That(body.Spin, Is.EqualTo(0)); } + [Test] + public void Pvp_Judge_from_A_emits_spin0_to_B() + { + var (s, a, b) = NewPvpSession(); + DriveToAfterReady(s, a); + DriveToAfterReady(s, b); + + var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Judge)); + + Assert.That(routes.Count, Is.EqualTo(1)); + Assert.That(routes[0].Target, Is.SameAs(b)); + Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.Judge)); + var body = (SVSim.BattleNode.Protocol.Bodies.JudgeBody)routes[0].Frame.Body; + Assert.That(body.Spin, Is.EqualTo(0)); + } + [Test] public void Pvp_PlayActions_synthesizes_knownList_from_sender_deck() {