mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +00:00
Long expeditions D7-b: drive autopilot camp from SimRunner
maybeAutoCamp / pitchAutopilotCamp / pitchBossSafetyCamp now take a now time.Time so the sim can inject a synthetic clock; tryAutoRun still passes time.Now().UTC(). SimRunner.RunExpedition advances simNow by autoRunCooldown per walk and runs the production camp scheduler after each soft stop (and pitchBossSafetyCamp on stopBossSafety), dwelling minAutoCampDwell + breakAutoCampIfDue so the next walk can proceed. Effect: HP-low mid-day rests, base-camp waypoints, Night-camp rollovers, and boss-safety holds all fire under the sim; D7-a's tickEventAnchoredRollover shortcut is retained on TickDay for tests and the pre-cutoff legacy path.
This commit is contained in:
@@ -7,6 +7,205 @@ import (
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// D7-b: applyAutoCamp drives maybeAutoCamp under the sim's synthetic
|
||||
// clock. When simNow is past the nightCampWindow on an event-anchored
|
||||
// expedition, the scheduler pitches a Night camp — current_day++,
|
||||
// supplies burn, last_briefing_at stamped. The helper then advances
|
||||
// simNow past minAutoCampDwell and breaks the camp so the next walk
|
||||
// proceeds.
|
||||
func TestSimRunner_ApplyAutoCamp_NightCampAdvancesDay(t *testing.T) {
|
||||
setupZoneRunTestDB(t)
|
||||
uid := id.UserID("@sim-applycamp-night:example")
|
||||
campTestCharacter(t, uid, 3)
|
||||
defer cleanupExpeditions(uid)
|
||||
|
||||
run, err := startZoneRun(uid, ZoneGoblinWarrens, 3, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("startZoneRun: %v", err)
|
||||
}
|
||||
exp, err := startExpedition(uid, ZoneGoblinWarrens, run.RunID,
|
||||
ExpeditionSupplies{Current: 20, Max: 20, DailyBurn: 2, HarshMod: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
useEventAnchored(t, exp)
|
||||
|
||||
sim := &SimRunner{P: &AdventurePlugin{}}
|
||||
// Park simNow past the night-camp window so decideAutopilotCamp
|
||||
// flags Night=true on a healthy character.
|
||||
simNow := exp.StartDate.Add(nightCampWindow + time.Hour)
|
||||
startDay := exp.CurrentDay
|
||||
startSU := exp.Supplies.Current
|
||||
before := simNow
|
||||
|
||||
night, err := sim.applyAutoCamp(exp, &simNow)
|
||||
if err != nil {
|
||||
t.Fatalf("applyAutoCamp: %v", err)
|
||||
}
|
||||
if !night {
|
||||
t.Fatal("expected Night=true beyond nightCampWindow")
|
||||
}
|
||||
if simNow.Sub(before) < minAutoCampDwell {
|
||||
t.Errorf("simNow advanced %v, want >= %v", simNow.Sub(before), minAutoCampDwell)
|
||||
}
|
||||
|
||||
got, _ := getExpedition(exp.ID)
|
||||
if got == nil {
|
||||
t.Fatal("expedition missing after night camp")
|
||||
}
|
||||
if got.CurrentDay != startDay+1 {
|
||||
t.Errorf("CurrentDay = %d, want %d", got.CurrentDay, startDay+1)
|
||||
}
|
||||
if got.Supplies.Current >= startSU {
|
||||
t.Errorf("Supplies.Current = %v, want < %v (burn should have landed)", got.Supplies.Current, startSU)
|
||||
}
|
||||
if got.Camp != nil && got.Camp.Active {
|
||||
t.Errorf("camp should have been broken after dwell, got %+v", got.Camp)
|
||||
}
|
||||
}
|
||||
|
||||
// HP-low mid-day rest: under nightCampWindow with a hurt character,
|
||||
// maybeAutoCamp pitches a Standard camp (cleared room → standard) but
|
||||
// doesn't advance the day. HP gets restored by applyCampRest; DayTicks
|
||||
// stays untouched.
|
||||
func TestSimRunner_ApplyAutoCamp_HPLowMidDayRest(t *testing.T) {
|
||||
setupZoneRunTestDB(t)
|
||||
uid := id.UserID("@sim-applycamp-hp:example")
|
||||
campTestCharacter(t, uid, 3)
|
||||
c, _ := LoadDnDCharacter(uid)
|
||||
c.HPCurrent = 1 // far below autoCampHPPct (55%)
|
||||
_ = SaveDnDCharacter(c)
|
||||
defer cleanupExpeditions(uid)
|
||||
|
||||
run, err := startZoneRun(uid, ZoneGoblinWarrens, 3, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("startZoneRun: %v", err)
|
||||
}
|
||||
exp, err := startExpedition(uid, ZoneGoblinWarrens, run.RunID,
|
||||
ExpeditionSupplies{Current: 10, Max: 10, DailyBurn: 1, HarshMod: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
useEventAnchored(t, exp)
|
||||
|
||||
sim := &SimRunner{P: &AdventurePlugin{}}
|
||||
// simNow well inside the active-day window — no Night pitch.
|
||||
simNow := exp.StartDate.Add(2 * time.Hour)
|
||||
startDay := exp.CurrentDay
|
||||
startHP := c.HPCurrent
|
||||
|
||||
night, err := sim.applyAutoCamp(exp, &simNow)
|
||||
if err != nil {
|
||||
t.Fatalf("applyAutoCamp: %v", err)
|
||||
}
|
||||
if night {
|
||||
t.Error("expected non-Night pitch within active-day window")
|
||||
}
|
||||
|
||||
got, _ := getExpedition(exp.ID)
|
||||
if got == nil {
|
||||
t.Fatal("expedition missing")
|
||||
}
|
||||
if got.CurrentDay != startDay {
|
||||
t.Errorf("CurrentDay = %d, want %d (no day advance on HP rest)", got.CurrentDay, startDay)
|
||||
}
|
||||
c2, _ := LoadDnDCharacter(uid)
|
||||
if c2 == nil || c2.HPCurrent <= startHP {
|
||||
t.Errorf("HPCurrent = %v, want > %v (rest should heal)", c2.HPCurrent, startHP)
|
||||
}
|
||||
if got.Camp != nil && got.Camp.Active {
|
||||
t.Errorf("camp should have been broken after dwell, got %+v", got.Camp)
|
||||
}
|
||||
}
|
||||
|
||||
// stopBossSafety: pitchBossSafetyCamp force-pitches a camp regardless
|
||||
// of the regular HP threshold or the boss-room block in decideAutopilotCamp.
|
||||
// applyAutoCampBossSafety wraps that and runs the same dwell/break dance.
|
||||
func TestSimRunner_ApplyAutoCampBossSafety_PitchesAndHeals(t *testing.T) {
|
||||
setupZoneRunTestDB(t)
|
||||
uid := id.UserID("@sim-bosssafety:example")
|
||||
campTestCharacter(t, uid, 3)
|
||||
c, _ := LoadDnDCharacter(uid)
|
||||
// HP at 80% — above autoCampHPPct so maybeAutoCamp would NOT fire,
|
||||
// but the boss-safety path should still pitch.
|
||||
c.HPCurrent = (c.HPMax * 80) / 100
|
||||
if c.HPCurrent < 1 {
|
||||
c.HPCurrent = 1
|
||||
}
|
||||
_ = SaveDnDCharacter(c)
|
||||
defer cleanupExpeditions(uid)
|
||||
|
||||
run, err := startZoneRun(uid, ZoneGoblinWarrens, 3, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("startZoneRun: %v", err)
|
||||
}
|
||||
exp, err := startExpedition(uid, ZoneGoblinWarrens, run.RunID,
|
||||
ExpeditionSupplies{Current: 5, Max: 5, DailyBurn: 1, HarshMod: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
useEventAnchored(t, exp)
|
||||
|
||||
sim := &SimRunner{P: &AdventurePlugin{}}
|
||||
simNow := exp.StartDate.Add(2 * time.Hour)
|
||||
startSU := exp.Supplies.Current
|
||||
startHP := c.HPCurrent
|
||||
before := simNow
|
||||
|
||||
if _, err := sim.applyAutoCampBossSafety(exp, &simNow); err != nil {
|
||||
t.Fatalf("applyAutoCampBossSafety: %v", err)
|
||||
}
|
||||
if simNow.Sub(before) < minAutoCampDwell {
|
||||
t.Errorf("simNow advanced %v, want >= %v", simNow.Sub(before), minAutoCampDwell)
|
||||
}
|
||||
|
||||
got, _ := getExpedition(exp.ID)
|
||||
if got == nil {
|
||||
t.Fatal("expedition missing after boss-safety pitch")
|
||||
}
|
||||
if got.Supplies.Current >= startSU {
|
||||
t.Errorf("Supplies.Current = %v, want < %v (camp cost should have debited)", got.Supplies.Current, startSU)
|
||||
}
|
||||
c2, _ := LoadDnDCharacter(uid)
|
||||
if c2 == nil || c2.HPCurrent <= startHP {
|
||||
t.Errorf("HPCurrent = %v, want > %v (rest should heal)", c2.HPCurrent, startHP)
|
||||
}
|
||||
if got.Camp != nil && got.Camp.Active {
|
||||
t.Errorf("camp should have been broken after dwell, got %+v", got.Camp)
|
||||
}
|
||||
}
|
||||
|
||||
// No trigger → no pitch: healthy HP, fresh simNow, no region cleared.
|
||||
// maybeAutoCamp should bail and applyAutoCamp returns (false, nil).
|
||||
func TestSimRunner_ApplyAutoCamp_NoTriggerIsNoop(t *testing.T) {
|
||||
setupZoneRunTestDB(t)
|
||||
uid := id.UserID("@sim-applycamp-noop:example")
|
||||
campTestCharacter(t, uid, 3)
|
||||
defer cleanupExpeditions(uid)
|
||||
|
||||
exp, err := startExpedition(uid, ZoneGoblinWarrens, "",
|
||||
ExpeditionSupplies{Current: 10, Max: 10, DailyBurn: 1, HarshMod: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
useEventAnchored(t, exp)
|
||||
|
||||
sim := &SimRunner{P: &AdventurePlugin{}}
|
||||
simNow := exp.StartDate.Add(time.Hour)
|
||||
before := simNow
|
||||
|
||||
night, err := sim.applyAutoCamp(exp, &simNow)
|
||||
if err != nil {
|
||||
t.Fatalf("applyAutoCamp: %v", err)
|
||||
}
|
||||
if night {
|
||||
t.Error("expected no Night pitch from no-op call")
|
||||
}
|
||||
if !simNow.Equal(before) {
|
||||
t.Errorf("simNow advanced %v on no-op call", simNow.Sub(before))
|
||||
}
|
||||
}
|
||||
|
||||
// D7-a: SimRunner.TickDay must advance event-anchored expeditions. The
|
||||
// production deliverBriefing branch reads wall-clock time for its safety-
|
||||
// net check, so the sim path short-circuits to processNightCamp + a
|
||||
|
||||
Reference in New Issue
Block a user