mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 00:52:40 +00:00
R1: split "where am I" from "how far have I walked"
Backtracking (revisit R2) breaks the assumption every zone-run caller quietly relied on: that CurrentRoom == len(VisitedNodes)-1. Position and progress have been the same number only because navigation was forward-only. Split them. CurrentRoom is now the first-entry index of CurrentNode in VisitedNodes -- the room number the player was shown on the way in, and the salt that enemy/trap/harvest/encounter keys hash. A revisited room therefore resolves to the same room. RoomsTraversed is a new monotonic step counter, persisted, backfilled from visited_nodes for in-flight rows. The audit found only two progress-shaped reads. narrationCadence moves to RoomsTraversed so a backtracking player draws fresh flavor rather than replaying the entry-room lines. The other was a latent bug: zoneCmdGo labelled the room it moved into as CurrentRoom+1, which is only correct at the frontier; advanceZoneRunNode now returns the true path index. VisitedNodes becomes an ordered set and RoomsCleared becomes idempotent -- both no-ops while navigation is forward-only, both load-bearing after R2. No player-visible behavior change. Nothing to route off RoomsTraversed for threat/SU: verified at HEAD that movement charges neither. Threat comes from combat, supplies burn per day. The revisit plan's cost model claimed otherwise and has been corrected -- R2's "discount" is really a net-new cost decision.
This commit is contained in:
35
internal/plugin/bootstrap_rooms_traversed.go
Normal file
35
internal/plugin/bootstrap_rooms_traversed.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"gogobee/internal/db"
|
||||
)
|
||||
|
||||
// bootstrapRoomsTraversed backfills dnd_zone_run.rooms_traversed for rows
|
||||
// that predate the revisit R1 column. Before R1 the step counter was implicit
|
||||
// — len(visited_nodes) — because navigation was forward-only, so replaying
|
||||
// that identity is an exact reconstruction, not an estimate.
|
||||
//
|
||||
// The ALTER lands DEFAULT 0, which would tell an in-flight run it has walked
|
||||
// nowhere: ambient narration cadence would reset to the entry-room line and
|
||||
// R2's revisit preflight would read a fresh run. Hence the backfill.
|
||||
//
|
||||
// Idempotent, and safe to run against live rows: every row written after R1
|
||||
// inserts rooms_traversed >= 1 (the entry node counts as one traversal), so
|
||||
// `rooms_traversed = 0` uniquely identifies a row this has not yet touched.
|
||||
// Kept in-tree permanently — a fresh deploy restoring an old backup needs it
|
||||
// (feedback_loader_rewire_needs_bootstrap).
|
||||
func bootstrapRoomsTraversed() {
|
||||
res, err := db.Get().Exec(`
|
||||
UPDATE dnd_zone_run
|
||||
SET rooms_traversed = MAX(json_array_length(visited_nodes), 1)
|
||||
WHERE rooms_traversed = 0`)
|
||||
if err != nil {
|
||||
slog.Error("bootstrap: rooms_traversed backfill failed", "err", err)
|
||||
return
|
||||
}
|
||||
if n, _ := res.RowsAffected(); n > 0 {
|
||||
slog.Warn("bootstrap: rooms_traversed backfilled from visited_nodes", "rows", n)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user