Files
Pete/adventure_ask7_equipment_mgmt.md
prosolis b0aeffd218 adventure: ask 7 — full equipment management from the web
Extends equip-from-the-web (ask 5, magic-only) to all five standard gear
slots. Owners get an Equipment panel on their own who page with:
  - Take off for worn masterwork/arena pieces (round-trippable to pack)
  - Upgrade to the next shop tier (spends euros, confirm-gated)
  - Repair a damaged slot (spends euros, confirm-gated)
The public Gear panel is hidden for the owner since this supersedes it.

Wire: equip_orders gains a tier column; new actions upgrade/repair; new
verdicts rejected_downgrade / rejected_insufficient_funds / rejected_max_tier.
PlayerDetail carries Slots (EquipSlotView x5) + Balance for the confirm
dialogs. handleEquipOrder resolves take-off/upgrade/repair from pd.Slots
server-side and rejects a client-forged tier (409), same as ask 5 trusts
only Pete's own record.

Verified: full suite green, headless render of the panel + confirm dialog
in both day and night phases. gogobee ships the poll-apply half separately;
Pete deploys first so its ingest accepts the new verdicts before gogobee
emits them.
2026-07-17 20:34:24 -07:00

348 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Adventure ask 7 — full equipment management from the web (HANDOFF SPEC)
Status: **BUILT + TESTED + SCREENSHOT-VERIFIED, DEPLOY PENDING** (as of 2026-07-17).
Both sides compile, vet clean, and pass their suites. The owner Equipment panel was
rendered headless (Chrome) in BOTH day and night phases, and the euro confirm dialog
was exercised (upgrade €25,000 and repair €40) — cost + balance math and thousands
separators read right, no purple-on-night contrast issue, public Gear panel correctly
hidden for the owner. Next: commit Pete, commit gogobee, deploy Pete first, then
gogobee (see Build order step 8), then run the live prosolis probe. This doc is the
complete contract + file:line map. Companion to `adventure_expansion_spec.md`
(asks 16) and the memory `project_adventure_expansion.md`.
## What got built (deviations from the spec below, all intentional)
- Pete: `EquipOrder.Tier` + `tier` column (schema + migration); new actions
`upgrade`/`repair`; new verdicts `rejected_downgrade`/`rejected_insufficient_funds`/
`rejected_max_tier`. `EquipSlotView` + `Slots`/`Balance` on `PlayerDetail`.
`handleEquipOrder` resolves take-off/upgrade/repair from `pd.Slots` server-side and
rejects a client-forged tier (409). who.html gained an owner "Equipment" panel with
an in-DOM confirm (cost + balance) for the money actions; public Gear panel hidden
for the owner. output.css rebuilt. Tests in equip_test.go / who exercised via the
real template.
- gogobee: `peteclient` mirror types. New file `pete_equip_manage.go` holds the
headless mutators: `applyMasterworkEquip`/`applyMasterworkUnequip` (free funcs,
sentinel `errEquipDowngrade`), `purchaseEquipmentTier`/`repairSlot` (methods on
`*AdventurePlugin`, euro-idempotent), `buildEquipSlotViews`, `isEquipmentSlot`.
`applyEquipOrder` routes on item Type / slot vocabulary. `itemViews` now gives
masterwork/arena backpack rows an equip id. `buildDetailSnapshot` is now a METHOD
(`p.buildDetailSnapshot`) so it can read the euro balance (nil-guarded for tests).
Tests in pete_equip_manage_test.go.
- **DEVIATION 1 — downgrade block placement:** the masterwork-equip downgrade check
lives INSIDE `applyMasterworkEquip` (returns `errEquipDowngrade`), not in the
router. Behavior/verdict identical; keeps the rule next to the mutation and unit-
testable. Router maps the sentinel to `rejected_downgrade`.
- **DEVIATION 2 — no refund on save fault (both euro mutators):** the spec suggested
`CreditIdem` refund on a later DB error. That is UNSAFE with guid-idempotent retry:
a refund on a fresh id followed by a guid-guarded retry that no longer re-debits
hands the player both gear and money. Instead we return `retry=true` and let the
next poll re-run — the debit is guid-idempotent (skipped) and the slot write is
idempotent. This matches the casino escrow precedent exactly. Do NOT "fix" this by
adding a refund.
- **DEVIATION 3 — upgrades only over PLAIN slots:** `buildEquipSlotViews` offers
`NextTier` only when the slot is plain shop-tier (not masterwork/arena) and sub-max,
and `purchaseEquipmentTier` rejects an upgrade over special gear as
`rejected_downgrade` ("take it off first"). This honors "block downgrades" (buying a
plain tier over special strips its bonus) AND keeps the upgrade path free of the
non-idempotent eviction step, so the retry-safety above holds with no eviction to
reconcile.
Repos: Pete at `/home/reala-misaki/git/pete` (web mirror, deploys to parodia).
gogobee at `/home/reala-misaki/git/gogobee` (game engine, deploys to millenia
`reala@192.168.1.212`). One-way data flow gogobee→Pete; the only route back is the
poll-queue (Pete records intent, gogobee polls + applies + files a verdict).
## Why this exists
Ask 5 built "equip from the web" but scoped it to **magic items only**. The user's
worn gear is almost all the OTHER equipment systems, so the feature touched almost
nothing they own. Diagnosis of user "prosolis" / character "Rurina" (live prod):
- Worn (the 5 `adventure_equipment` slots): weapon **Vorpal Sword** T5 (shop, not mw),
armor **The Deepforged Carapace** T5 **masterwork**, helmet **Crown of the Fallen**
T5 (shop), boots **Ranger's Boots** T4 (shop), tool **Mithril Pickaxe** T4 (shop).
- Backpack (250 items): 1 `MasterworkGear` **The Wandering Sole** (boots, T3), 3 slotted
magic items (Wand of the War Mage off_hand, 2 Weapons main_hand), rest consumables/
materials. `equipped` magic count = 0 (magic slots empty).
So today prosolis gets 3 buried Equip buttons (magic) and nothing else. They want to
manage ALL five slots.
## The three equipment subsystems (do not conflate — this is where it breaks)
1. **Standard tiered gear** — the 5 `EquipmentSlot`s (weapon/armor/helmet/boots/tool),
power = integer `Tier` 0..5, raised by BUYING a tier in the shop (euros). No
inventory item; the slot's tier IS the gear. This is 4 of Rurina's 5 worn pieces.
2. **Masterwork / Arena gear** — special items that live in the backpack
(`adventure_inventory`, `item_type` = `MasterworkGear` / `ArenaGear`), equipped INTO
an `EquipmentSlot` (a swap), round-trippable to the pack.
3. **Magic items** — backpack `item_type='magic_item'`, equipped into DnD slots
(off_hand/main_hand/ring_1…), a DISJOINT slot namespace. Already handled by ask 5.
## User decisions (locked)
- **Scope:** full management incl. shop-tier gear (not just inventory items).
- **Sequencing:** build BOTH phases, deploy together (one drop).
- **Euro spend:** yes, upgrade/repair debit euros from the web, but behind a **confirm
step** showing cost + balance.
- **Downgrades:** BLOCK them (equip and upgrade).
---
# HANDOFF STATE (2026-07-17 — resume here next session)
Both repos build, vet clean, full suites green. **Uncommitted on purpose** — screenshot-
verify the Equipment panel FIRST, then commit each side, then deploy Pete→gogobee, then
the live prosolis probe.
**Order for next session:**
1. **Screenshot-verify** the owner Equipment panel (never rendered in a browser yet; the
`TestEquipPanelRenders` test drives the real template but is not a visual check).
Recipe (same as prior asks): a throwaway test calling `seedEquip`/`getWho` (both in
`internal/web/equip_test.go` / `who_test.go`) that writes the rendered body to an HTML
file, served with `python3 -m http.server` from a dir with a `static` symlink into
`internal/web/static`. `seedEquip` already seeds `Slots` (a masterwork weapon with
Take off + Repair, plain boots with an Upgrade offer) + `Balance` 100000, so the panel
and the confirm dialog both render. Check day AND night phase; confirm the euro
confirm box (cost + balance) pops on Upgrade/Repair click, and the `€` amounts read
right. Watch the [[pete_theme_contrast]] purple-on-night hazard.
2. **Commit Pete** — every changed file below is ask 7 (incl. this doc):
`internal/storage/{db,detail,equip,schema,equip_test}.go`,
`internal/web/{equip,equip_test}.go`, `internal/web/templates/who.html`,
`internal/web/static/css/output.css`, `adventure_ask7_equipment_mgmt.md`.
3. **Commit gogobee** — stage ONLY the ask-7 files (the `gogobee_*.md` plan files are
unrelated mid-flight postgame work — leave them):
`git add internal/peteclient/client.go internal/plugin/pete_detail_test.go internal/plugin/pete_equip.go internal/plugin/pete_roster.go internal/plugin/pete_equip_manage.go internal/plugin/pete_equip_manage_test.go`
4. **Deploy Pete first** (build-on-server-with-cgo per deploy_topology), then gogobee on
millenia. A gogobee verdict string Pete's `validEquipVerdict` rejected would 400+park
the order, so Pete's ingest must accept the new verdicts before gogobee emits them.
5. **Live prosolis probe** (see the Verification section at the bottom). Expected: Take
off on armor (Deepforged Carapace T5 masterwork), Upgrade offers on boots + tool
(T4→T5, €25000), Repair where condition<100, Equip on The Wandering Sole BLOCKED as a
downgrade vs worn T4 boots, + the 3 magic items.
Baseline unchanged: Pete tip `1159e64`, gogobee tip `b29dcf4`. Nothing committed yet.
---
# THE WIRE CONTRACT (both repos must agree)
## equip_orders (Pete `internal/storage/equip.go` + gogobee `peteclient.EquipOrder`)
- **New column** `tier INTEGER NOT NULL DEFAULT 0` on `equip_orders`
(Pete `schema.go` CREATE at :176-191 + `addColumnIfMissing(d,"equip_orders","tier",...)`
in `db.go`, following the existing pattern at db.go:81+). Add `Tier int` to the
`EquipOrder` struct (Pete storage + gogobee `peteclient/client.go:689-698`), plumb
through Insert/scan/Pending/ByOwner and the JSON.
- **Actions** (`Action` field): existing `equip`, `unequip`; NEW `upgrade`, `repair`.
- **Field use per action:**
- `equip` (magic OR masterwork/arena): `ItemID` = `adventure_inventory` row id,
`Slot` = the item's slot (DnD slot for magic, EquipmentSlot for masterwork).
- `unequip` / take-off: `Slot` only (DnD slot → magic path; EquipmentSlot → masterwork).
- `upgrade`: `Slot` = EquipmentSlot, `Tier` = target tier. `ItemID` unused.
- `repair`: `Slot` = EquipmentSlot. `Tier`/`ItemID` unused.
- **Verdicts** (add to Pete `validEquipVerdict` + gogobee return strings): existing
`applied`, `rejected_not_owned`, `rejected_not_worn`, `rejected_not_equippable`;
NEW `rejected_downgrade`, `rejected_insufficient_funds`, `rejected_max_tier`.
Give each a friendly message in Pete's who.html JS `verdict` map (who.html ~:430).
## Detail push (Pete `internal/storage/detail.go` PlayerDetail + gogobee peteclient)
Add to `PlayerDetail`:
- `Slots []EquipSlotView` — the 5 standard slots, owner-only, for the management panel.
- `Balance float64` (`json:"balance,omitempty"`) — the owner's euro balance, for the
confirm dialogs.
New type (both repos):
```go
type EquipSlotView struct {
Slot string `json:"slot"` // weapon|armor|helmet|boots|tool
Name string `json:"name"`
Tier int `json:"tier"`
Condition int `json:"condition"`
Masterwork bool `json:"masterwork,omitempty"`
ArenaTier int `json:"arena_tier,omitempty"`
CanTakeOff bool `json:"can_take_off,omitempty"` // masterwork/arena → round-trippable
NextTier int `json:"next_tier,omitempty"` // 0 = at max tier (5)
NextName string `json:"next_name,omitempty"`
NextPrice float64 `json:"next_price,omitempty"`
RepairCost int `json:"repair_cost,omitempty"` // 0 = full condition
}
```
Worn masterwork/arena pieces are represented HERE (via `CanTakeOff`), NOT duplicated
into `Equipped`. `Equipped` stays magic-only (the DnD slots). Backpack items
(`Inventory`) keep the ItemView shape; masterwork/arena backpack rows now also get an
equip `ID` (see gogobee itemViews change) so they render Equip buttons.
---
# PHASE A — equip / take off inventory gear (masterwork + arena + magic). No money.
### gogobee changes
1. **`itemViews`** (`pete_roster.go:222-263`): currently sets `ItemView.ID = it.ID`
only for slotted magic items (:242-255); masterwork/arena backpack rows fall to the
`else if it.Slot != ""` branch (:256-259) with no id. ALSO set `v.ID = it.ID` when
`it.Type == "MasterworkGear" || it.Type == "ArenaGear"` (they carry a slot). This is
the whole reason a masterwork backpack item currently has no Equip button.
2. **`attachInventoryCompares`** (`pete_roster.go:269-280`): it decorates any row with
`ID != 0` by calling `magicItemCompare`. Now that masterwork rows have ids, GUARD it
to magic-only (skip rows where `magicItemFromAdvItem` fails). Masterwork gets no
compare card for now (fine).
3. **`equippedViews`** stays magic-only (`pete_roster.go:408-431`). Worn masterwork/arena
are surfaced via `Slots`/`EquipSlotView` instead (see Phase-common detail build).
4. **Extract headless mutators** mirroring `applyMagicEquip`/`applyMagicUnequip`
(`magic_items_gameplay.go:592-654` / `:665-688`) and the DM confirm logic
(`adventure_masterwork.go:487-587`):
- `applyMasterworkEquip(uid id.UserID, it AdvItem) (mwEquipOutcome, error)`:
require `it.Slot != ""` and Type MasterworkGear/ArenaGear else `errItemNotEquippable`
(reuse the sentinel at `magic_items_gameplay.go:573`). Load `loadAdvEquipment`; if the
current occupant is special (`Masterwork || ArenaTier>0`) evict it back to inventory
as a MasterworkGear/ArenaGear `AdvItem` (see the confirm handler :530-545 for the
exact reconstruction, incl. `arenaGearByName(name).SetKey` at :563). **Anti-dup
ordering = magic's**: `removeAdvInventoryItem(it.ID)` FIRST, then `saveAdvEquipment`,
restore the inventory row on save failure. Set the new row fields exactly like the
confirm handler :547-571 (Tier, Condition=100, Name, ActionsUsed=0, and Masterwork+
SkillSource OR ArenaTier+ArenaSet).
- `applyMasterworkUnequip(uid id.UserID, slot EquipmentSlot) (mwUnequipOutcome, error)`:
load equip; if the slot is NOT special (`!Masterwork && ArenaTier==0`) → `errSlotEmpty`
(`:576`) → `rejected_not_worn` (there is nothing round-trippable to take off; plain
shop-tier reverts via Phase B, not here). Otherwise move the piece to inventory
(MasterworkGear/ArenaGear AdvItem) and RESET the slot row to its tier-0 default:
`tier=0, condition=100, name = equipmentTiers[slot][0].Name, actions_used=0,
arena_tier=0, arena_set='', masterwork=0, skill_source=''` (matches the creation
seed at `adventure_character.go:528-537`). Keep the row (do NOT delete — the 5 rows
are an invariant; PK user_id+slot).
5. **`applyEquipOrder`** (`pete_equip.go:124-173`) routing:
- `equip`: load the AdvItem by `order.ItemID` (as today, :126-152). Branch on Type:
`MasterworkGear`/`ArenaGear``applyMasterworkEquip` (with downgrade block, below);
else → `applyMagicEquip` (unchanged). Miss → `rejected_not_owned`.
- `unequip`: branch on `order.Slot`: if it's an EquipmentSlot value
(weapon/armor/helmet/boots/tool) → `applyMasterworkUnequip(EquipmentSlot)`;
else → `applyMagicUnequip(DnDSlot)`. (Slot vocabularies are DISJOINT —
`EquipmentSlot` vs `DnDSlot` — confirmed, so the string alone disambiguates.)
6. **Downgrade block** (masterwork equip): before applying, compare
`advEffectiveTier(incoming)` vs `advEffectiveTier(currentOccupant)`
(`adventure_character.go:421-432`: arena ×1.5, masterwork ×1.25, else ×1). If
`incoming <= current` return `rejected_downgrade`. Magic equip is NOT downgrade-blocked
(its target DnD slots are usually empty and the compare card already informs).
### Pete changes (Phase A)
- Buttons already render off `ID`/worn in `who.go itemRows` (:123-145) + the `itemrow`
template (who.html :8-47). Take-off for masterwork slots is rendered from `Slots`
(see Pete Phase-common UI). Add `rejected_downgrade` to `validEquipVerdict`
(`storage/equip.go:63`) + the JS verdict map.
- `handleEquipOrder` (`web/equip.go:47-134`) already resolves an equip item from
`pd.Inventory` by id and an unequip from `pd.Equipped` by slot. Take-off of a masterwork
slot comes from `Slots`, so add resolution of a take-off/upgrade/repair against
`pd.Slots` (see Phase B handler notes — same code path).
---
# PHASE B — upgrade / repair the 5 standard slots (web shop). Spends euros (confirm-gated).
### gogobee changes
1. **Extract `purchaseEquipmentTier(uid id.UserID, slot EquipmentSlot, tier int, guid string) (outcome, error)`**
from the body of `advBuyEquipment` (`adventure_shop.go:742-829`), MINUS flavor text:
- `def := equipmentTiers[slot][tier]` (`adventure_character.go:179-220`; 6 tiers 0..5;
`EquipmentDef{Name,Tier,Description,Price}` at :172-177). Guard `tier` in range;
`tier >= len``rejected_max_tier`.
- **Downgrade block** = existing shop rule: block if `float64(def.Tier) <= advEffectiveTier(current)`
for masterwork, `def.Tier <= current.ArenaTier` for arena, `current.Tier >= def.Tier`
for plain (see `adventure_shop.go:744` / :750-757) → `rejected_downgrade`.
- **Idempotent euro**: `if !p.euro.HasExternalTx(guid)` gate the affordability check,
then `p.euro.DebitIdem(uid, def.Price, "adventure_equip_upgrade", guid)`
(`euro.go:452-461`; balance/ok/err). Insufficient → `rejected_insufficient_funds`.
Refund on later DB error via `CreditIdem` (`:465-475`). DO NOT use `Debit`/`Credit`
(non-idempotent) — the euro header at `euro.go:434-446` says web-initiated MUST use
the Idem variants. Precedent: casino escrow `pete_games.go:101-130`.
- Move old special gear to inventory (like the shop does), then `saveAdvEquipment`
with the new tier row (Tier, Condition=100, Name=def.Name, ActionsUsed=0,
Masterwork=false, ArenaTier=0). Community-pot 5% cut is OPTIONAL for web — decide;
simplest to skip it or mirror `communityPotAdd` (`adventure_shop.go:564-567`).
- NOTE: shop charges FULL `def.Price` for the chosen tier (not incremental). The web
UI should offer upgrading to the NEXT tier only (NextTier/NextPrice in EquipSlotView)
to keep it simple; the order carries the explicit target `Tier`.
2. **Extract headless repair** from `executeRepair` (`adventure_blacksmith.go:262-333`)
— it already takes only `userID` + a confirm struct and is Matrix-free except the
trailing `SendDM`. `repair(uid, slot, guid)`: recompute `blacksmithRepairCost(eq)`
(`:17-40`, base rates `:15`), `HasExternalTx`-gate + `DebitIdem(uid, cost,
"adventure_repair", guid)`, set `eq.Condition=100`, `saveAdvEquipment`, refund on error.
Condition already full → `rejected_no_change` (or just `applied` no-op; pick one and add
to the verdict set if used).
3. **Poller routing** in `applyEquipOrder`: `upgrade` → `purchaseEquipmentTier(owner,
order.Slot, order.Tier, order.GUID)`; `repair` → `repair(owner, order.Slot, order.GUID)`.
The GUID is the idempotency key for BOTH the euro move (DebitIdem externalID) AND the
existing `equip_applied_orders` ledger (`pete_equip.go:213-240`) — belt and suspenders.
4. **Build `Slots` + `Balance` in the detail push** (`buildDetailSnapshot` /
PlayerDetail assembly `pete_roster.go:190-211`). For each `allSlots` slot read
`loadAdvEquipment` (already used for public gear at rosterDetail :136): fill Name/Tier/
Condition/Masterwork/ArenaTier; `CanTakeOff = Masterwork || ArenaTier>0`;
`NextTier/NextName/NextPrice` from `equipmentTiers[slot][Tier+1]` if `Tier < 5` and it
isn't a downgrade; `RepairCost = blacksmithRepairCost(eq)` if `Condition < 100`.
`Balance = p.euro.GetBalance(uid)` (`euro.go:408-417`).
### Pete changes (Phase B)
- `storage/equip.go`: add `Tier` to EquipOrder + Insert/scan/queries; add the three new
verdicts to `validEquipVerdict`; add `upgrade`/`repair` to `validEquipAction`.
- `web/equip.go handleEquipOrder`: accept `upgrade`/`repair` actions. Resolve the slot
from `pd.Slots` (verify it exists and, for upgrade, that `req.Tier == slot.NextTier`
and `NextTier != 0`; for repair that `RepairCost > 0`). Reject client-forged tiers —
trust only the pushed `EquipSlotView`, exactly as ask 5 resolves item facts server-side.
- `web/who.html` + `who.go`: build an owner "Equipment" panel from `.Slots` — per slot a
card showing Name (T{Tier}, {Condition}%), and buttons: **Take off** if `CanTakeOff`,
**Upgrade to {NextName} · €{NextPrice}** if `NextTier>0`, **Repair · €{RepairCost}** if
`RepairCost>0`. Magic worn + backpack panels stay as they are. **Hide the public "Gear"
panel (who.html :120-135) for the owner** (`{{if not .HasSelf}}`) since this panel
supersedes it. New CSS classes → rebuild + commit `output.css`
(`npx tailwindcss -i internal/web/static/css/input.css -o …/output.css --minify`).
- **Confirm step**: for `upgrade`/`repair` (euro-spending) the JS must pop a confirm
showing cost + `page.Balance` before POSTing the order (per user decision). `equip`/
`unequip`/take-off place directly (no money). Reuse the equip JS at who.html ~:416-490.
---
# Cross-cutting / gotchas
- **Deploy order**: Pete ingest + verdict handlers accept the new actions/verdicts BEFORE
gogobee emits them. New order actions are additive on Pete's side. New DETAIL fields are
`omitempty` → safe either order. But a new gogobee VERDICT string that Pete's
`validEquipVerdict` rejects would 400 and park the order — so ship Pete first. (Same rule
as ask 1's event_type.)
- **Pete builds WITH cgo ON the server** (sqlite). See `deploy_topology` memory. gogobee
builds on millenia. Deploy = push to gitea → server `git pull --ff-only` +
`CGO_ENABLED=1 go build -o pete.new .` → swap → restart screen `pete`.
- **output.css is a committed build artifact** — new Tailwind classes silently no-op in
prod if not rebuilt+committed (bit us on `sm:grid-cols-5`).
- **Euro debt limit** applies (`BLACKJACK_DEBT_LIMIT` default 1000). A web upgrade that
would breach it is refused by `DebitIdem` → `rejected_insufficient_funds`.
- **Idempotency is doubled**: the order GUID keys BOTH `DebitIdem`'s externalID AND the
`equip_applied_orders` ledger. A retried poll re-files the stored verdict and moves no
money. Verify a mid-apply crash can't double-charge (DebitIdem is the guard; record the
applied-order ledger AFTER a successful apply, as fulfilEquipOrder already does :104).
# Testing (both repos)
- gogobee: unit-test each headless mutator; assert downgrade block, max-tier, insufficient-
funds, idempotent replay (same guid twice → one debit), masterwork equip evicts special
occupant / overwrites plain, take-off resets to tier-0.
- Pete: seed a PlayerDetail with `Slots` + a masterwork worn piece + masterwork backpack
item and assert the who page renders Take off / Upgrade / Repair buttons and the confirm
data. Use the `seedWho`/`getWho` throwaway render pattern (see
`project_adventure_expansion` memory + prior sessions' scratch test). Assert
`handleEquipOrder` rejects a client-forged tier and a non-owner.
- Verify verdict strings round-trip; `TestClearCookie…`-style table tests fit.
# Verification against live data (prod probe scripts were in this session's scratchpad,
# which is EPHEMERAL — re-create as needed). Read prod detail via
# `ssh reala@www.parodia.dev 'cd /opt/pete && python3 -'` piping a small script that opens
# data/pete.db and json-loads player_self_detail.detail_json (localpart 'prosolis') or
# adventure_roster.detail_json (name 'rurina'). Expected post-ship for prosolis: Take off
# on armor (Deepforged Carapace), Upgrade offered on weapon→? (already T5 max → none),
# boots Upgrade T4→T5 (€25000) etc., Repair where condition<100, Equip on The Wandering
# Sole (BLOCKED as downgrade vs worn T4 boots) + 3 magic items.
# Deployed baseline at handoff
- Pete tip `1159e64` live on parodia. gogobee tip `b29dcf4` live on millenia (contains all
ask 16 commits). Nothing for ask 7 written yet. gogobee has unrelated uncommitted
postgame-zone work in its tree — keep ask-7 edits in separate commits, stage by name.
# Build order (tasks)
1. Wire contract types both repos (EquipOrder.Tier, EquipSlotView, verdicts, actions).
2. gogobee headless mutators (masterwork equip/unequip, purchaseEquipmentTier, repair).
3. gogobee detail push (Slots + Balance; itemViews masterwork id; compare guard).
4. gogobee poller routing + downgrade block.
5. Pete storage (tier column, actions, verdicts) + handlers.
6. Pete who.html/who.go equipment panel + confirm JS + hide public Gear for owner.
7. Tests both sides; rebuild+commit output.css; gofmt.
8. Deploy Pete first, then gogobee; verify live with prosolis.