test(battle-node): drop scripted dispatch tests; retarget generic fixture to PvP
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,46 +12,6 @@ namespace SVSim.UnitTests.BattleNode.Sessions;
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class BattleSessionDispatchTests
|
public class BattleSessionDispatchTests
|
||||||
{
|
{
|
||||||
[Test]
|
|
||||||
public void InitNetwork_acks_to_sender_transitions_to_AwaitingInitBattle()
|
|
||||||
{
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(1));
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
|
||||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.InitNetwork));
|
|
||||||
Assert.That(routes[0].NoStock, Is.True);
|
|
||||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingInitBattle));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void InitBattle_pushes_Matched_to_sender_only()
|
|
||||||
{
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(1));
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
|
||||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.Matched));
|
|
||||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Loaded_pushes_BattleStart_then_Deal_to_sender()
|
|
||||||
{
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded));
|
|
||||||
|
|
||||||
Assert.That(routes.Select(r => r.Frame.Uri),
|
|
||||||
Is.EqualTo(new[] { NetworkBattleUri.BattleStart, NetworkBattleUri.Deal }));
|
|
||||||
Assert.That(routes.All(r => ReferenceEquals(r.Target, a)), Is.True);
|
|
||||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingSwap));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Pvp_Loaded_from_A_assigns_turnState_0()
|
public void Pvp_Loaded_from_A_assigns_turnState_0()
|
||||||
{
|
{
|
||||||
@@ -76,92 +36,6 @@ public class BattleSessionDispatchTests
|
|||||||
Assert.That(bs.TurnState, Is.EqualTo(1), "B (second arriver) goes second.");
|
Assert.That(bs.TurnState, Is.EqualTo(1), "B (second arriver) goes second.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Scripted_Loaded_from_player_assigns_turnState_0()
|
|
||||||
{
|
|
||||||
// Real player is constructed as A in scripted sessions, so it always goes first.
|
|
||||||
var (s, a, _) = NewSession();
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded));
|
|
||||||
|
|
||||||
var bs = (BattleStartBody)routes[0].Frame.Body;
|
|
||||||
Assert.That(bs.TurnState, Is.EqualTo(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
[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));
|
|
||||||
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(a.Phase, Is.EqualTo(BattleSessionPhase.AfterReady));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TurnEnd_from_real_forwards_to_other_participant()
|
|
||||||
{
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap));
|
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnEnd));
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(1));
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(b));
|
|
||||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnEnd));
|
|
||||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AfterReady));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Scripted_TurnEndFinal_forwards_envelope_and_pushes_paired_BattleFinish()
|
|
||||||
{
|
|
||||||
// Unified TurnEndFinal handling: forward the envelope to other (matches prod
|
|
||||||
// capture battle-traffic_tk2_regular.ndjson:273) + push BattleFinish per-side
|
|
||||||
// with player-perspective codes (LifeWin to winner, LifeLose to loser).
|
|
||||||
// In Scripted mode the "loser" is a ScriptedBotParticipant; the loser-side
|
|
||||||
// BattleFinish push is harmless (bot swallows non-TurnEnd URIs).
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap));
|
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.TurnEndFinal));
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(3),
|
|
||||||
"TurnEndFinal must produce: forwarded envelope + BattleFinish(LifeWin) to from + BattleFinish(LifeLose) to other.");
|
|
||||||
|
|
||||||
// Route 0: forwarded TurnEndFinal envelope to other.
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(b));
|
|
||||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnEndFinal));
|
|
||||||
|
|
||||||
// Route 1: BattleFinish(LifeWin) to from (the winner who declared the final turn).
|
|
||||||
Assert.That(routes[1].Target, Is.SameAs(a));
|
|
||||||
Assert.That(routes[1].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
|
||||||
Assert.That(routes[1].NoStock, Is.True);
|
|
||||||
var winBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[1].Frame.Body;
|
|
||||||
Assert.That(winBody.Result, Is.EqualTo(BattleResult.LifeWin),
|
|
||||||
"Winner gets LifeWin (101) — player-perspective: 'I won by life' → WIN UI.");
|
|
||||||
|
|
||||||
// Route 2: BattleFinish(LifeLose) to other (the loser).
|
|
||||||
Assert.That(routes[2].Target, Is.SameAs(b));
|
|
||||||
Assert.That(routes[2].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
|
||||||
Assert.That(routes[2].NoStock, Is.True);
|
|
||||||
var loseBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[2].Frame.Body;
|
|
||||||
Assert.That(loseBody.Result, Is.EqualTo(BattleResult.LifeLose),
|
|
||||||
"Loser gets LifeLose (102) — player-perspective: 'I lost by life' → LOSE UI.");
|
|
||||||
|
|
||||||
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal),
|
|
||||||
"Session must transition to Terminal so the RunAsync cascade doesn't synthesize a second BattleFinish.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Handshake_dispatch_reads_per_participant_Phase_not_session_Phase()
|
public void Handshake_dispatch_reads_per_participant_Phase_not_session_Phase()
|
||||||
{
|
{
|
||||||
@@ -185,102 +59,10 @@ public class BattleSessionDispatchTests
|
|||||||
Assert.That(b.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded));
|
Assert.That(b.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ScriptedBot_emitted_OpponentTurnStart_forwards_to_real()
|
|
||||||
{
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
// Bot emits a TurnStart frame (carrying ViewerId == FakeOpponentViewerId per the
|
|
||||||
// ScriptedBotParticipant impl). Session should route it to the real participant.
|
|
||||||
var botFrame = ScriptedLifecycle.BuildOpponentTurnStart();
|
|
||||||
var routes = s.ComputeFrames(b, botFrame);
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(1));
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
|
||||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnStart));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ScriptedBot_emitted_Judge_forwards_to_real()
|
|
||||||
{
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
var botFrame = ScriptedLifecycle.BuildOpponentJudge();
|
|
||||||
var routes = s.ComputeFrames(b, botFrame);
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(1));
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
|
||||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.Judge));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ScriptedBot_emitted_TurnEnd_forwards_to_real()
|
|
||||||
{
|
|
||||||
// TurnEnd from the bot is also one of the burst frames. The case is handled
|
|
||||||
// by the TurnEnd-from-scripted arm (bot ViewerId matches FakeOpponentViewerId).
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitBattle));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Loaded));
|
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap));
|
|
||||||
// Drive player TurnEnd first (transitions Phase if needed) — but TurnEnd from bot
|
|
||||||
// arrives in Phase AfterReady too. The bot's TurnEnd is routed via the dispatch
|
|
||||||
// arm that forwards any frame from the FakeOpponentViewerId participant.
|
|
||||||
|
|
||||||
var botFrame = ScriptedLifecycle.BuildOpponentTurnEnd();
|
|
||||||
var routes = s.ComputeFrames(b, botFrame);
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(1));
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
|
||||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.TurnEnd));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Retire_pushes_paired_BattleFinish_RetireLose_to_from_and_RetireWin_to_other()
|
|
||||||
{
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Retire));
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(2));
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
|
||||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
|
||||||
Assert.That(routes[0].NoStock, Is.True);
|
|
||||||
var loseBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[0].Frame.Body;
|
|
||||||
Assert.That(loseBody.Result, Is.EqualTo(BattleResult.RetireLose),
|
|
||||||
"Retirer gets RetireLose=106 — player-perspective: 'I lost by retire'.");
|
|
||||||
|
|
||||||
Assert.That(routes[1].Target, Is.SameAs(b));
|
|
||||||
Assert.That(routes[1].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
|
||||||
Assert.That(routes[1].NoStock, Is.True);
|
|
||||||
var winBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[1].Frame.Body;
|
|
||||||
Assert.That(winBody.Result, Is.EqualTo(BattleResult.RetireWin),
|
|
||||||
"Survivor gets RetireWin=105. In Scripted mode the bot swallows it; in PvP the opponent renders 'opponent retired'.");
|
|
||||||
|
|
||||||
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Kill_pushes_paired_BattleFinish_RetireLose_to_from_and_RetireWin_to_other()
|
|
||||||
{
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Kill));
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(2));
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
|
||||||
Assert.That(routes[0].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
|
||||||
var loseBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[0].Frame.Body;
|
|
||||||
Assert.That(loseBody.Result, Is.EqualTo(BattleResult.RetireLose));
|
|
||||||
|
|
||||||
Assert.That(routes[1].Target, Is.SameAs(b));
|
|
||||||
Assert.That(routes[1].Frame.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
|
||||||
var winBody = (SVSim.BattleNode.Protocol.Bodies.BattleFinishBody)routes[1].Frame.Body;
|
|
||||||
Assert.That(winBody.Result, Is.EqualTo(BattleResult.RetireWin));
|
|
||||||
|
|
||||||
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void OutOfOrder_dispatch_returns_empty_and_does_not_advance_phase()
|
public void OutOfOrder_dispatch_returns_empty_and_does_not_advance_phase()
|
||||||
{
|
{
|
||||||
var (s, a, _) = NewSession();
|
var (s, a, _) = NewPvpSession();
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap));
|
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Swap));
|
||||||
|
|
||||||
Assert.That(routes, Is.Empty);
|
Assert.That(routes, Is.Empty);
|
||||||
@@ -627,23 +409,6 @@ public class BattleSessionDispatchTests
|
|||||||
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal));
|
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Scripted_Retire_pushes_RetireLose_to_player_and_RetireWin_to_bot()
|
|
||||||
{
|
|
||||||
// Unified with PvP — paired BattleFinish per-side. In Scripted mode the "loser"
|
|
||||||
// is a ScriptedBotParticipant; its loser-side push is swallowed (it only reacts
|
|
||||||
// to TurnEnd). The wire-correct codes are still emitted in case future work
|
|
||||||
// wants to inspect them or run a real two-real-participant session.
|
|
||||||
var (s, a, b) = NewSession();
|
|
||||||
var routes = s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.Retire));
|
|
||||||
|
|
||||||
Assert.That(routes.Count, Is.EqualTo(2));
|
|
||||||
Assert.That(routes[0].Target, Is.SameAs(a));
|
|
||||||
Assert.That(((BattleFinishBody)routes[0].Frame.Body).Result, Is.EqualTo(BattleResult.RetireLose));
|
|
||||||
Assert.That(routes[1].Target, Is.SameAs(b));
|
|
||||||
Assert.That(((BattleFinishBody)routes[1].Frame.Body).Result, Is.EqualTo(BattleResult.RetireWin));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (BattleSession, FakeRealParticipant, FakeParticipant) NewBotSession()
|
private static (BattleSession, FakeRealParticipant, FakeParticipant) NewBotSession()
|
||||||
{
|
{
|
||||||
var a = new FakeRealParticipant(viewerId: 1001, PlayerACtx());
|
var a = new FakeRealParticipant(viewerId: 1001, PlayerACtx());
|
||||||
@@ -822,14 +587,6 @@ public class BattleSessionDispatchTests
|
|||||||
EmblemId: "701441022", DegreeId: "300004", FieldId: 44, IsOfficial: 0,
|
EmblemId: "701441022", DegreeId: "300004", FieldId: 44, IsOfficial: 0,
|
||||||
BattleType: 11);
|
BattleType: 11);
|
||||||
|
|
||||||
private static (BattleSession, FakeRealParticipant, FakeParticipant) NewSession()
|
|
||||||
{
|
|
||||||
var a = new FakeRealParticipant(viewerId: 1, FixtureCtx());
|
|
||||||
var b = new FakeParticipant(viewerId: ScriptedLifecycle.FakeOpponentViewerId, ScriptedBotContext());
|
|
||||||
var s = new BattleSession("bid-1", BattleType.Scripted, a, b, NullLogger<BattleSession>.Instance);
|
|
||||||
return (s, a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MatchContext FixtureCtx() => new(
|
private static MatchContext FixtureCtx() => new(
|
||||||
SelfDeckCardIds: Enumerable.Range(1, 30).Select(_ => 100_011_010L).ToList(),
|
SelfDeckCardIds: Enumerable.Range(1, 30).Select(_ => 100_011_010L).ToList(),
|
||||||
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
|
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
|
||||||
@@ -837,13 +594,6 @@ public class BattleSessionDispatchTests
|
|||||||
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
|
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
|
||||||
BattleType: 11);
|
BattleType: 11);
|
||||||
|
|
||||||
private static MatchContext ScriptedBotContext() => new(
|
|
||||||
SelfDeckCardIds: Array.Empty<long>(),
|
|
||||||
ClassId: "0", CharaId: "0", CardMasterName: "card_master_node_10015",
|
|
||||||
CountryCode: "JPN", UserName: "Opponent", SleeveId: "704141010",
|
|
||||||
EmblemId: "400001100", DegreeId: "120027", FieldId: 5, IsOfficial: 0,
|
|
||||||
BattleType: 0);
|
|
||||||
|
|
||||||
private static MsgEnvelope NewEnvelope(NetworkBattleUri uri) =>
|
private static MsgEnvelope NewEnvelope(NetworkBattleUri uri) =>
|
||||||
new(uri, ViewerId: 1, Uuid: "u", Bid: null, Try: 0,
|
new(uri, ViewerId: 1, Uuid: "u", Bid: null, Try: 0,
|
||||||
Cat: EmitCategory.Battle, PubSeq: null, PlaySeq: null,
|
Cat: EmitCategory.Battle, PubSeq: null, PlaySeq: null,
|
||||||
|
|||||||
Reference in New Issue
Block a user