FEAR stains the forbidden ground in the cockpit too
The aura's warp-walking diamond — dreadDistance ≤ 3, extracted to one shared fearCells so the board's dashed ring, the cockpit's floor stain, and the engine's refusal can never disagree — now tints the flagstones violet in first person, breathing like the board's aura, beneath the dread ring already at the bearer's feet. 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
203b6101bd
commit
19888f9e84
@@ -16,6 +16,7 @@ import {
|
|||||||
type SustainedEffect,
|
type SustainedEffect,
|
||||||
type TreasureState,
|
type TreasureState,
|
||||||
type TurnState,
|
type TurnState,
|
||||||
|
dreadDistance,
|
||||||
} from "./game";
|
} from "./game";
|
||||||
|
|
||||||
export interface PlayerPublicView {
|
export interface PlayerPublicView {
|
||||||
@@ -193,6 +194,22 @@ export function viewFor(state: GameState, playerId: PlayerId): GameView {
|
|||||||
* reflects what this player knows (illusion walls they believe in block it).
|
* reflects what this player knows (illusion walls they believe in block it).
|
||||||
* The basis for the client's "dim the ineligible squares" targeting aid.
|
* The basis for the client's "dim the ineligible squares" targeting aid.
|
||||||
*/
|
*/
|
||||||
|
/** Every square inside a living FEAR-bearer's aura — dreadDistance walks
|
||||||
|
* through warps, the engine's own yardstick, so the painted aura and the
|
||||||
|
* movement refusal can never disagree. */
|
||||||
|
export function fearCells(view: GameView): Set<string> {
|
||||||
|
const out = new Set<string>();
|
||||||
|
for (const fp of view.players) {
|
||||||
|
if (!fp.alive) continue;
|
||||||
|
if (!view.sustained.some((e) => e.cardId === "fear" && e.targetId === fp.id)) continue;
|
||||||
|
for (const k of Object.keys(view.board.cells)) {
|
||||||
|
const [x, y] = k.split(",").map(Number) as [number, number];
|
||||||
|
if (dreadDistance(view.board, fp.position, { x, y }) <= 3) out.add(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
export function sightedCellsFor(view: GameView): Set<string> {
|
export function sightedCellsFor(view: GameView): Set<string> {
|
||||||
const out = new Set<string>();
|
const out = new Set<string>();
|
||||||
const me = view.players.find((p) => p.id === view.you);
|
const me = view.players.find((p) => p.id === view.you);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { Component } from "svelte";
|
import type { Component } from "svelte";
|
||||||
import type { GameView, SightTrace } from "@wizwar/engine";
|
import type { GameView, SightTrace } from "@wizwar/engine";
|
||||||
import type { Side } from "@wizwar/engine";
|
import type { Side } from "@wizwar/engine";
|
||||||
import { dreadDistance } from "@wizwar/engine";
|
import { fearCells } from "@wizwar/engine";
|
||||||
import type { BoardFx } from "./fx";
|
import type { BoardFx } from "./fx";
|
||||||
import { colorIndexOf as sharedColorIndex, wizardColor } from "./colors";
|
import { colorIndexOf as sharedColorIndex, wizardColor } from "./colors";
|
||||||
import { CREATURE_ART, objectArt, TERRAIN_ART, tokenArt } from "./art";
|
import { CREATURE_ART, objectArt, TERRAIN_ART, tokenArt } from "./art";
|
||||||
@@ -212,18 +212,7 @@
|
|||||||
// ("even if walls separate you"), never diagonally — and the maze wraps,
|
// ("even if walls separate you"), never diagonally — and the maze wraps,
|
||||||
// so the diamond continues from the opposite rim. The engine's own
|
// so the diamond continues from the opposite rim. The engine's own
|
||||||
// yardstick, so the aura and the refusal can never disagree.
|
// yardstick, so the aura and the refusal can never disagree.
|
||||||
const fearCells = $derived.by(() => {
|
const fearAura = $derived(fearCells(view));
|
||||||
const out = new Set<string>();
|
|
||||||
for (const fp of view.players) {
|
|
||||||
if (!fp.alive) continue;
|
|
||||||
if (!view.sustained.some((e) => e.cardId === "fear" && e.targetId === fp.id)) continue;
|
|
||||||
for (const k of Object.keys(view.board.cells)) {
|
|
||||||
const [x, y] = k.split(",").map(Number) as [number, number];
|
|
||||||
if (dreadDistance(view.board, fp.position, { x, y }) <= 3) out.add(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Warp pairs share a letter, like the printed openings on the real boards.
|
// Warp pairs share a letter, like the printed openings on the real boards.
|
||||||
const warpLetters = $derived.by(() => {
|
const warpLetters = $derived.by(() => {
|
||||||
@@ -692,7 +681,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
<!-- FEAR's bubble: the three-space diamond, through walls, wrapping the rim -->
|
<!-- FEAR's bubble: the three-space diamond, through walls, wrapping the rim -->
|
||||||
{#each fearCells as key (key)}
|
{#each fearAura as key (key)}
|
||||||
{@const bx = Number(key.split(",")[0])}
|
{@const bx = Number(key.split(",")[0])}
|
||||||
{@const by = Number(key.split(",")[1])}
|
{@const by = Number(key.split(",")[1])}
|
||||||
<rect x={bx * CELL + 1.5} y={by * CELL + 1.5} width={CELL - 3} height={CELL - 3}
|
<rect x={bx * CELL + 1.5} y={by * CELL + 1.5} width={CELL - 3} height={CELL - 3}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
import { terrainFallback, TERRAIN3D } from "./terrain3d";
|
import { terrainFallback, TERRAIN3D } from "./terrain3d";
|
||||||
import { tokenArt } from "../art";
|
import { tokenArt } from "../art";
|
||||||
import { PLAYER_COLORS } from "../colors";
|
import { PLAYER_COLORS } from "../colors";
|
||||||
import type { GameView } from "@wizwar/engine";
|
import { fearCells } from "@wizwar/engine";
|
||||||
|
import type { GameView } from "@wizwar/engine";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
view,
|
view,
|
||||||
@@ -120,6 +121,23 @@
|
|||||||
}
|
}
|
||||||
return px;
|
return px;
|
||||||
}
|
}
|
||||||
|
// The dread-stained squares, rasterized once per view (null = no fear).
|
||||||
|
const dreadCache = new WeakMap<object, Uint8Array | null>();
|
||||||
|
function dreadGridOf(v: GameView): Uint8Array | null {
|
||||||
|
if (dreadCache.has(v)) return dreadCache.get(v)!;
|
||||||
|
const cells = fearCells(v);
|
||||||
|
let grid: Uint8Array | null = null;
|
||||||
|
if (cells.size > 0) {
|
||||||
|
grid = new Uint8Array(v.board.width * v.board.height);
|
||||||
|
for (const k of cells) {
|
||||||
|
const [fx, fy] = k.split(",").map(Number) as [number, number];
|
||||||
|
if (fx >= 0 && fy >= 0 && fx < v.board.width && fy < v.board.height) grid[fy * v.board.width + fx] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dreadCache.set(v, grid);
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
let frame: ImageData | null = null;
|
let frame: ImageData | null = null;
|
||||||
|
|
||||||
// The floor wears decals: home emblems in their owners' colors, pits
|
// The floor wears decals: home emblems in their owners' colors, pits
|
||||||
@@ -222,6 +240,10 @@
|
|||||||
if (lx >= 0 && ly >= 0 && lx < litBw && ly < litBh) litGrid[ly * litBw + lx] = 1;
|
if (lx >= 0 && ly >= 0 && lx < litBw && ly < litBh) litGrid[ly * litBw + lx] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// FEAR's aura stains the forbidden ground itself, as the board draws
|
||||||
|
// its dashed diamond — the same warp-walking yardstick behind both.
|
||||||
|
const dreadGrid = dreadGridOf(view);
|
||||||
|
const dreadBlend = dreadGrid ? 0.16 + 0.07 * Math.sin(time / 400) : 0;
|
||||||
for (let row = 0; row < H; row++) {
|
for (let row = 0; row < H; row++) {
|
||||||
const below = row > half;
|
const below = row > half;
|
||||||
const dz = below ? row - half : half - row;
|
const dz = below ? row - half : half - row;
|
||||||
@@ -260,6 +282,14 @@
|
|||||||
const gx = (wx - u) | 0, gy = (wy - v) | 0;
|
const gx = (wx - u) | 0, gy = (wy - v) | 0;
|
||||||
if (gx < 0 || gy < 0 || gx >= litBw || gy >= litBh || !litGrid[gy * litBw + gx]) sh *= 0.3;
|
if (gx < 0 || gy < 0 || gx >= litBw || gy >= litBh || !litGrid[gy * litBw + gx]) sh *= 0.3;
|
||||||
}
|
}
|
||||||
|
if (dreadGrid && below) {
|
||||||
|
const gx = (wx - u) | 0, gy = (wy - v) | 0;
|
||||||
|
if (gx >= 0 && gy >= 0 && gx < litBw && gy < litBh && dreadGrid[gy * litBw + gx]) {
|
||||||
|
r = r * (1 - dreadBlend) + 122 * dreadBlend;
|
||||||
|
g = g * (1 - dreadBlend) + 74 * dreadBlend;
|
||||||
|
b = b * (1 - dreadBlend) + 138 * dreadBlend;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (dec) {
|
if (dec) {
|
||||||
const hx = (wx - u) | 0, hy = (wy - v) | 0;
|
const hx = (wx - u) | 0, hy = (wy - v) | 0;
|
||||||
if (hx >= 0 && hy >= 0 && hx < dec.bw && hy < dec.bh) {
|
if (hx >= 0 && hy >= 0 && hx < dec.bw && hy < dec.bh) {
|
||||||
|
|||||||
Reference in New Issue
Block a user