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

@@ -145,13 +145,14 @@ func TestRollManorCursedTrinket(t *testing.T) {
}
func TestRestoreHarvestNodesInRoom(t *testing.T) {
nodeID := deriveLegacyNodeID(ZoneFeywildCrossing, 0)
exp := &Expedition{
ZoneID: ZoneFeywildCrossing,
CurrentRegion: "",
RegionState: map[string]any{
regionStateHarvestKey: map[string]map[string][]HarvestNode{
"": {
"0": {
nodeID: {
{ResourceID: "fey_dust", CurrentCharges: 0, MaxCharges: 2},
{ResourceID: "enchanted_petal", CurrentCharges: 1, MaxCharges: 2},
},
@@ -159,11 +160,11 @@ func TestRestoreHarvestNodesInRoom(t *testing.T) {
},
},
}
if !restoreHarvestNodesInRoom(exp, 0) {
if !restoreHarvestNodesInRoom(exp, nodeID) {
t.Fatalf("restore should report changes")
}
table := loadHarvestTable(exp)
got := table[""]["0"]
got := table[""][nodeID]
for _, n := range got {
if n.CurrentCharges != n.MaxCharges {
t.Errorf("node %s: charges=%d max=%d (expected restored)", n.ResourceID, n.CurrentCharges, n.MaxCharges)
@@ -173,8 +174,8 @@ func TestRestoreHarvestNodesInRoom(t *testing.T) {
func TestRestoreHarvestNodesInRoom_NoOpEmpty(t *testing.T) {
exp := &Expedition{ZoneID: ZoneFeywildCrossing, RegionState: map[string]any{}}
if got := restoreHarvestNodesInRoom(exp, -1); got {
t.Errorf("negative roomIdx should no-op")
if got := restoreHarvestNodesInRoom(exp, ""); got {
t.Errorf("empty nodeID should no-op")
}
}