refactor(battlenode): low-churn §B/§D/§E/§F quality cleanups

Behavior-preserving; 231 BattleNode tests green.

- §D: MsgEnvelope.Try -> RetryAttempt (drops keyword-escape; wire key stays "try");
  SocketIoFrame.AckResponse arg -> pubSeqEcho.
- §B: Gungnir.EmitInterval -> BattleNodeOptions.AliveEmitInterval (unused literal
  moved to its config home); deck-idx 4L -> InitialHand.Length + 1.
- §E: shared Wire.WireJsonOptions.CamelCase replaces the duplicated camelCase
  JsonSerializerOptions in EngineIoHandshake and MsgEnvelope.
- §F: do-NOT-consistency-fix polarity notes on TurnEndFinalHandler (From wins)
  and RetireKillHandler (From loses).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-04 23:06:44 -04:00
parent e70f32db79
commit 7d4da69f22
22 changed files with 84 additions and 62 deletions

View File

@@ -1,6 +1,6 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using SVSim.BattleNode.Wire;
namespace SVSim.BattleNode.Protocol;
@@ -14,32 +14,21 @@ public sealed record MsgEnvelope(
long ViewerId,
string Uuid,
string? Bid,
int Try,
int RetryAttempt,
EmitCategory Cat,
long? PubSeq,
long? PlaySeq,
IMsgBody Body)
{
private static readonly JsonSerializerOptions Options = CreateOptions();
// Bare-camelCase wire serialization, single-sourced in Wire.WireJsonOptions (shared with
// EngineIoHandshake). Every wire key here is explicit via the manual ToJson layering below.
private static readonly JsonSerializerOptions Options = WireJsonOptions.CamelCase;
private static readonly HashSet<string> ReservedEnvelopeKeys = new()
{
"uri", "viewerId", "uuid", "bid", "try", "cat", "pubSeq", "playSeq",
};
private static JsonSerializerOptions CreateOptions()
{
var opt = new JsonSerializerOptions
{
// Wire-key casing is bare camelCase via per-field [JsonPropertyName] —
// NOT EmulatedEntrypoint's snake_case policy. The naming-policy line
// that was here previously was dead code (every wire key is explicit).
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
opt.Converters.Add(new JsonStringEnumConverter());
return opt;
}
public static string ToJson(MsgEnvelope env)
{
// Envelope fields MUST come before body fields on the wire. The client's
@@ -51,7 +40,7 @@ public sealed record MsgEnvelope(
result["uri"] = env.Uri.ToString();
result["viewerId"] = env.ViewerId;
result["uuid"] = env.Uuid;
result["try"] = env.Try;
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;
@@ -133,7 +122,7 @@ public sealed record MsgEnvelope(
var viewerId = root.GetProperty("viewerId").GetInt64();
var uuid = root.GetProperty("uuid").GetString()!;
var bid = root.TryGetProperty("bid", out var bidEl) ? bidEl.GetString() : null;
var @try = root.TryGetProperty("try", out var tryEl) ? tryEl.GetInt32() : 0;
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;
@@ -145,7 +134,7 @@ public sealed record MsgEnvelope(
bodyDict[prop.Name] = ToObject(prop.Value);
}
return new MsgEnvelope(uri, viewerId, uuid, bid, @try, cat, pubSeq, playSeq, new RawBody(bodyDict));
return new MsgEnvelope(uri, viewerId, uuid, bid, retryAttempt, cat, pubSeq, playSeq, new RawBody(bodyDict));
}
private static object? ToObject(JsonElement el) => el.ValueKind switch