Files
gogobee/internal/plugin/adventure_events_test.go
prosolis b5493a0e79 N1/A4+A6: wire the stubbed milestone rewards, re-anchor mid-day events
A4 — the three "deferred to hookup" milestone grants now pay out:

  Long Game (T5 clear)   guaranteed Legendary via pickMagicItemForRarity
                         -> dropMagicItemLoot, rendered in a new
                         milestoneAward.Extra block.
  Survivalist (clean T3+) writes AdventureCharacter.Title and announces to
                         the games room. No schema bump — player_meta.title
                         already exists and saveAdvCharacter persists it.
  Two Weeks (day 15)     restocks 3 days of rations (clamped to Supplies.Max)
                         and grants 3 zone-tier consumables.

Two Weeks was specced as +5 max HP "via the expedition row". Dropped: combat
MaxHP comes from stats.HPBonus, built in combat_stats.go from gear/arena/
housing with no expedition in scope. Threading one through would leak an
expedition-only buff into the sim's balance corpus. A supply cache
self-expires with the run and needs no combat math.

A6 — mid-day events rolled 0.5%/player/day from a deferred ticker slot: one
sighting per ~200 days. The roll and its roll-minute scheduler are gone.
Events now fire where the player is demonstrably present and reading a DM:
the end-of-day digest (8%), a sale at Thom's (5%), and an arena cashout (5%),
capped at one event per player per UTC day. A player who hits all three lands
at ~1.19/week. tryTriggerEvent returns bool so a bail (dead / mid-fight /
event already active) hands the day's slot back rather than burning it.

The frequency test drives its own seeded PCG over the chance constants and
the daily cap, so it measures the policy and can't flake on global RNG order.
2026-07-09 18:49:19 -07:00

84 lines
2.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package plugin
import (
"math/rand/v2"
"testing"
"maunium.net/go/mautrix/id"
)
// TestAnchoredEventWeeklyRate pins the A6 rebalance: a player who reads an
// end-of-day digest, sells at Thom's, and cashes out of the arena every day
// should see roughly one mid-day event per week. The old 0.5%/day roll put
// that at one per ~200 days.
//
// The simulation drives its own seeded RNG rather than the package-global
// one, so it measures the policy — the chance constants and the one-event-
// per-day cap — and never flakes on RNG ordering elsewhere in the suite.
func TestAnchoredEventWeeklyRate(t *testing.T) {
anchors := []float64{advEventChanceDigest, advEventChanceSell, advEventChanceArena}
const weeks = 20000
rng := rand.New(rand.NewPCG(0x5EED, 0xA6))
events := 0
for range weeks {
for range 7 {
firedToday := false
for _, chance := range anchors {
if firedToday {
break // one event per player per UTC day
}
if rng.Float64() < chance {
firedToday = true
}
}
if firedToday {
events++
}
}
}
perWeek := float64(events) / weeks
if perWeek < 0.8 || perWeek > 1.5 {
t.Errorf("anchored events = %.3f/week, want ~1 (0.81.5)", perWeek)
}
}
// TestClaimDailyEventSlot_OnePerDay guards the cap the rate test assumes.
func TestClaimDailyEventSlot_OnePerDay(t *testing.T) {
uid := id.UserID("@events-slot:example")
const day = "2026-07-09"
advEventFiredMu.Lock()
advEventFired = nil
advEventFiredDay = ""
advEventFiredMu.Unlock()
if !claimDailyEventSlot(uid, day) {
t.Fatal("first claim of the day should succeed")
}
if claimDailyEventSlot(uid, day) {
t.Error("second claim on the same day should be refused")
}
// A trigger that bailed before reaching the player hands the slot back.
releaseDailyEventSlot(uid)
if !claimDailyEventSlot(uid, day) {
t.Error("claim after release should succeed")
}
// A new UTC day resets everyone.
if !claimDailyEventSlot(uid, "2026-07-10") {
t.Error("claim on a new day should succeed")
}
}
// TestClaimDailyEventSlot_PerUser — one player's event doesn't consume another's.
func TestClaimDailyEventSlot_PerUser(t *testing.T) {
const day = "2026-07-11"
if !claimDailyEventSlot(id.UserID("@events-a:example"), day) {
t.Fatal("player A should claim")
}
if !claimDailyEventSlot(id.UserID("@events-b:example"), day) {
t.Error("player B should claim independently of A")
}
}