# Adventure expansion — progress & handoff

Last updated 2026-07-17. Companion to `adventure_expansion_spec.md` (the wire
contract). This file is the state of the work: what's done, what's next, and
what will bite whoever picks it up.

---

## Read this first

1. `adventure_expansion_spec.md` — payloads, endpoints, file:line refs into both
   repos. It supersedes any older sketch of these asks.
2. This file — where the work actually stands.

The gogobee repo is at `/home/reala-misaki/git/gogobee`. It runs on millenia
(`reala@192.168.1.212`). Pete runs on `reala@www.parodia.dev` (bare `./pete` in
a screen session, built **on the server, with cgo**).

---

## Status of the five asks

| # | Ask | State |
|---|---|---|
| — | Trophy case / timeline ("The record" / "The trail") | **Built, tests green, committed, NOT pushed, NOT deployed** |
| — | Contract spec for the five | **Written** (`adventure_expansion_spec.md`) |
| 4 | Richer item view | **Next up.** Spec'd. Partly buildable — see below |
| 3 | Room graph → dungeon map + fog of war | Spec'd. Graph already exists in gogobee |
| 5 | `equip_orders` queue | Spec'd. Copies mischief |
| 1 | `treasure_found` → treasures | Spec'd, but **blocked on a gogobee feature** — no loot fact exists |
| 2 | LLM-authored dispatches | Spec'd. **Do last** — inverts a security guarantee |

Build order and the reasoning behind it are in the spec's last section.

---

## ⚠️ Unpushed and undeployed

Pete has **two unpushed commits on `main`**:

- `cbe9e67` — adventure: keep the facts, not just the sentence we made of them
  (the trophy case / timeline feature — `adventure_events` table,
  `internal/storage/adventure.go`, the who-page sections)
- `5fac163` — adventure: pin the gogobee contract before Pete assumes it
  (the spec)

**Nothing is deployed.** The record/trail sections are live nowhere.

This is not a neutral delay. **Trophies are only countable going forward** —
they're built from `adventure_events`, which only starts filling once the
feature is deployed. Every fact gogobee emits between now and the deploy is
counted by nobody and cannot be counted back out later (the past is prose in the
story feed). The longer it sits, the more of every adventurer's history is
permanently uncountable. Deploy it soon, independent of where the expansion goes.

gogobee's tree has **unrelated uncommitted postgame-zone work** (combat engine,
expedition sim, plan docs). It's mid-flight and not part of this project — leave
it alone, and keep adventure-expansion edits in separate files and commits.

---

## Next: ask 4, richer items

Chosen because it's additive-only, has real sources, carries no deploy-order
hazard and no security surface, and is the smallest change that visibly improves
the who page.

**gogobee:** `itemViews` (`internal/plugin/pete_roster.go:210-225`) currently
sends five of `AdvItem`'s fields and drops two that already exist:

- `Slot` (`EquipmentSlot`, non-empty for MasterworkGear)
- `SkillSource` (string, non-empty for MasterworkGear)

Also worth sending, from adjacent structs:

- `Desc` — resolve **at the push site** from `MagicItem.Desc`
  (`magic_items.go:35-46`) or `EquipmentDef.Description`
  (`adventure_character.go:172-177`). `AdvItem` rows carry no description of
  their own, which is why this is a push-site join and not a field copy.
- `Attunement` (does it need a bond) and `Attuned` (does it have one). Distinct,
  and both matter to a player deciding what to wear — the bond cap is 3.

**Pete:** extend `ItemView` (`internal/storage/detail.go:23-29`) to match
`peteclient/client.go:356-362`, then surface it on the who page.

Rides the private `/api/ingest/detail` push, so **either side can deploy first**.

**Not buildable — do not spec it in:** stat modifiers and requirements are *not
modeled anywhere*, not merely unsent. No attack/AC/ability deltas exist on
`AdvItem` or `MagicItem`; effects derive from Tier/Slot/SkillSource in
`combat_stats.go:36` and `combat_bridge.go:396,489`. "+2 to hit" needs a gogobee
engine change first. Deriving a display-only approximation from Tier/Slot at the
push site is a lie the first time the engine and the display disagree.

---

## Landmines

Things that cost money to learn, or that will cost money if forgotten.

**Deploy order is a data-loss rule, not a preference.** An unknown `event_type`
is a 400 on Pete; gogobee retries with backoff to `maxAttempts=8` and then
**parks the bulletin forever** (`peteclient/client.go:79-86`,
`plugin/pete.go:279-281`). Pete's handler ships *before* gogobee emits
`treasure_found`, or the first treasures are gone permanently. Additive *fields*
on existing event types are safe in either order.

**A limit on the fetch truncates a tally, not a list.** `EventsBySubject(name, 0)`
means unlimited, and anything that *counts* must pass 0 and cap in the caller
(`storage/adventure.go:108-114`). A capped read would freeze a veteran's kill
count at 40 forever, reading as a fact rather than a missing page.

**`INSERT OR IGNORE` on the guid is load-bearing**, not defensive habit
(`storage/adventure.go:45-58`). gogobee retries facts whose ack it lost. This is
the only adventure store where a duplicate is *permanently* wrong — the roster
forgives one by replacing itself; a double-counted boss kill is in the tally
forever.

**Only `subject` earns a trophy, never `opponent`.** A duel Josie *lost* still
names her (as the opponent in the winner's dispatch). It belongs on her trail,
but crediting it would score a loss as a win. Pinned by
`TestTrophyCaseIgnoresOpponentCredit`.

**Events key on character name**, not roster token — that's what a fact carries;
gogobee puts no stable character id on the wire. A rename takes the history with
it.

**Snapshots are dropped on failure, never queued** (`peteclient/client.go:320-327`).
A retried snapshot is a lie about a moment that has passed, and the silence is
what makes Pete's 12-minute staleness timer honest. Asks 3 and 4 ride snapshots:
a dropped push means a stale map, not a wrong one.

**The who page's tests render the real template**, so a field slip 500s in the
suite — but only on paths the test data reaches. `HasHistory` false skips the
whole history block, which is why `TestWhoHistoryPanels` seeds facts through the
real ingest handler.

**`/api/mischief/claim` is misnamed.** It's a *verdict* endpoint; mischief has no
`claimed` state (`storage/mischief.go:37-42`). Don't repeat the name in
`equip_orders` — the spec calls it `/api/equip/verdict`.

**The LLM dispatch guard inversion (ask 2).** `factGuard` only checks the
*structured* `Subject`/`Opponent` fields, which is safe today only because Pete's
templates can print nothing Pete didn't interpolate. The moment gogobee's LLM
authors the prose, the guard is checking fields that are **no longer the thing
being rendered**, and character names are player-chosen. A prose-level guard is
required *in the same change* that accepts `headline`/`lede`, not as a
follow-up. **Templates stop being the renderer and become the safety net — do not
delete them.**

---

## Decisions already made (don't relitigate)

- **Room graph, not coordinates or a bare trail.** Rooms with exits, a real
  graph. It already exists in gogobee (`zone_graph.go`); the wire throws it away.
- **Fog of war is a server-side cut**, not a CSS style. Send visited nodes plus a
  one-hop ring with `kind: "unknown"`. The map is a public page; "view source to
  find the boss room" is not fog of war.
- **`equip_orders` copies mischief, not escrow.** An equip is a desired end state,
  not a delta, so a replay converges — mischief's precondition exactly. No
  `claimed` state, no stale-reoffer window; the poll loop is its own retry.
- **Equip UI says "queued", never claims it landed.** It lands on gogobee's next
  poll tick, up to 30s out. The order row's status is the truth.
- **"Treasures found" won't be faked from vault contents.** A *bought* sword is
  not a trophy. Needs a real `treasure_found` fact.
- **Blank state over a wall of zeroes.** Adventurers predating `adventure_events`
  render no record section at all. A clean absence, deliberate.
