fix(battlenode): PlayedCardTribe degrades to 0 not empty; clan/tribe builder tests (M-HC-4e review)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-07 00:23:07 -04:00
parent 693fba5003
commit d3508d7bd4
3 changed files with 38 additions and 6 deletions

View File

@@ -61,7 +61,7 @@ internal sealed class PlayActionsHandler : IFrameHandler
// value would miss it). Prod always emits both on every knownList entry: clan as the int ClanType
// ordinal, tribe as the comma-joined int TribeType string ("0" for none). Same senderSeat mapping.
int playedClan = ctx.Engine.PlayedCardClan(senderSeat, playIdx, fallback: 0);
string playedTribe = ctx.Engine.PlayedCardTribe(senderSeat, playIdx);
string playedTribe = ctx.Engine.PlayedCardTribe(senderSeat, playIdx, fallback: "0");
var played = KnownListBuilder.BuildPlayedCard(
deckMap, playIdx, orderList, cost: playedCost, spellboost: playedSpellboost,

View File

@@ -302,13 +302,18 @@ internal sealed class SessionBattleEngine
/// <see cref="BattleCardBase.Tribe"/>, whose getter folds in any skill-applied tribe CHANGE/ADD over
/// <c>BaseParameter.Tribe</c> (and drops ALL when the resolved list has ≥2 entries) — so the wire carries
/// the LIVE tribe, the faithful value over the static card-master one.
/// <para>Same post-resolution zone search as <see cref="PlayedCardCost"/>; no engine / no card → "" (an
/// engine that isn't owned this session emits no card, so the caller's BuildPlayedCard never fires).</para></summary>
public string PlayedCardTribe(bool playerSeat, int idx)
/// <para>Same post-resolution zone search + degrade-to-<paramref name="fallback"/> contract as
/// <see cref="PlayedCardClan"/>: no engine / no card → <paramref name="fallback"/> (default <c>"0"</c>, the
/// prod no-tribe form — NEVER empty, which is wire-illegal: prod always sends tribe as a non-empty string,
/// the client reads it via <c>item.Value.ToString()</c> at NetworkBattleReceiver.cs:2382). The degrade is
/// LIVE, not dead: a second concurrent battle that loses the single-active-engine gate has <c>_mgr is null</c>
/// yet still emits a knownList entry (KnownListBuilder.BuildPlayedCard gates on the deck map, not engine
/// ownership), so this path must hand back a legal wire value.</para></summary>
public string PlayedCardTribe(bool playerSeat, int idx, string fallback = "0")
{
if (_mgr is null) return string.Empty;
if (_mgr is null) return fallback;
var card = FindByIndex(Seat(playerSeat), idx);
if (card is null) return string.Empty;
if (card is null) return fallback;
var tribe = card.Tribe;
// Prod's no-tribe form is the single "0" (TribeType.ALL == 0), never an empty string; an empty list
// (defensive) renders the same "0".