mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
expedition-sim: -pet-level flag to model a base housing pet
Synthetic sim chars are vanilla base-class (no pet, no subclass), so the per-round pet attack/deflect/whiff path was never exercised in the sim. Add -pet-level N (1-10) which stamps a base Massive Dog (no armor) onto the AdventureCharacter via the normal save path before the run, so combat's DerivePlayerStats sees HasPet()==true. 0 (default) stays petless. Measured lift (n=40, 10 classes x L3/7/12 x 3 zones): overall clear-rate 38.3% petless -> 47.8% at pet L10 (+9.4pp); biggest gains go to the caster trailers (bard +13.3, warlock +11.9, cleric +11.4), narrowing class spread.
This commit is contained in:
@@ -43,10 +43,17 @@ func main() {
|
|||||||
runs = flag.Int("runs", 1, "replicates per (class,level,zone) cell (matrix mode)")
|
runs = flag.Int("runs", 1, "replicates per (class,level,zone) cell (matrix mode)")
|
||||||
|
|
||||||
trace = flag.Bool("trace", false, "include raw per-round CombatEvent stream on the LAST combat of each expedition (boss room) — for J2 diagnostic sweeps")
|
trace = flag.Bool("trace", false, "include raw per-round CombatEvent stream on the LAST combat of each expedition (boss room) — for J2 diagnostic sweeps")
|
||||||
|
|
||||||
|
petLevel = flag.Int("pet-level", 0, "attach a base housing pet at this level (1-10) to every sim character; 0 = no pet (default, matches prod char-creation)")
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if *petLevel < 0 || *petLevel > 10 {
|
||||||
|
fail("pet-level must be 0-10, got", *petLevel)
|
||||||
|
}
|
||||||
|
|
||||||
plugin.SetSimIncludeTrace(*trace)
|
plugin.SetSimIncludeTrace(*trace)
|
||||||
|
plugin.SetSimPetLevel(*petLevel)
|
||||||
|
|
||||||
if *matrix {
|
if *matrix {
|
||||||
// Matrix default: drop log to keep stdout manageable; explicit
|
// Matrix default: drop log to keep stdout manageable; explicit
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ func (s *SimRunner) BuildCharacter(uid id.UserID, class DnDClass, level int) (*D
|
|||||||
if err := createAdvCharacter(uid, "sim_"+string(class)); err != nil {
|
if err := createAdvCharacter(uid, "sim_"+string(class)); err != nil {
|
||||||
return nil, fmt.Errorf("createAdvCharacter: %w", err)
|
return nil, fmt.Errorf("createAdvCharacter: %w", err)
|
||||||
}
|
}
|
||||||
|
if simPetLevel > 0 {
|
||||||
|
if err := attachSimPet(uid, simPetLevel); err != nil {
|
||||||
|
return nil, fmt.Errorf("attachSimPet: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
c := &DnDCharacter{
|
c := &DnDCharacter{
|
||||||
UserID: uid,
|
UserID: uid,
|
||||||
Race: RaceHuman,
|
Race: RaceHuman,
|
||||||
@@ -170,6 +175,26 @@ func simConsumableBundle(tier int) map[string]int {
|
|||||||
// L7-9 → T3, L10-12 → T4, L13+ → T5). It's a "kitted-out at expected
|
// L7-9 → T3, L10-12 → T4, L13+ → T5). It's a "kitted-out at expected
|
||||||
// difficulty" baseline, not a min-max — players past the appropriate
|
// difficulty" baseline, not a min-max — players past the appropriate
|
||||||
// shop visit should be at or above this band.
|
// shop visit should be at or above this band.
|
||||||
|
// attachSimPet stamps a base housing pet (Massive Dog, no armor) at the
|
||||||
|
// given level onto the synthetic character via the normal adv-char save
|
||||||
|
// path, so combat's DerivePlayerStats sees HasPet()==true. Dog vs cat is
|
||||||
|
// numerically identical today, so type is arbitrary; armor tier stays 0 to
|
||||||
|
// model the plain "base pet" rather than a kitted one.
|
||||||
|
func attachSimPet(uid id.UserID, level int) error {
|
||||||
|
char, err := loadAdvCharacter(uid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
char.PetType = "dog"
|
||||||
|
char.PetName = "SimDog"
|
||||||
|
char.PetLevel = level
|
||||||
|
char.PetArmorTier = 0
|
||||||
|
char.PetArrived = true
|
||||||
|
char.PetChasedAway = false
|
||||||
|
char.PetXP = 0
|
||||||
|
return saveAdvCharacter(char)
|
||||||
|
}
|
||||||
|
|
||||||
func outfitSimCharacter(uid id.UserID, level int) error {
|
func outfitSimCharacter(uid id.UserID, level int) error {
|
||||||
tier := simGearTierForLevel(level)
|
tier := simGearTierForLevel(level)
|
||||||
equip, err := loadAdvEquipment(uid)
|
equip, err := loadAdvEquipment(uid)
|
||||||
@@ -300,6 +325,20 @@ var simIncludeTrace = false
|
|||||||
// room). Callers should flip this on before BuildCharacter / RunExpedition.
|
// room). Callers should flip this on before BuildCharacter / RunExpedition.
|
||||||
func SetSimIncludeTrace(on bool) { simIncludeTrace = on }
|
func SetSimIncludeTrace(on bool) { simIncludeTrace = on }
|
||||||
|
|
||||||
|
// simPetLevel, when > 0, attaches a base housing pet (Massive Dog, no
|
||||||
|
// armor) at that level to every synthetic character. 0 (the default) leaves
|
||||||
|
// the character petless, matching prod char-creation. Pets are otherwise
|
||||||
|
// unreachable in the sim — synthetic chars never trigger the arrival flow —
|
||||||
|
// so this is the only way to exercise the per-round pet attack / deflect /
|
||||||
|
// whiff path for balance measurement. Combat reads pet stats off the
|
||||||
|
// AdventureCharacter (see DerivePlayerStats), so BuildCharacter stamps the
|
||||||
|
// fields there, not on the DnDCharacter.
|
||||||
|
var simPetLevel = 0
|
||||||
|
|
||||||
|
// SetSimPetLevel attaches a base pet at the given level (1-10) to sim
|
||||||
|
// characters. 0 disables. Flip before BuildCharacter.
|
||||||
|
func SetSimPetLevel(level int) { simPetLevel = level }
|
||||||
|
|
||||||
// SimLogEntry is a JSONL-friendly projection of one dnd_expedition_log
|
// SimLogEntry is a JSONL-friendly projection of one dnd_expedition_log
|
||||||
// row. We expose just the fields a post-hoc analyzer needs without
|
// row. We expose just the fields a post-hoc analyzer needs without
|
||||||
// pulling the full ExpeditionEntry type.
|
// pulling the full ExpeditionEntry type.
|
||||||
|
|||||||
Reference in New Issue
Block a user