adventure: give the Siege a war room instead of a Matrix-only rumour

The Siege is the one mechanic where the whole town works on a single
object, and it existed exclusively in Matrix — so anybody not in the room
at the time never knew it happened. A communal event nobody can see is a
communal event that fails.

gogobee now pushes the war room on the roster tick and Pete replaces its
copy, the same snapshot contract the board has and for the same reason:
the shared pool is state, not history, and a retried snapshot would be a
lie about how much HP is left.

/adventure/siege draws it. The load-bearing detail is one CSS line — the
health bar has a width transition, so a poll that lands a lower pool
slides the bar down instead of snapping it. That is the difference
between watching the town chip a boss down and reading a report about it.
Alongside: a countdown to the window's close, past sieges with the bar
each one ended on, and the muster split into who took today's bout and
who still has one going spare — the mechanic is one fight per person per
day, so that second column is a hit the town hasn't taken yet.

The opt-out rule here deliberately differs from the board's. The board
omits an opted-out player outright, because class + level + zone
re-identifies them. A Siege contributor is anonymised instead: their
damage is part of what the town did to the boss, and deleting it would
understate the shared effort and stop the totals adding up. They keep
their rank, lose their name, and carry no token — so there is no link
back to a page that names them. An opted-out player who never fought is
still omitted; there is nothing to account for.

siege_start / siege_win / siege_loss are wired on the gogobee side; the
templates for all three have been sitting unused in renderAdventure
since the section shipped.
This commit is contained in:
prosolis
2026-07-24 15:21:14 -07:00
parent 91d25e9da1
commit 23563b6a6a
10 changed files with 1148 additions and 2 deletions
+24
View File
@@ -14,6 +14,30 @@
</section>
{{if .ShowRoster}}
{{/* The Siege strip. Above the board on purpose: the board is where everyone is,
the Siege is where everyone should be. When one is camped this is a live bar
and a door into the war room; when none is, it stays as the quiet doorway to
the history, which is the other half of making the next one feel like it
counts. */}}
{{if .Siege.Active}}
<a href="/adventure/siege" class="block mb-6 rounded-3xl bg-theme-adventure text-white p-5 sm:p-6 shadow-pete relative overflow-hidden group {{if not .Siege.Stale}}siege-live{{end}}">
<div class="absolute -top-6 -right-4 text-[8rem] opacity-20 select-none" aria-hidden="true">🏰</div>
<div class="relative">
<p class="text-xs uppercase tracking-[0.2em] opacity-80">🏰 The Siege · now</p>
<h2 class="font-display text-2xl font-bold mt-1 group-hover:underline">{{.Siege.BossName}} is at the gates</h2>
<div class="mt-3 siege-track">
<div class="siege-fill" style="width: {{.Siege.HPPercent}}%"></div>
</div>
<p class="mt-2 text-sm opacity-90 tabular-nums">{{.Siege.HPCurrent}} / {{.Siege.HPMax}} HP · {{.Siege.HPPercent}}% standing · {{len .Siege.Waiting}} bout{{if ne (len .Siege.Waiting) 1}}s{{end}} still going spare</p>
</div>
</a>
{{else if .Siege.History}}
<a href="/adventure/siege" class="flex items-center gap-3 mb-6 rounded-2xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 px-5 py-3 shadow-pete hover:border-theme-adventure/40 transition">
<span class="text-lg" aria-hidden="true">🏰</span>
<span class="text-sm text-[color:var(--ink)]/70">Nothing camped outside town right now — <span class="font-semibold text-theme-adventure">the sieges we've fought</span></span>
</a>
{{end}}
<section class="mb-10" id="roster" data-stale="{{.RosterStale}}">
<div class="flex items-baseline justify-between mb-3">
<h2 class="font-display text-2xl font-bold">Out there right now</h2>