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
+40
-12
@@ -221,7 +221,8 @@ export interface GameConfig {
|
||||
* Rev 7 briefly restricted warp sight to aisle corners; the table's
|
||||
* reading prevailed and all board openings carry sight in every revision.
|
||||
* DIMENSIONAL WARP's tokens never do ("There is no L.O.S. through the
|
||||
* warp." — the card face).
|
||||
* warp." — the card face). Rev 8: nothing can be created on a warp token,
|
||||
* and a SPEED bonus turn burns a turn of durations on the hastened wizard.
|
||||
*/
|
||||
deckRev?: number;
|
||||
}
|
||||
@@ -2401,6 +2402,11 @@ function emptySquareTarget(
|
||||
if (state.players.some((p) => p.alive && cellKey(p.position) === key)) return "someone is standing there";
|
||||
if (state.treasures.some((t) => t.position && cellKey(t.position) === key)) return "a treasure rests there";
|
||||
if ((state.groundObjects[key] ?? []).length > 0) return "an object lies there";
|
||||
// "You can't create an object on a DIMENSIONAL WARP" (rules rev 8).
|
||||
if ((state.config.deckRev ?? 1) >= 8 &&
|
||||
state.dimWarps.some((w) => cellKey(w.a) === key || cellKey(w.b) === key)) {
|
||||
return "the warp shimmers there — nothing can form on it";
|
||||
}
|
||||
if (!gameLos(state, caster.position, cell)) return "no line of sight";
|
||||
return cell;
|
||||
}
|
||||
@@ -5266,17 +5272,7 @@ function beginTurnFor(state: GameState, events: GameEvent[], index: number): voi
|
||||
if (s.casterId === player.id) {
|
||||
s.remainingTurns--;
|
||||
if (s.remainingTurns <= 0) {
|
||||
events.push({ type: "spellExpired", effectId: s.id, cardId: s.cardId, target: s.targetId });
|
||||
// Edge-bound spells clean up their edge (WALL OF FIRE burns out).
|
||||
if (s.edge && state.edgeOverrides[s.edge] === "firewall") {
|
||||
delete state.edgeOverrides[s.edge];
|
||||
delete state.createdEdges[s.edge];
|
||||
events.push({ type: "firewallExpired", edge: s.edge });
|
||||
}
|
||||
// GLUE dries out: the cell key rides in the same field.
|
||||
if (s.cardId === "glue" && s.edge) {
|
||||
delete state.gluedCells[s.edge];
|
||||
}
|
||||
expireEffect(state, events, s);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -5339,6 +5335,21 @@ function beginTurnFor(state: GameState, events: GameEvent[], index: number): voi
|
||||
};
|
||||
}
|
||||
|
||||
/** An effect's turns ran out: announce it and clean up what it built. */
|
||||
function expireEffect(state: GameState, events: GameEvent[], s: SustainedEffect): void {
|
||||
events.push({ type: "spellExpired", effectId: s.id, cardId: s.cardId, target: s.targetId });
|
||||
// Edge-bound spells clean up their edge (WALL OF FIRE burns out).
|
||||
if (s.edge && state.edgeOverrides[s.edge] === "firewall") {
|
||||
delete state.edgeOverrides[s.edge];
|
||||
delete state.createdEdges[s.edge];
|
||||
events.push({ type: "firewallExpired", edge: s.edge });
|
||||
}
|
||||
// GLUE dries out: the cell key rides in the same field.
|
||||
if (s.cardId === "glue" && s.edge) {
|
||||
delete state.gluedCells[s.edge];
|
||||
}
|
||||
}
|
||||
|
||||
function doEndTurn(prev: GameState, draw: number): CommandResult {
|
||||
if (draw < 0 || draw > DRAW_PER_TURN) return err(`you may draw 0-${DRAW_PER_TURN} cards`);
|
||||
|
||||
@@ -5408,6 +5419,23 @@ function doEndTurn(prev: GameState, draw: number): CommandResult {
|
||||
if (p.extraTurns > 0) {
|
||||
p.extraTurns--;
|
||||
beginTurnFor(state, events, state.turn.activeIndex);
|
||||
// "If you have SPEED on you while under the influence of a duration
|
||||
// spell, it uses up a turn of that duration" — recipient-counted (FAQ;
|
||||
// rules rev 8). Self-cast durations already burned in beginTurnFor.
|
||||
if ((state.config.deckRev ?? 1) >= 8) {
|
||||
const surviving: SustainedEffect[] = [];
|
||||
for (const s of state.sustained) {
|
||||
if (s.targetId === p.id && s.casterId !== p.id) {
|
||||
s.remainingTurns--;
|
||||
if (s.remainingTurns <= 0) {
|
||||
expireEffect(state, events, s);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
surviving.push(s);
|
||||
}
|
||||
state.sustained = surviving;
|
||||
}
|
||||
events.push({ type: "extraTurnStarted", player: p.id });
|
||||
events.push({ type: "turnStarted", player: p.id, round: state.turn.round });
|
||||
return { ok: true, state, events };
|
||||
|
||||
Reference in New Issue
Block a user