package plugin import "testing" // Phase 11 D1a — zone registry tests. Validates that: // 1. Tier 1 zones are registered. // 2. All enemy/boss BestiaryID references resolve. // 3. Level gating (zonesForLevel) respects the 2-tier-above ceiling. // 4. Loot tables either have explicit drop chances in [0,1] or // UniqueAlways set. func TestZoneRegistry_Tier1Present(t *testing.T) { if _, ok := getZone(ZoneGoblinWarrens); !ok { t.Fatal("Goblin Warrens not registered") } if _, ok := getZone(ZoneCryptValdris); !ok { t.Fatal("Crypt of Valdris not registered") } t1 := zonesByTier(ZoneTierBeginner) if len(t1) != 2 { t.Fatalf("expected 2 Tier 1 zones, got %d", len(t1)) } } func TestZoneRegistry_Tier2Present(t *testing.T) { if _, ok := getZone(ZoneForestShadows); !ok { t.Fatal("Forest of Shadows not registered") } if _, ok := getZone(ZoneSunkenTemple); !ok { t.Fatal("Sunken Temple not registered") } t2 := zonesByTier(ZoneTierApprentice) if len(t2) != 2 { t.Fatalf("expected 2 Tier 2 zones, got %d", len(t2)) } } func TestZoneRegistry_Tier3Present(t *testing.T) { if _, ok := getZone(ZoneManorBlackspire); !ok { t.Fatal("Haunted Manor of Blackspire not registered") } if _, ok := getZone(ZoneUnderforge); !ok { t.Fatal("The Underforge not registered") } t3 := zonesByTier(ZoneTierJourneyman) if len(t3) != 2 { t.Fatalf("expected 2 Tier 3 zones, got %d", len(t3)) } } 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 { if _, ok := dndBestiary[e.BestiaryID]; !ok { t.Errorf("zone %s enemy %s missing from bestiary", z.ID, e.BestiaryID) } } if _, ok := dndBestiary[z.Boss.BestiaryID]; !ok { t.Errorf("zone %s boss %s missing from bestiary", z.ID, z.Boss.BestiaryID) } } } func TestZoneRegistry_BossCRMatchesZoneTier(t *testing.T) { // Sanity: boss CR should be at least at the high end of tier // CR range. Tier 1 = CR 1/8–1 enemies, but boss is CR 3–5. for _, z := range allZones() { if z.Boss.CR < 1 { t.Errorf("zone %s boss CR %.1f too low", z.ID, z.Boss.CR) } } } func TestZonesForLevel_TierGate(t *testing.T) { // L1 player (tier 1): max tier visible = 1+2 = 3. With T1+T2+T3 // registered (D1a + D3a + D4a), L1 sees all six. got := zonesForLevel(1) if len(got) != 6 { t.Fatalf("L1 should see 6 zones (2 T1 + 2 T2 + 2 T3), got %d", len(got)) } // L20 player should also see all zones (no upper cutoff). got = zonesForLevel(20) if len(got) < 6 { t.Fatalf("L20 should see all zones, got %d", len(got)) } } func TestZoneRegistry_LootDropChances(t *testing.T) { for _, z := range allZones() { for _, l := range z.Loot { if l.UniqueAlways { continue } if l.DropChance <= 0 || l.DropChance > 1 { t.Errorf("zone %s loot %s drop chance %.2f out of range", z.ID, l.ItemID, l.DropChance) } } } } func TestZoneRegistry_RoomCountSane(t *testing.T) { // Long-expedition plan §2 widens the bands per tier: T1 12–14 up to // T5 ~36–44. The guard floor stays at 5 (no zone should ever drop // below that) and the ceiling tracks the T5 target. for _, z := range allZones() { if z.MinRooms < 5 || z.MaxRooms > 44 || z.MinRooms > z.MaxRooms { t.Errorf("zone %s rooms %d-%d outside design (5-44, min<=max)", z.ID, z.MinRooms, z.MaxRooms) } } } func TestZoneRegistry_ElitesFlagged(t *testing.T) { // Each Tier 1 zone roster should contain at least one elite (per // design doc each zone lists ELITE entries). for _, z := range allZones() { anyElite := false for _, e := range z.Enemies { if e.IsElite { anyElite = true break } } if !anyElite { t.Errorf("zone %s has no elite enemy", z.ID) } } } func TestZoneRegistry_LevelRangeMatchesTier(t *testing.T) { // Tier→expected level range per design doc §2. expected := map[ZoneTier][2]int{ ZoneTierBeginner: {1, 3}, ZoneTierApprentice: {3, 5}, ZoneTierJourneyman: {5, 8}, ZoneTierVeteran: {8, 12}, ZoneTierLegendary: {12, 20}, } for _, z := range allZones() { want, ok := expected[z.Tier] if !ok { t.Errorf("zone %s has unknown tier %d", z.ID, z.Tier) continue } if z.LevelMin < want[0] || z.LevelMax > want[1] { t.Errorf("zone %s level %d-%d outside tier %d range %d-%d", z.ID, z.LevelMin, z.LevelMax, z.Tier, want[0], want[1]) } } }