feat(battle-node): PvP TurnEnd broadcast + flipped Retire/Kill result
Pvp TurnEnd/TurnEndFinal broadcasts TurnEnd+Judge to BOTH so each client's JudgeOperation advances. Pvp Retire/Kill pushes BattleFinish with flipped result (sender=Lose, other=Win). Scripted Retire keeps Phase 1 behaviour (sender-only Win via BuildBattleFinishNoContest).
This commit is contained in:
@@ -340,6 +340,97 @@ public class BattleSessionDispatchTests
|
||||
"PvP gameplay forwarding must wait until BOTH sides reach AfterReady.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_TurnEnd_from_A_in_BothAfterReady_broadcasts_TurnEnd_plus_Judge_to_both()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
DriveToAfterReady(s, b);
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnEnd));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(4));
|
||||
Assert.That(routes.Select(r => (r.Target, r.Frame.Uri)), Is.EquivalentTo(new[]
|
||||
{
|
||||
((IBattleParticipant)a, NetworkBattleUri.TurnEnd),
|
||||
((IBattleParticipant)b, NetworkBattleUri.TurnEnd),
|
||||
((IBattleParticipant)a, NetworkBattleUri.Judge),
|
||||
((IBattleParticipant)b, NetworkBattleUri.Judge),
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_TurnEndFinal_from_A_in_BothAfterReady_broadcasts_TurnEnd_plus_Judge_to_both()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
DriveToAfterReady(s, b);
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnEndFinal));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(4));
|
||||
Assert.That(routes.Select(r => r.Frame.Uri).Distinct(),
|
||||
Is.EquivalentTo(new[] { NetworkBattleUri.TurnEnd, NetworkBattleUri.Judge }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_TurnEnd_when_B_still_AwaitingSwap_drops()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
// B not at AfterReady.
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnEnd));
|
||||
|
||||
Assert.That(routes, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_Retire_from_A_pushes_BattleFinish_Lose_to_A_and_Win_to_B()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
DriveToAfterReady(s, b);
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Retire));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(2));
|
||||
var aRoute = routes.Single(r => ReferenceEquals(r.Target, a));
|
||||
var bRoute = routes.Single(r => ReferenceEquals(r.Target, b));
|
||||
Assert.That(aRoute.Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
||||
Assert.That(((BattleFinishBody)aRoute.Frame.Body).Result, Is.EqualTo(BattleResult.Lose));
|
||||
Assert.That(bRoute.Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
||||
Assert.That(((BattleFinishBody)bRoute.Frame.Body).Result, Is.EqualTo(BattleResult.Win));
|
||||
Assert.That(aRoute.NoStock, Is.True);
|
||||
Assert.That(bRoute.NoStock, Is.True);
|
||||
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_Kill_from_A_same_as_Retire()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
DriveToAfterReady(s, b);
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Kill));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(2));
|
||||
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Scripted_Retire_still_pushes_BattleFinish_Win_to_sender_only()
|
||||
{
|
||||
// Regression guard — Phase 1 behavior preserved for Scripted.
|
||||
var (s, a, _) = NewSession();
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Retire));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(1));
|
||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
||||
Assert.That(((BattleFinishBody)routes[0].Frame.Body).Result, Is.EqualTo(BattleResult.Win));
|
||||
}
|
||||
|
||||
private static (BattleSession, FakeRealParticipant, FakeRealParticipant) NewPvpSession()
|
||||
{
|
||||
var a = new FakeRealParticipant(viewerId: 1001, PlayerACtx());
|
||||
|
||||
Reference in New Issue
Block a user