mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
§8.3 siege economics: - applyDailyBurn now takes an explicit siege flag and enforces a 2× floor on supply burn even when HarshMod is below 2 (tier-1 zones still get starved out per spec). - currentBurn mirrors the same precedence so status/briefing readouts stay consistent. - Briefing rollover passes e.SiegeMode through, decoupling siege from the harsh-conditions composite. §8.3 threat-70 warning: applyDailyThreatDrift emits a one-time appendApproachingSiegeLog when the level crosses 70 (prevLevel<70 and new level≥70). Pulls from a new flavor.ThreatClockApproachingSiege pool seeded with the spec's verbatim warning beat plus two voice-matched alternates. Other siege effects (boss +20 HP / Legendary Resistance, cleared-room respawn, no-short-rest enforcement) stay deferred to the combat-link phase — ThreatBandInfo already exposes the flags that engine will read. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
173 lines
4.7 KiB
Go
173 lines
4.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
|
||
wantErr bool
|
||
}{
|
||
{SupplyPurchase{StandardPacks: 1}, false},
|
||
{SupplyPurchase{StandardPacks: 3, DeluxePacks: 1}, false}, // max
|
||
{SupplyPurchase{StandardPacks: 4}, true}, // over standard cap
|
||
{SupplyPurchase{DeluxePacks: 2}, true}, // over deluxe cap
|
||
{SupplyPurchase{StandardPacks: -1}, true},
|
||
{SupplyPurchase{}, true}, // none purchased
|
||
}
|
||
for _, c := range cases {
|
||
err := c.p.Validate()
|
||
if (err != nil) != c.wantErr {
|
||
t.Errorf("%+v: err = %v, wantErr=%v", c.p, err, c.wantErr)
|
||
}
|
||
}
|
||
}
|
||
|
||
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 TestApplyDailyBurn_DrainsAndClamps(t *testing.T) {
|
||
s := ExpeditionSupplies{Current: 5, Max: 10, DailyBurn: 2, HarshMod: 1.5, ForagedToday: true}
|
||
s, burn := applyDailyBurn(s, false, false)
|
||
if burn != 2 {
|
||
t.Errorf("burn = %v, want 2", burn)
|
||
}
|
||
if s.Current != 3 {
|
||
t.Errorf("current = %v, want 3", s.Current)
|
||
}
|
||
if s.ForagedToday {
|
||
t.Error("forage flag should reset on day rollover")
|
||
}
|
||
// Harsh active doubles via mult.
|
||
s, burn = applyDailyBurn(s, true, false)
|
||
if burn != 3 { // 2 × 1.5
|
||
t.Errorf("harsh burn = %v, want 3", burn)
|
||
}
|
||
// Siege forces a 2× floor even when HarshMod is below 2 (tier 1).
|
||
t1 := ExpeditionSupplies{Current: 10, Max: 10, DailyBurn: 1, HarshMod: 1}
|
||
_, sb := applyDailyBurn(t1, false, true)
|
||
if sb != 2 {
|
||
t.Errorf("siege tier1 burn = %v, want 2 (forced floor)", sb)
|
||
}
|
||
// Siege at higher tier still uses HarshMod when it exceeds 2.
|
||
t5 := ExpeditionSupplies{Current: 10, Max: 10, DailyBurn: 1, HarshMod: 3}
|
||
_, sb5 := applyDailyBurn(t5, false, true)
|
||
if sb5 != 3 {
|
||
t.Errorf("siege tier5 burn = %v, want 3 (HarshMod)", sb5)
|
||
}
|
||
// Drain to floor.
|
||
for i := 0; i < 5; i++ {
|
||
s, _ = applyDailyBurn(s, false, false)
|
||
}
|
||
if s.Current != 0 {
|
||
t.Errorf("expected current clamped to 0, got %v", s.Current)
|
||
}
|
||
}
|