mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
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.
84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
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.8–1.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")
|
||
}
|
||
}
|