adventure: keep the facts, not just the sentence we made of them
gogobee already sends boss/opponent/zone/outcome/level on every dispatch. renderAdventure melted them into prose and only the prose was persisted, so "how many bosses has she downed" was answerable only by parsing English back out of a headline. adventure_events keeps the fact as fact. It's the one adventure table that's a log rather than a snapshot: the roster answers where Josie is now and is replaced every tick; this answers what she has ever done, which no snapshot can. INSERT OR IGNORE on the guid because gogobee retries a fact whose ack it lost, and this is the only adventure store where a duplicate is permanently wrong — the roster forgives one by replacing itself, a double-counted kill is in the tally forever. On top of it the who page grows two public sections, counted from dispatches that were already public: the record (tallies, per-boss and per-zone, realm firsts, milestones) and the trail (the last 40 facts, each linking to the dispatch that told it). One read feeds both — the pool is MaxOpenConns(1), so six COUNT queries would serialize for an answer that fits in memory. Only the trail is capped. A limit on the read would truncate a *tally* rather than a list, and a veteran's kill count frozen at 40 reads as a fact instead of a missing page. Only the subject of a fact earns a trophy: a duel you lost still names you, and it belongs on your trail but not in your record. Trophies count forward only. Every adventurer's past is prose in the feed and can't be counted back out, so they show no record until they next do something — a clean absence rather than a wall of zeroes. No treasures: there's no loot fact on the wire, and inventory is current-state with no history, so counting the vault would score a bought sword as a trophy. Needs a fact type upstream.
This commit is contained in:
@@ -79,6 +79,107 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
{{if .HasHistory}}
|
||||
<!-- The record. Public, like the dispatches it's counted from — this is the
|
||||
same information the /adventure feed already printed, only as numbers
|
||||
instead of as forty separate sentences. Not a gogobee snapshot: Pete
|
||||
counted these itself out of the facts it kept. -->
|
||||
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold mb-4">The record</h2>
|
||||
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.BossKills}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">bosses down</div>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.ZoneClears}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">zones cleared</div>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.Deaths}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">death{{if ne .Trophies.Deaths 1}}s{{end}}</div>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-[color:var(--ink)]/5 px-2 py-3 text-center">
|
||||
<div class="font-display text-2xl font-bold leading-none">{{.Trophies.Retreats}}</div>
|
||||
<div class="text-[10px] uppercase tracking-wider text-[color:var(--ink)]/50 mt-1.5">walked out</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if or .Trophies.BossFirsts .Trophies.ZoneFirsts}}
|
||||
<p class="mt-3 text-sm text-theme-adventure font-semibold">
|
||||
★ {{.Trophies.BossFirsts}} realm-first boss{{if ne .Trophies.BossFirsts 1}}es{{end}}{{if .Trophies.ZoneFirsts}}, {{.Trophies.ZoneFirsts}} first clear{{if ne .Trophies.ZoneFirsts 1}}s{{end}}{{end}} — nobody had done it before.
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
<div class="mt-6 grid gap-6 sm:grid-cols-2">
|
||||
{{if .Trophies.Bosses}}
|
||||
<div>
|
||||
<h3 class="font-display text-lg font-bold mb-3">Bosses fought</h3>
|
||||
<ul class="space-y-1.5 text-sm max-h-56 overflow-y-auto pr-1">
|
||||
{{range .Trophies.Bosses}}
|
||||
<li class="flex items-baseline justify-between gap-3">
|
||||
<span class="flex-1">{{.Boss}}{{if .First}} <span class="text-theme-adventure" title="first in the realm to clear it">★</span>{{end}}</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">×{{.Kills}}</span>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Trophies.Zones}}
|
||||
<div>
|
||||
<h3 class="font-display text-lg font-bold mb-3">Ground covered</h3>
|
||||
<ul class="space-y-1.5 text-sm max-h-56 overflow-y-auto pr-1">
|
||||
{{range .Trophies.Zones}}
|
||||
<li class="flex items-baseline justify-between gap-3">
|
||||
<span class="flex-1">{{.Zone}}{{if .First}} <span class="text-theme-adventure" title="first clear in the realm">★</span>{{end}}{{if .Region}} <span class="text-[color:var(--ink)]/45">{{.Region}}</span>{{end}}</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/50 shrink-0">×{{.Clears}}</span>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .Trophies.Milestones}}
|
||||
<div class="mt-6 pt-4 border-t border-[color:var(--ink)]/10">
|
||||
<h3 class="font-display text-lg font-bold mb-3">Milestones</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{{range .Trophies.Milestones}}
|
||||
<span class="rounded-full bg-theme-adventure/10 text-theme-adventure text-xs font-semibold px-3 py-1">🏅 {{.}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<!-- The trail: every fact we have, newest first, each linking to the dispatch
|
||||
that told it. -->
|
||||
<section class="mt-8 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<h2 class="font-display text-xl font-bold mb-4">The trail</h2>
|
||||
<ol class="relative border-l-2 border-[color:var(--ink)]/10 ml-3 space-y-4">
|
||||
{{range .Timeline}}
|
||||
<li class="relative pl-6">
|
||||
<span class="absolute -left-[13px] top-0.5 flex h-6 w-6 items-center justify-center rounded-full bg-[color:var(--card)] border-2 {{if .Notable}}border-theme-adventure{{else}}border-[color:var(--ink)]/10{{end}} text-xs" aria-hidden="true">{{.Emoji}}</span>
|
||||
<a href="{{.Permalink}}" class="group block">
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<span class="font-semibold group-hover:text-theme-adventure transition {{if .Notable}}text-theme-adventure{{end}}">{{.Label}}</span>
|
||||
<span class="text-xs text-[color:var(--ink)]/45 shrink-0">{{.When}}</span>
|
||||
</div>
|
||||
{{if .Line}}<p class="text-sm text-[color:var(--ink)]/60 mt-0.5">{{.Line}}</p>{{end}}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ol>
|
||||
{{if .MoreHistory}}
|
||||
<p class="mt-5 pt-4 border-t border-[color:var(--ink)]/10 text-xs text-[color:var(--ink)]/45">
|
||||
Showing the most recent {{len .Timeline}}. The counts above cover everything.
|
||||
</p>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
{{if .HasSelf}}
|
||||
<!-- Owner-only: this is you. Inventory, vault, house, pets — private, served
|
||||
only because your signed-in localpart owns this page's token. -->
|
||||
|
||||
Reference in New Issue
Block a user