diff --git a/SVSim.BattleNode/Protocol/MsgEnvelope.cs b/SVSim.BattleNode/Protocol/MsgEnvelope.cs
index 7baa7a6..0d505dd 100644
--- a/SVSim.BattleNode/Protocol/MsgEnvelope.cs
+++ b/SVSim.BattleNode/Protocol/MsgEnvelope.cs
@@ -24,9 +24,25 @@ public sealed record MsgEnvelope(
// EngineIoHandshake). Every wire key here is explicit via the manual ToJson layering below.
private static readonly JsonSerializerOptions Options = WireJsonOptions.CamelCase;
+ /// The fixed envelope wire keys, single-sourced. ,
+ /// the writes, and the reads all draw from here, so
+ /// the three encodings can't drift — adding a key in one place but not another (which would let a
+ /// body key silently shadow an envelope field) is no longer possible.
+ private static class Keys
+ {
+ public const string Uri = "uri";
+ public const string ViewerId = "viewerId";
+ public const string Uuid = "uuid";
+ public const string Bid = "bid";
+ public const string Try = "try";
+ public const string Cat = "cat";
+ public const string PubSeq = "pubSeq";
+ public const string PlaySeq = "playSeq";
+ }
+
private static readonly HashSet ReservedEnvelopeKeys = new()
{
- "uri", "viewerId", "uuid", "bid", "try", "cat", "pubSeq", "playSeq",
+ Keys.Uri, Keys.ViewerId, Keys.Uuid, Keys.Bid, Keys.Try, Keys.Cat, Keys.PubSeq, Keys.PlaySeq,
};
public static string ToJson(MsgEnvelope env)
@@ -37,14 +53,14 @@ public sealed record MsgEnvelope(
// field processed before "uri" is wiped before Matching.StartBattleLoad reads
// it back. The prod wire emits envelope keys first; we must too.
var result = new JsonObject();
- result["uri"] = env.Uri.ToString();
- result["viewerId"] = env.ViewerId;
- result["uuid"] = env.Uuid;
- result["try"] = env.RetryAttempt;
- result["cat"] = (int)env.Cat;
- if (env.Bid is not null) result["bid"] = env.Bid;
- if (env.PubSeq.HasValue) result["pubSeq"] = env.PubSeq.Value;
- if (env.PlaySeq.HasValue) result["playSeq"] = env.PlaySeq.Value;
+ result[Keys.Uri] = env.Uri.ToString();
+ result[Keys.ViewerId] = env.ViewerId;
+ result[Keys.Uuid] = env.Uuid;
+ result[Keys.Try] = env.RetryAttempt;
+ result[Keys.Cat] = (int)env.Cat;
+ if (env.Bid is not null) result[Keys.Bid] = env.Bid;
+ if (env.PubSeq.HasValue) result[Keys.PubSeq] = env.PubSeq.Value;
+ if (env.PlaySeq.HasValue) result[Keys.PlaySeq] = env.PlaySeq.Value;
if (env.Body is RawBody raw)
{
@@ -118,14 +134,14 @@ public sealed record MsgEnvelope(
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
- var uri = Enum.Parse(root.GetProperty("uri").GetString()!);
- var viewerId = root.GetProperty("viewerId").GetInt64();
- var uuid = root.GetProperty("uuid").GetString()!;
- var bid = root.TryGetProperty("bid", out var bidEl) ? bidEl.GetString() : null;
- var retryAttempt = root.TryGetProperty("try", out var tryEl) ? tryEl.GetInt32() : 0;
- var cat = root.TryGetProperty("cat", out var catEl) ? (EmitCategory)catEl.GetInt32() : EmitCategory.Battle;
- var pubSeq = root.TryGetProperty("pubSeq", out var psEl) ? psEl.GetInt64() : (long?)null;
- var playSeq = root.TryGetProperty("playSeq", out var plsEl) ? plsEl.GetInt64() : (long?)null;
+ var uri = Enum.Parse(root.GetProperty(Keys.Uri).GetString()!);
+ var viewerId = root.GetProperty(Keys.ViewerId).GetInt64();
+ var uuid = root.GetProperty(Keys.Uuid).GetString()!;
+ var bid = root.TryGetProperty(Keys.Bid, out var bidEl) ? bidEl.GetString() : null;
+ var retryAttempt = root.TryGetProperty(Keys.Try, out var tryEl) ? tryEl.GetInt32() : 0;
+ var cat = root.TryGetProperty(Keys.Cat, out var catEl) ? (EmitCategory)catEl.GetInt32() : EmitCategory.Battle;
+ var pubSeq = root.TryGetProperty(Keys.PubSeq, out var psEl) ? psEl.GetInt64() : (long?)null;
+ var playSeq = root.TryGetProperty(Keys.PlaySeq, out var plsEl) ? plsEl.GetInt64() : (long?)null;
var bodyDict = new Dictionary();
foreach (var prop in root.EnumerateObject())