using SVSim.BattleNode.Bridge; using SVSim.BattleNode.Protocol; namespace SVSim.BattleNode.Sessions; /// /// One side of a battle. Two of these are held by a BattleSession; the session /// brokers between them. Concrete impls: /// /// RealParticipant — WS-backed (used for BattleType.Pvp). /// NoOpBotParticipant — silent; for BattleType.Bot (AI-passive). /// /// public interface IBattleParticipant : IAsyncDisposable { /// Real viewer id, or a synthetic stable id for bots /// (). long ViewerId { get; } /// Per-battle MatchContext snapshot, used for building Matched/BattleStart /// selfInfo when this participant is "self" in the perspective. MatchContext Context { get; } /// Session calls this to deliver a frame from the OTHER participant /// (or a server-synthesized broadcast). Real impl: encode + WS-send. /// NoOp: swallow. /// True for control frames (BattleFinish, JudgeResult, ack); /// bypasses playSeq assignment + archive. Task PushAsync(MsgEnvelope envelope, bool noStock, CancellationToken ct); /// Participant fires this when it has a frame to send TO the session /// (its own gameplay action). Real impl: fires on WS recv. NoOp: never fires. event Func? FrameEmitted; /// Drives the participant's inbound loop. For Real: the WS read loop /// (returns when the WS closes). For NoOp: completes immediately (the /// session keeps running as long as the OTHER participant's RunAsync is alive). Task RunAsync(CancellationToken ct); /// Called when the battle ends. Concrete impls clean up (close WS, etc.). Task TerminateAsync(BattleFinishReason reason); }