refactor(battlenode): centralize inbound wire-key literals in WireKeys (§C)

Behavior-preserving; 231 BattleNode tests green (capture-conformance suite drives
real prod frames, so a wrong constant would fail).

New Sessions/Dispatch/WireKeys.cs holds the 28 inbound-body read keys (orderList /
keyAction / targetList / uList field names). KnownListBuilder, PlayActionsHandler,
EchoHandler, and BattleFrames.ExtractIdxList now read through it instead of repeated
inline strings, so a parse-side typo ("isSelf" vs "IsSelf") can no longer silently
degrade token resolution. Outbound [JsonPropertyName] attributes left as-is (already
single-source per DTO).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-05 07:30:02 -04:00
parent 3e8901eec3
commit 7e167b1cef
5 changed files with 100 additions and 50 deletions

View File

@@ -6,7 +6,7 @@ namespace SVSim.BattleNode.Sessions.Dispatch;
/// <summary>Pure transforms from the active player's RawBody sub-structures to the opponent-facing
/// shapes. No session state, no wire I/O — unit-testable in isolation. RawBody nested values arrive
/// as <c>Dictionary&lt;string,object?&gt;</c> / <c>List&lt;object?&gt;</c> with numeric leaves boxed
/// as long/int/double (see MsgEnvelope.FromJson).</summary>
/// as long/int/double (see MsgEnvelope.FromJson). Inbound wire keys come from <see cref="WireKeys"/>.</summary>
internal static class KnownListBuilder
{
/// <summary>The played card's knownList entry, or null when its identity can't be synthesized
@@ -32,11 +32,11 @@ internal static class KnownListBuilder
foreach (var op in ops)
{
if (op is not IDictionary<string, object?> opDict) continue;
if (!opDict.TryGetValue("move", out var moveRaw) || moveRaw is not IDictionary<string, object?> move) continue;
if (move.TryGetValue("idx", out var idxRaw) && idxRaw is IEnumerable<object?> idxList)
if (!opDict.TryGetValue(WireKeys.Move, out var moveRaw) || moveRaw is not IDictionary<string, object?> move) continue;
if (move.TryGetValue(WireKeys.Idx, out var idxRaw) && idxRaw is IEnumerable<object?> idxList)
{
foreach (var i in idxList)
if (AsLong(i) == playIdx && move.TryGetValue("to", out var toRaw))
if (AsLong(i) == playIdx && move.TryGetValue(WireKeys.To, out var toRaw))
return (int)AsLong(toRaw);
}
}
@@ -61,16 +61,16 @@ internal static class KnownListBuilder
foreach (var op in ops)
{
if (op is not IDictionary<string, object?> opDict) continue;
if (!opDict.TryGetValue("add", out var addRaw) || addRaw is not IDictionary<string, object?> add) continue;
if (!opDict.TryGetValue(WireKeys.Add, out var addRaw) || addRaw is not IDictionary<string, object?> add) continue;
add.TryGetValue("isSelf", out var isSelfRaw);
add.TryGetValue(WireKeys.IsSelf, out var isSelfRaw);
var isSelf = (CardOwner)(int)AsLong(isSelfRaw);
if (!add.TryGetValue("card", out var cardRaw) || cardRaw is not IDictionary<string, object?> card) continue;
if (!card.TryGetValue("cardId", out var cardIdRaw)) continue; // candidates/isChoice → no identity yet
if (!add.TryGetValue(WireKeys.Card, out var cardRaw) || cardRaw is not IDictionary<string, object?> card) continue;
if (!card.TryGetValue(WireKeys.CardId, out var cardIdRaw)) continue; // candidates/isChoice → no identity yet
var cardId = AsLong(cardIdRaw);
if (!add.TryGetValue("idx", out var idxRaw) || idxRaw is not IEnumerable<object?> idxList) continue;
if (!add.TryGetValue(WireKeys.Idx, out var idxRaw) || idxRaw is not IEnumerable<object?> idxList) continue;
foreach (var i in idxList)
yield return new MinedToken((int)AsLong(i), cardId, isSelf);
}
@@ -98,8 +98,8 @@ internal static class KnownListBuilder
foreach (var ka in kaEntries)
{
if (ka is not IDictionary<string, object?> kaDict) continue;
if (!kaDict.TryGetValue("selectCard", out var scRaw) || scRaw is not IDictionary<string, object?> sc) continue;
if (!sc.TryGetValue("cardId", out var idsRaw) || idsRaw is not IEnumerable<object?> ids) continue;
if (!kaDict.TryGetValue(WireKeys.SelectCard, out var scRaw) || scRaw is not IDictionary<string, object?> sc) continue;
if (!sc.TryGetValue(WireKeys.CardId, out var idsRaw) || idsRaw is not IEnumerable<object?> ids) continue;
foreach (var id in ids) picks.Add(AsLong(id));
}
}
@@ -108,10 +108,10 @@ internal static class KnownListBuilder
foreach (var op in ops)
{
if (op is not IDictionary<string, object?> opDict) continue;
if (!opDict.TryGetValue("add", out var addRaw) || addRaw is not IDictionary<string, object?> add) continue;
if (!add.ContainsKey("isChoice")) continue;
if (!add.TryGetValue("card", out var cardRaw) || cardRaw is not IDictionary<string, object?> card) continue;
if (!card.TryGetValue("candidates", out var candRaw) || candRaw is not IEnumerable<object?> candidates) continue;
if (!opDict.TryGetValue(WireKeys.Add, out var addRaw) || addRaw is not IDictionary<string, object?> add) continue;
if (!add.ContainsKey(WireKeys.IsChoice)) continue;
if (!add.TryGetValue(WireKeys.Card, out var cardRaw) || cardRaw is not IDictionary<string, object?> card) continue;
if (!card.TryGetValue(WireKeys.Candidates, out var candRaw) || candRaw is not IEnumerable<object?> candidates) continue;
// The chosen cardId is the candidate that the active player picked (∈ picks). One per op.
long? chosen = null;
@@ -122,10 +122,10 @@ internal static class KnownListBuilder
}
if (chosen is null) continue; // no pick in this op's pool — skip (no desync, just no record)
add.TryGetValue("isSelf", out var isSelfRaw);
add.TryGetValue(WireKeys.IsSelf, out var isSelfRaw);
var isSelf = (CardOwner)(int)AsLong(isSelfRaw);
if (!add.TryGetValue("idx", out var idxRaw) || idxRaw is not IEnumerable<object?> idxList) continue;
if (!add.TryGetValue(WireKeys.Idx, out var idxRaw) || idxRaw is not IEnumerable<object?> idxList) continue;
foreach (var i in idxList)
yield return new MinedToken((int)AsLong(i), chosen.Value, isSelf);
}
@@ -153,20 +153,20 @@ internal static class KnownListBuilder
foreach (var op in ops)
{
if (op is not IDictionary<string, object?> opDict) continue;
if (!opDict.TryGetValue("add", out var addRaw) || addRaw is not IDictionary<string, object?> add) continue;
if (!opDict.TryGetValue(WireKeys.Add, out var addRaw) || addRaw is not IDictionary<string, object?> add) continue;
if (!add.TryGetValue("card", out var cardRaw) || cardRaw is not IDictionary<string, object?> card) continue;
if (card.ContainsKey("cardId")) continue; // concrete token → MineAddOps
if (!card.TryGetValue("baseIdx", out var baseRaw)) continue; // not a copy (candidates → MineChoicePicks)
if (!add.TryGetValue(WireKeys.Card, out var cardRaw) || cardRaw is not IDictionary<string, object?> card) continue;
if (card.ContainsKey(WireKeys.CardId)) continue; // concrete token → MineAddOps
if (!card.TryGetValue(WireKeys.BaseIdx, out var baseRaw)) continue; // not a copy (candidates → MineChoicePicks)
if (baseRaw is string) continue; // private-group copy → string baseIdx, skip
var baseIdx = (int)AsLong(baseRaw);
add.TryGetValue("isSelf", out var isSelfRaw);
add.TryGetValue(WireKeys.IsSelf, out var isSelfRaw);
var isSelf = (CardOwner)(int)AsLong(isSelfRaw);
var map = isSelf == CardOwner.Self ? selfMap : otherMap;
if (!map.TryGetValue(baseIdx, out var cardId)) continue; // unknown source → degrade
if (!add.TryGetValue("idx", out var idxRaw) || idxRaw is not IEnumerable<object?> idxList) continue;
if (!add.TryGetValue(WireKeys.Idx, out var idxRaw) || idxRaw is not IEnumerable<object?> idxList) continue;
foreach (var i in idxList)
yield return new MinedToken((int)AsLong(i), cardId, isSelf);
}
@@ -185,19 +185,19 @@ internal static class KnownListBuilder
foreach (var e in entries)
{
if (e is not IDictionary<string, object?> d) continue;
d.TryGetValue("type", out var typeRaw);
d.TryGetValue(WireKeys.Type, out var typeRaw);
var type = (KeyActionType)(int)AsLong(typeRaw);
if (type is not (KeyActionType.Choice or KeyActionType.HaveBeforeSkillChoice)) continue;
d.TryGetValue("cardId", out var cardIdRaw);
d.TryGetValue(WireKeys.CardId, out var cardIdRaw);
var cardId = AsLong(cardIdRaw);
SelectCardEntry? selectCard = null;
if (d.TryGetValue("selectCard", out var scRaw) && scRaw is IDictionary<string, object?> sc)
if (d.TryGetValue(WireKeys.SelectCard, out var scRaw) && scRaw is IDictionary<string, object?> sc)
{
sc.TryGetValue("open", out var openRaw);
sc.TryGetValue(WireKeys.Open, out var openRaw);
var open = (ChoiceVisibility)(int)AsLong(openRaw);
if (open != ChoiceVisibility.Hidden && sc.TryGetValue("cardId", out var idsRaw) && idsRaw is IEnumerable<object?> ids)
if (open != ChoiceVisibility.Hidden && sc.TryGetValue(WireKeys.CardId, out var idsRaw) && idsRaw is IEnumerable<object?> ids)
selectCard = new SelectCardEntry(ids.Select(AsLong).ToList(), open);
}
result.Add(new KeyActionEntry(type, cardId, selectCard));
@@ -214,8 +214,8 @@ internal static class KnownListBuilder
foreach (var e in entries)
{
if (e is not IDictionary<string, object?> d) continue;
d.TryGetValue("targetIdx", out var targetIdxRaw);
d.TryGetValue("isSelf", out var isSelfRaw);
d.TryGetValue(WireKeys.TargetIdx, out var targetIdxRaw);
d.TryGetValue(WireKeys.IsSelf, out var isSelfRaw);
result.Add(new OppoTargetEntry(
TargetIdx: (int)AsLong(targetIdxRaw),
IsSelf: (CardOwner)(int)AsLong(isSelfRaw)));
@@ -238,11 +238,11 @@ internal static class KnownListBuilder
{
if (e is not IDictionary<string, object?> d) continue;
d.TryGetValue("idxList", out var idxRaw);
d.TryGetValue("from", out var fromRaw);
d.TryGetValue("to", out var toRaw);
d.TryGetValue("isSelf", out var isSelfRaw);
d.TryGetValue("skill", out var skillRaw);
d.TryGetValue(WireKeys.IdxList, out var idxRaw);
d.TryGetValue(WireKeys.From, out var fromRaw);
d.TryGetValue(WireKeys.To, out var toRaw);
d.TryGetValue(WireKeys.IsSelf, out var isSelfRaw);
d.TryGetValue(WireKeys.Skill, out var skillRaw);
result.Add(new UnapprovedCardEntry(
IdxList: AsIntList(idxRaw) ?? new List<int>(),
@@ -250,13 +250,13 @@ internal static class KnownListBuilder
To: (int)AsLong(toRaw),
IsSelf: (CardOwner)(int)AsLong(isSelfRaw),
Skill: skillRaw as string ?? "",
CardId: d.TryGetValue("cardId", out var c) ? AsLong(c) : null,
Clan: d.TryGetValue("clan", out var cl) ? (int)AsLong(cl) : null,
Cost: d.TryGetValue("cost", out var co) ? (int)AsLong(co) : null,
SkillKeyCardIdx: AsIntList(d.TryGetValue("skillKeyCardIdx", out var sk) ? sk : null),
RandomTargetIdx: AsIntList(d.TryGetValue("randomTargetIdx", out var rt) ? rt : null),
IsInvoke: d.TryGetValue("isInvoke", out var iv) ? AsLong(iv) != 0 : null,
AttachTarget: d.TryGetValue("attachTarget", out var at) ? at as string : null));
CardId: d.TryGetValue(WireKeys.CardId, out var c) ? AsLong(c) : null,
Clan: d.TryGetValue(WireKeys.Clan, out var cl) ? (int)AsLong(cl) : null,
Cost: d.TryGetValue(WireKeys.Cost, out var co) ? (int)AsLong(co) : null,
SkillKeyCardIdx: AsIntList(d.TryGetValue(WireKeys.SkillKeyCardIdx, out var sk) ? sk : null),
RandomTargetIdx: AsIntList(d.TryGetValue(WireKeys.RandomTargetIdx, out var rt) ? rt : null),
IsInvoke: d.TryGetValue(WireKeys.IsInvoke, out var iv) ? AsLong(iv) != 0 : null,
AttachTarget: d.TryGetValue(WireKeys.AttachTarget, out var at) ? at as string : null));
}
return result.Count == 0 ? null : result;
}