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:
gamer147
2026-06-01 21:51:27 -04:00
parent 72dc1887d9
commit db054205b3
2 changed files with 151 additions and 6 deletions

View File

@@ -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());