using SVSim.BattleNode.Lifecycle; using SVSim.BattleNode.Protocol; using SVSim.BattleNode.Sessions.Participants; // IHasHandshakePhase namespace SVSim.BattleNode.Sessions.Dispatch.Handlers; internal sealed class SwapHandler : IFrameHandler { public IReadOnlyList Handle(FrameDispatchContext ctx) { if (ctx.SenderPhase != BattleSessionPhase.AwaitingSwap) return Array.Empty(); var routes = new List(); var hand = ServerBattleFrames.ComputeHandAfterSwap(BattleFrames.ExtractIdxList(ctx.Env)); // SwapResponse is always immediate — completes the sender's own mulligan UI. routes.Add(new DispatchRoute(ctx.From, ServerBattleFrames.BuildSwapResponse(hand), false)); ctx.State.PostSwapHands[ctx.From] = hand; ctx.SenderPhase = BattleSessionPhase.AfterReady; // Release Ready to every swapper once all handshake-driving participants have swapped. // IHasHandshakePhase membership IS the "participates in mulligan" set. var swappers = new[] { ctx.A, ctx.B }.Where(p => p is IHasHandshakePhase).ToList(); if (swappers.All(ctx.State.PostSwapHands.ContainsKey)) { foreach (var p in swappers) { var opponent = ReferenceEquals(p, ctx.A) ? ctx.B : ctx.A; var ready = opponent is IHasHandshakePhase && ctx.State.PostSwapHands.TryGetValue(opponent, out var oppoHand) ? ServerBattleFrames.BuildReady(ctx.State.PostSwapHands[p], oppoHand) : ServerBattleFrames.BuildReady(ctx.State.PostSwapHands[p]); routes.Add(new DispatchRoute(p, ready, false)); } } return routes; } }