Sim: short-rest between rooms; H5 alone doesn't close caster gap

autoResolveCombat now calls maybeShortRest after a won fight (HP < 60%
or any slots used, charges > 0), mirroring what a competent prod player
does between rooms. Re-baseline at n=100 across the full 10×3×5 matrix
written to baseline_h5sim_all10.jsonl.

Headline (vs baseline_j2a_v2_all10.jsonl, full delta in h5sim_findings.md):
mage L12 manor +8pp, cleric L12 manor +7pp, but bard L12 manor stays at
1pp and bard/cleric L12 underdark still 0. J2c (defaultKnownSpells
damage option at L3+ for bard, L4+ for cleric) is still the lever for
the trailers. Four borderline -9pp regressions on n=100 cells; flagged
for an n=300 re-run before declaring a sim defect.
This commit is contained in:
prosolis
2026-05-17 16:06:58 -07:00
parent dd25de71f0
commit 68b2122e13
5 changed files with 39325 additions and 0 deletions

View File

@@ -384,6 +384,12 @@ func (s *SimRunner) RunExpedition(uid id.UserID, zoneID ZoneID, walkCap int) (*S
res.Outcome = "fled"
}
i = walkCap
} else {
// Combat won — a competent prod player would short-rest
// between fights when wounded or down on slots (H5 added
// the partial slot refresh). Without this the sim
// under-counts caster mid-expedition staying power.
s.maybeShortRest(ctx, uid)
}
// Boss kill closes the run via combat resolution → continue
// the loop so the next walk picks up the cleared-run state.
@@ -602,6 +608,39 @@ func (s *SimRunner) autoResolveCombat(ctx MessageContext) (bool, error) {
return false, fmt.Errorf("combat exceeded %d rounds", autoCombatRoundCap)
}
// simShortRestHPPct is the HP-percentage threshold below which the sim
// pulls the short-rest lever after a combat. 60% mirrors the
// "competent prod player" model: rest when you've taken real damage and
// have charges to spend, but don't burn charges on scratches.
const simShortRestHPPct = 60
// maybeShortRest fires a short rest after a won combat when the
// character has charges and either (a) HP is below simShortRestHPPct or
// (b) any spell slots have been used. Mirrors what a prod player would
// type between rooms; H5 (commit 1cd53eb) made this slot-recovering for
// casters. Errors are swallowed — short rest is best-effort QoL and a
// failure should not abort the sim run.
func (s *SimRunner) maybeShortRest(ctx MessageContext, uid id.UserID) {
c, _ := LoadDnDCharacter(uid)
if c == nil || c.ShortRestCharges <= 0 {
return
}
needsHP := c.HPMax > 0 && c.HPCurrent*100 < c.HPMax*simShortRestHPPct
hasUsedSlots, _ := casterHasUsedSlots(uid)
if !needsHP && !hasUsedSlots {
return
}
_ = s.P.handleDnDShortRest(ctx)
// handleDnDShortRest sets RestingUntil = now+1h. The autopilot walk
// doesn't gate on that, but TickDay's overnight camp / future zone
// transitions might — clear it so the sim isn't accidentally
// locked out of state it would otherwise reach.
if c2, _ := LoadDnDCharacter(uid); c2 != nil && c2.RestingUntil != nil {
c2.RestingUntil = nil
_ = SaveDnDCharacter(c2)
}
}
// simHealHPThresholdPct is the player-HP percentage below which the sim
// reaches for a heal consumable before its !attack/!cast. 40% mirrors
// the "competent player" prod assumption — heal early enough to absorb