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:
gamer147
2026-06-03 20:27:57 -04:00
parent b0e3783757
commit e9493e24c4
4 changed files with 7 additions and 79 deletions

View File

@@ -6,13 +6,11 @@ using SVSim.BattleNode.Protocol.Bodies;
namespace SVSim.BattleNode.Lifecycle;
/// <summary>
/// v1 hand-rolled scripted opponent. Static frame builders for the five lifecycle uris
/// (Matched / BattleStart / Deal / Swap response / Ready) plus a trivial opponent TurnStart
/// the dispatch pushes after the player's TurnEnd. The values are templated from the TK2
/// captures at <c>data_dumps/captures/battle-traffic_tk2_regular.ndjson</c> — anything
/// hardcoded here came from a real prod frame, with names + provenance in
/// <see cref="ScriptedProfiles"/>. The player-half of Matched/BattleStart now reads from
/// <see cref="MatchContext"/> instead of <see cref="ScriptedProfiles"/>.
/// Server-authored battle frames pushed to the client during match setup and teardown
/// (Matched / BattleStart / Deal / Swap response / Ready) plus the post-mulligan hand
/// computation. Used by every battle mode's handshake/mulligan dispatch arms. Hardcoded
/// values are templated from the TK2 prod captures (battle-traffic_tk2_*.ndjson); see
/// <see cref="ScriptedProfiles"/> for provenance.
/// </summary>
public static class ScriptedLifecycle
{
@@ -127,66 +125,6 @@ public static class ScriptedLifecycle
IdxChangeSeed: ScriptedProfiles.ReadyIdxChangeSeed,
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)
{
var list = new List<PosIdx>(hand.Count);

View File

@@ -24,11 +24,6 @@ internal static class ScriptedProfiles
public const int ReadyIdxChangeSeed = 771_335_280;
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>
/// 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;

View File

@@ -14,9 +14,4 @@ public enum BattleType
/// path; matched only in rank rotation / rank unlimited per prod). Server is
/// ack-only. <c>p2</c> must be null.</summary>
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,
}

View File

@@ -340,7 +340,7 @@ public class BattleSessionDispatchTests
[Test]
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();
DriveToAfterReady(s, a);
DriveToAfterReady(s, b);
@@ -539,7 +539,7 @@ public class BattleSessionDispatchTests
[Test]
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.
var (s, a, b) = NewBotSession();
s.ComputeFrames(a, NewEnvelope(NetworkBattleUri.InitNetwork));