Files
gogobee/gogobee_revisit_plan.md
prosolis 6d402343e6 games: gogobee learns to poll Pete for money it has to move
Pete holds the chips; we hold the euros and are the only one who can move
them. This is the loop that turns an escrow row on games.parodia.dev into a
real balance change here.

It is a poll because Pete cannot call us and isn't going to be able to. Every
step is keyed on the escrow guid, because every step can be interrupted in the
worst possible place: die after DebitIdem and before the verdict is queued,
and Pete re-offers the row, we claim it again, the debit replays as a no-op
and reports the same answer. The player is charged once. That is the only
property here that really matters, and there is a test that kills us three
times to prove it.

The verdict rides the queue that already carries adventure facts — it wants
the same durability, backoff and parking, so the row now says where it's going
rather than the queue growing a twin. Flush sends it at once instead of after
a 15s sender tick, because there's a person at the other end watching a
spinner.

First GET gogobee has ever made to Pete.
2026-07-13 23:00:11 -07:00

268 lines
12 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.
# Revisit / Backtrack Navigation — Design Plan
Lets players walk back to previously visited rooms within an active zone
run to re-harvest, re-fork (e.g. after acquiring a key), or pick up
something they skipped. Forward-only navigation has been the rule since
Phase G; this plan retires that assumption deliberately.
## Goals
- Re-harvest depleted nodes after a long rest (primary use case).
- Allow general exploration freedom — wander back, look around.
- Re-pick a fork after acquiring a key or changing strategy.
- Preserve the existing pacing pressure (threat clock, SU drain) but
make backtracking *cheaper* than fresh exploration to reflect that
the route is already known.
## Non-goals
- Teleporting to arbitrary discovered rooms.
- Backtracking during `!expedition run` autopilot (autopilot is
forward-only by design).
- Re-fighting cleared encounters or re-arming disarmed traps.
## Player-facing surface
- **`!revisit <N>`** — walks one edge from the current node toward a
previously-visited node. `N` is the 1-indexed room number printed in
`!map`'s Path strip.
- Must be in `VisitedNodes`.
- Must be adjacent to `CurrentNode` via an edge in the zone graph
(forward or reverse direction; graph edges are directed but we
allow reverse traversal for revisit).
- Costs threat and SU at a reduced rate (see Cost Model).
- No combat re-roll, no trap re-arm in cleared rooms.
- **`!map`** — already shows the numbered Path strip; players read N
from there.
- **`!zone advance`** post-revisit — unchanged. From any node, picks
the first outgoing edge (or shows the fork prompt). Re-forking a
previously-resolved fork node clears the prior choice silently.
## Cost model
> **CORRECTED 2026-07-09 during R1.** The table below originally claimed a
> fresh `!zone advance` costs +2 threat / 1.0 SU. It does not. Verified at
> HEAD: **movement itself is free.** There is no per-room tick anywhere.
> - **Supplies burn per *day*,** not per room — `applyDailyBurn` is called
> only from `dnd_expedition_cycle.go:108` (the day rollover),
> `dnd_expedition_extract.go:52`, and region transit. Never from advance.
> - **Threat comes from *combat*,** not from walking:
> `applyRoomCombatThreatForUser` (+5 normal, +8 elite/boss) fires from the
> three combat-resolution sites, plus daily drift, harvest noise (+2), and
> ambient. `zoneCmdAdvance` / `advanceOnce` touch neither clock.
>
> This inverts R2's central premise. A revisited room fires no combat
> (terminal `CombatSession` rows gate re-entry), so backtracking is already
> free — the design problem is not *discounting* a cost, it's that there is
> **no pacing pressure to discount**. Whatever R2 charges is a net-new cost.
| Move | Threat tick | SU tick |
|------|-------------|---------|
| Fresh-room `!zone advance` | **0** (actual) | **0** (actual) |
| Combat resolved in a room | +5, +8 elite/boss | 0 |
| Day rollover | drift (mood-scaled) | daily burn |
| `!revisit <N>` (one edge) | **TBD — see below** | n/a (no per-move SU exists) |
**DECIDED 2026-07-09 — option (1), +1 threat per revisit edge.** Shipped in
R2 as `revisitThreatCost`. Standalone (non-expedition) zone runs have no
threat clock and so pay nothing. A `revisitSiegeGuard` refuses the move at
threat ≥ 99: siege is one-way sticky, and a player must not be able to strand
their own expedition on a move they made to go pick up a herb.
Options as they were weighed, cheapest first:
1. **Charge +1 threat per revisit edge.** One `applyThreatDelta` call,
mirrors "harvest noise". Reads as "you stir up attention doubling back."
Cheap, honest, and it's the only clock revisit can plausibly touch.
2. **Charge nothing.** Backtracking is already free of combat; the real
cost is the day clock, which a long detour burns anyway. Simplest, and
arguably correct — the pacing pressure is the multi-day supply budget,
not the step count.
3. **Charge a fractional day.** Rejected: the day counter is the spine of
every milestone, digest and anchored event. Don't make it fractional.
Recommend **(1)** at +1: it's the smallest lever that makes a detour a real
choice, and it rides an existing, tested seam.
## State model
The hard problem: `CurrentRoom` is currently derived as
`len(VisitedNodes)-1` (dnd_zone_run.go:377), which assumes
monotonically-growing visit history. Backtracking breaks that.
### Schema changes
- **`VisitedNodes`** — unchanged semantics: ordered set of nodes ever
entered, first-entry order. Stays append-only when entering a *new*
node; revisits do not append.
- **`CurrentNode`** — already the source-of-truth for "where am I."
- **`CurrentRoom`** — repurpose from `len(VisitedNodes)-1` to "index of
CurrentNode within VisitedNodes" (the room's first-entry index, which
is the path-relative label the player sees).
- **`RoomsTraversed`** (NEW, int column on `dnd_zone_run`) — monotonic
step counter. Drives threat/SU ticks. Bootstrapped to
`len(VisitedNodes)` for existing rows.
### Audit pass required
Every read of `r.CurrentRoom` and `len(r.VisitedNodes)` needs to be
classified:
- **"Path index" reads (keep as CurrentRoom)**: display headers
("Room 3/7"), encounter ID derivation (`encounterIDForRoom`), enemy
salt seeds, harvest room keys, status/abandon strings, camp room
index.
- **"Progress counter" reads (switch to RoomsTraversed)**: threat
clock ticks, supplies ticks, ambient narration cadence, autopilot
preflight, fatigue/exhaustion accrual if any.
Concrete callsite list will be produced during implementation; estimate
~1015 callsites total.
## Fork re-pick
Re-entering a fork node clears its resolved choice in `node_choices`
(or equivalent). Next `!zone advance` shows the fork prompt fresh,
evaluated against current inventory (so a newly-acquired key unlocks
previously-locked edges).
Silent — no warning. Graph-back navigation means the player has
walked back through their previously-chosen path; nothing in
`VisitedNodes` is destroyed; the alternate branch they previously
took is still revisitable. There is no "discard" semantics.
## RoomsCleared semantics
Idempotent — exiting a room a second time doesn't re-append to
`RoomsCleared`. The "advanced past" flag is sticky.
## Preflight checks (rejections)
`!revisit` rejects with a clear DM when:
- Target room is not in `VisitedNodes` ("You haven't been there yet.")
- Target equals `CurrentNode` (no-op)
- Target is not adjacent to `CurrentNode` via any graph edge
("Room X isn't directly connected — backtrack one step at a time.")
- Active combat session in current room
("Finish the fight first.")
- Threat clock would tick past max (use existing camp-eligibility
preflight pattern)
- SU would drop below survival floor (same)
- Character is dead / expedition is paused (same gates as `!zone advance`)
- Autopilot run is active
## Rollout phases
Tentative ordering. Each phase ships independently.
### R1 — schema + audit ✅ **DONE 2026-07-09** (branch `n1-restoration`)
Shipped: `rooms_traversed` column + `bootstrapRoomsTraversed` backfill;
`CurrentRoom` re-derived via `pathIndexOf(VisitedNodes, CurrentNode)`;
`appendVisited` / `appendClearedRoom` made set-semantic; `narrationCadence`
routed off `RoomsTraversed`; `advanceZoneRunNode` now returns the moved-to
path index. Tests in `zone_revisit_r1_test.go`. Full suite green. No
player-visible behavior change.
**Audit outcome — the callsite split was smaller than estimated (~1015).**
Almost every `CurrentRoom` read is a *path index* and correctly stays put:
enemy/trap salts (`pickZoneEnemy`, `pickZoneTrap`, `rollTrapDamage`), loot
RNG seed, `encounterIDForRoom`, harvest room keys, camp `RoomIndex`, map
render, "Room X/Y" headers. Keying those on the node's first-entry index is
exactly what makes a revisited room resolve to *the same room*.
Only two reads were progress-shaped:
- `narrationCadence` → now `RoomsTraversed-1`, so a backtracking player
draws fresh flavor instead of replaying the lines they read on the way in.
- `nextIdx := run.CurrentRoom + 1` (`dnd_zone_cmd_graph.go`) — not a
progress read but a latent bug: "+1" only labels correctly while the
player is at the frontier. `advanceZoneRunNode` now returns the true index.
**Nothing to route off `RoomsTraversed` for threat/SU** — there are no
per-room ticks to route (see the corrected Cost model above). The counter
currently drives narration cadence and stands ready for R2's cost decision.
Two behaviors were hardened in passing, both no-ops under forward-only
navigation but load-bearing the moment R2 lands:
- `VisitedNodes` is an ordered **set** — a re-entered node is not
re-appended, so room numbers never renumber under the player.
- `RoomsCleared` is **idempotent** — exiting a room twice can't inflate it
past `TotalRooms` and skew the "N/M rooms" renders.
### R2 — `!revisit` command ✅ **DONE 2026-07-09** (branch `n1-restoration`)
Shipped: `!revisit <N>` (alias `!zone revisit`), `adjacentNodes` reverse-edge
traversal, full preflight, +1 threat, `!map` now prints a **Back to:** strip
of legal targets. Fork re-pick still deferred to R3 — revisit refuses while a
fork prompt is pending, because both `!zone advance` and `!zone go` resolve
`node_choices` without checking which node the player is standing on.
**"No combat/trap re-trigger logic needed" was wrong — and it was an exploit.**
Terminal `CombatSession` rows gate *only* Elite and Boss rooms, at the doorway
check in `advanceOnceWithOpts`. An Exploration room re-enters
`resolveCombatRoom` unconditionally and a Trap room re-arms. Left alone,
`!revisit` would have been an infinite farm: step back one room, `!zone
advance`, repeat for loot and XP. Fixed with a `RoomIsCleared(CurrentRoom)`
early-return at the top of `resolveRoom` — a no-op forward-only, since advance
clears a room only *after* resolving it. Guarded by
`TestRevisitedRoom_DoesNotReResolve` and `TestFreshRoom_StillResolves`.
**Autopilot needed a stopgap, sooner than R5 planned.** Autopilot has no
on/off switch — `tryAutoRun` is ticker-driven for every active expedition on a
2h CAS cooldown. Without intervention the background walker marches the player
straight back out of the room they just revisited, and the whole feature reads
as broken. `grantAutorunGrace` stamps `last_autorun_at` on revisit, buying one
full cooldown window. **R5 still owes the real policy decision** (refuse to
auto-walk from a non-frontier node, vs. walk back to the frontier first).
Tests in `zone_revisit_r2_test.go`. Full suite green.
### R3 — fork re-pick
- On revisit landing into a fork node, clear `node_choices` for that
node.
- Next advance re-prompts.
- Tests: lock-with-key acquired between two fork visits, alternate
branch selection, no-change re-pick.
### R4 — UX polish
- DM line on revisit explaining the cost ("You retrace your steps to
Room 3 (the fork). Threat +1, SU 0.5.").
- Surface `!revisit` in `!help` and `!zone` help blocks.
- Ambient/narration audit: any "you press deeper into the dungeon"
lines that no longer fit when player is backtracking.
### R5 — autopilot guard
- `!expedition run` refuses to start while at a non-frontier node?
Or auto-walks back to frontier first? Decide based on R1R4 feel.
Likely: refuse with a "use !zone advance to return to the frontier
first" hint.
## Open questions (lower priority)
- Should there be a small flavor-only narrative beat the first time
the player backtracks in an expedition? ("TwinBee notes you doubling
back — there's no shame in it. Sometimes the answer was in a room
you'd already passed.")
- Does a babysitter's safe-rest affect revisit cost? (Probably not —
babysitter is a camp upgrade, revisit is movement.)
- Multi-region expeditions — does revisit work across region
boundaries within the same expedition? (Initial answer: no, only
within the current zone run; cross-region travel is its own seam.)
## Effort estimate
Comparable to a small Phase pass (G6-sized). Roughly:
- R1: 1 session (schema bump + audit + tests).
- R2: 1 session.
- R3: 0.5 session.
- R4: 0.5 session.
- R5: 0.5 session.
Total: ~34 working sessions.