mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
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>
71 lines
1.7 KiB
Go
71 lines
1.7 KiB
Go
package plugin
|
|
|
|
import "testing"
|
|
|
|
func TestBestiaryAllWellFormed(t *testing.T) {
|
|
if len(dndBestiary) == 0 {
|
|
t.Fatal("dndBestiary empty")
|
|
}
|
|
for id, m := range dndBestiary {
|
|
if m.ID != id {
|
|
t.Errorf("%s: ID mismatch (%s)", id, m.ID)
|
|
}
|
|
if m.Name == "" {
|
|
t.Errorf("%s: empty Name", id)
|
|
}
|
|
if m.HP < 1 {
|
|
t.Errorf("%s: HP=%d", id, m.HP)
|
|
}
|
|
if m.AC < 10 {
|
|
t.Errorf("%s: AC=%d (want ≥ 10)", id, m.AC)
|
|
}
|
|
if m.Attack < 1 {
|
|
t.Errorf("%s: Attack=%d", id, m.Attack)
|
|
}
|
|
if m.XPValue < 0 {
|
|
t.Errorf("%s: XPValue=%d", id, m.XPValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBestiaryByCR(t *testing.T) {
|
|
low := dndBestiaryByCR(1.0)
|
|
for _, m := range low {
|
|
if m.CR > 1.0 {
|
|
t.Errorf("CR filter leaked: %s CR=%v", m.Name, m.CR)
|
|
}
|
|
}
|
|
// Cap above CR 24 to cover Infernax (Ancient Red Dragon, T5 boss).
|
|
high := dndBestiaryByCR(30.0)
|
|
if len(high) != len(dndBestiary) {
|
|
t.Errorf("CR≤30 filter returned %d, want all %d", len(high), len(dndBestiary))
|
|
}
|
|
}
|
|
|
|
func TestBestiaryToCombatStats(t *testing.T) {
|
|
dragon := dndBestiary["ancient_dragon"]
|
|
stats, mods := dragon.toCombatStats()
|
|
if stats.MaxHP != 367 {
|
|
t.Errorf("dragon HP = %d, want 367", stats.MaxHP)
|
|
}
|
|
if stats.AC != 22 {
|
|
t.Errorf("dragon AC = %d, want 22", stats.AC)
|
|
}
|
|
if stats.AttackBonus != 14 {
|
|
t.Errorf("dragon AttackBonus = %d, want 14", stats.AttackBonus)
|
|
}
|
|
if mods.DamageReduct != 1.0 {
|
|
t.Errorf("DamageReduct = %v, want 1.0", mods.DamageReduct)
|
|
}
|
|
}
|
|
|
|
func TestBestiaryStarterMonsters(t *testing.T) {
|
|
// Section 8.2 of v1.0 lists 6 starter monsters; verify all present.
|
|
wantIDs := []string{"goblin", "skeleton", "orc_grunt", "troll", "wyvern", "ancient_dragon"}
|
|
for _, id := range wantIDs {
|
|
if _, ok := dndBestiary[id]; !ok {
|
|
t.Errorf("starter bestiary missing: %s", id)
|
|
}
|
|
}
|
|
}
|