diff --git a/SVSim.BattleNode/Protocol/Bodies/PlayActionsBroadcastBody.cs b/SVSim.BattleNode/Protocol/Bodies/PlayActionsBroadcastBody.cs
new file mode 100644
index 0000000..b93f9ac
--- /dev/null
+++ b/SVSim.BattleNode/Protocol/Bodies/PlayActionsBroadcastBody.cs
@@ -0,0 +1,30 @@
+using System.Text.Json.Serialization;
+
+namespace SVSim.BattleNode.Protocol.Bodies;
+
+/// Opponent-facing PlayActions frame the node synthesizes from the active player's
+/// send. KnownList reveals the played card's identity (null = token reveal deferred, see
+/// the deterministic-turn slice). OppoTargetList is the renamed targetList
+/// (independent of KnownList — a targeted hand play carries both). Both omitted when null via the
+/// envelope's WhenWritingNull policy.
+public sealed record PlayActionsBroadcastBody(
+ [property: JsonPropertyName("playIdx")] int PlayIdx,
+ [property: JsonPropertyName("type")] int Type,
+ [property: JsonPropertyName("knownList")] IReadOnlyList? KnownList,
+ [property: JsonPropertyName("oppoTargetList")] IReadOnlyList? OppoTargetList) : IMsgBody;
+
+/// One revealed card in a knownList. Vanilla slice fills cardId from the sender's
+/// deck map and leaves spellboost 0 / attachTarget "" (cost/clan/tribe deferred to the card-master
+/// port — the receiver re-derives them from cardId).
+public sealed record KnownCardEntry(
+ [property: JsonPropertyName("idx")] int Idx,
+ [property: JsonPropertyName("cardId")] long CardId,
+ [property: JsonPropertyName("to")] int To,
+ [property: JsonPropertyName("spellboost")] int Spellboost,
+ [property: JsonPropertyName("attachTarget")] string AttachTarget);
+
+/// Renamed targetList entry. isSelf is actor-relative and passes through
+/// verbatim — no perspective flip (bullet-3 audit F2).
+public sealed record OppoTargetEntry(
+ [property: JsonPropertyName("targetIdx")] int TargetIdx,
+ [property: JsonPropertyName("isSelf")] int IsSelf);