diff --git a/internal/plugin/adventure_treasure.go b/internal/plugin/adventure_treasure.go index 8c01423..d598466 100644 --- a/internal/plugin/adventure_treasure.go +++ b/internal/plugin/adventure_treasure.go @@ -46,6 +46,16 @@ var advTreasureDropRates = map[int]float64{ 3: 0.008, 4: 0.004, 5: 0.0015, + // Tier 6 (Mythic post-game) breaks the downward curve on purpose. The rate + // per roll falls tier over tier because the lower tiers are ground daily; + // a Mythic run is gated behind L18 and both T5 bosses and happens rarely, + // so the same declining rate would mean a postgame player effectively never + // sees a treasure. Slightly above T5 keeps the per-run odds in the band the + // other tiers land in. + // + // NOTE: this rate is inert until advAllTreasures gains a tier 6 pool — see + // the TODO there. A tier with a rate but no pool drops nothing. + 6: 0.002, } const advMaxTreasures = 3 @@ -221,6 +231,13 @@ var advAllTreasures = map[int][]AdvTreasureDef{ InventoryDesc: "The Ocarina (Cracked). Three songs. +10 all skills. Do not play the third one.", }, }, + // TODO(t6-treasures): there is no tier 6 pool yet, so Mythic zones drop no + // treasure at all — advTreasureDropRates has a tier 6 rate waiting for it. + // What's missing is the content: four Mythic treasures with bonuses above + // the T5 line, InventoryDesc + RoomAnnounce strings for each (RoomAnnounce + // must use {location_mid}, never a literal zone name), and a TIER 6 register + // in TreasureDiscovery. Write them against internal/flavor/VOICE_CANON.md; + // the T5 pool below is the tonal floor to clear, not the ceiling. 5: { { Key: "shard_of_unnamed", Name: "Shard of the Unnamed", Tier: 5, @@ -285,7 +302,12 @@ func rollAdvTreasureDropDetailed(tier int, userID id.UserID, chatLevel int, weig pool, ok := advAllTreasures[tier] if !ok || len(pool) == 0 { - return nil, roll, rate + // A tier that has a rate but no pool (tier 6, until the TODO above is + // filled) cannot drop anything. Report a zero rate rather than the + // configured one: the caller turns a close roll into a "just missed" + // DM, and telling a postgame player they nearly won a treasure that + // cannot be won is worse than staying quiet. + return nil, 0, 0 } // Pick random treasure diff --git a/internal/plugin/adventure_treasure_test.go b/internal/plugin/adventure_treasure_test.go index 0922d7f..e014cb5 100644 --- a/internal/plugin/adventure_treasure_test.go +++ b/internal/plugin/adventure_treasure_test.go @@ -66,6 +66,36 @@ func TestAdvTreasureDropDetailed_ForcedRollGrantsTreasure(t *testing.T) { } } +// TestAdvTreasureTier6_PoolStillMissing pins the known tier 6 gap: Mythic zones +// carry a drop rate but no treasure pool, so a forced roll yields nothing and +// the player is told nothing either (a "just missed" DM for an unwinnable +// treasure would be a lie). +// +// This test is the reminder. Writing advAllTreasures[6] — see TODO(t6-treasures) +// in adventure_treasure.go — turns it red, and the fix is to delete it and +// assert the tier 6 drop instead, the way ForcedRollGrantsTreasure does for +// tier 1. +func TestAdvTreasureTier6_PoolStillMissing(t *testing.T) { + if err := db.Init(t.TempDir()); err != nil { + t.Fatalf("db.Init: %v", err) + } + if _, ok := advAllTreasures[6]; ok { + t.Fatal("a tier 6 treasure pool now exists — drop this test and assert the drop instead") + } + // The rate is configured and waiting, so the gap is the pool alone. + if advTreasureDropRates[6] == 0 { + t.Error("tier 6 drop rate went missing; a Mythic pool would be unreachable") + } + drop, roll, rate := rollAdvTreasureDropDetailed(6, "@mythic:example.org", 0, 1000) + if drop != nil { + t.Fatalf("tier 6 produced a drop from an empty pool: %+v", drop.Def) + } + // Zeroed, not merely no-drop: this is what keeps the near-miss DM quiet. + if roll != 0 || rate != 0 { + t.Errorf("empty pool reported roll=%v rate=%v, want 0/0 so no near-miss fires", roll, rate) + } +} + // The treasure and masterwork systems predate zones and speak AdvLocation. func TestAdvLocForZone(t *testing.T) { loc := advLocForZone(ZoneGoblinWarrens)