The opening roll-off steps into the light

First wizard was never random: createGame has always rolled a die
per player from the seed, re-rolling ties, and the gameStarted event
carried every pip — straight into a log line that said only "X goes
first." Now the table sees the ceremony: a dice modal as the boards
flip, one row per wizard, the trophy on the high roll, a note when
tied wizards rolled again — in online games and hotseat alike. The
chronicle line keeps the full roll-off too. Pure presentation; the
dice were always honest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 21:47:31 -04:00
co-authored by Claude Fable 5
parent 2950380e23
commit 22153b1e1b
3 changed files with 63 additions and 1 deletions
+45
View File
@@ -27,6 +27,11 @@
}
/** Which pending attack the player has already acknowledged (modal dismissed). */
let attackNoticeSeen = $state<string | null>(null);
const openingRolls = $derived(net.openingRolls ?? local.openingRolls);
function dismissRolls() {
net.openingRolls = null;
local.openingRolls = null;
}
/** A face-up card being peeked at from the scoresheet (view-only). */
let peekCard = $state<CardInstance | null>(null);
/** When the peeked card is a creature on the board, its live stats ride along. */
@@ -887,6 +892,28 @@
</div>
{/if}
{#if openingRolls}
<div class="scrim attack-scrim" role="alertdialog" aria-label="the roll for first wizard">
<div class="attack-notice">
<div class="attack-headline">The dice decide who casts first</div>
<div class="rolloff">
{#each openingRolls.players as p (p)}
<div class="rolloff-row" class:rolloff-winner={p === openingRolls.first}>
<span class="rolloff-name">{p === openingRolls.first ? "🏆 " : ""}{p}</span>
<span class="rolloff-dice">
{#each openingRolls.rolls[p] ?? [] as r, i (i)}<span class="die-face">🎲{r}</span>{/each}
</span>
</div>
{/each}
</div>
{#if Object.values(openingRolls.rolls).some((r) => r.length > 1)}
<div class="rolloff-note">Tied wizards rolled again.</div>
{/if}
<button class="stamp big" onclick={dismissRolls}>{openingRolls.first} leads begin!</button>
</div>
</div>
{/if}
{#if view?.stack && attackNoticeKey && attackNoticeSeen !== attackNoticeKey && !noFanfare}
<div class="scrim attack-scrim" role="alertdialog" aria-label="you are under attack">
<div class="attack-notice">
@@ -2094,6 +2121,24 @@
margin: 0.3rem auto 0;
}
.attack-standing.nullified { color: #b3372b; }
.rolloff {
display: flex;
flex-direction: column;
gap: 0.35rem;
margin: 0.4rem 0 0.6rem;
}
.rolloff-row {
display: flex;
justify-content: space-between;
gap: 1.2rem;
font-size: 1rem;
color: #43331f;
}
.rolloff-winner { font-weight: 700; }
.rolloff-name { font-family: "Oswald", sans-serif; letter-spacing: 0.04em; }
.rolloff-dice { font-family: "Courier Prime", monospace; }
.die-face { margin-left: 0.4rem; }
.rolloff-note { font-size: 0.8rem; color: #6b5a41; font-style: italic; margin-bottom: 0.3rem; }
.attack-fist { font-size: 1.1rem; color: #43331f; }
.table-talk {