diff --git a/packages/engine/src/game.ts b/packages/engine/src/game.ts index ab35d15..eb8a90c 100644 --- a/packages/engine/src/game.ts +++ b/packages/engine/src/game.ts @@ -61,6 +61,8 @@ export interface PlayerState { hand: CardInstance[]; /** Instance ids of cards displayed face-up (Master Key, Wizardblade, stones). */ displayed: string[]; + /** The hand held at the moment of elimination — for the post-game reveal. */ + finalHand?: CardInstance[]; carriedTreasureId: string | null; lostTurns: number; extraTurns: number; @@ -4473,6 +4475,7 @@ function applyDamage( if (target.life > 0) return; target.alive = false; + target.finalHand = [...target.hand]; events.push({ type: "died", player: target.id, killedBy: attackerId }); events.push({ type: "playerEliminated", player: target.id, reason: "killed" }); state.sustained = state.sustained.filter((s) => s.targetId !== target.id && s.casterId !== target.id); @@ -4656,6 +4659,7 @@ function checkVictory(state: GameState, events: GameEvent[]): void { }); if (lost) { p.alive = false; + p.finalHand = [...p.hand]; state.discard.push(...p.hand.splice(0)); p.displayed = []; events.push({ type: "playerEliminated", player: p.id, reason: "treasuresLost" }); diff --git a/packages/engine/src/view.ts b/packages/engine/src/view.ts index 597210f..3c10e71 100644 --- a/packages/engine/src/view.ts +++ b/packages/engine/src/view.ts @@ -110,7 +110,7 @@ export function viewFor(state: GameState, playerId: PlayerId): GameView { })), yourHand: you ? [...you.hand] : [], revealedHands: state.phase === "finished" - ? Object.fromEntries(state.players.map((p) => [p.id, [...p.hand]])) + ? Object.fromEntries(state.players.map((p) => [p.id, p.alive ? [...p.hand] : [...(p.finalHand ?? p.hand)]])) : null, treasures: state.treasures.map((t) => ({ ...t })), deckCount: state.deck.length, diff --git a/packages/engine/test/casting.test.ts b/packages/engine/test/casting.test.ts index 1311c64..32caf97 100644 --- a/packages/engine/test/casting.test.ts +++ b/packages/engine/test/casting.test.ts @@ -315,3 +315,26 @@ describe("speedstone", () => { expect(state.turn.movementAllowance).toBe(4); }); }); + +describe("the post-game reveal", () => { + it("shows an eliminated player's hand as they fell, not their emptied one", async () => { + const { viewFor } = await import("../src/view"); + let { state } = newGame(); + state = toRound2(state); + const attacker = activePlayer(state); + const defender = state.players.find((p) => p.id !== attacker.id)!; + defender.position = { ...attacker.position }; + defender.life = 1; + defender.hand = [ + { instanceId: "fireball#J", cardId: "fireball" }, + { instanceId: "number-6#J", cardId: "number-6" }, + ]; + state = must(state, attacker.id, { type: "punch", targetId: defender.id }); + state = must(state, defender.id, { type: "pass" }); + expect(state.phase).toBe("finished"); + // The killer took the cards — but the reveal remembers the fallen hand. + const view = viewFor(state, attacker.id); + const fallen = view.revealedHands?.[defender.id] ?? []; + expect(fallen.map((c) => c.cardId).sort()).toEqual(["fireball", "number-6"]); + }); +}); diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index ca84da6..9430438 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -1219,6 +1219,7 @@