mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
Closes D1. Both T5 zones follow the D1-a/b/c/d pattern: extend the graph so the longest entry→boss walk lands in band, keep the zone's authored topology intact. Also backfills the missing per-node RegionID authoring against dnd_expedition_region.go — the D1-d deferral note flagged this as a prerequisite for T5. dragons_lair (T5): 12 → 47 nodes, longest 38 (band 36-44) abyss_portal (T5): 13 → 51 nodes, longest 39 (band 36-44) Topology preserved per zone: - Dragon's Lair: 4-region authoring (every node carries a valid RegionID); binary-converging fork1 (ash_bridge TRAP / treasure_vault Perception 15) still converges at the R3 wyrmlings_nest elite; capstone fork2 still 3-way (LockNone / CHA 16 / Perception 17 SECRET LootBias 2.5) with each spur preserving its own named capstone node. New R4 infernax_doors MERGE consolidates the long final hall instead of triplicating it. - Abyss Portal: 4-region authoring; three sequential forks preserved (binary / binary / ternary) — fork1 Perception 16, fork2 CON 16 (full STR/DEX/CON/INT/WIS/CHA roster still covered), fork3 capstone 3-way with reality_seam SECRET LootBias 3.0. New R4 belaxath_doors MERGE consolidates the final tear-approach. MinRooms/MaxRooms re-pitched 9-10 → 36-44 so the dice fallback also lands in band for any future graphless variant.
122 lines
3.6 KiB
Go
122 lines
3.6 KiB
Go
package plugin
|
|
|
|
import "testing"
|
|
|
|
func TestAbyssPortalGraph_Registered(t *testing.T) {
|
|
g, ok := zoneGraphRegistry[ZoneAbyssPortal]
|
|
if !ok {
|
|
t.Fatal("zoneAbyssPortalGraph not registered")
|
|
}
|
|
// Long-expedition D1-e widened this zone from 13 → 51 nodes so the
|
|
// longest entry→boss walk lands in the T5 [36,44] traversal band.
|
|
if len(g.Nodes) != 51 {
|
|
t.Errorf("nodes = %d, want 51", len(g.Nodes))
|
|
}
|
|
}
|
|
|
|
func TestAbyssPortalGraph_LongestPathInBand(t *testing.T) {
|
|
g := zoneAbyssPortalGraph()
|
|
got := graphLongestPath(g)
|
|
if got < 36 || got > 44 {
|
|
t.Errorf("longest path = %d, want in T5 band [36,44]", got)
|
|
}
|
|
}
|
|
|
|
// TestAbyssPortalGraph_AllNodesHaveRegion confirms D1-e backfilled the
|
|
// missing RegionID authoring per dnd_expedition_region.go: every node
|
|
// carries a non-empty RegionID matching the registry.
|
|
func TestAbyssPortalGraph_AllNodesHaveRegion(t *testing.T) {
|
|
g := zoneAbyssPortalGraph()
|
|
validRegions := map[string]bool{
|
|
"abyss_outer_rift": true,
|
|
"abyss_demon_assembly": true,
|
|
"abyss_wardens_post": true,
|
|
"abyss_the_tear": true,
|
|
}
|
|
for id, n := range g.Nodes {
|
|
if n.RegionID == "" {
|
|
t.Errorf("node %s has empty RegionID — D1-e requires region authoring on every node", id)
|
|
}
|
|
if !validRegions[n.RegionID] {
|
|
t.Errorf("node %s RegionID = %q, not in dnd_expedition_region.go registry", id, n.RegionID)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestAbyssPortalGraph_AllFourRegionsRepresented confirms each authored
|
|
// region has at least one node.
|
|
func TestAbyssPortalGraph_AllFourRegionsRepresented(t *testing.T) {
|
|
g := zoneAbyssPortalGraph()
|
|
regions := map[string]int{}
|
|
for _, n := range g.Nodes {
|
|
regions[n.RegionID]++
|
|
}
|
|
for _, r := range []string{
|
|
"abyss_outer_rift",
|
|
"abyss_demon_assembly",
|
|
"abyss_wardens_post",
|
|
"abyss_the_tear",
|
|
} {
|
|
if regions[r] == 0 {
|
|
t.Errorf("region %q has no nodes — multi-region invariant broken", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestAbyssPortalGraph_ThreeSequentialForks confirms the design shape:
|
|
// three fork nodes in series, each binary or ternary.
|
|
func TestAbyssPortalGraph_ThreeSequentialForks(t *testing.T) {
|
|
g := zoneAbyssPortalGraph()
|
|
wants := map[string]int{
|
|
"abyss_portal.fork1": 2,
|
|
"abyss_portal.fork2": 2,
|
|
"abyss_portal.fork3": 3,
|
|
}
|
|
for id, want := range wants {
|
|
if got := len(g.outgoingEdges(id)); got != want {
|
|
t.Errorf("%s outgoing = %d, want %d", id, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAbyssPortalGraph_AllCapstoneLeavesReachBoss(t *testing.T) {
|
|
g := zoneAbyssPortalGraph()
|
|
for _, leaf := range []string{
|
|
"abyss_portal.direct_assault",
|
|
"abyss_portal.usurper_throne",
|
|
"abyss_portal.reality_seam",
|
|
} {
|
|
if !reachable(g, leaf, "abyss_portal.boss") {
|
|
t.Errorf("%s unreachable to boss", leaf)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestAbyssPortalGraph_FullStatRosterCoverage confirms the
|
|
// project-wide claim: by G8h, all six abilities (STR/DEX/CON/INT/WIS/
|
|
// CHA) appear as authored stat-check locks across shipping zones.
|
|
// CON is the missing one prior to this zone — locked here on
|
|
// mind_corridor.
|
|
func TestAbyssPortalGraph_FullStatRosterCoverage(t *testing.T) {
|
|
g := zoneAbyssPortalGraph()
|
|
conSeen := false
|
|
for _, outs := range g.Edges {
|
|
for _, e := range outs {
|
|
if e.Lock == LockStatCheck && lockDataString(e.LockData, "stat") == "CON" {
|
|
conSeen = true
|
|
}
|
|
}
|
|
}
|
|
if !conSeen {
|
|
t.Error("expected at least one CON stat-check edge — completes ability roster by G8h")
|
|
}
|
|
}
|
|
|
|
func TestAbyssPortalGraph_RealitySeamHighestBias(t *testing.T) {
|
|
g := zoneAbyssPortalGraph()
|
|
seam := g.Nodes["abyss_portal.reality_seam"]
|
|
if seam.Content.LootBias < 3.0 {
|
|
t.Errorf("reality_seam LootBias = %v, want >= 3.0 (Abyss capstone)", seam.Content.LootBias)
|
|
}
|
|
}
|