refactor(battlenode): rename mode-id field off BattleType, add BattleModes (§D)

Behavior-preserving; 271 BattleNode/Matching/Services tests green, full solution builds.

"BattleType" meant two things: the Sessions.BattleType enum (Pvp/Bot) and an int
"mode id" field. Renamed the int field on MatchContext AND the BattleStartBody wire
DTO to BattleModeId (wire key stays "battleType" via JsonPropertyName), so BattleType
now means only the enum project-wide.

New Bridge/BattleModes.cs (TakeTwo = 11) replaces every 11 literal — both prod
MatchContextBuilder sites and the test fixtures/assertions. The arbitrary-passthrough
42 and bot 0 stay literal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-05 07:44:02 -04:00
parent d119d2c277
commit 578d0a75ef
24 changed files with 57 additions and 37 deletions

View File

@@ -0,0 +1,14 @@
namespace SVSim.BattleNode.Bridge;
/// <summary>
/// Known values for <see cref="MatchContext.BattleModeId"/> — the prod do_matching battle-mode id,
/// forwarded verbatim onto the wire (<c>battleType</c> field on BattleStart). Names the otherwise
/// magic <c>11</c>. Distinct from the <see cref="Sessions.BattleType"/> enum (Pvp/Bot), which is the
/// session topology, not the game mode.
/// </summary>
public static class BattleModes
{
/// <summary>Take Two (TK2) — the two-pick draft mode the v1 captures were taken from. Prod
/// rank-battle frames carry the same value (see <c>MatchContextBuilder</c>).</summary>
public const int TakeTwo = 11;
}

View File

@@ -25,5 +25,8 @@ public sealed record MatchContext(
int FieldId,
int IsOfficial, // 0 or 1
// Battle-mode hint, currently TK2 == 11. Future modes populate their own value.
int BattleType);
// Battle-mode hint (the prod do_matching mode id). Named BattleModeId, NOT BattleType, to
// avoid colliding with the <see cref="Sessions.BattleType"/> enum (Pvp/Bot) — a different axis.
// Known values live in <see cref="BattleModes"/> (currently just TK2 == 11). Future modes add
// their own constant.
int BattleModeId);

View File

@@ -55,7 +55,7 @@ public static class ServerBattleFrames
EnvelopeForPush(NetworkBattleUri.BattleStart,
new BattleStartBody(
TurnState: turnState, // First = this side goes first, Second = second. Caller decides.
BattleType: selfCtx.BattleType,
BattleModeId: selfCtx.BattleModeId,
SelfInfo: new BattleStartSelfInfo(
Rank: BattleFrameDefaults.PlayerRank,
BattlePoint: BattleFrameDefaults.PlayerBattlePoint,

View File

@@ -5,7 +5,9 @@ namespace SVSim.BattleNode.Protocol.Bodies;
public sealed record BattleStartBody(
[property: JsonPropertyName("turnState")]
[property: JsonConverter(typeof(JsonNumberEnumConverter<TurnState>))] TurnState TurnState,
[property: JsonPropertyName("battleType")] int BattleType,
// Wire key stays "battleType" (the client's contract); the CLR name is BattleModeId so the
// project keeps one meaning of "BattleType" — the Sessions.BattleType enum (Pvp/Bot).
[property: JsonPropertyName("battleType")] int BattleModeId,
[property: JsonPropertyName("selfInfo")] BattleStartSelfInfo SelfInfo,
[property: JsonPropertyName("oppoInfo")] BattleStartOppoInfo OppoInfo,
[property: JsonPropertyName("resultCode")] int ResultCode = (int)ReceiveNodeResultCode.Success) : IMsgBody;

View File

@@ -23,7 +23,7 @@ public sealed class NoOpBotParticipant : IBattleParticipant
ClassId: "0", CharaId: "0", CardMasterName: BotCardMasterName,
CountryCode: "", UserName: "Bot", SleeveId: "0",
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0,
BattleType: 0);
BattleModeId: 0);
// Required by IBattleParticipant, but a silent bot never raises it — suppress the
// "event is never used" warning rather than keeping a dead null-emitting method.

View File

@@ -67,7 +67,7 @@ public class MatchContextBuilder : IMatchContextBuilder
// Hardcoded v1; needs equipped-MyPageBackground lookup (see spec §Deferred).
FieldId: 43,
IsOfficial: viewer.Info.IsOfficial ? 1 : 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
}
public async Task<MatchContext> BuildForRankBattleAsync(long viewerId, Format format, int deckNo)
@@ -114,6 +114,6 @@ public class MatchContextBuilder : IMatchContextBuilder
DegreeId: degreeId,
FieldId: 43,
IsOfficial: viewer.Info.IsOfficial ? 1 : 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
}
}

View File

@@ -96,5 +96,5 @@ public class MatchingBridgeTests
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
}

View File

@@ -72,7 +72,7 @@ public class WaitingRoomTests
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "0",
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
return new RealParticipant(ws, viewerId, ctx, NullLogger<RealParticipant>.Instance);
}
}

View File

@@ -26,7 +26,7 @@ public class BattleNodeFlowTests
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
/// <summary>
/// End-to-end: a viewer with a real TK2 run sees their drafted card-ids in the Matched

View File

@@ -69,22 +69,22 @@ public class ServerBattleFramesTests
}
[Test]
public void BuildBattleStart_HasTurnStateZero_AndUsesContextBattleType()
public void BuildBattleStart_HasTurnStateZero_AndUsesContextBattleModeId()
{
var env = ServerBattleFrames.BuildBattleStart(FixtureCtx(), FakeOpponentCtx(), selfViewerId: 1, turnState: TurnState.First);
var body = (BattleStartBody)env.Body;
Assert.That(body.TurnState, Is.EqualTo(TurnState.First));
Assert.That(body.BattleType, Is.EqualTo(11));
Assert.That(body.BattleModeId, Is.EqualTo(BattleModes.TakeTwo));
}
[Test]
public void BuildBattleStart_class_chara_cardMaster_battleType_flow_from_context()
public void BuildBattleStart_class_chara_cardMaster_battleModeId_flow_from_context()
{
var ctx = FixtureCtx() with
{
ClassId = "7", CharaId = "5000123",
CardMasterName = "card_master_test_v2",
BattleType = 42,
BattleModeId = 42,
};
var env = ServerBattleFrames.BuildBattleStart(ctx, FakeOpponentCtx(), selfViewerId: 1, turnState: TurnState.First);
@@ -93,7 +93,7 @@ public class ServerBattleFramesTests
Assert.That(body.SelfInfo.ClassId, Is.EqualTo("7"));
Assert.That(body.SelfInfo.CharaId, Is.EqualTo("5000123"));
Assert.That(body.SelfInfo.CardMasterName, Is.EqualTo("card_master_test_v2"));
Assert.That(body.BattleType, Is.EqualTo(42));
Assert.That(body.BattleModeId, Is.EqualTo(42));
}
[Test]
@@ -171,7 +171,7 @@ public class ServerBattleFramesTests
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
// A prod-captured opponent MatchContext fixture that the BuildMatched/BuildBattleStart
// helpers read from for the oppo half.
@@ -180,5 +180,5 @@ public class ServerBattleFramesTests
ClassId: "8", CharaId: "8", CardMasterName: "card_master_node_10015",
CountryCode: "JPN", UserName: "Opponent", SleeveId: "704141010",
EmblemId: "400001100", DegreeId: "120027", FieldId: 5, IsOfficial: 0,
BattleType: 0);
BattleModeId: 0);
}

View File

@@ -158,7 +158,7 @@ public class TypedBodyWireShapeTests
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
// Prod-captured opponent fixture — 30-card deck and the prod-captured opponent
// cosmetics (ClassId/CharaId "8") so the wire bytes asserted below (oppoInfo classId/charaId,
@@ -169,5 +169,5 @@ public class TypedBodyWireShapeTests
ClassId: "8", CharaId: "8", CardMasterName: "card_master_node_10015",
CountryCode: "JPN", UserName: "Opponent", SleeveId: "704141010",
EmblemId: "400001100", DegreeId: "120027", FieldId: 5, IsOfficial: 0,
BattleType: 0);
BattleModeId: 0);
}

View File

@@ -1,6 +1,7 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using NUnit.Framework;
using SVSim.BattleNode.Bridge;
using SVSim.BattleNode.Protocol;
using SVSim.BattleNode.Protocol.Bodies;
@@ -13,7 +14,7 @@ public class BattleStartBodyTests
public void Serializes_TopLevelFields_WithCorrectWireKeys()
{
var body = new BattleStartBody(
TurnState: TurnState.First, BattleType: 11,
TurnState: TurnState.First, BattleModeId: BattleModes.TakeTwo,
SelfInfo: new BattleStartSelfInfo("10", "6270", "1", "1", "card_master_node_10015"),
OppoInfo: new BattleStartOppoInfo("1", "0", 0, "0", "8", "8", "card_master_node_10015"));

View File

@@ -64,13 +64,13 @@ public class BattleSessionDispatchConcurrencyTests
SelfDeckCardIds: Enumerable.Range(1, 30).Select(_ => 100_011_010L).ToList(),
ClassId: "3", CharaId: "3", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "PlayerA", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0, BattleType: 11);
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0, BattleModeId: BattleModes.TakeTwo);
private static MatchContext CtxB() => new(
SelfDeckCardIds: Enumerable.Range(1, 30).Select(_ => 200_011_010L).ToList(),
ClassId: "5", CharaId: "5", CardMasterName: "card_master_node_10015",
CountryCode: "JPN", UserName: "PlayerB", SleeveId: "3000022",
EmblemId: "701441022", DegreeId: "300004", FieldId: 44, IsOfficial: 0, BattleType: 11);
EmblemId: "701441022", DegreeId: "300004", FieldId: 44, IsOfficial: 0, BattleModeId: BattleModes.TakeTwo);
/// <summary>Tracks the peak number of dispatches in flight at once. Records the count under a
/// short lock, then holds (outside the lock) to widen the overlap window so a serialization bug

View File

@@ -894,7 +894,7 @@ public class BattleSessionDispatchTests
SelfDeckCardIds: Array.Empty<long>(),
ClassId: "0", CharaId: "0", CardMasterName: "card_master_node_10015",
CountryCode: "", UserName: "Bot", SleeveId: "0",
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleType: 0);
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleModeId: 0);
[Test]
public void Bot_InitNetwork_acks_to_sender()
@@ -1051,21 +1051,21 @@ public class BattleSessionDispatchTests
ClassId: "3", CharaId: "3", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "PlayerA", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
private static MatchContext PlayerBCtx() => new(
SelfDeckCardIds: Enumerable.Range(1, 30).Select(_ => 200_011_010L).ToList(),
ClassId: "5", CharaId: "5", CardMasterName: "card_master_node_10015",
CountryCode: "JPN", UserName: "PlayerB", SleeveId: "3000022",
EmblemId: "701441022", DegreeId: "300004", FieldId: 44, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
private static MatchContext FixtureCtx() => new(
SelfDeckCardIds: Enumerable.Range(1, 30).Select(_ => 100_011_010L).ToList(),
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
private static MsgEnvelope NewEnvelope(NetworkBattleUri uri) =>
new(uri, ViewerId: 1, Uuid: "u", Bid: null, RetryAttempt: 0,

View File

@@ -24,7 +24,7 @@ public class BattleSessionStateTests
private static MatchContext Ctx(params long[] deck) => new(
SelfDeckCardIds: deck, ClassId: "1", CharaId: "1", CardMasterName: "cm",
CountryCode: "KOR", UserName: "P", SleeveId: "0", EmblemId: "0", DegreeId: "0",
FieldId: 0, IsOfficial: 0, BattleType: 11);
FieldId: 0, IsOfficial: 0, BattleModeId: BattleModes.TakeTwo);
[Test]
public void GetOrSeedDeckMap_maps_idx_1based_to_the_shuffled_order()

View File

@@ -54,5 +54,5 @@ public class BattleSessionTerminateCascadeTests
SelfDeckCardIds: Array.Empty<long>(),
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "JP", UserName: "Test", SleeveId: "0",
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleType: 11);
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleModeId: BattleModes.TakeTwo);
}

View File

@@ -48,5 +48,5 @@ public class InMemoryBattleSessionStoreTests
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
}

View File

@@ -150,5 +150,5 @@ public class RealParticipantHandEventTests
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
}

View File

@@ -174,7 +174,7 @@ public class RealParticipantTests
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
private static MsgEnvelope NewEnvelope(NetworkBattleUri uri) =>
new(uri, ViewerId: 1, Uuid: "u", Bid: null, RetryAttempt: 0,

View File

@@ -14,7 +14,7 @@ public class BotRosterTests
SelfDeckCardIds: Array.Empty<long>(),
ClassId: classId, CharaId: classId, CardMasterName: "card_master_node_10015",
CountryCode: "JP", UserName: userName, SleeveId: "0",
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleType: 11);
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleModeId: BattleModes.TakeTwo);
private static async Task<BotRoster> NewRosterAsync(SVSimTestFactory factory)
{

View File

@@ -48,7 +48,7 @@ public class InProcessPairUpRankFallbackTests
SelfDeckCardIds: Array.Empty<long>(), ClassId: "0", CharaId: "0",
CardMasterName: "card_master_node_10015",
CountryCode: "JP", UserName: $"P{id}", SleeveId: "0",
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleType: 11));
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleModeId: BattleModes.TakeTwo));
[Test]
public async Task TK2_policy_is_PvpOnly_no_fallback_regression()

View File

@@ -94,5 +94,5 @@ public class InProcessPairUpTests
ClassId: "1", CharaId: "1", CardMasterName: "card_master_node_10015",
CountryCode: "KOR", UserName: "Player", SleeveId: "3000011",
EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0,
BattleType: 11);
BattleModeId: BattleModes.TakeTwo);
}

View File

@@ -31,7 +31,7 @@ public class MatchingResolverTests
SelfDeckCardIds: Array.Empty<long>(), ClassId: "0", CharaId: "0",
CardMasterName: "card_master_node_10015",
CountryCode: "JP", UserName: $"P{vid}", SleeveId: "0",
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleType: 11));
EmblemId: "0", DegreeId: "0", FieldId: 0, IsOfficial: 0, BattleModeId: BattleModes.TakeTwo));
[Test]
public async Task When_neither_flag_set_calls_pairUp_and_parks_returns_3002_with_empty_url()

View File

@@ -64,7 +64,7 @@ public class MatchContextBuilderTests
Assert.That(ctx.EmblemId, Is.EqualTo(emblemId.ToString()));
Assert.That(ctx.DegreeId, Is.EqualTo(degreeId.ToString()));
Assert.That(ctx.IsOfficial, Is.EqualTo(0));
Assert.That(ctx.BattleType, Is.EqualTo(11));
Assert.That(ctx.BattleModeId, Is.EqualTo(BattleModes.TakeTwo));
// Hardcoded v1 fixtures (see spec §Deferred plumbing)
Assert.That(ctx.CardMasterName, Is.EqualTo("card_master_node_10015"));
Assert.That(ctx.FieldId, Is.EqualTo(43));
@@ -131,7 +131,7 @@ public class MatchContextBuilderTests
var ctx = await builder.BuildForRankBattleAsync(viewerId, Format.Rotation, deckNo: 1);
Assert.That(ctx.UserName, Is.EqualTo("Ranker"));
Assert.That(ctx.BattleType, Is.EqualTo(11), "BattleType=11 matches the prod rank-battle wire value (same as TK2).");
Assert.That(ctx.BattleModeId, Is.EqualTo(BattleModes.TakeTwo), "rank-battle carries the same mode id as TK2 on the wire.");
Assert.That(ctx.ClassId, Is.Not.Null.And.Not.Empty, "ClassId from the selected deck's class.");
Assert.That(ctx.CardMasterName, Is.EqualTo("card_master_node_10015"));
Assert.That(ctx.FieldId, Is.EqualTo(43));