diff --git a/SVSim.BattleNode/Sessions/BattleSession.cs b/SVSim.BattleNode/Sessions/BattleSession.cs index 8af0e11..bbdf5e4 100644 --- a/SVSim.BattleNode/Sessions/BattleSession.cs +++ b/SVSim.BattleNode/Sessions/BattleSession.cs @@ -38,6 +38,7 @@ public sealed class BattleSession { [NetworkBattleUri.InitNetwork] = new InitNetworkHandler(), [NetworkBattleUri.InitBattle] = new InitBattleHandler(), + [NetworkBattleUri.Loaded] = new LoadedHandler(), }; private FrameDispatchContext BuildContext(IBattleParticipant from, MsgEnvelope env) => diff --git a/SVSim.BattleNode/Sessions/Dispatch/Handlers/LoadedHandler.cs b/SVSim.BattleNode/Sessions/Dispatch/Handlers/LoadedHandler.cs new file mode 100644 index 0000000..9d2f326 --- /dev/null +++ b/SVSim.BattleNode/Sessions/Dispatch/Handlers/LoadedHandler.cs @@ -0,0 +1,34 @@ +using SVSim.BattleNode.Lifecycle; +using SVSim.BattleNode.Protocol; + +namespace SVSim.BattleNode.Sessions.Dispatch.Handlers; + +internal sealed class LoadedHandler : IFrameHandler +{ + public IReadOnlyList Handle(FrameDispatchContext ctx) + { + // case 3: Bot — silent (client populates opponent state from AIBattleStart HTTP data). + if (ctx.Type == BattleType.Bot && ctx.SenderPhase == BattleSessionPhase.AwaitingLoaded) + { + ctx.SenderPhase = BattleSessionPhase.AwaitingSwap; + return Array.Empty(); + } + + // case 6: general — BattleStart (per-perspective) + Deal to the sender. + if (ctx.SenderPhase == BattleSessionPhase.AwaitingLoaded) + { + // A goes first deterministically (turnState 0); B goes second (turnState 1). + var turnState = ReferenceEquals(ctx.From, ctx.A) ? 0 : 1; + var r = new List + { + new(ctx.From, ScriptedLifecycle.BuildBattleStart( + ctx.From.Context, ctx.Other.Context, ctx.From.ViewerId, turnState), false), + new(ctx.From, ScriptedLifecycle.BuildDeal(), false), + }; + ctx.SenderPhase = BattleSessionPhase.AwaitingSwap; + return r; + } + + return Array.Empty(); + } +}