refactor(battle-node): move session phase + post-swap hands into BattleSessionState
This commit is contained in:
@@ -22,19 +22,13 @@ public sealed class BattleSession
|
|||||||
{
|
{
|
||||||
private readonly ILogger<BattleSession> _log;
|
private readonly ILogger<BattleSession> _log;
|
||||||
|
|
||||||
// Mulligan barrier: each side's post-mulligan hand, captured on its Swap. Ready is
|
private readonly BattleSessionState _state = new();
|
||||||
// withheld until every IHasHandshakePhase participant has swapped (prod withholds it
|
|
||||||
// ~3-6 s — see data_dumps/captures/battle-traffic_tk2_*.ndjson). Keyed on the
|
|
||||||
// IBattleParticipant so the per-side hand is retrievable when releasing both Readys.
|
|
||||||
// NOTE: mutated from ComputeFrames, which is not lock-guarded — same pre-existing
|
|
||||||
// single-threaded-dispatch assumption as the Phase mutations (see plan § Out of scope).
|
|
||||||
private readonly Dictionary<IBattleParticipant, long[]> _postSwapHands = new();
|
|
||||||
|
|
||||||
public string BattleId { get; }
|
public string BattleId { get; }
|
||||||
public BattleType Type { get; }
|
public BattleType Type { get; }
|
||||||
public IBattleParticipant A { get; }
|
public IBattleParticipant A { get; }
|
||||||
public IBattleParticipant B { get; }
|
public IBattleParticipant B { get; }
|
||||||
public BattleSessionPhase Phase { get; private set; } = BattleSessionPhase.AwaitingInitNetwork;
|
public BattleSessionPhase Phase => _state.SessionPhase;
|
||||||
|
|
||||||
public BattleSession(string battleId, BattleType type, IBattleParticipant a, IBattleParticipant b,
|
public BattleSession(string battleId, BattleType type, IBattleParticipant a, IBattleParticipant b,
|
||||||
ILogger<BattleSession> log)
|
ILogger<BattleSession> log)
|
||||||
@@ -82,7 +76,7 @@ public sealed class BattleSession
|
|||||||
"BattleSession {Bid}: failed to push BattleFinish to survivor (their WS may also be closed)",
|
"BattleSession {Bid}: failed to push BattleFinish to survivor (their WS may also be closed)",
|
||||||
BattleId);
|
BattleId);
|
||||||
}
|
}
|
||||||
Phase = BattleSessionPhase.Terminal;
|
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||||
}
|
}
|
||||||
|
|
||||||
cts.Cancel(); // unblock the survivor's RunAsync read loop
|
cts.Cancel(); // unblock the survivor's RunAsync read loop
|
||||||
@@ -235,7 +229,7 @@ public sealed class BattleSession
|
|||||||
var hand = ScriptedLifecycle.ComputeHandAfterSwap(ExtractIdxList(env));
|
var hand = ScriptedLifecycle.ComputeHandAfterSwap(ExtractIdxList(env));
|
||||||
// SwapResponse is always immediate — it completes the sender's own mulligan UI.
|
// SwapResponse is always immediate — it completes the sender's own mulligan UI.
|
||||||
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildSwapResponse(hand), false));
|
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildSwapResponse(hand), false));
|
||||||
_postSwapHands[from] = hand;
|
_state.PostSwapHands[from] = hand;
|
||||||
phaseFrom!.Phase = BattleSessionPhase.AfterReady;
|
phaseFrom!.Phase = BattleSessionPhase.AfterReady;
|
||||||
|
|
||||||
// Release Ready to every swapper once all handshake-driving participants have
|
// Release Ready to every swapper once all handshake-driving participants have
|
||||||
@@ -244,14 +238,14 @@ public sealed class BattleSession
|
|||||||
// Scripted → {player, bot} (bot now emits Swap) → waits for both
|
// Scripted → {player, bot} (bot now emits Swap) → waits for both
|
||||||
// Bot/AINet → {real} only (NoOp isn't a phase impl)→ releases on the one Swap
|
// Bot/AINet → {real} only (NoOp isn't a phase impl)→ releases on the one Swap
|
||||||
var swappers = new[] { A, B }.Where(p => p is IHasHandshakePhase).ToList();
|
var swappers = new[] { A, B }.Where(p => p is IHasHandshakePhase).ToList();
|
||||||
if (swappers.All(_postSwapHands.ContainsKey))
|
if (swappers.All(_state.PostSwapHands.ContainsKey))
|
||||||
{
|
{
|
||||||
foreach (var p in swappers)
|
foreach (var p in swappers)
|
||||||
{
|
{
|
||||||
var opponent = ReferenceEquals(p, A) ? B : A;
|
var opponent = ReferenceEquals(p, A) ? B : A;
|
||||||
var ready = opponent is IHasHandshakePhase && _postSwapHands.TryGetValue(opponent, out var oppoHand)
|
var ready = opponent is IHasHandshakePhase && _state.PostSwapHands.TryGetValue(opponent, out var oppoHand)
|
||||||
? ScriptedLifecycle.BuildReady(_postSwapHands[p], oppoHand) // both hands known
|
? ScriptedLifecycle.BuildReady(_state.PostSwapHands[p], oppoHand) // both hands known
|
||||||
: ScriptedLifecycle.BuildReady(_postSwapHands[p]); // non-interactive opponent
|
: ScriptedLifecycle.BuildReady(_state.PostSwapHands[p]); // non-interactive opponent
|
||||||
result.Add(new DispatchRoute(p, ready, false));
|
result.Add(new DispatchRoute(p, ready, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,7 +284,7 @@ public sealed class BattleSession
|
|||||||
result.Add(new DispatchRoute(other, env, false));
|
result.Add(new DispatchRoute(other, env, false));
|
||||||
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.LifeWin), true));
|
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.LifeWin), true));
|
||||||
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.LifeLose), true));
|
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.LifeLose), true));
|
||||||
Phase = BattleSessionPhase.Terminal;
|
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Retire / Kill: sender concedes (Retire) or the client requested an immediate
|
// Retire / Kill: sender concedes (Retire) or the client requested an immediate
|
||||||
@@ -300,7 +294,7 @@ public sealed class BattleSession
|
|||||||
case NetworkBattleUri.Kill:
|
case NetworkBattleUri.Kill:
|
||||||
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.RetireLose), true));
|
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.RetireLose), true));
|
||||||
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.RetireWin), true));
|
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.RetireWin), true));
|
||||||
Phase = BattleSessionPhase.Terminal;
|
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Frames emitted by the scripted bot (TurnStart / TurnEnd / Judge) — forward
|
// Frames emitted by the scripted bot (TurnStart / TurnEnd / Judge) — forward
|
||||||
|
|||||||
11
SVSim.BattleNode/Sessions/Dispatch/BattleSessionState.cs
Normal file
11
SVSim.BattleNode/Sessions/Dispatch/BattleSessionState.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace SVSim.BattleNode.Sessions.Dispatch;
|
||||||
|
|
||||||
|
/// <summary>Mutable per-session state shared across frame handlers. Today: the session-level
|
||||||
|
/// phase (only ever advanced to <see cref="BattleSessionPhase.Terminal"/>) and the mulligan
|
||||||
|
/// barrier's post-swap hands. FUTURE (PvP equivalency, NOT this refactor): per-side idx->cardId
|
||||||
|
/// maps + reveal-gating state land here.</summary>
|
||||||
|
internal sealed class BattleSessionState
|
||||||
|
{
|
||||||
|
public BattleSessionPhase SessionPhase { get; set; } = BattleSessionPhase.AwaitingInitNetwork;
|
||||||
|
public Dictionary<IBattleParticipant, long[]> PostSwapHands { get; } = new();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user