fix(battle-node): type-agnostic mulligan barrier withholds Ready until both swap
Ready was sent per-side immediately carrying the placeholder opponent hand, so one client cleared mulligan before the other. The barrier now releases Ready to every IHasHandshakePhase participant only once all have swapped, each carrying the opponent's real post-mulligan hand. No Type check — NoOp (Bot/AINetwork) isn't a phase impl, so that mode still releases immediately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,7 @@ public class BattleSessionDispatchTests
|
||||
[Test]
|
||||
public void Swap_pushes_SwapResponse_then_Ready_to_sender()
|
||||
{
|
||||
// Opponent stub is not IHasHandshakePhase → not a barrier swapper → Ready releases immediately.
|
||||
var (s, a, b) = NewSession();
|
||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
||||
@@ -341,7 +342,7 @@ public class BattleSessionDispatchTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_Swap_from_A_pushes_SwapResponse_plus_Ready_to_A_only()
|
||||
public void Pvp_Swap_from_A_alone_pushes_SwapResponse_only_Ready_withheld()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
||||
@@ -349,12 +350,37 @@ public class BattleSessionDispatchTests
|
||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded));
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap));
|
||||
|
||||
Assert.That(routes.Select(r => r.Frame.Uri),
|
||||
Is.EqualTo(new[] { NetworkBattleUri.Swap, NetworkBattleUri.Ready }));
|
||||
Assert.That(routes.All(r => ReferenceEquals(r.Target, a)), Is.True);
|
||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AfterReady));
|
||||
Assert.That(b.Phase, Is.EqualTo(BattleSessionPhase.AwaitingInitNetwork),
|
||||
"Swap from A doesn't advance B's phase.");
|
||||
Assert.That(routes.Select(r => r.Frame.Uri), Is.EqualTo(new[] { NetworkBattleUri.Swap }),
|
||||
"Ready is withheld until BOTH sides have mulliganed.");
|
||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AfterReady),
|
||||
"Phase advances on Swap even though Ready is withheld.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_Swap_from_both_releases_Ready_to_both_with_opponent_hands()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
foreach (var p in new[] { a, b })
|
||||
{
|
||||
s.ComputeFrames(p, NewEnvelope(NetworkBattleUri.InitNetwork));
|
||||
s.ComputeFrames(p, NewEnvelope(NetworkBattleUri.InitBattle));
|
||||
s.ComputeFrames(p, NewEnvelope(NetworkBattleUri.Loaded));
|
||||
}
|
||||
|
||||
var aRoutes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap)); // first swapper
|
||||
Assert.That(aRoutes.Select(r => r.Frame.Uri), Is.EqualTo(new[] { NetworkBattleUri.Swap }));
|
||||
|
||||
var bRoutes = s.ComputeFrames(b, NewEnvelope(NetworkBattleUri.Swap)); // second swapper releases both
|
||||
// Expect: B's own SwapResponse, then Ready to B, then Ready to A.
|
||||
Assert.That(bRoutes.Count, Is.EqualTo(3));
|
||||
Assert.That(bRoutes[0].Target, Is.SameAs(b));
|
||||
Assert.That(bRoutes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.Swap));
|
||||
|
||||
var readyToB = bRoutes.Single(r => ReferenceEquals(r.Target, b) && r.Frame.Uri == NetworkBattleUri.Ready);
|
||||
var readyToA = bRoutes.Single(r => ReferenceEquals(r.Target, a) && r.Frame.Uri == NetworkBattleUri.Ready);
|
||||
// Empty mulligans → each hand is the dealt [1,2,3]; oppo mirrors the other side's hand.
|
||||
Assert.That(((ReadyBody)readyToB.Frame.Body).Oppo.Select(p => p.Idx), Is.EqualTo(new[] { 1, 2, 3 }));
|
||||
Assert.That(((ReadyBody)readyToA.Frame.Body).Oppo.Select(p => p.Idx), Is.EqualTo(new[] { 1, 2, 3 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -609,6 +635,7 @@ public class BattleSessionDispatchTests
|
||||
[Test]
|
||||
public void Bot_Swap_per_sender_SwapResponse_plus_Ready()
|
||||
{
|
||||
// Opponent stub is not IHasHandshakePhase → not a barrier swapper → Ready releases immediately.
|
||||
var (s, a, _) = NewBotSession();
|
||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
||||
|
||||
Reference in New Issue
Block a user