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

@@ -1,6 +1,7 @@
// SVSim.UnitTests/BattleNode/Sessions/BattleSessionDispatchTests.cs
using Microsoft.Extensions.Logging.Abstractions;
using NUnit.Framework;
using SVSim.BattleNode.Bridge;
using SVSim.BattleNode.Protocol;
using SVSim.BattleNode.Protocol.Bodies;
using SVSim.BattleNode.Sessions;
@@ -13,9 +14,16 @@ public class BattleSessionDispatchTests
private static BattleSession NewSession()
{
// ws is unused by ComputeResponses; pass null! and rely on the test never invoking the pump.
return new BattleSession(ws: null!, battleId: "bid-1", viewerId: 1, log: NullLogger<BattleSession>.Instance);
return new BattleSession(ws: null!, battleId: "bid-1", viewerId: 1, context: FixtureCtx(), log: NullLogger<BattleSession>.Instance);
}
private static MatchContext FixtureCtx() => new(
SelfDeckCardIds: 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);
private static MsgEnvelope NewEnvelope(NetworkBattleUri uri) =>
new(uri, ViewerId: 1, Uuid: "u", Bid: null, Try: 0, Cat: EmitCategory.Battle,
PubSeq: null, PlaySeq: null, Body: new RawBody(new Dictionary<string, object?>()));

View File

@@ -4,6 +4,7 @@ using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NUnit.Framework;
using SVSim.BattleNode.Bridge;
using SVSim.BattleNode.Protocol;
using SVSim.BattleNode.Sessions;
using SVSim.BattleNode.Wire;
@@ -39,7 +40,7 @@ public class BattleSessionPumpTests
{
var ws = new TestWebSocket();
var session = new BattleSession(
ws: ws, battleId: "bid-pump", viewerId: 906243102,
ws: ws, battleId: "bid-pump", viewerId: 906243102, context: FixtureCtx(),
log: NullLogger<BattleSession>.Instance);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(8));
@@ -84,7 +85,7 @@ public class BattleSessionPumpTests
{
var ws = new TestWebSocket();
var session = new BattleSession(
ws: ws, battleId: "bid-cancel", viewerId: 906243102,
ws: ws, battleId: "bid-cancel", viewerId: 906243102, context: FixtureCtx(),
log: NullLogger<BattleSession>.Instance);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(8));
@@ -127,7 +128,7 @@ public class BattleSessionPumpTests
var ws = new TestWebSocket();
var logCapture = new CapturingLogger();
var session = new BattleSession(
ws: ws, battleId: "bid-clip", viewerId: 906243102, log: logCapture);
ws: ws, battleId: "bid-clip", viewerId: 906243102, context: FixtureCtx(), log: logCapture);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(8));
var runTask = session.RunAsync(cts.Token);
@@ -208,6 +209,13 @@ public class BattleSessionPumpTests
return NodeCrypto.GenerateKey(() => (seq++ * 7) % 16);
}
private static MatchContext FixtureCtx() => new(
SelfDeckCardIds: 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);
private sealed class CapturingLogger : ILogger<BattleSession>
{
public List<string> Warnings { get; } = new();

View File

@@ -1,4 +1,5 @@
using NUnit.Framework;
using SVSim.BattleNode.Bridge;
using SVSim.BattleNode.Sessions;
namespace SVSim.UnitTests.BattleNode.Sessions;
@@ -13,7 +14,7 @@ public class InMemoryBattleSessionStoreTests
[Test]
public void RegisterThenGet_ReturnsRegisteredBattle()
{
var battle = new PendingBattle("bid-1", 906243102);
var battle = new PendingBattle("bid-1", 906243102, FixtureCtx());
_store.RegisterPending(battle);
Assert.That(_store.TryGetPending("bid-1"), Is.EqualTo(battle));
@@ -28,7 +29,7 @@ public class InMemoryBattleSessionStoreTests
[Test]
public void Remove_ReturnsTrueWhenPresent_FalseWhenAbsent()
{
_store.RegisterPending(new PendingBattle("bid", 1));
_store.RegisterPending(new PendingBattle("bid", 1, FixtureCtx()));
Assert.That(_store.RemovePending("bid"), Is.True);
Assert.That(_store.RemovePending("bid"), Is.False);
}
@@ -36,8 +37,15 @@ public class InMemoryBattleSessionStoreTests
[Test]
public void Register_DuplicateBattleId_OverwritesPrior()
{
_store.RegisterPending(new PendingBattle("bid", 1));
_store.RegisterPending(new PendingBattle("bid", 2));
_store.RegisterPending(new PendingBattle("bid", 1, FixtureCtx()));
_store.RegisterPending(new PendingBattle("bid", 2, FixtureCtx()));
Assert.That(_store.TryGetPending("bid")!.ViewerId, Is.EqualTo(2));
}
private static MatchContext FixtureCtx() => new(
SelfDeckCardIds: 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);
}