From d3c4b3083e5bbe50a641a00d722374c74bb0b0f8 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sun, 31 May 2026 22:31:04 -0400 Subject: [PATCH] feat(battle-node): IMatchingBridge + MatchingBridge mint battle id + node url --- SVSim.BattleNode/Bridge/BattleNodeOptions.cs | 10 ++++++ SVSim.BattleNode/Bridge/IMatchingBridge.cs | 12 +++++++ SVSim.BattleNode/Bridge/MatchingBridge.cs | 23 ++++++++++++ .../BattleNode/Bridge/MatchingBridgeTests.cs | 35 +++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 SVSim.BattleNode/Bridge/BattleNodeOptions.cs create mode 100644 SVSim.BattleNode/Bridge/IMatchingBridge.cs create mode 100644 SVSim.BattleNode/Bridge/MatchingBridge.cs create mode 100644 SVSim.UnitTests/BattleNode/Bridge/MatchingBridgeTests.cs diff --git a/SVSim.BattleNode/Bridge/BattleNodeOptions.cs b/SVSim.BattleNode/Bridge/BattleNodeOptions.cs new file mode 100644 index 0000000..7210e9f --- /dev/null +++ b/SVSim.BattleNode/Bridge/BattleNodeOptions.cs @@ -0,0 +1,10 @@ +namespace SVSim.BattleNode.Bridge; + +/// +/// DI-injected options for the battle node. The web host populates these — typically +/// nodeServerUrl is "ws://localhost:5148" matching ASPNETCORE_URLS. +/// +public sealed class BattleNodeOptions +{ + public string NodeServerUrl { get; set; } = "ws://localhost:5148"; +} diff --git a/SVSim.BattleNode/Bridge/IMatchingBridge.cs b/SVSim.BattleNode/Bridge/IMatchingBridge.cs new file mode 100644 index 0000000..bc41966 --- /dev/null +++ b/SVSim.BattleNode/Bridge/IMatchingBridge.cs @@ -0,0 +1,12 @@ +namespace SVSim.BattleNode.Bridge; + +public interface IMatchingBridge +{ + /// + /// Mint a battle id, register a pending session for the given viewer, and return the + /// URL the client should open a socket to. + /// + PendingMatch RegisterPendingBattle(long viewerId); +} + +public sealed record PendingMatch(string BattleId, string NodeServerUrl); diff --git a/SVSim.BattleNode/Bridge/MatchingBridge.cs b/SVSim.BattleNode/Bridge/MatchingBridge.cs new file mode 100644 index 0000000..e812714 --- /dev/null +++ b/SVSim.BattleNode/Bridge/MatchingBridge.cs @@ -0,0 +1,23 @@ +using SVSim.BattleNode.Sessions; + +namespace SVSim.BattleNode.Bridge; + +public sealed class MatchingBridge : IMatchingBridge +{ + private readonly IBattleSessionStore _store; + private readonly BattleNodeOptions _options; + + public MatchingBridge(IBattleSessionStore store, BattleNodeOptions options) + { + _store = store; + _options = options; + } + + public PendingMatch RegisterPendingBattle(long viewerId) + { + // 12-digit decimal battle id mirrors the captures (e.g. "975695075012"). + var battleId = (Math.Abs(Guid.NewGuid().GetHashCode()) % 1_000_000_000_000L).ToString("D12"); + _store.RegisterPending(new PendingBattle(battleId, viewerId)); + return new PendingMatch(battleId, _options.NodeServerUrl); + } +} diff --git a/SVSim.UnitTests/BattleNode/Bridge/MatchingBridgeTests.cs b/SVSim.UnitTests/BattleNode/Bridge/MatchingBridgeTests.cs new file mode 100644 index 0000000..7dec521 --- /dev/null +++ b/SVSim.UnitTests/BattleNode/Bridge/MatchingBridgeTests.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; +using SVSim.BattleNode.Bridge; +using SVSim.BattleNode.Sessions; + +namespace SVSim.UnitTests.BattleNode.Bridge; + +[TestFixture] +public class MatchingBridgeTests +{ + [Test] + public void RegisterPendingBattle_RegistersInStoreAndReturnsNodeUrl() + { + var store = new InMemoryBattleSessionStore(); + var bridge = new MatchingBridge(store, new BattleNodeOptions { NodeServerUrl = "ws://localhost:5148" }); + + var match = bridge.RegisterPendingBattle(viewerId: 906243102); + + Assert.That(match.NodeServerUrl, Is.EqualTo("ws://localhost:5148")); + Assert.That(match.BattleId, Is.Not.Empty); + var pending = store.TryGetPending(match.BattleId); + Assert.That(pending, Is.Not.Null); + Assert.That(pending!.ViewerId, Is.EqualTo(906243102)); + } + + [Test] + public void RegisterPendingBattle_MintsUniqueBattleIds() + { + var bridge = new MatchingBridge(new InMemoryBattleSessionStore(), new BattleNodeOptions()); + + var a = bridge.RegisterPendingBattle(1); + var b = bridge.RegisterPendingBattle(2); + + Assert.That(a.BattleId, Is.Not.EqualTo(b.BattleId)); + } +}