Adv 2.0 D&D Phase 11 D5a: Tier 4-5 zones + bestiary

Underdark + Feywild Crossing (T4) and Dragon's Lair + Abyss Portal (T5)
registered. Zone hooks, atmosphere, factions, and boss descriptions
reuse the prewritten flavor in gogobee_dungeon_zones.md §5.

New bestiary entries:
  Underdark: drow, drow_elite_warrior, drow_mage, mind_flayer,
    hook_horror, roper, boss_ilvaras_xunyl
  Feywild: redcap, will_o_wisp, quickling, night_hag, fomorian,
    boss_thornmother (reuses Tier 2 green_hag)
  Dragon's Lair: kobold, guard_drake, kobold_scale_sorcerer,
    dragonborn_cultist, young_red_dragon, boss_infernax
  Abyss Portal: quasit, vrock, hezrou, nalfeshnee, marilith,
    boss_belaxath

Boss-entry pools (BossEntryInfernax, BossEntryBelaxath) and per-zone
RoomEntry pools for Underdark + Dragon's Lair already live in
twinbee_gm_flavor.go from a prior phase and are wired in
dnd_zone_narration.go — no narration switch changes here. Feywild and
Abyss room/boss/elite flavor lands in D5b.

Bumps TestBestiaryByCR cap from CR≤20 to CR≤30 to cover Infernax (CR
24, Ancient Red Dragon). New T4/T5 presence tests added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-08 13:45:02 -07:00
parent eea3126412
commit 807864ac8d
4 changed files with 445 additions and 2 deletions

View File

@@ -48,6 +48,32 @@ func TestZoneRegistry_Tier3Present(t *testing.T) {
}
}
func TestZoneRegistry_Tier4Present(t *testing.T) {
if _, ok := getZone(ZoneUnderdark); !ok {
t.Fatal("The Underdark not registered")
}
if _, ok := getZone(ZoneFeywildCrossing); !ok {
t.Fatal("Feywild Crossing not registered")
}
t4 := zonesByTier(ZoneTierVeteran)
if len(t4) != 2 {
t.Fatalf("expected 2 Tier 4 zones, got %d", len(t4))
}
}
func TestZoneRegistry_Tier5Present(t *testing.T) {
if _, ok := getZone(ZoneDragonsLair); !ok {
t.Fatal("Dragon's Lair not registered")
}
if _, ok := getZone(ZoneAbyssPortal); !ok {
t.Fatal("The Abyss Portal not registered")
}
t5 := zonesByTier(ZoneTierLegendary)
if len(t5) != 2 {
t.Fatalf("expected 2 Tier 5 zones, got %d", len(t5))
}
}
func TestZoneRegistry_BestiaryRefsResolve(t *testing.T) {
for _, z := range allZones() {
for _, e := range z.Enemies {