Two issues caught during v1 smoke at the mulligan / first-turn boundary:
1) BuildSwapResponse ignored the player's idxList and echoed the same
3-card hand back. The client diffs the new self[] against the Deal
to compute "drawn cards" — empty diff against the same hand throws
"Card swap failed: AbandonCards[X]/DrawCards[]". Replace swapped
idxs with fresh deck idxs (initial hand was 1/2/3, deck has 4..30
still available). Same hand must flow into Ready since the client
diffs again there. Move the hand computation into a new helper
ComputeHandAfterSwap and have ComputeResponses thread it through
both BuildSwapResponse and BuildReady.
2) The client doesn't transition to the "Opponent's turn…" display
on its own after sending TurnEnd — it waits for the server to push
an opponent TurnStart (per prod TK2 capture line 14). Without it
the UI just sits on the end-of-turn frame. Add a TurnEnd handler
that pushes a minimal TurnStart{spin} and transitions to a new
OpponentTurn phase, which IS the documented v1 stopping point.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
112 lines
4.8 KiB
C#
112 lines
4.8 KiB
C#
// SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NUnit.Framework;
|
|
using SVSim.BattleNode.Protocol;
|
|
using SVSim.BattleNode.Sessions;
|
|
|
|
namespace SVSim.UnitTests.BattleNode.Sessions;
|
|
|
|
[TestFixture]
|
|
public class BattleSessionDispatchTests
|
|
{
|
|
private static BattleSession NewSession()
|
|
{
|
|
// ws is unused by ComputeResponses; pass null! and rely on the test never invoking the pump.
|
|
return new BattleSession(ws: null!, battleId: "bid-1", viewerId: 1, log: NullLogger<BattleSession>.Instance);
|
|
}
|
|
|
|
private static MsgEnvelope NewEnvelope(NetworkBattleUri uri) =>
|
|
new(uri, ViewerId: 1, Uuid: "u", Bid: null, Try: 0, Cat: EmitCategory.Battle,
|
|
PubSeq: null, PlaySeq: null, Body: new Dictionary<string, object?>());
|
|
|
|
[Test]
|
|
public void InitNetwork_PushesAckOnly_TransitionsToAwaitingInitBattle()
|
|
{
|
|
var s = NewSession();
|
|
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
Assert.That(responses.Select(r => r.Envelope.Uri),
|
|
Is.EqualTo(new[] { NetworkBattleUri.InitNetwork }));
|
|
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.AwaitingInitBattle));
|
|
}
|
|
|
|
[Test]
|
|
public void InitBattle_PushesMatched_TransitionsToAwaitingLoaded()
|
|
{
|
|
var s = NewSession();
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitBattle));
|
|
Assert.That(responses.Single().Envelope.Uri, Is.EqualTo(NetworkBattleUri.Matched));
|
|
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.AwaitingLoaded));
|
|
}
|
|
|
|
[Test]
|
|
public void Loaded_PushesBattleStartThenDeal_TransitionsToAwaitingSwap()
|
|
{
|
|
var s = NewSession();
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitBattle));
|
|
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.Loaded));
|
|
Assert.That(responses.Select(r => r.Envelope.Uri),
|
|
Is.EqualTo(new[] { NetworkBattleUri.BattleStart, NetworkBattleUri.Deal }));
|
|
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.AwaitingSwap));
|
|
}
|
|
|
|
[Test]
|
|
public void Swap_PushesSwapResponseThenReady_TransitionsToAfterReady()
|
|
{
|
|
var s = NewSession();
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitBattle));
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.Loaded));
|
|
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.Swap));
|
|
Assert.That(responses.Select(r => r.Envelope.Uri),
|
|
Is.EqualTo(new[] { NetworkBattleUri.Swap, NetworkBattleUri.Ready }));
|
|
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.AfterReady));
|
|
}
|
|
|
|
[Test]
|
|
public void TurnEnd_AfterReady_PushesOpponentTurnStart_TransitionsToOpponentTurn()
|
|
{
|
|
var s = NewSession();
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitNetwork));
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.InitBattle));
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.Loaded));
|
|
s.ComputeResponses(NewEnvelope(NetworkBattleUri.Swap));
|
|
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.TurnEnd));
|
|
Assert.That(responses.Single().Envelope.Uri, Is.EqualTo(NetworkBattleUri.TurnStart));
|
|
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.OpponentTurn));
|
|
}
|
|
|
|
[Test]
|
|
public void Retire_PushesBattleFinishNoContest_TransitionsToTerminal()
|
|
{
|
|
var s = NewSession();
|
|
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.Retire));
|
|
var (env, noStock) = responses.Single();
|
|
Assert.That(env.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
|
Assert.That(noStock, Is.True);
|
|
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal));
|
|
}
|
|
|
|
[Test]
|
|
public void Kill_PushesBattleFinishNoContest_TransitionsToTerminal()
|
|
{
|
|
var s = NewSession();
|
|
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.Kill));
|
|
var (env, noStock) = responses.Single();
|
|
Assert.That(env.Uri, Is.EqualTo(NetworkBattleUri.BattleFinish));
|
|
Assert.That(noStock, Is.True);
|
|
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.Terminal));
|
|
}
|
|
|
|
[Test]
|
|
public void Swap_ArrivingBeforeLoaded_ProducesNoResponseAndDoesNotAdvancePhase()
|
|
{
|
|
var s = NewSession();
|
|
// Skip Loaded — fire Swap straight out of AwaitingInitNetwork.
|
|
var responses = s.ComputeResponses(NewEnvelope(NetworkBattleUri.Swap));
|
|
Assert.That(responses, Is.Empty);
|
|
Assert.That(s.Phase, Is.EqualTo(BattleSessionPhase.AwaitingInitNetwork));
|
|
}
|
|
}
|