diff --git a/adventure_expansion_progress.md b/adventure_expansion_progress.md new file mode 100644 index 0000000..45dd0a6 --- /dev/null +++ b/adventure_expansion_progress.md @@ -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.