Branching zones G6: dependent surfaces re-keyed on graph nodes

- Harvest tables (expedition + standalone) now keyed by node_id; legacy
  room-idx entries auto-migrate via read-fallback + drop-on-save.
- Narration salts swapped from run.CurrentRoom to narrationCadence(run)
  (= len(visited_nodes)-1) so flavor pickers survive G9 column drop.
- !zone map renders the graph (BFS by PosX/PosY) when the gate is on:
  ✓/▶/· status, ╳ for locked-only edges, secrets hidden until visited.
- Region-boundary hook in graph advance/!zone go updates expedition
  CurrentRegion + visited list when from/to nodes differ — without
  burning supplies or retiring runs (the graph IS the run state).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-09 15:17:30 -07:00
parent 2d249d7d0a
commit 893d3dacad
14 changed files with 562 additions and 64 deletions

View File

@@ -330,6 +330,22 @@ func eliteRoomEntryLine(zoneID ZoneID, runID string, roomIdx int) string {
return "🎭 **TwinBee:** " + line
}
// narrationCadence returns the salt index used to seed narration
// pickers — the count of nodes visited so far (0-based, matching the
// pre-G6 CurrentRoom semantics). Both linear-mode and graph-mode
// advance bump len(VisitedNodes) in lockstep with CurrentRoom, so this
// preserves existing pick determinism while letting G9 drop the legacy
// current_room column without re-keying every flavor pool seed.
func narrationCadence(run *DungeonRun) int {
if run == nil {
return 0
}
if n := len(run.VisitedNodes); n > 0 {
return n - 1
}
return run.CurrentRoom
}
// pickLineDeterministic — stable selection across (runID, salt). Same
// inputs always yield the same line, so a player who re-reads status
// sees the same prose; different rooms / different runs vary.