diff --git a/deploy/scene-goldens/come-here-s10-board.png b/deploy/scene-goldens/come-here-s10-board.png index 0f8b715..22efeac 100644 Binary files a/deploy/scene-goldens/come-here-s10-board.png and b/deploy/scene-goldens/come-here-s10-board.png differ diff --git a/deploy/scene-goldens/come-here-s10-fpv.png b/deploy/scene-goldens/come-here-s10-fpv.png index ff8888d..e151107 100644 Binary files a/deploy/scene-goldens/come-here-s10-fpv.png and b/deploy/scene-goldens/come-here-s10-fpv.png differ diff --git a/deploy/scene-goldens/come-here-s13-fpv.png b/deploy/scene-goldens/come-here-s13-fpv.png index 1d7a916..1417d8f 100644 Binary files a/deploy/scene-goldens/come-here-s13-fpv.png and b/deploy/scene-goldens/come-here-s13-fpv.png differ diff --git a/deploy/scene-goldens/fire-in-the-webs-s11-board.png b/deploy/scene-goldens/fire-in-the-webs-s11-board.png index fb6bb32..1a59590 100644 Binary files a/deploy/scene-goldens/fire-in-the-webs-s11-board.png and b/deploy/scene-goldens/fire-in-the-webs-s11-board.png differ diff --git a/deploy/scene-goldens/fire-in-the-webs-s11-fpv.png b/deploy/scene-goldens/fire-in-the-webs-s11-fpv.png index 8bad7ae..ba4ba2e 100644 Binary files a/deploy/scene-goldens/fire-in-the-webs-s11-fpv.png and b/deploy/scene-goldens/fire-in-the-webs-s11-fpv.png differ diff --git a/deploy/scene-goldens/fire-in-the-webs-s14-fpv.png b/deploy/scene-goldens/fire-in-the-webs-s14-fpv.png index f395e74..c6bee7d 100644 Binary files a/deploy/scene-goldens/fire-in-the-webs-s14-fpv.png and b/deploy/scene-goldens/fire-in-the-webs-s14-fpv.png differ diff --git a/deploy/scene-goldens/shield-holds-s11-board.png b/deploy/scene-goldens/shield-holds-s11-board.png index e38265d..439c31c 100644 Binary files a/deploy/scene-goldens/shield-holds-s11-board.png and b/deploy/scene-goldens/shield-holds-s11-board.png differ diff --git a/deploy/scene-goldens/shield-holds-s11-fpv.png b/deploy/scene-goldens/shield-holds-s11-fpv.png index c4d908a..2631917 100644 Binary files a/deploy/scene-goldens/shield-holds-s11-fpv.png and b/deploy/scene-goldens/shield-holds-s11-fpv.png differ diff --git a/deploy/scene-goldens/shield-holds-s12-fpv.png b/deploy/scene-goldens/shield-holds-s12-fpv.png index 1920826..924a62d 100644 Binary files a/deploy/scene-goldens/shield-holds-s12-fpv.png and b/deploy/scene-goldens/shield-holds-s12-fpv.png differ diff --git a/deploy/scene-goldens/springing-the-trap-s5-board.png b/deploy/scene-goldens/springing-the-trap-s5-board.png index f2a6711..ea0c948 100644 Binary files a/deploy/scene-goldens/springing-the-trap-s5-board.png and b/deploy/scene-goldens/springing-the-trap-s5-board.png differ diff --git a/deploy/scene-goldens/springing-the-trap-s5-fpv.png b/deploy/scene-goldens/springing-the-trap-s5-fpv.png index d11af9e..e0ba288 100644 Binary files a/deploy/scene-goldens/springing-the-trap-s5-fpv.png and b/deploy/scene-goldens/springing-the-trap-s5-fpv.png differ diff --git a/deploy/scene-goldens/springing-the-trap-s6-fpv.png b/deploy/scene-goldens/springing-the-trap-s6-fpv.png index baf5c56..fbbd61a 100644 Binary files a/deploy/scene-goldens/springing-the-trap-s6-fpv.png and b/deploy/scene-goldens/springing-the-trap-s6-fpv.png differ diff --git a/deploy/scene-goldens/stone-gaze-s10-board.png b/deploy/scene-goldens/stone-gaze-s10-board.png index 833972f..ced6d73 100644 Binary files a/deploy/scene-goldens/stone-gaze-s10-board.png and b/deploy/scene-goldens/stone-gaze-s10-board.png differ diff --git a/deploy/scene-goldens/stone-gaze-s10-fpv.png b/deploy/scene-goldens/stone-gaze-s10-fpv.png index 9057ba5..3c0f7dc 100644 Binary files a/deploy/scene-goldens/stone-gaze-s10-fpv.png and b/deploy/scene-goldens/stone-gaze-s10-fpv.png differ diff --git a/deploy/scene-goldens/stone-gaze-s14-fpv.png b/deploy/scene-goldens/stone-gaze-s14-fpv.png index 5ae0580..ec07f3b 100644 Binary files a/deploy/scene-goldens/stone-gaze-s14-fpv.png and b/deploy/scene-goldens/stone-gaze-s14-fpv.png differ diff --git a/deploy/scene-goldens/the-leap-s8-board.png b/deploy/scene-goldens/the-leap-s8-board.png index f46af2f..a7dfdc8 100644 Binary files a/deploy/scene-goldens/the-leap-s8-board.png and b/deploy/scene-goldens/the-leap-s8-board.png differ diff --git a/deploy/scene-goldens/the-leap-s8-fpv.png b/deploy/scene-goldens/the-leap-s8-fpv.png index 01de0af..ce36aeb 100644 Binary files a/deploy/scene-goldens/the-leap-s8-fpv.png and b/deploy/scene-goldens/the-leap-s8-fpv.png differ diff --git a/deploy/scene-goldens/the-leap-s9-fpv.png b/deploy/scene-goldens/the-leap-s9-fpv.png index b81815f..decf9de 100644 Binary files a/deploy/scene-goldens/the-leap-s9-fpv.png and b/deploy/scene-goldens/the-leap-s9-fpv.png differ diff --git a/deploy/scene-goldens/the-rout-s11-board.png b/deploy/scene-goldens/the-rout-s11-board.png index 53fc7ce..37c7f77 100644 Binary files a/deploy/scene-goldens/the-rout-s11-board.png and b/deploy/scene-goldens/the-rout-s11-board.png differ diff --git a/deploy/scene-goldens/the-rout-s11-fpv.png b/deploy/scene-goldens/the-rout-s11-fpv.png index 7200cff..aa52f83 100644 Binary files a/deploy/scene-goldens/the-rout-s11-fpv.png and b/deploy/scene-goldens/the-rout-s11-fpv.png differ diff --git a/deploy/scene-goldens/the-rout-s4-fpv.png b/deploy/scene-goldens/the-rout-s4-fpv.png index d243a33..d14392c 100644 Binary files a/deploy/scene-goldens/the-rout-s4-fpv.png and b/deploy/scene-goldens/the-rout-s4-fpv.png differ diff --git a/deploy/scene-goldens/wall-comes-down-s6-board.png b/deploy/scene-goldens/wall-comes-down-s6-board.png index 393a8f8..0f390e5 100644 Binary files a/deploy/scene-goldens/wall-comes-down-s6-board.png and b/deploy/scene-goldens/wall-comes-down-s6-board.png differ diff --git a/deploy/scene-goldens/wall-comes-down-s6-fpv.png b/deploy/scene-goldens/wall-comes-down-s6-fpv.png index a9997bb..e78fffc 100644 Binary files a/deploy/scene-goldens/wall-comes-down-s6-fpv.png and b/deploy/scene-goldens/wall-comes-down-s6-fpv.png differ diff --git a/deploy/scene-goldens/wall-comes-down-s7-fpv.png b/deploy/scene-goldens/wall-comes-down-s7-fpv.png index dfe7b80..d687cce 100644 Binary files a/deploy/scene-goldens/wall-comes-down-s7-fpv.png and b/deploy/scene-goldens/wall-comes-down-s7-fpv.png differ diff --git a/deploy/scene-goldens/warp-sniper-s16-board.png b/deploy/scene-goldens/warp-sniper-s16-board.png index 13fa995..6f22f93 100644 Binary files a/deploy/scene-goldens/warp-sniper-s16-board.png and b/deploy/scene-goldens/warp-sniper-s16-board.png differ diff --git a/deploy/scene-goldens/warp-sniper-s16-fpv.png b/deploy/scene-goldens/warp-sniper-s16-fpv.png index 5e262f1..9697cc2 100644 Binary files a/deploy/scene-goldens/warp-sniper-s16-fpv.png and b/deploy/scene-goldens/warp-sniper-s16-fpv.png differ diff --git a/deploy/scene-goldens/warp-sniper-s6-fpv.png b/deploy/scene-goldens/warp-sniper-s6-fpv.png index 0ed564d..41e4054 100644 Binary files a/deploy/scene-goldens/warp-sniper-s6-fpv.png and b/deploy/scene-goldens/warp-sniper-s6-fpv.png differ diff --git a/deploy/scene-goldens/wormhole-heist-s10-fpv.png b/deploy/scene-goldens/wormhole-heist-s10-fpv.png index ffa6153..ed893d8 100644 Binary files a/deploy/scene-goldens/wormhole-heist-s10-fpv.png and b/deploy/scene-goldens/wormhole-heist-s10-fpv.png differ diff --git a/deploy/scene-goldens/wormhole-heist-s5-board.png b/deploy/scene-goldens/wormhole-heist-s5-board.png index 7d1bdc1..02a031d 100644 Binary files a/deploy/scene-goldens/wormhole-heist-s5-board.png and b/deploy/scene-goldens/wormhole-heist-s5-board.png differ diff --git a/deploy/scene-goldens/wormhole-heist-s5-fpv.png b/deploy/scene-goldens/wormhole-heist-s5-fpv.png index 0f924f2..2c537bd 100644 Binary files a/deploy/scene-goldens/wormhole-heist-s5-fpv.png and b/deploy/scene-goldens/wormhole-heist-s5-fpv.png differ diff --git a/packages/web/src/Replay.svelte b/packages/web/src/Replay.svelte index 581d482..954b533 100644 --- a/packages/web/src/Replay.svelte +++ b/packages/web/src/Replay.svelte @@ -2,14 +2,14 @@ import Board from "./Board.svelte"; import FirstPerson from "./fpv/FirstPerson.svelte"; import { untrack } from "svelte"; - import { humanize } from "./net.svelte"; + import { humanize, spellName } from "./net.svelte"; import { tokenArt } from "./art"; import { scheduleFx, type BoardFx } from "./fx"; import { fpFxForEvents, type FpFx } from "./fpv/fx3d"; import { castRay, edgeMid } from "./fpv/raycast"; import { cutawayStand, deepestFacing, aimOfEvents, gatherGlides, hurledIn, shortestArc } from "./fpv/director"; import { prefs } from "./prefs.svelte"; - import { stackSightTrace } from "@wizwar/engine"; + import { cardDef, isPermanentDuration, stackSightTrace } from "@wizwar/engine"; import type { GameEvent, GameView } from "@wizwar/engine"; let { @@ -99,6 +99,51 @@ return p ? tokenArt(`wizard-${p.colorIndex}`, "players") : null; }); + /** The caption's right-hand column — the reel's viewer may never have + * seen the game, so the acting wizard's state stands beside their + * deed: strides still to spend this turn, life, and what they carry, + * display, or suffer. */ + const status = $derived.by(() => { + const v = step.view; + const p = v.players.find((x) => x.id === step.actor); + if (!p) return null; + const acting = v.activePlayerId === p.id; + const steps = acting ? Math.max(0, v.turn.movementAllowance - v.turn.movementUsed) : null; + const chips: string[] = [`♥ ${p.life}`]; + if (p.carriedTreasureId) chips.push("carrying a treasure"); + for (const c of p.displayed) chips.push(cardDef(c.cardId).name.toLowerCase()); + for (const e of v.sustained) { + if (e.targetId !== p.id) continue; + chips.push(`✦ ${spellName(e.cardId).toLowerCase()}${isPermanentDuration(e.remainingTurns) ? "" : ` · ${e.remainingTurns}`}`); + } + if (p.lostTurns > 0) chips.push(`loses ${p.lostTurns} turn${p.lostTurns === 1 ? "" : "s"}`); + if (p.passWallCharges > 0) chips.push(`pass wall × ${p.passWallCharges}`); + if (acting && v.turn.attackUsed) chips.push("attack spent"); + return { steps, chips }; + }); + + /** A change of eyes wipes a slate across the pane: the cut to another + * wizard's sight must read as a cut, never as a wizard teleporting. */ + let lastPov: string | null = null; + let slateSeq = 0; + $effect(() => { + const who = povId; + if (!fp) { lastPov = null; return; } + if (lastPov !== null && lastPov !== who) { + const p = step.view.players.find((x) => x.id === who); + const fx: FpFx = { + id: -(++slateSeq), kind: "slate", name: who, // negative: never a stager id + art: p ? tokenArt(`wizard-${p.colorIndex}`, "players") : null, + t0: performance.now(), dur: 1400 / speed, + }; + fpFx = [...untrack(() => fpFx), fx]; + const t = setTimeout(() => (fpFx = fpFx.filter((f) => f.id !== fx.id)), fx.dur + 80); + lastPov = who; + return () => clearTimeout(t); + } + lastPov = who; + }); + // The reel draws the same sight line the live table shows for an LOS // attack in progress, so a replay-watcher can see how a spell reached them. const sightTrace = $derived(stackSightTrace(step.view)); @@ -464,7 +509,22 @@ g.font = "21px 'Courier Prime', monospace"; const said = lines.slice(0, 2); if (said.length === 0) said.push("…considers the maze."); - said.forEach((line, i) => g.fillText(line, textX, 720 - 32 + i * 26, 1224 - (textX - 28))); + said.forEach((line, i) => g.fillText(line, textX, 720 - 32 + i * 26, 760)); + if (status) { + g.textAlign = "right"; + if (status.steps !== null) { + g.fillStyle = "#efe8d4"; + g.font = "600 34px Oswald, sans-serif"; + g.fillText(String(status.steps), 1280 - 28, 720 - 52); + g.fillStyle = "#a49c86"; + g.font = "600 14px Oswald, sans-serif"; + g.fillText(status.steps === 1 ? "STEP LEFT" : "STEPS LEFT", 1280 - 28, 720 - 34); + } + g.fillStyle = "#d8d2c0"; + g.font = "16px 'Courier Prime', monospace"; + g.fillText(status.chips.join(" · "), 1280 - 28, 720 - 12, 420); + g.textAlign = "left"; + } // The colophon corner. g.fillStyle = "rgba(224, 179, 74, 0.6)"; g.font = "600 20px Oswald, sans-serif"; @@ -552,6 +612,16 @@