mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
Mechanical `gofmt -w ./internal ./cmd`. Mostly struct-field realignment that had drifted, plus a few trailing-newline fixes. No behaviour change — gofmt is semantics-preserving, and build/vet/test are green either side. Split out from the code-review fixes that follow so those stay reviewable instead of hiding inside a wall of realignment.
275 lines
8.7 KiB
Go
275 lines
8.7 KiB
Go
package plugin
|
||
|
||
import "testing"
|
||
|
||
func TestSupplyDailyBurn_AllTiers(t *testing.T) {
|
||
cases := []struct {
|
||
tier ZoneTier
|
||
want float32
|
||
}{
|
||
{ZoneTierBeginner, 1.0},
|
||
{ZoneTierApprentice, 1.5},
|
||
{ZoneTierJourneyman, 2.0},
|
||
{ZoneTierVeteran, 3.0},
|
||
{ZoneTierLegendary, 4.0},
|
||
}
|
||
for _, c := range cases {
|
||
if got := supplyDailyBurn(c.tier); got != c.want {
|
||
t.Errorf("tier %d: got %v, want %v", c.tier, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSupplyHarshMultiplier_AllTiers(t *testing.T) {
|
||
cases := []struct {
|
||
tier ZoneTier
|
||
want float32
|
||
}{
|
||
{ZoneTierBeginner, 1.0},
|
||
{ZoneTierApprentice, 1.5},
|
||
{ZoneTierJourneyman, 2.0},
|
||
{ZoneTierVeteran, 2.5},
|
||
{ZoneTierLegendary, 3.0},
|
||
}
|
||
for _, c := range cases {
|
||
if got := supplyHarshMultiplier(c.tier); got != c.want {
|
||
t.Errorf("tier %d: got %v, want %v", c.tier, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSupplyDepletion_Brackets(t *testing.T) {
|
||
cases := []struct {
|
||
current float32
|
||
max float32
|
||
want SupplyDepletionState
|
||
}{
|
||
{10, 10, SupplyNormal}, // 100%
|
||
{3, 10, SupplyNormal}, // 30%
|
||
{2.5, 10, SupplyNormal}, // 25% — boundary stays normal
|
||
{2, 10, SupplyRationing}, // 20%
|
||
{1, 10, SupplyRationing}, // 10% — boundary stays rationing
|
||
{0.9, 10, SupplySevereRationing},
|
||
{0.1, 10, SupplySevereRationing},
|
||
{0, 10, SupplyStarvation},
|
||
}
|
||
for _, c := range cases {
|
||
s := ExpeditionSupplies{Current: c.current, Max: c.max}
|
||
if got := supplyDepletion(s); got != c.want {
|
||
t.Errorf("%v/%v: got %d, want %d", c.current, c.max, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSupplyRollModifier(t *testing.T) {
|
||
cases := map[SupplyDepletionState]int{
|
||
SupplyNormal: 0,
|
||
SupplyRationing: -1,
|
||
SupplySevereRationing: -2,
|
||
SupplyStarvation: -2,
|
||
}
|
||
for state, want := range cases {
|
||
if got := supplyRollModifier(state); got != want {
|
||
t.Errorf("state %d: got %d, want %d", state, got, want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSupplyAllowsLongRest(t *testing.T) {
|
||
cases := map[SupplyDepletionState]bool{
|
||
SupplyNormal: true,
|
||
SupplyRationing: true,
|
||
SupplySevereRationing: false,
|
||
SupplyStarvation: false,
|
||
}
|
||
for state, want := range cases {
|
||
if got := supplyAllowsLongRest(state); got != want {
|
||
t.Errorf("state %d: got %v, want %v", state, got, want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSupplyPurchase_Validate(t *testing.T) {
|
||
cases := []struct {
|
||
p SupplyPurchase
|
||
tier ZoneTier
|
||
wantErr bool
|
||
}{
|
||
// T3 is the only tier whose caps are unchanged from the pre-D5
|
||
// shape (3 standard / 1 deluxe); use it to anchor the
|
||
// happy-path / over-cap parity assertions.
|
||
{SupplyPurchase{StandardPacks: 1}, ZoneTierJourneyman, false},
|
||
{SupplyPurchase{StandardPacks: 3, DeluxePacks: 1}, ZoneTierJourneyman, false},
|
||
{SupplyPurchase{StandardPacks: 4}, ZoneTierJourneyman, true},
|
||
{SupplyPurchase{DeluxePacks: 2}, ZoneTierJourneyman, true},
|
||
{SupplyPurchase{StandardPacks: -1}, ZoneTierJourneyman, true},
|
||
{SupplyPurchase{}, ZoneTierJourneyman, true}, // none purchased
|
||
|
||
// D5-a per-tier caps. T1/T2 tighten to (2,1); T4 widens to (5,1);
|
||
// T5 widens to (7,2). A T3-legal 3-standard loadout fails on T1.
|
||
{SupplyPurchase{StandardPacks: 2, DeluxePacks: 1}, ZoneTierBeginner, false},
|
||
{SupplyPurchase{StandardPacks: 3}, ZoneTierBeginner, true},
|
||
{SupplyPurchase{StandardPacks: 5, DeluxePacks: 1}, ZoneTierVeteran, false},
|
||
{SupplyPurchase{StandardPacks: 6}, ZoneTierVeteran, true},
|
||
{SupplyPurchase{StandardPacks: 7, DeluxePacks: 2}, ZoneTierLegendary, false},
|
||
{SupplyPurchase{StandardPacks: 7, DeluxePacks: 3}, ZoneTierLegendary, true},
|
||
}
|
||
for _, c := range cases {
|
||
err := c.p.Validate(c.tier)
|
||
if (err != nil) != c.wantErr {
|
||
t.Errorf("%+v T%d: err = %v, wantErr=%v", c.p, int(c.tier), err, c.wantErr)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSupplyPackCaps_PerTier(t *testing.T) {
|
||
cases := []struct {
|
||
tier ZoneTier
|
||
wantStd int
|
||
wantDlx int
|
||
}{
|
||
{ZoneTierBeginner, 2, 1},
|
||
{ZoneTierApprentice, 2, 1},
|
||
{ZoneTierJourneyman, 3, 1},
|
||
{ZoneTierVeteran, 5, 1},
|
||
{ZoneTierLegendary, 7, 2},
|
||
}
|
||
for _, c := range cases {
|
||
s, d := supplyPackCaps(c.tier)
|
||
if s != c.wantStd || d != c.wantDlx {
|
||
t.Errorf("T%d caps: got (%d,%d), want (%d,%d)", int(c.tier), s, d, c.wantStd, c.wantDlx)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSupplyPurchase_TotalAndCost(t *testing.T) {
|
||
p := SupplyPurchase{StandardPacks: 3, DeluxePacks: 1}
|
||
if p.Total() != 50 {
|
||
t.Errorf("total = %v, want 50", p.Total())
|
||
}
|
||
if p.Cost() != 240 {
|
||
t.Errorf("cost = %d, want 240", p.Cost())
|
||
}
|
||
}
|
||
|
||
func TestMakeSupplies_FillsFromTier(t *testing.T) {
|
||
p := SupplyPurchase{StandardPacks: 2}
|
||
s := makeSupplies(ZoneTierJourneyman, p)
|
||
if s.Max != 20 || s.Current != 20 {
|
||
t.Errorf("supplies: %+v", s)
|
||
}
|
||
if s.DailyBurn != 2.0 {
|
||
t.Errorf("daily burn = %v, want 2", s.DailyBurn)
|
||
}
|
||
if s.PacksStandard != 2 {
|
||
t.Errorf("packs std = %d", s.PacksStandard)
|
||
}
|
||
}
|
||
|
||
func TestApplyRangerForage(t *testing.T) {
|
||
ranger := &DnDCharacter{Class: ClassRanger}
|
||
fighter := &DnDCharacter{Class: ClassFighter}
|
||
det := func(n int) int { return n - 1 } // always rolls the max (1d4 = 4)
|
||
|
||
// Ranger, fresh day, plenty of headroom: max 1d4 = 4 SU added, flag set.
|
||
exp := &Expedition{Supplies: ExpeditionSupplies{Current: 10, Max: 50}}
|
||
if gain := applyRangerForage(exp, ranger, det); gain != 4 {
|
||
t.Errorf("ranger forage gain = %v, want 4", gain)
|
||
}
|
||
if exp.Supplies.Current != 14 {
|
||
t.Errorf("current after forage = %v, want 14", exp.Supplies.Current)
|
||
}
|
||
if !exp.Supplies.ForagedToday {
|
||
t.Error("ForagedToday should be set after a successful grant")
|
||
}
|
||
|
||
// Same day, second call: no-op (already foraged).
|
||
if gain := applyRangerForage(exp, ranger, det); gain != 0 {
|
||
t.Errorf("repeat forage gain = %v, want 0", gain)
|
||
}
|
||
if exp.Supplies.Current != 14 {
|
||
t.Errorf("current after repeat = %v, want 14", exp.Supplies.Current)
|
||
}
|
||
|
||
// Non-Ranger: never grants, never sets the flag.
|
||
exp2 := &Expedition{Supplies: ExpeditionSupplies{Current: 10, Max: 50}}
|
||
if gain := applyRangerForage(exp2, fighter, det); gain != 0 {
|
||
t.Errorf("non-ranger gain = %v, want 0", gain)
|
||
}
|
||
if exp2.Supplies.ForagedToday {
|
||
t.Error("non-ranger should not stamp ForagedToday")
|
||
}
|
||
|
||
// Headroom cap: 2 SU short of Max → grant clamps to 2 even on a max roll.
|
||
exp3 := &Expedition{Supplies: ExpeditionSupplies{Current: 48, Max: 50}}
|
||
if gain := applyRangerForage(exp3, ranger, det); gain != 2 {
|
||
t.Errorf("headroom-capped gain = %v, want 2", gain)
|
||
}
|
||
if exp3.Supplies.Current != 50 {
|
||
t.Errorf("current should clamp to Max, got %v", exp3.Supplies.Current)
|
||
}
|
||
|
||
// Already at Max: no grant, but flag still set so the day's roll is spent.
|
||
exp4 := &Expedition{Supplies: ExpeditionSupplies{Current: 50, Max: 50}}
|
||
if gain := applyRangerForage(exp4, ranger, det); gain != 0 {
|
||
t.Errorf("full-bag gain = %v, want 0", gain)
|
||
}
|
||
if !exp4.Supplies.ForagedToday {
|
||
t.Error("full-bag should still consume the day's forage attempt")
|
||
}
|
||
|
||
// Nil character / nil expedition: never panics, returns 0.
|
||
if gain := applyRangerForage(exp, nil, det); gain != 0 {
|
||
t.Errorf("nil char gain = %v, want 0", gain)
|
||
}
|
||
if gain := applyRangerForage(nil, ranger, det); gain != 0 {
|
||
t.Errorf("nil exp gain = %v, want 0", gain)
|
||
}
|
||
}
|
||
|
||
func TestApplyDailyBurn_DrainsAndClamps(t *testing.T) {
|
||
// Phase 5-B (shipped): applyDailyBurn now scales by
|
||
// phase5BDailyBurnRatePct = 50 by default — every expected value
|
||
// here is halved relative to the pre-Phase-5-B baseline. The
|
||
// math-pure shape (harsh-mult, siege-floor) is unchanged; only
|
||
// the final multiplier is new. See applyDailyBurn doc for the
|
||
// difficulty-pass motivation.
|
||
s := ExpeditionSupplies{Current: 5, Max: 10, DailyBurn: 2, HarshMod: 1.5, ForagedToday: true}
|
||
s, burn := applyDailyBurn(s, false, false)
|
||
if burn != 1 { // 2 × 50%
|
||
t.Errorf("burn = %v, want 1", burn)
|
||
}
|
||
if s.Current != 4 { // 5 - 1
|
||
t.Errorf("current = %v, want 4", s.Current)
|
||
}
|
||
if s.ForagedToday {
|
||
t.Error("forage flag should reset on day rollover")
|
||
}
|
||
// Harsh active applies HarshMod (1.5), then phase5B halves: 2*1.5*0.5 = 1.5.
|
||
s, burn = applyDailyBurn(s, true, false)
|
||
if burn != 1.5 {
|
||
t.Errorf("harsh burn = %v, want 1.5", burn)
|
||
}
|
||
// Siege forces a 2× floor even when HarshMod is below 2 (tier 1);
|
||
// phase5B halves: 1 × 2 × 0.5 = 1.
|
||
t1 := ExpeditionSupplies{Current: 10, Max: 10, DailyBurn: 1, HarshMod: 1}
|
||
_, sb := applyDailyBurn(t1, false, true)
|
||
if sb != 1 {
|
||
t.Errorf("siege tier1 burn = %v, want 1 (forced 2× floor × phase5B 50%%)", sb)
|
||
}
|
||
// Siege at higher tier still uses HarshMod when it exceeds 2:
|
||
// 1 × 3 × 0.5 = 1.5.
|
||
t5 := ExpeditionSupplies{Current: 10, Max: 10, DailyBurn: 1, HarshMod: 3}
|
||
_, sb5 := applyDailyBurn(t5, false, true)
|
||
if sb5 != 1.5 {
|
||
t.Errorf("siege tier5 burn = %v, want 1.5 (HarshMod × phase5B)", sb5)
|
||
}
|
||
// Drain to floor.
|
||
for i := 0; i < 10; i++ {
|
||
s, _ = applyDailyBurn(s, false, false)
|
||
}
|
||
if s.Current != 0 {
|
||
t.Errorf("expected current clamped to 0, got %v", s.Current)
|
||
}
|
||
}
|