The whole punch list: rev 8, general FAQ, and four promoted UX items
Rules rev 8 closes the last two fidelity threads. A SPEED bonus turn burns a turn of duration spells on the hastened wizard — recipient- counted, per the FAQ's most obscure ruling — with self-cast durations already burning through the normal turn-start sweep (expiry extracted into expireEffect so both paths share the cleanup). And nothing can be created on a DIMENSIONAL WARP token, as the card face always said. The general-topic FAQ sections (Combat, Line of Sight, Monsters, Treasures, and five more) join the rules tab verbatim under a double- ruled divider, completing the FAQ's absorption: card rulings on the cards, general rulings in the rules. The four promoted UX items: cell-target spells now dim ineligible squares (creations mirror emptySquareTarget from the viewer's knowledge, summons want clear sighted squares, teleport BFSes its four spaces exactly as the engine does, stone-to-water lights only stone, dispel lights only creations); hotseat games get the full replay reel, each step seen from its actor's own seat; a "stop announcing attacks on this device" link in the fanfare modal (undo lives beside the notification toggle in the lobby); the corner inspector now serves only the selected casting card, with every examine-a-card peek unified on the centered treatment; and the dev socket URL keys on vite's port instead of hijacking every localhost. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f657144127
commit
d6d34887e3
@@ -13,6 +13,8 @@ import {
|
||||
type GameState,
|
||||
type GameView,
|
||||
type PlayerId,
|
||||
type GameEvent,
|
||||
redactEvent,
|
||||
} from "@wizwar/engine";
|
||||
import { humanize, net } from "./net.svelte";
|
||||
|
||||
@@ -46,6 +48,8 @@ class LocalGame {
|
||||
/** Set while the device should be handed to the named player. */
|
||||
handoffTo = $state<PlayerId | null>(null);
|
||||
log = $state<string[]>([]);
|
||||
/** The finished game as a reel, each step from its actor's own seat. */
|
||||
replaySteps = $state<{ seq: number; actor: PlayerId; events: GameEvent[]; view: GameView }[] | null>(null);
|
||||
view = $derived(
|
||||
this.gameState && this.viewerId ? viewFor(this.gameState, this.viewerId) : null,
|
||||
);
|
||||
@@ -56,6 +60,26 @@ class LocalGame {
|
||||
private activeMs = 0;
|
||||
private lastMoveAt = 0;
|
||||
|
||||
/** Rebuild the whole game as replay steps (finished games only). */
|
||||
buildReplay(): void {
|
||||
if (!this.config || this.gameState?.phase !== "finished") return;
|
||||
const { state } = createGame(this.config);
|
||||
let current = state;
|
||||
const steps: { seq: number; actor: PlayerId; events: GameEvent[]; view: GameView }[] = [];
|
||||
this.commands.forEach((c, i) => {
|
||||
const r = applyCommand(current, c.playerId, c.command);
|
||||
if (!r.ok) return;
|
||||
current = r.state;
|
||||
steps.push({
|
||||
seq: i,
|
||||
actor: c.playerId,
|
||||
events: r.events.map((e) => redactEvent(e, c.playerId)).filter((e): e is GameEvent => e !== null),
|
||||
view: viewFor(current, c.playerId),
|
||||
});
|
||||
});
|
||||
this.replaySteps = steps;
|
||||
}
|
||||
|
||||
hasSave(): boolean {
|
||||
return localStorage.getItem(SAVE_KEY) !== null;
|
||||
}
|
||||
@@ -101,7 +125,7 @@ class LocalGame {
|
||||
seed,
|
||||
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
||||
...(colors ? { colors } : {}),
|
||||
deckRev: 7,
|
||||
deckRev: 8,
|
||||
};
|
||||
const { state, events } = createGame(config);
|
||||
this.config = config;
|
||||
|
||||
Reference in New Issue
Block a user