The boobytrap keeps its secret
The public boobytrapPlaced event and every subsequent view shipped the four token cells in casting order — with the real trap always first, a tell readable by any client. Tokens are now stored and broadcast in canonical cell order while the truth lives only in realKey (and the caster's private event). Deterministic sort, no rng touched, replays unaffected; a test casts in reverse order and checks that position whispers nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
co-authored by
Claude Fable 5
parent
8ba0a934cc
commit
eaa672236b
@@ -2013,8 +2013,13 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
|
||||
}
|
||||
const uniq = new Set(cells.map(cellKey));
|
||||
if (uniq.size !== 4) return "the four tokens go on four different squares";
|
||||
state.boobytraps.push({ casterId: caster.id, cells: [...cells], realKey: cellKey(cells[0]!) });
|
||||
events.push({ type: "boobytrapPlaced", caster: caster.id, cells: [...cells] });
|
||||
// The FIRST cell in the command is the real trap — but that truth
|
||||
// lives only in realKey. Stored and broadcast in canonical order,
|
||||
// the tokens carry no tell: position must never whisper which one
|
||||
// bites (the placement order once did, to anyone reading events).
|
||||
const laid = [...cells].sort((a, b) => cellKey(a).localeCompare(cellKey(b)));
|
||||
state.boobytraps.push({ casterId: caster.id, cells: laid, realKey: cellKey(cells[0]!) });
|
||||
events.push({ type: "boobytrapPlaced", caster: caster.id, cells: laid.map((c) => ({ ...c })) });
|
||||
events.push({ type: "boobytrapPlacedPrivate", visibleTo: caster.id, realCell: cells[0]! });
|
||||
return null;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user