refactor(battle-node): WireConstants for SIO event names + crypto RNG battle id

This commit is contained in:
gamer147
2026-06-01 11:53:01 -04:00
parent 133346e3e8
commit ef3d7bb82b
4 changed files with 45 additions and 12 deletions

View File

@@ -1,3 +1,4 @@
using System.Security.Cryptography;
using SVSim.BattleNode.Sessions;
namespace SVSim.BattleNode.Bridge;
@@ -23,8 +24,13 @@ public sealed class MatchingBridge : IMatchingBridge
public PendingMatch RegisterPendingBattle(long viewerId)
{
// 12-digit decimal battle id mirrors the captures (e.g. "975695075012").
// Cast to long before Math.Abs to avoid OverflowException on int.MinValue.
var battleId = (Math.Abs((long)Guid.NewGuid().GetHashCode()) % 1_000_000_000_000L).ToString("D12");
// Two unbiased 6-digit draws concatenated — RandomNumberGenerator.GetInt32 uses
// rejection sampling so the result is uniform on [0, 10^6). The previous
// implementation (Guid.GetHashCode + mod) collapsed 128 bits into ~32 of entropy
// and tripped Math.Abs(int.MinValue) UB on the unlucky hash.
var hi = RandomNumberGenerator.GetInt32(0, 1_000_000);
var lo = RandomNumberGenerator.GetInt32(0, 1_000_000);
var battleId = $"{hi:D6}{lo:D6}";
_store.RegisterPending(new PendingBattle(battleId, viewerId));
return new PendingMatch(battleId, _options.NodeServerUrl);
}

View File

@@ -117,7 +117,7 @@ public static class ScriptedLifecycle
private static MsgEnvelope EnvelopeForPush(NetworkBattleUri uri, IMsgBody body, string? bid = null) =>
new(uri,
ViewerId: FakeOpponentViewerId,
Uuid: "node-stub",
Uuid: WireConstants.ServerUuid,
Bid: bid,
Try: 0,
Cat: EmitCategory.Battle,

View File

@@ -0,0 +1,27 @@
namespace SVSim.BattleNode.Protocol;
/// <summary>
/// String constants that show up on the wire as opaque tags. Lifting them out of
/// inline string literals gives each one a single source of truth and a name that
/// reads at the use site.
/// </summary>
internal static class WireConstants
{
/// <summary>SIO event name for ordered server-pushed frames (the lifecycle channel).</summary>
public const string SynchronizeEvent = "synchronize";
/// <summary>SIO event name for client-emitted msg frames + their ack-responses.</summary>
public const string MsgEvent = "msg";
/// <summary>SIO event name for Gungnir keepalive frames (both directions).</summary>
public const string AliveEvent = "alive";
/// <summary>
/// Placeholder UUID we stamp on every server-originated envelope. Prod servers stamp a
/// real per-request UUID; the client doesn't validate it.
/// </summary>
public const string ServerUuid = "node-stub";
/// <summary>Gungnir scs/ocs value the v1 server reports unconditionally.</summary>
public const string OnlineStatus = "ONLINE";
}

View File

@@ -112,10 +112,10 @@ public sealed class BattleSession
{
switch (frame.EventName)
{
case "msg" when frame.BinaryAttachments.Count == 1:
case WireConstants.MsgEvent when frame.BinaryAttachments.Count == 1:
await HandleMsgEventAsync(frame);
return;
case "alive" when frame.BinaryAttachments.Count == 1:
case WireConstants.AliveEvent when frame.BinaryAttachments.Count == 1:
await HandleAliveEventAsync(frame);
return;
}
@@ -178,14 +178,14 @@ public sealed class BattleSession
var aliveEnv = new MsgEnvelope(
Uri: NetworkBattleUri.Gungnir,
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
Uuid: "node-stub",
Uuid: WireConstants.ServerUuid,
Bid: null,
Try: 0,
Cat: EmitCategory.General,
PubSeq: null,
PlaySeq: null,
Body: new AlivePushBody(Scs: "ONLINE", Ocs: "ONLINE"));
await PushNoStockAsync(aliveEnv, eventName: "alive");
Body: new AlivePushBody(Scs: WireConstants.OnlineStatus, Ocs: WireConstants.OnlineStatus));
await PushNoStockAsync(aliveEnv, eventName: WireConstants.AliveEvent);
}
catch (Exception ex)
{
@@ -272,7 +272,7 @@ public sealed class BattleSession
private MsgEnvelope BuildAckedEnvelope(NetworkBattleUri uri) => new(
uri,
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
Uuid: "node-stub",
Uuid: WireConstants.ServerUuid,
Bid: null,
Try: 0,
Cat: EmitCategory.General,
@@ -283,7 +283,7 @@ public sealed class BattleSession
private MsgEnvelope BuildBattleFinishNoContest() => new(
NetworkBattleUri.BattleFinish,
ViewerId: ScriptedLifecycle.FakeOpponentViewerId,
Uuid: "node-stub",
Uuid: WireConstants.ServerUuid,
Bid: null,
Try: 0,
Cat: EmitCategory.Battle,
@@ -317,10 +317,10 @@ public sealed class BattleSession
return Array.Empty<long>();
}
private Task PushOrderedAsync(MsgEnvelope env, string eventName = "synchronize") =>
private Task PushOrderedAsync(MsgEnvelope env, string eventName = WireConstants.SynchronizeEvent) =>
EncodeAndSendAsync(Outbound.AssignAndArchive(env), eventName);
private Task PushNoStockAsync(MsgEnvelope env, string eventName = "synchronize") =>
private Task PushNoStockAsync(MsgEnvelope env, string eventName = WireConstants.SynchronizeEvent) =>
EncodeAndSendAsync(Outbound.WrapNoStock(env), eventName);
private async Task EncodeAndSendAsync(MsgEnvelope env, string eventName)