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;
|
||||
|
||||
// Mulligan barrier: each side's post-mulligan hand, captured on its Swap. Ready is
|
||||
// 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();
|
||||
private readonly BattleSessionState _state = new();
|
||||
|
||||
public string BattleId { get; }
|
||||
public BattleType Type { get; }
|
||||
public IBattleParticipant A { 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,
|
||||
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)",
|
||||
BattleId);
|
||||
}
|
||||
Phase = BattleSessionPhase.Terminal;
|
||||
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||
}
|
||||
|
||||
cts.Cancel(); // unblock the survivor's RunAsync read loop
|
||||
@@ -235,7 +229,7 @@ public sealed class BattleSession
|
||||
var hand = ScriptedLifecycle.ComputeHandAfterSwap(ExtractIdxList(env));
|
||||
// SwapResponse is always immediate — it completes the sender's own mulligan UI.
|
||||
result.Add(new DispatchRoute(from, ScriptedLifecycle.BuildSwapResponse(hand), false));
|
||||
_postSwapHands[from] = hand;
|
||||
_state.PostSwapHands[from] = hand;
|
||||
phaseFrom!.Phase = BattleSessionPhase.AfterReady;
|
||||
|
||||
// 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
|
||||
// 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();
|
||||
if (swappers.All(_postSwapHands.ContainsKey))
|
||||
if (swappers.All(_state.PostSwapHands.ContainsKey))
|
||||
{
|
||||
foreach (var p in swappers)
|
||||
{
|
||||
var opponent = ReferenceEquals(p, A) ? B : A;
|
||||
var ready = opponent is IHasHandshakePhase && _postSwapHands.TryGetValue(opponent, out var oppoHand)
|
||||
? ScriptedLifecycle.BuildReady(_postSwapHands[p], oppoHand) // both hands known
|
||||
: ScriptedLifecycle.BuildReady(_postSwapHands[p]); // non-interactive opponent
|
||||
var ready = opponent is IHasHandshakePhase && _state.PostSwapHands.TryGetValue(opponent, out var oppoHand)
|
||||
? ScriptedLifecycle.BuildReady(_state.PostSwapHands[p], oppoHand) // both hands known
|
||||
: ScriptedLifecycle.BuildReady(_state.PostSwapHands[p]); // non-interactive opponent
|
||||
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(from, BuildBattleFinish(BattleResult.LifeWin), true));
|
||||
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.LifeLose), true));
|
||||
Phase = BattleSessionPhase.Terminal;
|
||||
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||
break;
|
||||
|
||||
// Retire / Kill: sender concedes (Retire) or the client requested an immediate
|
||||
@@ -300,7 +294,7 @@ public sealed class BattleSession
|
||||
case NetworkBattleUri.Kill:
|
||||
result.Add(new DispatchRoute(from, BuildBattleFinish(BattleResult.RetireLose), true));
|
||||
result.Add(new DispatchRoute(other, BuildBattleFinish(BattleResult.RetireWin), true));
|
||||
Phase = BattleSessionPhase.Terminal;
|
||||
_state.SessionPhase = BattleSessionPhase.Terminal;
|
||||
break;
|
||||
|
||||
// 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