fix(battle-node): assign turnState per side instead of hardcoding 0
Both PvP clients received turnState:0 ('both go first'). BuildBattleStart
now takes turnState; the Loaded arm assigns 0 to A, 1 to B — no Type check,
correct in Scripted (real player = A = first) and PvP (first arriver first).
Updated three existing BuildBattleStart callers in the test suite to pass
turnState:0 (the param is now required).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -53,10 +53,10 @@ public static class ScriptedLifecycle
|
|||||||
bid: battleId);
|
bid: battleId);
|
||||||
|
|
||||||
public static MsgEnvelope BuildBattleStart(
|
public static MsgEnvelope BuildBattleStart(
|
||||||
MatchContext selfCtx, MatchContext oppoCtx, long selfViewerId) =>
|
MatchContext selfCtx, MatchContext oppoCtx, long selfViewerId, int turnState) =>
|
||||||
EnvelopeForPush(NetworkBattleUri.BattleStart,
|
EnvelopeForPush(NetworkBattleUri.BattleStart,
|
||||||
new BattleStartBody(
|
new BattleStartBody(
|
||||||
TurnState: 0, // player goes first
|
TurnState: turnState, // 0 = this side goes first, 1 = second. Caller decides.
|
||||||
BattleType: selfCtx.BattleType,
|
BattleType: selfCtx.BattleType,
|
||||||
SelfInfo: new BattleStartSelfInfo(
|
SelfInfo: new BattleStartSelfInfo(
|
||||||
Rank: ScriptedProfiles.PlayerRank,
|
Rank: ScriptedProfiles.PlayerRank,
|
||||||
|
|||||||
@@ -207,11 +207,19 @@ public sealed class BattleSession
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case NetworkBattleUri.Loaded when phaseFrom?.Phase == BattleSessionPhase.AwaitingLoaded:
|
case NetworkBattleUri.Loaded when phaseFrom?.Phase == BattleSessionPhase.AwaitingLoaded:
|
||||||
|
{
|
||||||
|
// Exactly one side goes first. A goes first deterministically: in Scripted that's
|
||||||
|
// the real player (constructed as A); in PvP that's the first arriver. No Type
|
||||||
|
// check — the rule is correct in both modes, and Bot/AINetwork never reaches this
|
||||||
|
// arm (its silent Loaded arm above wins the match). A per-battle coin-flip is a
|
||||||
|
// follow-up (see plan § Out of scope).
|
||||||
|
var turnState = ReferenceEquals(from, A) ? 0 : 1;
|
||||||
result.Add((from, ScriptedLifecycle.BuildBattleStart(
|
result.Add((from, ScriptedLifecycle.BuildBattleStart(
|
||||||
from.Context, other.Context, from.ViewerId), false));
|
from.Context, other.Context, from.ViewerId, turnState), false));
|
||||||
result.Add((from, ScriptedLifecycle.BuildDeal(), false));
|
result.Add((from, ScriptedLifecycle.BuildDeal(), false));
|
||||||
phaseFrom!.Phase = BattleSessionPhase.AwaitingSwap;
|
phaseFrom!.Phase = BattleSessionPhase.AwaitingSwap;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case NetworkBattleUri.Swap when phaseFrom?.Phase == BattleSessionPhase.AwaitingSwap:
|
case NetworkBattleUri.Swap when phaseFrom?.Phase == BattleSessionPhase.AwaitingSwap:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class ScriptedLifecycleTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void BuildBattleStart_HasTurnStateZero_AndUsesContextBattleType()
|
public void BuildBattleStart_HasTurnStateZero_AndUsesContextBattleType()
|
||||||
{
|
{
|
||||||
var env = ScriptedLifecycle.BuildBattleStart(FixtureCtx(), ScriptedBotCtx(), selfViewerId: 1);
|
var env = ScriptedLifecycle.BuildBattleStart(FixtureCtx(), ScriptedBotCtx(), selfViewerId: 1, turnState: 0);
|
||||||
var body = (BattleStartBody)env.Body;
|
var body = (BattleStartBody)env.Body;
|
||||||
Assert.That(body.TurnState, Is.EqualTo(0));
|
Assert.That(body.TurnState, Is.EqualTo(0));
|
||||||
Assert.That(body.BattleType, Is.EqualTo(11));
|
Assert.That(body.BattleType, Is.EqualTo(11));
|
||||||
@@ -87,7 +87,7 @@ public class ScriptedLifecycleTests
|
|||||||
BattleType = 42,
|
BattleType = 42,
|
||||||
};
|
};
|
||||||
|
|
||||||
var env = ScriptedLifecycle.BuildBattleStart(ctx, ScriptedBotCtx(), selfViewerId: 1);
|
var env = ScriptedLifecycle.BuildBattleStart(ctx, ScriptedBotCtx(), selfViewerId: 1, turnState: 0);
|
||||||
var body = (BattleStartBody)env.Body;
|
var body = (BattleStartBody)env.Body;
|
||||||
|
|
||||||
Assert.That(body.SelfInfo.ClassId, Is.EqualTo("7"));
|
Assert.That(body.SelfInfo.ClassId, Is.EqualTo("7"));
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class TypedBodyWireShapeTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void BuildBattleStart_SerializesAllWireKeysAndPreservesBattlePointAsymmetry()
|
public void BuildBattleStart_SerializesAllWireKeysAndPreservesBattlePointAsymmetry()
|
||||||
{
|
{
|
||||||
var env = ScriptedLifecycle.BuildBattleStart(FixtureCtx(), ScriptedBotCtx(), selfViewerId: 906243102);
|
var env = ScriptedLifecycle.BuildBattleStart(FixtureCtx(), ScriptedBotCtx(), selfViewerId: 906243102, turnState: 0);
|
||||||
|
|
||||||
var json = MsgEnvelope.ToJson(env);
|
var json = MsgEnvelope.ToJson(env);
|
||||||
var node = JsonNode.Parse(json)!.AsObject();
|
var node = JsonNode.Parse(json)!.AsObject();
|
||||||
|
|||||||
@@ -52,6 +52,43 @@ public class BattleSessionDispatchTests
|
|||||||
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingSwap));
|
Assert.That(a.Phase, Is.EqualTo(BattleSessionPhase.AwaitingSwap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Pvp_Loaded_from_A_assigns_turnState_0()
|
||||||
|
{
|
||||||
|
var (s, a, _) = NewPvpSession();
|
||||||
|
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), "A (first arriver) goes first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Pvp_Loaded_from_B_assigns_turnState_1()
|
||||||
|
{
|
||||||
|
var (s, _, b) = NewPvpSession();
|
||||||
|
s.ComputeFrames(b, NewEnvelope(NetworkBattleUri.InitNetwork));
|
||||||
|
s.ComputeFrames(b, NewEnvelope(NetworkBattleUri.InitBattle));
|
||||||
|
var routes = s.ComputeFrames(b, NewEnvelope(NetworkBattleUri.Loaded));
|
||||||
|
|
||||||
|
var bs = (BattleStartBody)routes[0].Frame.Body;
|
||||||
|
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]
|
[Test]
|
||||||
public void Swap_pushes_SwapResponse_then_Ready_to_sender()
|
public void Swap_pushes_SwapResponse_then_Ready_to_sender()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user