Mechanical `gofmt -w ./internal ./cmd`. Mostly struct-field realignment that
had drifted, plus a few trailing-newline fixes. No behaviour change — gofmt is
semantics-preserving, and build/vet/test are green either side.
Split out from the code-review fixes that follow so those stay reviewable
instead of hiding inside a wall of realignment.
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.
The two legacy columns are no longer needed at runtime. CurrentRoom is
derived from len(VisitedNodes)-1 on read (lockstep with what the
dual-write era stored), and RoomSeq is a transient in-memory field
populated only when a fresh run is started.
- INSERT, SELECT, and UPDATE statements drop the two columns. The
schema still carries them — they retire in G9c.
- scanZoneRun derives CurrentRoom and falls back to a linear-derived
CurrentNode only if a row predates the G4 dual-write deploy.
- markRoomCleared rewritten to walk the registered graph (first
outgoing edge / dead-end completion). Tests that use it as an
end-to-end run driver continue to pass.
- advanceZoneRunNode drops its current_room dual-write.
- adventure_activity_unified reads visited_nodes for the daily
"X/N rooms" progress fragment.
- Camp boss-room test pivots to setting current_node directly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 9 zones have hand-authored graphs (G8a–G8i), so the POC gate has
served its purpose. Make graph mode the only runtime path:
- Drop branchingZonesEnabled() and the os import in zone_graph_nav.go.
- Inline the gate-on branches in zoneCmdMap, zoneCmdAdvance,
CurrentRoomType, startZoneRun, and the expedition map renderer.
- Keep the legacy linear functions (markRoomCleared, renderZoneMap,
generateRoomSequence) standing — their tests still exercise them and
they'll retire alongside current_room / room_seq_json in G9b.
- Strip gate test (TestBranchingZonesGate, TestCurrentRoomType_GateOff)
and t.Setenv calls from the surviving graph tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the graph types from G2/G3/G4 into the !zone advance flow.
Default off; with the gate on, room-clear branches on outgoing-edge
count instead of the linear current_room+1 walk.
zone_graph_nav.go — env gate, edge unlock evaluation
(LockNone/Perception/Key/LevelMin/RegionClear/StatCheck), pendingFork
JSON shape with encode/decode helpers, perception roll seeded
deterministically from (runID, from, to) so reload doesn't grant
retries, plus fork prompt rendering and the
recordRoomCleared/advanceZoneRunNode/completeRunAtNode persistence
trio.
dnd_zone_cmd_graph.go — advanceTransitionGraph drives the graph-mode
branch in zoneCmdAdvance: 0 outs → run completes (boss_defeated set
when current node IsBoss); 1 unlocked out → auto-advance and emit
the same teaser the legacy path uses; 2+ outs (or a single locked
out) → write pending fork to node_choices and render the menu. New
!zone go <n> / !zone choose <n> consumes the prompt, validates the
chosen edge is unlocked, advances, and emits the arrival teaser.
Old "go" alias for "enter" was unused in tests — repurposed.
dnd_zone_cmd.go — extracts formatNextRoomMessage so the legacy and
graph-mode paths share the post-advance teaser. Adds the new
subcommand to the dispatch switch and help text.
Tests: pure-logic coverage for unlock evaluation, perception
determinism, pendingFork roundtrip + decode-edge-cases, choice
resolution, fork prompt rendering with hint vs no-hint locked
options, edge ordering by (Weight, To), and the env gate. Existing
zone tests pass with and without the gate set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>