Credibility pass: one voice, no scars
A three-reviewer sweep for tells of piecemeal machine generation, every finding verified against the code before touching it. No behavior changes; the full suite passes unchanged (plus two strengthened pins). Engine: removed four void-silenced fossils (a parseEdgeKey call voided where it stood, stoneEffect's ignored cardId parameter, the actualTarget remnant in doCast, a voided loop variable in shadow upkeep); fixed the initialize-then-overwrite narration in spawnCreature; replaced a filter(() => false) no-op; waterwall now rides waveFromEdge instead of carrying its own verbatim copy (and the single-caller washBack wrapper went with it); blind wall-bumps and LOS blockers each collapsed to one implementation; the wand-id list and the "permanent" duration sentinel became named constants; the ambush number local no longer shadows the imported numberValue function; assorted reviewer-aimed phrasings rewritten as the constraints they guard. Server/deploy: the protocol header now documents all eleven message types; dropped an eslint pragma with no eslint, a test script with no tests, and an rsync exclude anchored at a path that never existed (the real data/ dir now excluded); the Caddy vhost has one source of truth; stale "pending DNS" note removed — the record resolves. Web: ~90 lines of CSS swallowed verbatim into a mobile media query deduplicated; the reduced-motion guard on the board now actually stops the marked-cell pulse; one shared color module replaces two drifted palettes; an orphaned doc comment rejoined its function. Tests: the ten-times-pasted helper block became test/helpers.ts; wave-numbered files renamed for the behaviors they pin; deliberation comments and void-ed corpses of unwritten assertions deleted; silent seed-dependent early-returns now fail loudly; one assertion that compared a value to itself now pins the home-translation it meant to; the stored-log single-number command form gained the explicit compatibility test it deserved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f62fcf2510
commit
695307daa8
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { attentionLabel, net } from "./net.svelte";
|
||||
import Board from "./Board.svelte";
|
||||
import { PLAYER_COLORS, wizardColor } from "./colors";
|
||||
import Card from "./Card.svelte";
|
||||
import Help from "./Help.svelte";
|
||||
import Replay from "./Replay.svelte";
|
||||
@@ -23,7 +24,7 @@
|
||||
let peekCard = $state<CardInstance | null>(null);
|
||||
/** When the peeked card is a creature on the board, its live stats ride along. */
|
||||
let peekCreatureId = $state<string | null>(null);
|
||||
let helpTab = $state<"play" | "rules" | "cards" | "about">("play");
|
||||
let helpTab = $state<"play" | "rules" | "cards" | "about" | "tally">("play");
|
||||
let hotseatCount = $state(2);
|
||||
let setupName = $state("");
|
||||
let setupColor = $state(0);
|
||||
@@ -141,7 +142,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** Map a typed card name to its id (case-insensitive). */
|
||||
/** Suggest-as-you-type pool for the card-naming field, tuned per card. */
|
||||
const nameSuggestions = $derived.by(() => {
|
||||
if (!selectedCard) return [];
|
||||
@@ -158,6 +158,7 @@
|
||||
return selectedCard.cardId === "drop-object" ? ["Treasure", ...names] : names;
|
||||
});
|
||||
|
||||
/** Map a typed card name to its id (case-insensitive). */
|
||||
function nameToCardId(name: string): string | null {
|
||||
const wanted = name.trim().toLowerCase();
|
||||
if (!wanted) return null;
|
||||
@@ -653,11 +654,8 @@
|
||||
return `${Math.round(s / 86400)} d ago`;
|
||||
}
|
||||
|
||||
const PLAYER_COLORS = ["#1a9c46", "#d3352b", "#c9308f", "#3a3ac0", "#2ab0c9", "#c9a72a"];
|
||||
function playerColor(id: string): string {
|
||||
const p = view?.players.find((p) => p.id === id);
|
||||
const idx = p?.colorIndex ?? (view?.players.findIndex((q) => q.id === id) ?? 0);
|
||||
return PLAYER_COLORS[idx % PLAYER_COLORS.length]!;
|
||||
return view ? wizardColor(view, id) : PLAYER_COLORS[0]!;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1862,98 +1860,6 @@
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.attack-scrim {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(10, 12, 16, 0.72);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
z-index: 45;
|
||||
padding: 1rem;
|
||||
}
|
||||
.attack-notice {
|
||||
background: #efe8d4;
|
||||
border: 2px solid #43331f;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
|
||||
padding: 1.2rem 1.6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
max-width: min(22rem, 92vw);
|
||||
text-align: center;
|
||||
}
|
||||
.attack-headline {
|
||||
font-family: "Oswald", sans-serif;
|
||||
font-size: 1.15rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: #b3372b;
|
||||
}
|
||||
.attack-card :global(.card) { transform: scale(1.35); margin: 1.2rem 0; }
|
||||
.attack-card :global(.card:hover) { transform: scale(1.35); }
|
||||
.attack-power { font-family: "Courier Prime", monospace; color: #6b5a41; font-size: 0.9rem; }
|
||||
.attack-fist { font-size: 1.1rem; color: #43331f; }
|
||||
|
||||
.peek-note {
|
||||
margin-top: 0.4rem;
|
||||
background: #2b2218;
|
||||
color: #e8dfc6;
|
||||
font-family: "Courier Prime", monospace;
|
||||
font-size: 0.72rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
max-width: 11rem;
|
||||
}
|
||||
|
||||
.final-reveal {
|
||||
background: #efe8d4;
|
||||
border: 1px solid #b3a687;
|
||||
border-radius: 6px;
|
||||
padding: 0.7rem 0.9rem;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
.reveal-head {
|
||||
font-family: "Oswald", sans-serif;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: #43331f;
|
||||
border-bottom: 1px solid #b3a687;
|
||||
padding-bottom: 0.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.reveal-row { display: flex; align-items: flex-start; gap: 0.6rem; margin-bottom: 0.5rem; }
|
||||
.reveal-name {
|
||||
font-family: "Courier Prime", monospace;
|
||||
font-size: 0.8rem;
|
||||
color: #43331f;
|
||||
min-width: 6.5rem;
|
||||
padding-top: 0.4rem;
|
||||
}
|
||||
.reveal-name.reveal-winner { font-weight: 700; }
|
||||
.reveal-note { display: block; font-family: "Caveat", cursive; font-size: 0.85rem; color: #8a7a5e; }
|
||||
.reveal-cards { display: flex; flex-wrap: wrap; gap: 0.3rem; }
|
||||
.reveal-cards :global(.card) { transform: scale(0.82); transform-origin: top left; margin: -0.35rem -0.8rem -1.1rem 0; }
|
||||
.reveal-empty { font-family: "Caveat", cursive; color: #8a7a5e; padding-top: 0.5rem; }
|
||||
|
||||
.victory-notice { border-color: #a5842c; box-shadow: 0 0 0 4px rgba(201, 167, 42, 0.35), 0 18px 50px rgba(0, 0, 0, 0.6); }
|
||||
.victory-trophy { font-size: 3rem; line-height: 1; }
|
||||
.victory-name {
|
||||
font-family: "Oswald", sans-serif;
|
||||
font-size: 1.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #43331f;
|
||||
}
|
||||
.victory-how { font-family: "Caveat", cursive; font-size: 1.25rem; color: #6b5a41; }
|
||||
|
||||
.inspector { right: 0.5rem; bottom: 0.5rem; }
|
||||
.inspector-card { transform: scale(1.3); }
|
||||
}
|
||||
|
||||
.hand {
|
||||
display: flex;
|
||||
@@ -1968,6 +1874,8 @@
|
||||
/* One-column phone flow: board, then your controls and hand, THEN the
|
||||
paperwork — cards must never hide below the chronicle. */
|
||||
.game { display: contents; }
|
||||
.inspector { right: 0.5rem; bottom: 0.5rem; }
|
||||
.inspector-card { transform: scale(1.3); }
|
||||
.board-zone { order: 1; }
|
||||
.table-edge { order: 2; margin-top: 0.6rem; }
|
||||
.paper-rail { order: 3; margin-top: 0.9rem; }
|
||||
@@ -1984,6 +1892,5 @@
|
||||
.mast-title { font-size: 1.05rem; white-space: nowrap; }
|
||||
.mast-sub { display: none; }
|
||||
.mast-leave { font-size: 0.72rem; }
|
||||
.hand { min-height: auto; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { GameView } from "@wizwar/engine";
|
||||
import type { Side } from "@wizwar/engine";
|
||||
import { colorIndexOf as sharedColorIndex, wizardColor } from "./colors";
|
||||
|
||||
const CELL = 48;
|
||||
const WALL = 7;
|
||||
@@ -34,7 +35,6 @@
|
||||
litCells?: Set<string> | null;
|
||||
} = $props();
|
||||
|
||||
const PLAYER_COLORS = ["#1a9c46", "#d3352b", "#c9308f", "#3a3ac0", "#2ab0c9", "#c9a72a"];
|
||||
|
||||
// Press-and-hold peeks the square's card; a fired hold swallows the click.
|
||||
let peekTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -81,18 +81,14 @@
|
||||
return 1;
|
||||
}
|
||||
function wizardArt(id: string): string {
|
||||
return `/tokens/wizard-${colorIndexOf(id) % 6}.png`;
|
||||
return `/tokens/wizard-${sharedColorIndex(view, id) % 6}.png`;
|
||||
}
|
||||
function treasureArt(owner: string): string {
|
||||
return `/tokens/treasure-${colorIndexOf(owner) % 6}.png`;
|
||||
return `/tokens/treasure-${sharedColorIndex(view, owner) % 6}.png`;
|
||||
}
|
||||
|
||||
function colorIndexOf(id: string): number {
|
||||
const p = view.players.find((p) => p.id === id);
|
||||
return p?.colorIndex ?? Math.max(0, view.players.findIndex((q) => q.id === id));
|
||||
}
|
||||
function playerColor(id: string): string {
|
||||
return PLAYER_COLORS[colorIndexOf(id) % PLAYER_COLORS.length]!;
|
||||
return wizardColor(view, id);
|
||||
}
|
||||
|
||||
const cells = $derived(
|
||||
@@ -599,19 +595,7 @@
|
||||
50% { opacity: 0.35; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.dim-cell {
|
||||
fill: rgba(12, 9, 5, 0.55);
|
||||
pointer-events: none;
|
||||
}
|
||||
.marked-cell {
|
||||
fill: rgba(211, 133, 43, 0.18);
|
||||
stroke: #d3852b;
|
||||
stroke-width: 2.5;
|
||||
stroke-dasharray: 7 4;
|
||||
pointer-events: none;
|
||||
animation: warp-pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
.warp-dest { animation: none; }
|
||||
.marked-cell, .warp-dest { animation: none; }
|
||||
}
|
||||
.wizard { cursor: pointer; }
|
||||
.wizard-label {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// The six physical standee colors, in colorIndex order.
|
||||
import type { GameView } from "@wizwar/engine";
|
||||
|
||||
export const PLAYER_COLORS = ["#1a9c46", "#d3352b", "#c9308f", "#3a3ac0", "#2ab0c9", "#c9a72a"];
|
||||
|
||||
export function colorIndexOf(view: GameView, id: string): number {
|
||||
const p = view.players.find((p) => p.id === id);
|
||||
return p?.colorIndex ?? Math.max(0, view.players.findIndex((q) => q.id === id));
|
||||
}
|
||||
|
||||
export function wizardColor(view: GameView, id: string): string {
|
||||
return PLAYER_COLORS[colorIndexOf(view, id) % PLAYER_COLORS.length]!;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ const SAVE_KEY = "wizwar-hotseat";
|
||||
interface SavedHotseat {
|
||||
config: GameConfig;
|
||||
commands: { playerId: PlayerId; command: Command }[];
|
||||
/** Anonymous tally identity + table-time bookkeeping (added later; optional). */
|
||||
/** Anonymous tally identity + table-time bookkeeping; absent in older saves. */
|
||||
tallyId?: string;
|
||||
activeMs?: number;
|
||||
lastMoveAt?: number;
|
||||
|
||||
Reference in New Issue
Block a user