adventure: write the handoff down where the next session will find it
The spec says what the wire should look like. This says where the work actually stands, which is the part that rots silently: two unpushed commits, nothing deployed, and a trophy case that can only count forward from a deploy that hasn't happened. Also records the decisions already settled (room graph over coordinates, server-side fog cut, mischief shape for equip, no faking treasures from vault contents) so they don't get relitigated, and the landmines that cost money to learn the first time.
This commit is contained in:
168
adventure_expansion_progress.md
Normal file
168
adventure_expansion_progress.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user