diff --git a/SVSim.BattleNode/Sessions/Engine/SessionBattleEngine.cs b/SVSim.BattleNode/Sessions/Engine/SessionBattleEngine.cs
index 814867e..947295f 100644
--- a/SVSim.BattleNode/Sessions/Engine/SessionBattleEngine.cs
+++ b/SVSim.BattleNode/Sessions/Engine/SessionBattleEngine.cs
@@ -161,6 +161,14 @@ internal sealed class SessionBattleEngine
/// a card dealt from the seeded deck.
public int HandCardIndex(bool playerSeat, int handPos) => Seat(playerSeat).HandCardList[handPos].Index;
+ /// The real CardId (wire identity) of the in-play follower at
+ /// (0-based, skipping the leader/Class card at ClassAndInPlayCardList[0] — same convention as
+ /// ). Used to assert an opponent reveal seated the substituted card with its
+ /// true identity (M-HC-2): before the reveal the slot holds a hidden dummy (cardId 0); after, the
+ /// engine-resolved actual card carries the wire cardId.
+ public int InPlayCardId(bool playerSeat, int boardPos) =>
+ Seat(playerSeat).ClassAndInPlayCardList[boardPos + 1].CardId;
+
private engine::BattlePlayerBase Seat(bool playerSeat) =>
(_mgr ?? throw new InvalidOperationException("read before Setup")).GetBattlePlayer(playerSeat);
diff --git a/SVSim.UnitTests/BattleNode/Integration/HeadlessConductorTests.cs b/SVSim.UnitTests/BattleNode/Integration/HeadlessConductorTests.cs
index 28c891a..fed1845 100644
--- a/SVSim.UnitTests/BattleNode/Integration/HeadlessConductorTests.cs
+++ b/SVSim.UnitTests/BattleNode/Integration/HeadlessConductorTests.cs
@@ -92,6 +92,28 @@ public class HeadlessConductorTests
private static Dictionary TurnStartBody() => new() { ["spin"] = 0 };
private static Dictionary TurnEndBody() => new() { ["turnState"] = 0 };
+ // An opponent play that REVEALS the played card. The wire shape is taken verbatim from
+ // battle_test_cl2.ndjson's first opponent PlayActions frame:
+ // { playIdx, type:30, knownList:[{idx, cardId, to:30, spellboost:0, attachTarget:""}] }
+ // type 30 == PLAY_HAND; knownList[].idx == the hidden dummy's engine Index; cardId == the real
+ // identity to substitute; to 30 == NetworkCardPlaceState.Field (the card lands in play).
+ private static Dictionary RevealPlayBody(int idx, long cardId) => new()
+ {
+ ["playIdx"] = idx,
+ ["type"] = 30,
+ ["knownList"] = new List