boss-gauntlet-runner
PURPOSE
Dev-only harness that drives the live sandbox MissionHandle through every entry in BOSS_GAUNTLET_ORDER (or a single boss) under one of four modes (smoke / balance / survival / stress). Reuses the existing mission + engine rAF render loop, calling sandboxResetForTest() and sandboxSpawnBoss() between bosses for a clean slate. Injects scripted DPS directly on the boss anchor’s hp field (bypassing weapons) so the kill rate is deterministic and player loadout doesn’t contaminate the test. Samples telemetry on a frame-stride into per-boss BossGauntletReports and emits live progress to a registered listener.
OWNS
- Module-level singletons:
_missionRef,_cancelled,_onProgress,_lastReport. - The runner’s wall-clock pacing loop (
while (true) { … await _wait(16); }in_runOneBoss). - Per-boss scripted-damage application (
_applyScriptedDamage) — writes directly toenemy.hpon the first aliveisBoss && !_dyingenemy. - Telemetry frame construction (
BossTelemetryFrame) — built from world / ship / game state plusgetBossVfxPassMs()/bossVfxLayerCount(). - Pass/fail decision logic (
_decideOutcome) — mode-specific, plus universal NaN / exception gates. - Empty-report seeding (
_emptyReport) so the UI can render'queued'rows before any boss starts. window.__bossGauntletconsole API (run,runSingle,getReport,cancel).
READS FROM
BOSS_DEFS(../data/bosses) — boss def lookup;displayNamecopied into report.BOSS_GAUNTLET_MODE_CONFIGS,BOSS_GAUNTLET_ORDER,PROJECTILE_PERF_GATE,TELEMETRY_SAMPLE_EVERY_N_FRAMES(./boss-gauntlet-types) — all tuning lives there, not here.game.time,game.stats.damageTaken(../engine/core/state) — baseline + elapsed.ship.weapons,ship.hp,ship.hpMax,ship.shieldMax,ship.alive,ship.invulnerable,ship.invulnTimerplus dev-only flagsship._gauntletPlayerDamageDisabled,ship._dontStuckInvuln,ship._invulnWallTime.world.enemies(alive scan for boss anchor, bar HP rollup, leaked-sharing check),world.enemyBullets,world.playerBullets.getBossVfxPassMs()andbossVfxLayerCount()(../engine/vfx/boss-layers) — perf samples.Sig(../engine/core/signals) — subscribes to'boss_kill'per run with priority 50.
PUSHES TO
MissionHandleAPI on the active sandbox mission ref:sandboxResetForTest(),sandboxSpawnBoss(defId, 0),setGodMode(),setWorldKnobs({ enemyDamageMult }),patchShipStats({ hpMax, shieldMax }),fullHeal().- Mutates
ship.weapons(stash + restore),ship._gauntletPlayerDamageDisabled,ship._dontStuckInvuln,ship._invulnWallTime,ship.invulnerable,ship.invulnTimeron teardown. - Direct
enemy.hpwrites on the boss anchor (and sets_dyingwhen hp hits 0); does NOT route through the weapon / damage pipeline. - Listener callback registered via
setProgressListener— fires on every status change and every telemetry-sampled frame. window.__bossGauntlet(browser global) — exposesrun,runSingle,getReport,cancelfor console use.
DOES NOT
- Does NOT create a fresh
Missionper boss — reuses the existing sandbox mission via_missionRef. - Does NOT pump frames manually — wall-clock-paced via
await _wait(16); the engine’s main rAF loop keeps rendering. - Does NOT use the player’s weapons against the boss —
ship.weaponsis cleared and_gauntletPlayerDamageDisabledis set so artifact/echo damage is also suppressed. - Does NOT route scripted damage through the bullet / weapon system — direct hp write on the boss anchor.
- Does NOT define mode tuning — all numeric thresholds (
timeoutSec,playerDpsToBoss,godMode,playerLevel,enemyDamageMult) live inBOSS_GAUNTLET_MODE_CONFIGS. - Does NOT compute true ability fires —
abilitiesFiredCountis a rising-edge heuristic onworld.enemyBullets.length. - Does NOT persist reports —
_lastReportis in-memory only; consumer is responsible for surfacing it.
Signals
- Subscribes (per run):
Sig.on('boss_kill', killHandler, 50)— handler flips a closure-localbossKilledand breaks the wait loop. Unsubscribed in thefinallyblock.
Entry points
setMissionRef(ref)— caller (Ship Playground / dev UI) injects the live{ current: MissionHandle | null }ref. Required before any run.setProgressListener(cb | null)— registers a UI listener forBossGauntletProgressevents.getLastReport(): BossGauntletRunReport | null— read-only accessor for the last completed run.cancel(): void— flips_cancelled; the per-boss loop checks each tick and marks remaining bosses'skipped'.runGauntlet(mode): Promise<BossGauntletRunReport>— fullBOSS_GAUNTLET_ORDERsequence.runSingleBoss(bossId, mode): Promise<BossGauntletRunReport>— single-boss variant; same code path via_runSequence([bossId], mode).window.__bossGauntlet.{run,runSingle,getReport,cancel}— browser console mirror of the same surface.
Pattern notes
- Mission reuse, sandbox reset. Per-boss isolation is achieved by calling
sandboxResetForTest()before spawn and again after the wait loop exits. The post-run reset is best-effort (wrapped intry {} catch {}) so a teardown failure can’t break the whole sequence. - Stuck-invuln watchdog bypass ordering matters.
ship._dontStuckInvuln = cfg.godModeandship._invulnWallTime = 0are set BEFORE_configurePlayerForModecallssetGodMode(true). Skipping this seeds_invulnWallTimewhile the watchdog is live and risks a stale warning on later runs. - Weapon stash + belt-and-suspenders flag.
ship.weapons.slice()is held in a local, weapons array is emptied, and_gauntletPlayerDamageDisabled = trueis set as a redundant gate because clearing weapons alone doesn’t suppress artifact-tagged damage (Personal Space, Echo Generator). All three are restored in the post-run block AND on the early-exit spawn-error path. - Rising-edge heuristic for abilities.
abilitiesEstimateincrements wheneverworld.enemyBullets.lengthis greater than the prior sample. Coarse — meant for smoke-mode liveness, not balance accounting. - Bar HP rollup.
_readBarHp()sumshp/hpMaxacross all alive enemies withisBoss || sharesHealthWithBoss;sharingCount > 0is the proof-of-spawn signal (bossSpawned). - Mode-specific pass/fail in one switch.
_decideOutcomeruns after universal gates (exceptions, NaN bar HP) and then branches per mode.smokeexpects the boss to survive the timeout (it’s deliberately under-damaged);balance/survivalrequire a kill;stressonly requires no breakage. - Live-report mutation pattern.
liveReports[index] = { ...report }shallow-clones into the array each sample so the UI sees an updated snapshot via_emitwithout external consumers needing to subscribe to the underlying report. passfield patched on emit._emitrewritesr.pass = r.status === 'passed'on every emit so listeners don’t need to recompute. Final canonical status remainsreport.status.
EXTRACT-CANDIDATE
- Stuck-invuln watchdog bypass protocol (
_dontStuckInvuln = true; _invulnWallTime = 0; before setGodMode(true)) is duplicated between the pre-mission setup block and_configurePlayerForMode. Anywhere else in the codebase that toggles long-form invuln (god-mode console toggles, cinematic invuln) likely needs the same dance — worth a shared helper inengine/core/stateor wherever god-mode is centrally toggled. - Player-damage-suppression bundle (clear
ship.weapons, set_gauntletPlayerDamageDisabled, set_dontStuckInvuln) and its inverse teardown is a self-contained “dev-only no-damage player” mode. If any other harness (replay viewer, cinematic, screenshot tooling) needs the same, extractenterPlayerDamageDisabled()/exitPlayerDamageDisabled(stashed)totesting/player-damage-disabled.ts. - Scripted-damage-on-anchor primitive (
_applyScriptedDamage) is generic — any future test harness that wants deterministic kill rates without weapons can reuse it. Candidate to live alongsidesandboxSpawnBossin the bridge assandboxApplyScriptedBossDamage(amount).