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
+2
View File
@@ -88,6 +88,7 @@ export interface GameView {
chaosPending: { casterId: PlayerId; queue: PlayerId[] } | null;
/** A grab hangs while the treasure's owner decides their Ward. */
wardPending: { ownerId: PlayerId; takerId: PlayerId } | null;
slowDeathPending: { playerId: PlayerId; points: number } | null;
/** YOUR armed ambushes. Other players' ambushes are invisible. */
yourAmbushes: AmbushState[];
/** Once the game is finished, every hand goes face-up on the table. */
@@ -171,6 +172,7 @@ export function viewFor(state: GameState, playerId: PlayerId): GameView {
? { casterId: state.chaosPending.casterId, queue: [...state.chaosPending.queue] }
: null,
wardPending: state.wardPending ? { ...state.wardPending } : null,
slowDeathPending: state.slowDeathPending ? { ...state.slowDeathPending } : null,
yourAmbushes: state.ambushes
.filter((a) => a.ownerId === playerId)
.map((a) => ({ ...a, numbers: [...a.numbers] })),