refactor(battle-node): drop BattleType.Scripted and the scripted-only builders
Removes the Scripted enum value, the bot's client-shaped emissions (BuildClient*), the canned opponent turn (BuildOpponent*), and OpponentTurnStartSpin. The shared server-frame builders (Matched/BattleStart/Deal/Swap/Ready + ComputeHandAfterSwap) and OpponentJudgeSpin (Bot mode) stay. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,11 @@ using SVSim.BattleNode.Protocol.Bodies;
|
|||||||
namespace SVSim.BattleNode.Lifecycle;
|
namespace SVSim.BattleNode.Lifecycle;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// v1 hand-rolled scripted opponent. Static frame builders for the five lifecycle uris
|
/// Server-authored battle frames pushed to the client during match setup and teardown
|
||||||
/// (Matched / BattleStart / Deal / Swap response / Ready) plus a trivial opponent TurnStart
|
/// (Matched / BattleStart / Deal / Swap response / Ready) plus the post-mulligan hand
|
||||||
/// the dispatch pushes after the player's TurnEnd. The values are templated from the TK2
|
/// computation. Used by every battle mode's handshake/mulligan dispatch arms. Hardcoded
|
||||||
/// captures at <c>data_dumps/captures/battle-traffic_tk2_regular.ndjson</c> — anything
|
/// values are templated from the TK2 prod captures (battle-traffic_tk2_*.ndjson); see
|
||||||
/// hardcoded here came from a real prod frame, with names + provenance in
|
/// <see cref="ScriptedProfiles"/> for provenance.
|
||||||
/// <see cref="ScriptedProfiles"/>. The player-half of Matched/BattleStart now reads from
|
|
||||||
/// <see cref="MatchContext"/> instead of <see cref="ScriptedProfiles"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ScriptedLifecycle
|
public static class ScriptedLifecycle
|
||||||
{
|
{
|
||||||
@@ -127,66 +125,6 @@ public static class ScriptedLifecycle
|
|||||||
IdxChangeSeed: ScriptedProfiles.ReadyIdxChangeSeed,
|
IdxChangeSeed: ScriptedProfiles.ReadyIdxChangeSeed,
|
||||||
Spin: ScriptedProfiles.ReadySpin));
|
Spin: ScriptedProfiles.ReadySpin));
|
||||||
|
|
||||||
// --- Client-shaped emissions (legacy scripted-bot scaffolding, pending removal) so the
|
|
||||||
// session brokers the bot through the same handshake arms as a human. Bodies for the parameterless
|
|
||||||
// handshake frames are ignored by the session (it reads from.Context / phase); only
|
|
||||||
// Swap's idxList is consumed (empty = keep the dealt hand).
|
|
||||||
|
|
||||||
public static MsgEnvelope BuildClientInitNetwork() => ClientFrame(NetworkBattleUri.InitNetwork, EmitCategory.General);
|
|
||||||
public static MsgEnvelope BuildClientInitBattle() => ClientFrame(NetworkBattleUri.InitBattle, EmitCategory.General);
|
|
||||||
public static MsgEnvelope BuildClientLoaded() => ClientFrame(NetworkBattleUri.Loaded, EmitCategory.General);
|
|
||||||
|
|
||||||
public static MsgEnvelope BuildClientSwap() =>
|
|
||||||
new(NetworkBattleUri.Swap,
|
|
||||||
ViewerId: FakeOpponentViewerId,
|
|
||||||
Uuid: WireConstants.ServerUuid,
|
|
||||||
Bid: null,
|
|
||||||
Try: 0,
|
|
||||||
Cat: EmitCategory.Battle,
|
|
||||||
PubSeq: null,
|
|
||||||
PlaySeq: null,
|
|
||||||
Body: new RawBody(new Dictionary<string, object?> { ["idxList"] = new List<object?>() }));
|
|
||||||
|
|
||||||
private static MsgEnvelope ClientFrame(NetworkBattleUri uri, EmitCategory cat) =>
|
|
||||||
new(uri,
|
|
||||||
ViewerId: FakeOpponentViewerId,
|
|
||||||
Uuid: WireConstants.ServerUuid,
|
|
||||||
Bid: null,
|
|
||||||
Try: 0,
|
|
||||||
Cat: cat,
|
|
||||||
PubSeq: null,
|
|
||||||
PlaySeq: null,
|
|
||||||
Body: new ResultCodeOnlyBody());
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// First half of the v1.1 scripted opponent turn cycle: pushed after the player's
|
|
||||||
/// TurnEnd, transitions the client into "Opponent's turn…" state. Paired with
|
|
||||||
/// <see cref="BuildOpponentTurnEnd"/>, which immediately follows and hands control
|
|
||||||
/// back to the player.
|
|
||||||
/// </summary>
|
|
||||||
public static MsgEnvelope BuildOpponentTurnStart() =>
|
|
||||||
EnvelopeForPush(NetworkBattleUri.TurnStart,
|
|
||||||
new OpponentTurnStartBody(Spin: ScriptedProfiles.OpponentTurnStartSpin));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Server-pushed TurnEnd transition that closes the opponent's turn and hands control
|
|
||||||
/// back to the player. Paired with <see cref="BuildOpponentTurnStart"/> in the v1.1 loop.
|
|
||||||
/// Wire shape from prod capture battle-traffic_tk2_regular.ndjson L18:
|
|
||||||
/// <c>{"uri":"TurnEnd","turnState":0,"resultCode":1,"playSeq":N}</c>.
|
|
||||||
/// </summary>
|
|
||||||
public static MsgEnvelope BuildOpponentTurnEnd() =>
|
|
||||||
EnvelopeForPush(NetworkBattleUri.TurnEnd, new TurnEndBody(TurnState: 0));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Server-pushed Judge frame that follows the opponent's TurnEnd and unblocks the
|
|
||||||
/// client's <c>JudgeOperation</c> → <c>ControlTurnStartPlayer</c>, transitioning to the
|
|
||||||
/// player's next turn. Without this frame the client hangs on "Opponent's turn…" —
|
|
||||||
/// see <c>data_dumps/captures/battle-traffic.ndjson</c> line 14 (client emits its own
|
|
||||||
/// Judge then waits forever).
|
|
||||||
/// </summary>
|
|
||||||
public static MsgEnvelope BuildOpponentJudge() =>
|
|
||||||
EnvelopeForPush(NetworkBattleUri.Judge, new JudgeBody(Spin: ScriptedProfiles.OpponentJudgeSpin));
|
|
||||||
|
|
||||||
private static IReadOnlyList<PosIdx> BuildPosIdxList(IReadOnlyList<long> hand)
|
private static IReadOnlyList<PosIdx> BuildPosIdxList(IReadOnlyList<long> hand)
|
||||||
{
|
{
|
||||||
var list = new List<PosIdx>(hand.Count);
|
var list = new List<PosIdx>(hand.Count);
|
||||||
|
|||||||
@@ -24,11 +24,6 @@ internal static class ScriptedProfiles
|
|||||||
public const int ReadyIdxChangeSeed = 771_335_280;
|
public const int ReadyIdxChangeSeed = 771_335_280;
|
||||||
public const int ReadySpin = 243;
|
public const int ReadySpin = 243;
|
||||||
|
|
||||||
// Generic non-zero spin that lands the client in "Opponent's turn..."
|
|
||||||
// display state. v1 doesn't simulate the opponent — once this lands,
|
|
||||||
// the client sits there indefinitely.
|
|
||||||
public const int OpponentTurnStartSpin = 100;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server-pushed Judge frame spin value. Prod varies per push (55, 175, 73, ...) — it's
|
/// Server-pushed Judge frame spin value. Prod varies per push (55, 175, 73, ...) — it's
|
||||||
/// an animation seed, not a stateful value. Fixed at 100 here for test stability;
|
/// an animation seed, not a stateful value. Fixed at 100 here for test stability;
|
||||||
|
|||||||
@@ -14,9 +14,4 @@ public enum BattleType
|
|||||||
/// path; matched only in rank rotation / rank unlimited per prod). Server is
|
/// path; matched only in rank rotation / rank unlimited per prod). Server is
|
||||||
/// ack-only. <c>p2</c> must be null.</summary>
|
/// ack-only. <c>p2</c> must be null.</summary>
|
||||||
Bot,
|
Bot,
|
||||||
|
|
||||||
/// <summary>One real player; server scripts the opponent (today's v1.2
|
|
||||||
/// behaviour, preserved as a solo testing harness). <c>p2</c> currently null;
|
|
||||||
/// future server-driven bot config can ride on <c>p2</c>.</summary>
|
|
||||||
Scripted,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ public class BattleSessionDispatchTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void Pvp_TurnEndFinal_from_A_forwards_envelope_to_B_and_pushes_paired_BattleFinish()
|
public void Pvp_TurnEndFinal_from_A_forwards_envelope_to_B_and_pushes_paired_BattleFinish()
|
||||||
{
|
{
|
||||||
// Same unified handling as Scripted — A is the winner, B is the loser.
|
// Unified TurnEndFinal handling — A is the winner, B is the loser.
|
||||||
var (s, a, b) = NewPvpSession();
|
var (s, a, b) = NewPvpSession();
|
||||||
DriveToAfterReady(s, a);
|
DriveToAfterReady(s, a);
|
||||||
DriveToAfterReady(s, b);
|
DriveToAfterReady(s, b);
|
||||||
@@ -539,7 +539,7 @@ public class BattleSessionDispatchTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void Bot_Retire_pushes_paired_BattleFinish_RetireLose_to_player_RetireWin_to_bot()
|
public void Bot_Retire_pushes_paired_BattleFinish_RetireLose_to_player_RetireWin_to_bot()
|
||||||
{
|
{
|
||||||
// Unified Retire/Kill dispatch — same paired push as Scripted and PvP.
|
// Unified Retire/Kill dispatch — same paired push as PvP.
|
||||||
// NoOpBotParticipant swallows its push.
|
// NoOpBotParticipant swallows its push.
|
||||||
var (s, a, b) = NewBotSession();
|
var (s, a, b) = NewBotSession();
|
||||||
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));
|
||||||
|
|||||||
Reference in New Issue
Block a user