artifact-scenarios.ts
PURPOSE
Defines A/B test scenarios for every artifact. Each artifact has a paired BASELINE (no artifact) and ARTIFACT (with artifact) run sharing one ScenarioDef. The delta on a single KPI proves the artifact has a measurable effect. Also exports 3 matrix ScenarioTemplates (Swarm / Boss / Survival) used by the cross-artifact testing matrix.
OWNS
ArtifactKPItype —'kills' | 'dmgTaken' | 'survivalHp'.ArtifactABTestinterface —{ artifactId, name, kpi, kpiHigherIsBetter, minDeltaPct, scenario }.ARTIFACT_AB_TESTS: ArtifactABTest[]— the canonical A/B list (34 entries: original 8 + new 26).ARTIFACT_AB_MAP: Record<string, ArtifactABTest>— lookup byartifactId, built at module load.SCENARIO_TEMPLATES: Record<string, ScenarioTemplate>—SWARM,SINGLE_TARGET,SURVIVAL.SCENARIO_TEMPLATE_LIST: ScenarioTemplate[]— array view of the three templates.- Legacy exports:
ARTIFACT_TEST_SCENARIOS(just the.scenariofield of each entry),listArtifactScenarios(),getArtifactScenario(id, tier=0). getArtifactOverrides(artifactId)— extracts weapons / startHeat / non-grant postStart actions for matrix reuse.
Private (module-local) configs
- World presets:
STANDARD_COMBAT,HIGH_DAMAGE,DENSE_SWARM. - Ship presets:
STD_SHIP,TANKY_SHIP,FAST_SHIP,HEAT_SHIP,GODMODE_SHIP,MATRIX_SHIP,SURVIVAL_SHIP. ab(id, name, kpi, higher, minDelta, ship, world, flow, extraPostStart?)— builder that returns anArtifactABTestwithscenario.id = ab-<id>,group = 'artifacts',tags = ['ab', id], and a defaultpostStartof[{ type: 'grant_artifact', id, tier: 0 }, ...extraPostStart].
READS FROM
./scenario-types—ScenarioDef,ScenarioTemplate,PostStartAction(type-only imports).
PUSHES TO
- Nothing at runtime. This is a pure data module: it builds and exports static arrays/maps once at load.
- Consumers (test harness, matrix runner, scenario selector UI) import
ARTIFACT_AB_TESTS,ARTIFACT_AB_MAP,SCENARIO_TEMPLATES, or callgetArtifactScenario/getArtifactOverrides.
DOES NOT
- Does not run scenarios — only describes them.
- Does not grant artifacts itself; emits a
grant_artifactPostStartActionthat the scenario runner executes. - Does not validate that
artifactIdmatches a real artifact definition — assumed valid by the caller. - Does not own balance numbers for the artifacts (HP, damage multipliers, durations are scenario-shaping, not artifact stats).
- Does not handle tier > 0 in the A/B map itself —
getArtifactScenario(id, tier)overwrites thepostStartwith the requested tier; the bareARTIFACT_AB_TESTSentries are all tier 0. - Does not deduplicate or schema-check
ARTIFACT_AB_MAP— last write perartifactIdwins.
Signals
postStart entries reference signal names that the scenario runner fires. Used in this file:
crate_break— forcrate_buster.tbone_hit(with optionalstr1: 'tbone') — fortbone_shockwave,battering_ram.event_complete— forevent_healer,event_reward.player_healed— forvitality_surge.
Entry points
ARTIFACT_AB_TESTS— iterate for batch runs.ARTIFACT_AB_MAP[id]— direct lookup.getArtifactScenario(id, tier?)— returns a freshScenarioDefwith the requested tier in thegrant_artifactaction; falls back to a minimal placeholder scenario ifidis unknown.getArtifactOverrides(id)— for the matrix runner: returns{ weapons?, startHeat?, extraPostStart }with thegrant_artifactfiltered out so matrix templates can re-inject it themselves.SCENARIO_TEMPLATES.{SWARM,SINGLE_TARGET,SURVIVAL}— three combat contexts with their own KPI (kills/totalDamageDealt/survivalHp), ship, world, flow, and spawn config.
Pattern notes
- A/B parity. The same
ScenarioDefruns both arms; only thepostStartgrant_artifactdiffers. KPI compared as a percentage delta againstminDeltaPct. - Ship/world preset reuse. Per-artifact tweaks use object spread on a base preset, e.g.
{ ...STD_SHIP, statOverrides: { ...STD_SHIP.statOverrides, shieldMax: 0 } }forshieldless_fury. Keeps each entry to one line and makes the diff from the base obvious. - God-mode for trigger-condition artifacts. Artifacts that require sustained kill streaks or uninterrupted timers (
absorption_barrier,patient_fury,killstreak_rain,bloodlust) useGODMODE_SHIPso the trigger condition can’t be broken by incidental damage. - Weapon-coupled artifacts. Cannon-tied (
lingering_flames,chain_reaction), lightning-tied (static_charge,energy_kill_nova), and multi-weapon (versatility) tests swap weapons inline rather than building dedicated ship presets. minDeltaPctscales with effect confidence. Tight effects use 0.05; the bulk use 0.10; high-confidence multipliers (echo_generator,patient_fury,shieldless_fury,primary_mastery) use 0.15–0.20.- Matrix vs A/B split. A/B = bespoke conditions per artifact, proves the artifact does something. Matrix templates = uniform conditions across all artifacts, measures relative strength on a fixed yardstick.
speedMult: 4on every flow — these are headless turbo runs, not real gameplay.
EXTRACT-CANDIDATE
- Ship/world preset literals (
STD_SHIP,STANDARD_COMBAT, etc.) are scenario-shaping presets. If other scenario files (weapon-scenarios.ts,passive-scenarios.ts, etc. — if they exist) re-declare similar HP/shield/world tuples, hoist these into a sharedscenario-presets.ts. - The
ab(...)builder is specific to artifact A/B shape; a sibling builder for weapons/passives may want the samegrant_*+ signal pattern. If so, parameterize over thegrant_*action type. - KPI union (
'kills' | 'dmgTaken' | 'survivalHp') overlaps theScenarioTemplate.kpifield which adds'totalDamageDealt'. Consider unifying into a singleScenarioKPItype inscenario-types.ts.