mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
Three flavor arcs on existing per-player counters (no schema, golden byte-identical): - Misty: 3 deepening dialogue beats at MistyEncounterCount 5/15/30, prepended to the encounter opening the one time the counter lands on a threshold. Fiction only — no copy ties a donation to the hidden arena effects. - Robbie: every 10th visit he leaves a consumable "for the trouble," drawn from the dungeon pool at a level-matched tier. - Thom: paying off the Tier-4 mortgage (no next tier) sends a final letter instead of the stock payoff line, plus an inert pet-treat keepsake if the player keeps a pet. Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
92 lines
2.7 KiB
Go
92 lines
2.7 KiB
Go
package plugin
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// D2 NPC arcs — pure-helper coverage. None of these touch combat, so the
|
|
// characterization golden is unaffected; these just pin the arc thresholds,
|
|
// gift tiers, and the keepsake shape.
|
|
|
|
func TestMistyArcBeat_FiresOnlyAtThresholds(t *testing.T) {
|
|
want := map[int]bool{5: true, 15: true, 30: true}
|
|
for count := 0; count <= 40; count++ {
|
|
got := mistyArcBeat(count)
|
|
if want[count] {
|
|
if got == "" {
|
|
t.Errorf("encounter %d: expected an arc beat, got none", count)
|
|
}
|
|
} else if got != "" {
|
|
t.Errorf("encounter %d: expected no arc beat, got %q", count, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMistyArcBeats_DoNotLeakBuffMechanics(t *testing.T) {
|
|
// The arena effects are a hidden discovery mechanic; arc copy must never
|
|
// tie a donation to a combat outcome.
|
|
banned := []string{"arena", "damage", "buff", "combat", "attack", "heal", "bonus"}
|
|
for count, beat := range mistyArcBeats {
|
|
lower := strings.ToLower(beat)
|
|
for _, b := range banned {
|
|
if strings.Contains(lower, b) {
|
|
t.Errorf("beat %d leaks mechanic word %q: %s", count, b, beat)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRobbieGiftTier_MatchesArenaBands(t *testing.T) {
|
|
cases := []struct {
|
|
level int
|
|
tier int
|
|
}{
|
|
{1, 1}, {3, 1}, {4, 2}, {7, 2}, {8, 3}, {12, 3},
|
|
{13, 4}, {17, 4}, {18, 5}, {20, 5},
|
|
}
|
|
for _, c := range cases {
|
|
if got := robbieGiftTier(c.level); got != c.tier {
|
|
t.Errorf("robbieGiftTier(%d) = %d, want %d", c.level, got, c.tier)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRobbieGiftCadence(t *testing.T) {
|
|
// The gift lands on every 10th visit and no other.
|
|
for visit := 1; visit <= 35; visit++ {
|
|
gives := visit%robbieGiftEveryNVisits == 0
|
|
if gives != (visit == 10 || visit == 20 || visit == 30) {
|
|
t.Errorf("visit %d: unexpected gift cadence", visit)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestThomPetTreat_IsRobbieSafeKeepsake(t *testing.T) {
|
|
treat := thomPetTreatItem()
|
|
if treat.Name == "" {
|
|
t.Fatal("treat has no name")
|
|
}
|
|
// Robbie's sweep skips "card" items, so the keepsake survives him.
|
|
if treat.Type != "card" {
|
|
t.Errorf("treat type = %q, want card so Robbie's sweep skips it", treat.Type)
|
|
}
|
|
if treat.Value != 0 {
|
|
t.Errorf("treat value = %d, want 0 (inert keepsake)", treat.Value)
|
|
}
|
|
// Confirm the sweep filter actually excludes it.
|
|
if got := robbieQualifyingItems([]AdvItem{treat}, nil); len(got) != 0 {
|
|
t.Errorf("Robbie would take the treat: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestThomFinalLetterPet_SubstitutesPetName(t *testing.T) {
|
|
letter := strings.ReplaceAll(thomFinalLetterPet, "{pet}", "Biscuit")
|
|
if strings.Contains(letter, "{pet}") {
|
|
t.Error("unsubstituted {pet} placeholder remains")
|
|
}
|
|
if !strings.Contains(letter, "Biscuit") {
|
|
t.Error("pet name not substituted into the letter")
|
|
}
|
|
}
|