adventure: give the realm a map, a board and a history

Everything Pete published so far was either the present moment (the roster,
the Siege bar) or one thing that happened (a dispatch, a run report). None of
it said what this place IS — how many zones there are, which are harder, or
whether anybody has ever actually beaten them.

Three pages off one snapshot, because they are one question and every number
on all three comes off the same scan of the same run history upstream:

  /adventure/realm      the world, in difficulty order, with who is inside it
  /adventure/standings  the board, plus Pete's own duel record
  /adventure/firsts     the hall of firsts, as a dated history

The map is deliberately not who_map.go's layout engine. That lays out a graph,
and the realm has no edges — zones aren't connected, you pick one and go.
Forcing a graph onto a set would imply a topology the game doesn't have. What
it has is an order, so tier bands are what get drawn.

A zone nobody has ever cleared looks different rather than saying so in small
text, and that styling keys off the clear count, never off whether a name is
attached — otherwise an opt-out would silently redraw a conquered place as one
nobody has come out of.

The per-kind first dot goes through a custom property instead of a
.firsts-entry-zone::before rule: input.css is in Tailwind's content glob, so a
hand-written class survives the purge only if its literal name can be lifted
out of the file, and a name glued to ::before cannot. It failed silently.

Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
This commit is contained in:
prosolis
2026-07-24 17:54:49 -07:00
parent b4a276da36
commit 1dfd3ac9fb
12 changed files with 1745 additions and 7 deletions
+84
View File
@@ -212,6 +212,90 @@ CREATE TABLE IF NOT EXISTS adventure_run_beat (
PRIMARY KEY (run_id, seq)
);
-- The realm: the world map, the hall of firsts, and the board. Four tables from
-- one gogobee push, all replaced whole — the same contract as the roster and the
-- Siege, and for the same reason. Every row here is a *derived* answer (how many
-- clears, who was first, who is inside right now) recomputed on the game box from
-- its own run history. Pete keeping a stale one and merging into it would let a
-- correction upstream leave a wrong number here permanently.
--
-- Unlike the Siege there is no live/history lifetime split, because none of this
-- has a lifetime: a zone does not end. What varies is only how often it changes,
-- and that is handled on the gogobee side by pushing every ten minutes instead of
-- every two.
--
-- pos is the push order throughout, kept as the key for the same reason the siege
-- muster does: an opted-out player carries NO token, so several rows can
-- legitimately be tokenless and must not collide on one.
CREATE TABLE IF NOT EXISTS adventure_realm_zone (
pos INTEGER PRIMARY KEY, -- gogobee's design-doc zone order
zone_id TEXT NOT NULL,
display TEXT NOT NULL,
tier INTEGER NOT NULL DEFAULT 0,
level_min INTEGER NOT NULL DEFAULT 0,
level_max INTEGER NOT NULL DEFAULT 0,
faction TEXT NOT NULL DEFAULT '',
atmosphere TEXT NOT NULL DEFAULT '',
postgame INTEGER NOT NULL DEFAULT 0,
first_by TEXT NOT NULL DEFAULT '',
first_token TEXT NOT NULL DEFAULT '',
first_at INTEGER NOT NULL DEFAULT 0,
clears INTEGER NOT NULL DEFAULT 0,
clearers INTEGER NOT NULL DEFAULT 0
);
-- Who is standing in a zone right now. Its own table rather than a JSON blob on
-- the zone row so the map can be drawn with one join and "there are people in
-- there" is a count, not a parse.
CREATE TABLE IF NOT EXISTS adventure_realm_occupant (
pos INTEGER PRIMARY KEY,
zone_id TEXT NOT NULL,
token TEXT NOT NULL DEFAULT '',
name TEXT NOT NULL,
level INTEGER NOT NULL DEFAULT 0,
day INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_adv_realm_occ_zone ON adventure_realm_occupant(zone_id);
-- The hall of firsts: every thing that has happened in the realm exactly once.
-- holder is empty when the game can no longer say who did it (a treasure found
-- and later discarded leaves no owner anywhere) — an unattributed first is still
-- a first and is rendered as one.
CREATE TABLE IF NOT EXISTS adventure_realm_first (
pos INTEGER PRIMARY KEY, -- gogobee's order: oldest first
kind TEXT NOT NULL, -- "zone" | "treasure" | whatever comes next
target TEXT NOT NULL,
display TEXT NOT NULL,
tier INTEGER NOT NULL DEFAULT 0,
holder TEXT NOT NULL DEFAULT '',
token TEXT NOT NULL DEFAULT '',
at_unix INTEGER NOT NULL DEFAULT 0
);
-- The board. pos IS the rank, and the ranking is gogobee's — the ordering is a
-- statement about what the game values, and the game gets to make it.
CREATE TABLE IF NOT EXISTS adventure_realm_standing (
pos INTEGER PRIMARY KEY,
token TEXT NOT NULL DEFAULT '',
name TEXT NOT NULL,
level INTEGER NOT NULL DEFAULT 0,
class_race TEXT NOT NULL DEFAULT '',
deepest_tier INTEGER NOT NULL DEFAULT 0,
clears INTEGER NOT NULL DEFAULT 0,
zones INTEGER NOT NULL DEFAULT 0,
firsts INTEGER NOT NULL DEFAULT 0,
siege_damage INTEGER NOT NULL DEFAULT 0,
siege_fights INTEGER NOT NULL DEFAULT 0
);
-- One row, like adventure_siege: when the realm last arrived. Its own table
-- because "gogobee has never pushed a realm" and "gogobee pushed a realm that is
-- empty" are different states, and the page says different things about them.
CREATE TABLE IF NOT EXISTS adventure_realm_meta (
id INTEGER PRIMARY KEY CHECK (id = 1),
snapshot_at INTEGER NOT NULL DEFAULT 0
);
-- A signed-in buyer's own euro balance, as of the last snapshot gogobee pushed.
-- Keyed by localpart (== Authentik preferred_username == the session's Username),
-- a *separate keyspace* from the anonymous roster tokens on purpose: it is only