feat(battle-node): PvP gameplay-frame forwarding arms
TurnStart / PlayActions / Echo / TurnEndActions / JudgeResult from a real participant in Pvp mode forward to the other participant once BothAfterReady. Scripted's bot-burst case arms (gated on FakeOpponentViewerId) precede the PvP forwarder so they're unaffected. The bot-emission TurnStart/TurnEnd/Judge guard was tightened from `ReferenceEquals(from, A or B)` (always true) to call IsRealForwardableFromScripted directly in the `when` clause. The prior shape used `goto default` to drop non-bot senders, which would have short-circuited the new PvP forwarder for TurnStart in PvP mode.
This commit is contained in:
@@ -260,6 +260,86 @@ public class BattleSessionDispatchTests
|
||||
"Swap from A doesn't advance B's phase.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_TurnStart_from_A_in_BothAfterReady_forwards_to_B()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
DriveToAfterReady(s, b);
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnStart));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(1));
|
||||
Assert.That(routes[0].Target, Is.SameAs(b));
|
||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnStart));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_PlayActions_from_A_in_BothAfterReady_forwards_to_B()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
DriveToAfterReady(s, b);
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.PlayActions));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(1));
|
||||
Assert.That(routes[0].Target, Is.SameAs(b));
|
||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.PlayActions));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_Echo_from_A_in_BothAfterReady_forwards_to_B()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
DriveToAfterReady(s, b);
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Echo));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(1));
|
||||
Assert.That(routes[0].Target, Is.SameAs(b));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_TurnEndActions_from_A_in_BothAfterReady_forwards_to_B()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
DriveToAfterReady(s, b);
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnEndActions));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(1));
|
||||
Assert.That(routes[0].Target, Is.SameAs(b));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_JudgeResult_from_A_in_BothAfterReady_forwards_to_B()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
DriveToAfterReady(s, b);
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.JudgeResult));
|
||||
|
||||
Assert.That(routes.Count, Is.EqualTo(1));
|
||||
Assert.That(routes[0].Target, Is.SameAs(b));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Pvp_PlayActions_when_B_still_AwaitingSwap_drops()
|
||||
{
|
||||
var (s, a, b) = NewPvpSession();
|
||||
DriveToAfterReady(s, a);
|
||||
// B is still AwaitingInitNetwork — BothAfterReady is false.
|
||||
|
||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.PlayActions));
|
||||
|
||||
Assert.That(routes, Is.Empty,
|
||||
"PvP gameplay forwarding must wait until BOTH sides reach AfterReady.");
|
||||
}
|
||||
|
||||
private static (BattleSession, FakeRealParticipant, FakeRealParticipant) NewPvpSession()
|
||||
{
|
||||
var a = new FakeRealParticipant(viewerId: 1001, PlayerACtx());
|
||||
@@ -268,6 +348,15 @@ public class BattleSessionDispatchTests
|
||||
return (s, a, b);
|
||||
}
|
||||
|
||||
private static void DriveToAfterReady(BattleSession s, FakeRealParticipant p)
|
||||
{
|
||||
s.ComputeFrames(p, NewEnvelope(NetworkBattleUri.InitNetwork));
|
||||
s.ComputeFrames(p, NewEnvelope(NetworkBattleUri.InitBattle));
|
||||
s.ComputeFrames(p, NewEnvelope(NetworkBattleUri.Loaded));
|
||||
s.ComputeFrames(p, NewEnvelope(NetworkBattleUri.Swap));
|
||||
// p.Phase should now be AfterReady.
|
||||
}
|
||||
|
||||
private static MatchContext PlayerACtx() => new(
|
||||
SelfDeckCardIds: Enumerable.Range(1, 30).Select(_ => 100_011_010L).ToList(),
|
||||
ClassId: "3", CharaId: "3", CardMasterName: "card_master_node_10015",
|
||||
|
||||
Reference in New Issue
Block a user