feat(battle-node): thread MatchContext through bridge to BattleSession

IMatchingBridge.RegisterPendingBattle now takes a MatchContext; PendingBattle
carries it; BattleSession stores it. ArenaTwoPickBattleController builds ctx
from IMatchContextBuilder. ScriptedLifecycle still uses ScriptedProfiles for
the player half — Tasks 5/6 migrate the lifecycle.

Existing tests updated: MatchingBridgeTests, BattleNodeFlowTests,
InMemoryBattleSessionStoreTests, BattleSessionDispatchTests, BattleSession
PumpTests, ArenaTwoPickBattleControllerTests (which now seeds a TK2 run +
adds a no-active-run 400 case).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-01 12:44:42 -04:00
parent a0fdb0f3c5
commit 01f9bb722a
12 changed files with 144 additions and 44 deletions

View File

@@ -14,10 +14,9 @@ public class BattleNodeFlowTests
{
/// <summary>
/// End-to-end smoke for the v1 scripted lifecycle. Boots the EmulatedEntrypoint via
/// SVSimTestFactory (in-memory SQLite + reference-data CSV import), mints a battle
/// through IMatchingBridge, opens a raw Socket.IO v2 client against the in-process
/// TestServer, and drives InitNetwork → Loaded → Swap, asserting the right scripted
/// frames come back in order.
/// SVSimTestFactory, mints a battle through IMatchingBridge with a fixture MatchContext,
/// opens a raw Socket.IO v2 client against the in-process TestServer, and drives
/// InitNetwork → Loaded → Swap, asserting the right scripted frames come back in order.
/// </summary>
[Test]
[Timeout(30000)]
@@ -28,11 +27,10 @@ public class BattleNodeFlowTests
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
var ct = cts.Token;
var pending = bridge.RegisterPendingBattle(viewerId: 906243102);
var pending = bridge.RegisterPendingBattle(viewerId: 906243102, context: FixtureCtx());
var key = MakeKey();
var encryptedVid = NodeCrypto.EncryptForNode("906243102", key);
// TestServer ignores the host portion of the URI — only the path + query route.
var wsUri = new Uri($"ws://localhost/socket.io/?BattleId={pending.BattleId}&viewerId={Uri.EscapeDataString(encryptedVid)}&EIO=3&transport=websocket");
var wsClient = factory.Server.CreateWebSocketClient();
@@ -40,20 +38,16 @@ public class BattleNodeFlowTests
await using var client = new RawSocketIoTestClient(ws);
await client.ConsumeHandshakeAsync(ct);
// 1. InitNetwork → expect InitNetwork ack push only.
await client.SendMsgAsync(MakeEnvelope(NetworkBattleUri.InitNetwork, pubSeq: 1), key, ct);
Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.InitNetwork));
// 2. InitBattle → expect Matched (handler is now subscribed on the client side).
await client.SendMsgAsync(MakeEnvelope(NetworkBattleUri.InitBattle, pubSeq: 2), key, ct);
Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.Matched));
// 3. Loaded → expect BattleStart + Deal.
await client.SendMsgAsync(MakeEnvelope(NetworkBattleUri.Loaded, pubSeq: 3), key, ct);
Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.BattleStart));
Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.Deal));
// 4. Swap with empty idxList → expect Swap response + Ready.
await client.SendMsgAsync(MakeEnvelope(NetworkBattleUri.Swap, pubSeq: 4,
body: new Dictionary<string, object?> { ["idxList"] = new List<object?>() }), key, ct);
Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.Swap));
@@ -62,7 +56,6 @@ public class BattleNodeFlowTests
private static MsgEnvelope MakeEnvelope(NetworkBattleUri uri, long pubSeq, Dictionary<string, object?>? body = null) =>
new(uri, ViewerId: 906243102, Uuid: "udid-test", Bid: null, Try: 0,
// EmitMsgPack: InitNetwork → general(99); other matching URIs → matching(2); else battle(1).
Cat: uri == NetworkBattleUri.InitNetwork ? EmitCategory.General
: uri == NetworkBattleUri.InitBattle ? EmitCategory.Matching
: EmitCategory.Battle,
@@ -73,4 +66,11 @@ public class BattleNodeFlowTests
var seq = 0;
return NodeCrypto.GenerateKey(() => (seq++ * 13) % 16);
}
internal static MatchContext FixtureCtx(IReadOnlyList<long>? deck = null) => new(
SelfDeckCardIds: deck ?? Enumerable.Range(1, 30).Select(i => 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);
}