using SVSim.BattleNode.Protocol; namespace SVSim.BattleNode.Sessions.Dispatch.Handlers; 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) return new[] { new DispatchRoute(ctx.From, BattleFrames.BuildJudgeBroadcast(), false) }; // case 8: general AfterReady arm — matches (and consumes) for any non-Bot type once the // sender is AfterReady, even if it yields no routes (legacy `break;`). if (ctx.SenderPhase == BattleSessionPhase.AfterReady) { if (ctx.Type == BattleType.Pvp && ctx.BothAfterReady()) { var te = BattleFrames.BuildTurnEndBroadcast(); var jg = BattleFrames.BuildJudgeBroadcast(); return new[] { new DispatchRoute(ctx.From, te, false), new DispatchRoute(ctx.Other, te, false), new DispatchRoute(ctx.From, jg, false), new DispatchRoute(ctx.Other, jg, false), }; } if (ctx.Type == BattleType.Scripted) return new[] { new DispatchRoute(ctx.Other, ctx.Env, false) }; return Array.Empty(); // Pvp-not-both-ready → drop (Bot already returned above) } // case 11: scripted-bot TurnEnd whose sender has no handshake phase (test stub) → forward. if (ctx.IsScriptedBot(ctx.From)) return new[] { new DispatchRoute(ctx.Other, ctx.Env, false) }; return Array.Empty(); } }