J3 D8-prereq: split compact flag so sim drives the picker

Adds inlineBossCombat alongside compact in runAutopilotWalk and
advanceOnceWithOpts. Production background autorun keeps both true
(inline auto-resolve), foreground stays both false (manual !fight), the
sim now uses compact=true + inlineBossCombat=false so the boss/elite
doorway returns stopBoss/stopElite after the safety gate — autoResolveCombat
+ simPickCombatAction / simPickSpell drive the fight via the turn-based
engine. The picker (and D8-b upcasting) has been dead since D3's
compact-inline boss rooms; this re-wires it.

n=50/cell L10 smoke vs d7d (zones T1-T3):
  bard    forest_shadows  61 → 100   (+39)
  bard    manor_blackspire 10 →  34   (+24)
  cleric  forest_shadows  15 →  96   (+81)
  cleric  manor_blackspire 0 →  54   (+54)
  fighter T1-T3                100   (unchanged)

Also parallelizes matrix mode via subprocess workers (each child has its
own SQLite — db package globals preclude in-process parallelism). New
-jobs flag, defaults to runtime.NumCPU(). 8 workers gave ~7x speedup on
the smoke matrix.
This commit is contained in:
prosolis
2026-05-27 23:16:33 -07:00
parent ad2a8258ba
commit 631764bbbd
5 changed files with 118 additions and 32 deletions

View File

@@ -472,10 +472,17 @@ func (p *AdventurePlugin) zoneCmdAdvance(ctx MessageContext) error {
// doorways (boss still stops; boss is the climax beat). Foreground
// `!expedition run` / `!zone advance` always pass false.
func (p *AdventurePlugin) advanceOnce(ctx MessageContext) (advanceResult, error) {
return p.advanceOnceWithOpts(ctx, false)
return p.advanceOnceWithOpts(ctx, false, false)
}
func (p *AdventurePlugin) advanceOnceWithOpts(ctx MessageContext, compact bool) (advanceResult, error) {
// inlineBossCombat (only consulted when compact==true) selects between the
// two background combat paths at a boss/elite doorway. true keeps the
// long-expedition D3 inline auto-resolve (production autorun). false
// returns stopBoss/stopElite after the safety gate so a turn-based driver
// — currently only the headless sim's autoResolveCombat / simPickCombatAction
// — handles the fight via the regular !fight / !attack engine. The sim
// uses false so simPickSpell actually fires; D8-prereq re-wired this seam.
func (p *AdventurePlugin) advanceOnceWithOpts(ctx MessageContext, compact, inlineBossCombat bool) (advanceResult, error) {
run, err := getActiveZoneRun(ctx.Sender)
if err != nil {
return advanceResult{}, fmt.Errorf("Couldn't read run state: %s", err.Error())
@@ -560,6 +567,17 @@ func (p *AdventurePlugin) advanceOnceWithOpts(ctx MessageContext, compact bool)
}, nil
}
}
if !inlineBossCombat {
// Background caller wants to drive the fight itself (sim's
// autoResolveCombat / simPickCombatAction). Surface the
// doorway like the foreground path does, after the safety
// gate has had a chance to defer the engagement.
r := stopElite
if prev == RoomBoss {
r = stopBoss
}
return advanceResult{reason: r}, nil
}
// Compact-mode elite/boss auto-resolve. resolveCombatRoom
// selects monster + label by run.CurrentRoomType().
ai, ap, ao, aended, aerr := p.resolveCombatRoom(ctx.Sender, run, zone, prev == RoomElite, true)