Ops hardening plus two rules gaps close (rev 13)

Operational: auto-reboot for kernel patches (09:30 UTC), Caddy access
logs at /var/lib/caddy (self-rotating; the sandbox denies /var/log),
logrotate for the backup log, a restore drill proving a Spaces snapshot
boots 56/56 rooms clean, the about page's plain sentence on permanent
recording, and /wizwar-pulse to read it all weekly.

Rules: safes now smash — attacks aimed at the box accumulate on it and
the fifteenth point bursts it (widening, ungated). And SLOW DEATH's
bites pause for a victim holding an ABSORB — the card named by the FAQ,
not Absorb Spell — each soaking exactly one point, rev-gated at 13 with
the instant-bite legacy pinned. Bots weigh the soak against their life.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
Eric Wagoner
2026-09-01 10:35:52 -04:00
co-authored by Claude Fable 5
parent a66b058c68
commit 92613d7386
11 changed files with 281 additions and 2 deletions
+30
View File
@@ -793,6 +793,13 @@
clearSelection();
return;
}
if (selectedCard && cardDef(selectedCard.cardId).cardType === "attack" &&
view.squareContents[cellKey(cell)]?.kind === "safe") {
// Brute force: attacks batter the safe itself (fifteen points bursts it).
dispatch(withMods({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "cell", cell } }));
clearSelection();
return;
}
if (selectedCard?.cardId === "relocate-sector") {
// Any board click (re-)picks the sector; the landing is chosen from the
// dashed ghost slots beyond the maze, since every on-board slot is taken.
@@ -1555,6 +1562,29 @@
{/if}
{/if}
{#if view?.slowDeathPending && view.slowDeathPending.playerId === view.you}
{@const sdAbsorbs = view.yourHand.filter((c) => c.cardId === "absorb")}
<div class="scrim attack-scrim" role="alertdialog" aria-label="slow death bites">
<div class="attack-notice">
<div class="attack-title">Slow Death bites for {view.slowDeathPending.points}!</div>
<div class="attack-power">
"ABSORB against SLOW DEATH will only stop the damage incurred by
one card" — each Absorb you spend soaks exactly one point.
</div>
<div class="attack-actions">
{#if sdAbsorbs.length > 0}
{@const firstAbsorb = sdAbsorbs[0]!}
<button class="stamp primary"
onclick={() => dispatch({ type: "slowDeathChoice", absorbInstanceId: firstAbsorb.instanceId })}>
Absorb a point ({sdAbsorbs.length} in hand)</button>
{/if}
<button class="hint-cancel" onclick={() => dispatch({ type: "slowDeathChoice" })}>
take {view.slowDeathPending.points === 1 ? "the point" : `all ${view.slowDeathPending.points}`}</button>
</div>
</div>
</div>
{/if}
{#if feedbackOpen && net.roomId}
<div class="scrim attack-scrim" role="button" tabindex="-1"
onclick={() => (feedbackOpen = false)} onkeydown={() => {}}>