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

  • ArtifactKPI type — 'kills' | 'dmgTaken' | 'survivalHp'.
  • ArtifactABTest interface — { 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 by artifactId, 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 .scenario field 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 an ArtifactABTest with scenario.id = ab-<id>, group = 'artifacts', tags = ['ab', id], and a default postStart of [{ type: 'grant_artifact', id, tier: 0 }, ...extraPostStart].

READS FROM

  • ./scenario-typesScenarioDef, 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 call getArtifactScenario / getArtifactOverrides.

DOES NOT

  • Does not run scenarios — only describes them.
  • Does not grant artifacts itself; emits a grant_artifact PostStartAction that the scenario runner executes.
  • Does not validate that artifactId matches 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 the postStart with the requested tier; the bare ARTIFACT_AB_TESTS entries are all tier 0.
  • Does not deduplicate or schema-check ARTIFACT_AB_MAP — last write per artifactId wins.

Signals

postStart entries reference signal names that the scenario runner fires. Used in this file:

  • crate_break — for crate_buster.
  • tbone_hit (with optional str1: 'tbone') — for tbone_shockwave, battering_ram.
  • event_complete — for event_healer, event_reward.
  • player_healed — for vitality_surge.

Entry points

  • ARTIFACT_AB_TESTS — iterate for batch runs.
  • ARTIFACT_AB_MAP[id] — direct lookup.
  • getArtifactScenario(id, tier?) — returns a fresh ScenarioDef with the requested tier in the grant_artifact action; falls back to a minimal placeholder scenario if id is unknown.
  • getArtifactOverrides(id) — for the matrix runner: returns { weapons?, startHeat?, extraPostStart } with the grant_artifact filtered 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 ScenarioDef runs both arms; only the postStart grant_artifact differs. KPI compared as a percentage delta against minDeltaPct.
  • Ship/world preset reuse. Per-artifact tweaks use object spread on a base preset, e.g. { ...STD_SHIP, statOverrides: { ...STD_SHIP.statOverrides, shieldMax: 0 } } for shieldless_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) use GODMODE_SHIP so 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.
  • minDeltaPct scales 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: 4 on 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 shared scenario-presets.ts.
  • The ab(...) builder is specific to artifact A/B shape; a sibling builder for weapons/passives may want the same grant_* + signal pattern. If so, parameterize over the grant_* action type.
  • KPI union ('kills' | 'dmgTaken' | 'survivalHp') overlaps the ScenarioTemplate.kpi field which adds 'totalDamageDealt'. Consider unifying into a single ScenarioKPI type in scenario-types.ts.