Long expeditions D1-d: T4 zones to new 28-34 room band

Both T4 zones follow the D1-a/b/c pattern: extend the graph so the
longest entry→boss walk lands in band, keep the zone's authored
topology intact.

  underdark         (T4): 10 → 46 nodes, longest 30 (band 28-34)
  feywild_crossing  (T4):  9 → 53 nodes, longest 30 (band 28-34)

Topology preserved per zone:
- Underdark: 4-region authoring intact (every node carries a valid
  RegionID); 3-way fork1 (drow R2 / illithid R3 / deep_chasm R1)
  with LockNone / LockPerception / LockStatCheck CON; deep_chasm
  intentionally stays R1; all three arms converge at the R4
  throne_approach merge. Adds the missing Trap anchor (Collapsed
  Arch) in the R1 preamble.
- Feywild Crossing: woven forks intact — hag_circle still has two
  incoming edges (one per first-stage path), time_eddy stays
  grove-exclusive, illusion_garden stays marsh-exclusive, fork1
  remains CHA-vs-Perception with no LockNone option. Adds the
  missing Trap anchor (Cursed Thicket) and a fae_court MERGE so
  the three second-stage endings converge into one final boss
  approach instead of triplicating it.

MinRooms/MaxRooms re-pitched 8-10 → 28-34 so the dice fallback also
lands in band for any future graphless variant.

T5 zones (dragons_lair, abyss_portal) deferred to D1-e — those
need a RegionID backfill on top of the length extension since their
regionsByZone entries are not currently honored by their graphs.
This commit is contained in:
prosolis
2026-05-27 18:02:05 -07:00
parent bbc25fe958
commit d99f975074
5 changed files with 511 additions and 94 deletions

View File

@@ -7,8 +7,18 @@ func TestUnderdarkGraph_Registered(t *testing.T) {
if !ok {
t.Fatal("zoneUnderdarkGraph not registered")
}
if len(g.Nodes) != 10 {
t.Errorf("nodes = %d, want 10", len(g.Nodes))
// Long-expedition D1-d widened this zone from 10 → 46 nodes so the
// longest entry→boss walk lands in the T4 [28,34] traversal band.
if len(g.Nodes) != 46 {
t.Errorf("nodes = %d, want 46", len(g.Nodes))
}
}
func TestUnderdarkGraph_LongestPathInBand(t *testing.T) {
g := zoneUnderdarkGraph()
got := graphLongestPath(g)
if got < 28 || got > 34 {
t.Errorf("longest path = %d, want in T4 band [28,34]", got)
}
}
@@ -18,10 +28,10 @@ func TestUnderdarkGraph_Registered(t *testing.T) {
func TestUnderdarkGraph_AllNodesHaveRegion(t *testing.T) {
g := zoneUnderdarkGraph()
validRegions := map[string]bool{
"underdark_surface_tunnels": true,
"underdark_drow_outpost": true,
"underdark_illithid_warren": true,
"underdark_deep_throne": true,
"underdark_surface_tunnels": true,
"underdark_drow_outpost": true,
"underdark_illithid_warren": true,
"underdark_deep_throne": true,
}
for id, n := range g.Nodes {
if n.RegionID == "" {
@@ -95,3 +105,18 @@ func TestUnderdarkGraph_AllArmsReachBoss(t *testing.T) {
}
}
}
// TestUnderdarkGraph_TrapAnchor verifies D1-d added the missing Trap
// node. Original G8i graph had elite/boss/harvest but no trap.
func TestUnderdarkGraph_TrapAnchor(t *testing.T) {
g := zoneUnderdarkGraph()
var trapCount int
for _, n := range g.Nodes {
if n.Kind == NodeKindTrap {
trapCount++
}
}
if trapCount != 1 {
t.Errorf("trap nodes = %d, want 1", trapCount)
}
}