The voice: every game speaks in its own world
Conventions gain a section on in-game language: an image with every fact, the fact exact, unfolding effects told a turn at a time and naming the turn they end on, our own words rather than an older version's, and chronicle wording shipped without a rules revision. new-game.sh points at it, and the demo game's lines follow it. Born in Waving Hands' atmospheric chronicle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bce01a013a
commit
cd9d14ec35
@@ -158,6 +158,29 @@ emoji in the interface; the words do the work. Phone width first: a sticky
|
||||
bar for the move if the board runs long, lists that shorten, gutters of
|
||||
16px, no horizontal scroll.
|
||||
|
||||
## The voice
|
||||
|
||||
Every game speaks in its own world. Wherever the page tells a player what
|
||||
happened, it gives an image as well as the fact: not "Corwin takes 5 from
|
||||
a fireball" but "A fireball bursts over Corwin, for 5". The fact stays
|
||||
exact and whole (the number, the duration, who is affected, the turn a
|
||||
thing will happen), and the image carries it.
|
||||
|
||||
- **The chronicle** is where the voice matters most. Each move, blow,
|
||||
enchantment, arrival and death gets its own picture. Something that
|
||||
unfolds over turns (an illness, a siege, a fuse) is told a turn at a
|
||||
time, names the turn it ends on when it begins, and warns on the last
|
||||
turn anything can be done about it.
|
||||
- **The rest of the surround** speaks the same way: the lobby, the guide,
|
||||
the status lines, the verdict. Plain words for controls and buttons;
|
||||
the images belong to what happens, not to what you press.
|
||||
- **Our own words.** An older implementation's prose is a guide to the
|
||||
tone, never a source to copy.
|
||||
- **Changing text is not changing rules.** Chronicle wording is not part
|
||||
of the ledger, so it ships without a rules revision; the deploy gate
|
||||
compares outcomes, not sentences. A test that pins a phrase should pin
|
||||
the fact inside it.
|
||||
|
||||
## Things learned the hard way
|
||||
|
||||
- Caddy's `try_files` rewrites `/api` before `reverse_proxy` unless the
|
||||
|
||||
@@ -37,6 +37,8 @@ Next:
|
||||
and src/lib/components/Board.svelte with its board. Keep the tests green.
|
||||
2. Fill in the hall's pitch, tag and about panel (src/lib/components/Hall.svelte),
|
||||
the rules page, the guide, src/app.html's description, and the palette in src/app.css.
|
||||
Write every line a player reads in the game's own voice: an image with each
|
||||
fact (docs/conventions.md, "The voice").
|
||||
Add static/og.png (1200 by 630) and static/apple-touch-icon.png (180 by 180):
|
||||
app.html already points at them, and the static fallback serves the hall's
|
||||
HTML for a missing file, so a share card without the image breaks quietly.
|
||||
|
||||
@@ -56,12 +56,12 @@
|
||||
{:else if room.spectating}
|
||||
<section class="move">
|
||||
<h2>You watch from the Peanut Gallery</h2>
|
||||
<p class="muted">{room.awaitingText ? `${room.awaitingText} ${room.awaiting.length === 1 ? 'is' : 'are'} still choosing.` : 'The round is being written.'}</p>
|
||||
<p class="muted">{room.awaitingText ? `${room.awaitingText} ${room.awaiting.length === 1 ? 'turns a card over and over, still choosing.' : 'turn their cards over and over, still choosing.'}` : 'Cards are being chosen, face down.'}</p>
|
||||
</section>
|
||||
{:else}
|
||||
<section class="move">
|
||||
<h2>Round {g.rounds.length + 1}: name a number</h2>
|
||||
<p class="muted">The highest number named by exactly one player scores. First to {TARGET}.</p>
|
||||
<p class="muted">Every player lays a card face down; the highest card nobody else laid takes the point. First to {TARGET}.</p>
|
||||
<div class="picks">
|
||||
{#each Array.from({ length: HIGHEST }, (_, i) => i + 1) as n (n)}
|
||||
<button type="button" class="pick" class:chosen={pick === n} disabled={room.moved} onclick={() => (pick = n)}>{n}</button>
|
||||
|
||||
@@ -68,7 +68,7 @@ export function resolveRound(previous: State, inputs: Record<SeatId, Input>): St
|
||||
if (scorer) state.players[scorer].points += 1;
|
||||
state.rounds.push({ picks, scorer });
|
||||
if (scorer && state.players[scorer].points >= TARGET) {
|
||||
state.over = { winner: scorer, reason: `${state.players[scorer].name} reached ${TARGET} points.` };
|
||||
state.over = { winner: scorer, reason: `${state.players[scorer].name} lays down the last card and sweeps the table: ${TARGET} points, and the game.` };
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user