@alpaca-software/40kdc-data 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,6 +45,12 @@ function walk(node, source, opts, out) {
45
45
  case "bs-modifier":
46
46
  translateBsModifier(node, source, opts, out);
47
47
  return;
48
+ case "damage-reduction":
49
+ translateDamageReduction(node, source, opts, out);
50
+ return;
51
+ case "invulnerable-save":
52
+ translateInvulnerableSave(node, source, opts, out);
53
+ return;
48
54
  case "conditional":
49
55
  translateConditional(node, source, opts, out);
50
56
  return;
@@ -186,11 +192,26 @@ function translateRollModifier(node, source, opts, out) {
186
192
  return; // saves apply to the defender, not the attacker.
187
193
  }
188
194
  else {
189
- // target perspective: only `save` rolls on the buffed unit fire here.
190
- if (roll !== "save")
191
- return;
192
- if (!appliesToBuffedUnit(node, "target"))
195
+ // Target perspective accepts two shapes:
196
+ // - `target: "self"/"unit"/...` + `roll: "save"` — the buffed unit's
197
+ // own save rolls (mirrors the attacker path's reroll/stat handling).
198
+ // - `target: "attacker"` + `roll: "hit"/"wound"` — a defender-side
199
+ // rule that penalises the *incoming* attacker's hit/wound rolls (e.g.
200
+ // "subtract 1 from hit rolls targeting this unit"). Functionally
201
+ // identical to a `bs-modifier {target:"attacker"}` but with the
202
+ // canonical `roll-modifier` shape; the data uses both forms.
203
+ const cls = classifyTarget(node);
204
+ if (cls === "attacker") {
205
+ if (roll !== "hit" && roll !== "wound")
206
+ return; // damage/save against the attacker make no sense here.
207
+ }
208
+ else if (cls === "self") {
209
+ if (roll !== "save")
210
+ return;
211
+ }
212
+ else {
193
213
  return;
214
+ }
194
215
  }
195
216
  switch (roll) {
196
217
  case "hit":
@@ -356,7 +377,28 @@ function translateFeelNoPain(node, source, opts, out) {
356
377
  });
357
378
  return;
358
379
  }
359
- out.applied.push({ source, contribution: { type: "feel-no-pain", threshold } });
380
+ // `modifier.scope` {"all", "mortal"} (default "all"). Schema's `modifier`
381
+ // is `additionalProperties: true`, so any string lands here; we accept the
382
+ // two documented values and route everything else to unsupported so a typo
383
+ // ("mortals", "mortal-wound") can't silently masquerade as an all-FNP.
384
+ const rawScope = modifier.scope;
385
+ let scope = "all";
386
+ if (rawScope !== undefined) {
387
+ if (rawScope === "all" || rawScope === "mortal") {
388
+ scope = rawScope;
389
+ }
390
+ else {
391
+ out.unsupported.push({
392
+ reason: `feel-no-pain: unrecognised scope "${String(rawScope)}" (expected "all" or "mortal")`,
393
+ effectFragment: node,
394
+ });
395
+ return;
396
+ }
397
+ }
398
+ const contribution = scope === "mortal"
399
+ ? { type: "feel-no-pain", threshold, scope: "mortal" }
400
+ : { type: "feel-no-pain", threshold };
401
+ out.applied.push({ source, contribution });
360
402
  }
361
403
  function translateKeywordGrant(node, source, opts, out) {
362
404
  // Weapon-keyword grants ride with the attacker's profile (e.g. "your
@@ -437,6 +479,73 @@ const UNHONORABLE_NARROWING = ["weapon_name", "weapon_profile", "weapon_keyword"
437
479
  function unhonorableNarrowing(modifier) {
438
480
  return UNHONORABLE_NARROWING.find((k) => modifier[k] != null);
439
481
  }
482
+ /**
483
+ * Defender-side damage-reduction (`{type: "damage-reduction", modifier:
484
+ * {reduction: N | "half" | "to-zero"}}`). The buff layer only models the
485
+ * additive numeric form — `"half"` and `"to-zero"` are one-use ablation
486
+ * effects that don't fold into a deterministic expected-value crunch, so
487
+ * they surface as `unsupported`. Attacker-perspective walks drop silently
488
+ * (this is a defender stat).
489
+ */
490
+ function translateDamageReduction(node, source, opts, out) {
491
+ if (opts.perspective !== "target")
492
+ return;
493
+ if (!appliesToBuffedUnit(node, "target"))
494
+ return;
495
+ const modifier = node.modifier;
496
+ if (!isObject(modifier)) {
497
+ out.unsupported.push({
498
+ reason: "damage-reduction: missing modifier object",
499
+ effectFragment: node,
500
+ });
501
+ return;
502
+ }
503
+ const reduction = modifier.reduction;
504
+ if (typeof reduction === "number" && Number.isFinite(reduction) && reduction > 0) {
505
+ out.applied.push({ source, contribution: { type: "damage-reduction", value: reduction } });
506
+ return;
507
+ }
508
+ if (reduction === "half" || reduction === "to-zero") {
509
+ out.unsupported.push({
510
+ reason: `damage-reduction: "${reduction}" is a one-use ablation effect, not modelled by the expected-value engine`,
511
+ effectFragment: node,
512
+ });
513
+ return;
514
+ }
515
+ out.unsupported.push({
516
+ reason: `damage-reduction: unrecognised reduction "${String(reduction)}"`,
517
+ effectFragment: node,
518
+ });
519
+ }
520
+ /**
521
+ * Defender-side ability-granted invulnerable save (`{type: "invulnerable-save",
522
+ * modifier: {invuln_sv: N}}`). Best (lowest threshold) wins, combined with the
523
+ * unit's printed invuln by the engine. Attacker-perspective walks drop
524
+ * silently (this is a defender stat).
525
+ */
526
+ function translateInvulnerableSave(node, source, opts, out) {
527
+ if (opts.perspective !== "target")
528
+ return;
529
+ if (!appliesToBuffedUnit(node, "target"))
530
+ return;
531
+ const modifier = node.modifier;
532
+ if (!isObject(modifier)) {
533
+ out.unsupported.push({
534
+ reason: "invulnerable-save: missing modifier object",
535
+ effectFragment: node,
536
+ });
537
+ return;
538
+ }
539
+ const threshold = Number(modifier.invuln_sv);
540
+ if (!Number.isFinite(threshold) || threshold < 2 || threshold > 7) {
541
+ out.unsupported.push({
542
+ reason: `invulnerable-save: invuln_sv "${String(modifier.invuln_sv)}" is not a valid save threshold (2–7)`,
543
+ effectFragment: node,
544
+ });
545
+ return;
546
+ }
547
+ out.applied.push({ source, contribution: { type: "invulnerable-save", threshold } });
548
+ }
440
549
  function translateBsModifier(node, source, opts, out) {
441
550
  // A bs-modifier on `target: "attacker"` is a defender-side rule: it
442
551
  // penalises *incoming* hit rolls (e.g. Benefit of Cover). Translate it
@@ -749,7 +858,13 @@ function describeContribution(c) {
749
858
  case "reroll":
750
859
  return `re-roll ${c.roll}${c.subset === "ones" ? " 1s" : ""}`;
751
860
  case "feel-no-pain":
752
- return `feel no pain ${c.threshold}+`;
861
+ return c.scope === "mortal"
862
+ ? `feel no pain ${c.threshold}+ vs mortals`
863
+ : `feel no pain ${c.threshold}+`;
864
+ case "damage-reduction":
865
+ return `-${c.value} damage`;
866
+ case "invulnerable-save":
867
+ return `${c.threshold}+ invuln`;
753
868
  case "cover":
754
869
  return "cover";
755
870
  }
@@ -1 +1 @@
1
- {"version":3,"file":"from-dsl.js","sourceRoot":"","sources":["../../src/cruncher/from-dsl.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Translate an Ability DSL `effect` tree into the {@link Buff} stack it\n * contributes (for an attacker-perspective crunch) along with a list of\n * effect fragments the translator could not auto-apply.\n *\n * The buff layer is intentionally a subset of the DSL: it covers the math the\n * cruncher's expected-value engine reads (rerolls, die-roll modifiers, S/A/T\n * stat shifts, FNP, granted weapon keywords, cover) and reports everything\n * else — choice nodes (player decisions), dice-gated effects (stochastic),\n * defender-side bs-modifier, attack-restrictions, ability grants, mortal\n * wound triggers — as `unsupported` so the SPA can surface \"this ability has\n * effects we can't auto-apply\" rather than silently dropping them.\n *\n * The walker classifies an effect's `target` against the attacker\n * perspective: `self`, `bearer`, `unit`, `attached-unit`, `attacker`, and\n * `friendly-within-aura` are all treated as \"applies to my unit\". `defender`,\n * `enemy-within-aura`, and `all-enemy` are dropped without being marked\n * unsupported — those are defender-side mods and would surface from the\n * target's perspective (M3 work), not the attacker's.\n *\n * @packageDocumentation\n */\nimport type {\n Buff,\n BuffApplicability,\n BuffContribution,\n BuffSource,\n EngineContext,\n WeaponKeywordRef,\n} from \"./buffs.js\";\nimport type { Phase } from \"../generated.js\";\n\n/** A fragment we couldn't translate. The SPA can render these as warnings. */\nexport type UnsupportedFragment = {\n reason: string;\n effectFragment: unknown;\n};\n\n/**\n * A mutually-limited pool of {@link ActivatableBuff} levers. Dice-pool\n * allocations cap how many options fire at once (`max_activations`); a `choice`\n * lets the player pick exactly one. Levers sharing a `group.id` are subject to\n * that cap — the SPA greys out further checkboxes once it's reached, and an\n * optimizer enumerates subsets within it.\n */\nexport type ActivatableGroupRef = {\n id: string;\n maxActivations: number;\n};\n\n/**\n * A buff-bearing *player decision* the cruncher can't make on its own: a\n * dice-pool option, a `choice` branch, or an activation gated on a timing the\n * player controls (e.g. \"start of phase\"). It is not auto-applied — the\n * consumer opts in (a checkbox, or an optimizer's search) and then folds\n * {@link buffs} into the crunch. Conditions the activation still carries (a\n * target keyword, a phase) ride on each buff's `applicableWhen`, so the\n * resolver gates them per-target rather than the lever vanishing.\n */\nexport type ActivatableBuff = {\n /** Stable toggle id, e.g. `\"blessings-of-khorne#Warp Blades\"`. */\n id: string;\n /** Human label for the lever (option name, or a summary of its buffs). */\n label: string;\n /** Contributions this activation adds when the player opts in (≥1). */\n buffs: Buff[];\n /** Set when the lever belongs to a mutually-limited pool. */\n group?: ActivatableGroupRef;\n};\n\nexport type EffectTranslation = {\n applied: Buff[];\n unsupported: UnsupportedFragment[];\n /** Buffs sitting behind a player decision — see {@link ActivatableBuff}. */\n activatable: ActivatableBuff[];\n};\n\n/**\n * Whose perspective the translation runs from.\n *\n * - `\"attacker\"`: the buffed unit is *firing*. `target: \"unit\"/\"self\"` etc.\n * become attacker-side mods (re-rolls, hit/wound mods, A/S shifts, granted\n * keywords). `target: \"defender\"` is silently dropped — that's incoming\n * penalty math relevant when the buffed unit is the *target*, surfaced via\n * the `\"target\"` perspective instead.\n *\n * - `\"target\"`: the buffed unit is *being shot at*. Defensive mods on the\n * buffed unit (`stat-modifier T`, `stat-modifier Sv`, `feel-no-pain`,\n * `roll-modifier save`) become defender-side buffs. Conversely, attacker-\n * only mods (re-rolls, hit/wound mods, A/S shifts) drop silently because\n * they describe what the buffed unit does when *attacking*.\n *\n * The bs-modifier effect (a -1 to incoming hit rolls, e.g. Benefit of Cover)\n * becomes a `hit-mod` buff under target perspective so it stacks correctly\n * with attacker-side modifiers in the resolver's ±1 cap.\n */\nexport type TranslationPerspective = \"attacker\" | \"target\";\n\n/** Targets that resolve to the buffed unit itself. */\nconst SELF_TARGETS = new Set([\n \"self\",\n \"bearer\",\n \"unit\",\n \"attached-unit\",\n \"friendly-within-aura\",\n \"all-friendly\",\n]);\n\n/** Aliases the DSL uses when a node specifically calls out \"the attacker\". */\nconst ATTACKER_TARGET = \"attacker\";\n/** Aliases the DSL uses when a node specifically calls out \"the defender\". */\nconst DEFENDER_TARGETS = new Set([\"defender\", \"enemy-within-aura\", \"all-enemy\"]);\n\n/**\n * Walk an ability DSL `effect` tree and produce the buff stack it contributes\n * against `context` from the given `perspective`, plus an `unsupported` list\n * naming any branches the buff layer can't express today.\n */\nexport function effectToBuffs(\n effect: unknown,\n source: BuffSource,\n context: EngineContext,\n perspective: TranslationPerspective = \"attacker\",\n): EffectTranslation {\n const out: EffectTranslation = { applied: [], unsupported: [], activatable: [] };\n const abilityId = source.kind === \"ability\" ? source.abilityId : \"effect\";\n walk(effect, source, { context, perspective, abilityId }, out);\n return out;\n}\n\ntype WalkOpts = {\n context: EngineContext;\n perspective: TranslationPerspective;\n /** Owning ability id — seeds the stable ids of activatable levers. */\n abilityId: string;\n};\n\nfunction walk(\n node: unknown,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n if (!isObject(node)) return;\n const type = node.type;\n switch (type) {\n case \"re-roll\":\n translateReroll(node, source, opts, out);\n return;\n case \"roll-modifier\":\n translateRollModifier(node, source, opts, out);\n return;\n case \"stat-modifier\":\n translateStatModifier(node, source, opts, out);\n return;\n case \"feel-no-pain\":\n translateFeelNoPain(node, source, opts, out);\n return;\n case \"keyword-grant\":\n translateKeywordGrant(node, source, opts, out);\n return;\n case \"bs-modifier\":\n translateBsModifier(node, source, opts, out);\n return;\n case \"conditional\":\n translateConditional(node, source, opts, out);\n return;\n case \"sequence\":\n for (const step of (node.steps as unknown[]) ?? []) walk(step, source, opts, out);\n return;\n case \"choice\":\n // Player decision — each branch becomes an opt-in lever (pick one).\n enumerateChoice(node, source, opts, out);\n return;\n case \"dice-gated\":\n // Probabilistic; the buff layer is deterministic.\n out.unsupported.push({\n reason: \"dice-gated effect: stochastic; not expressible as a buff\",\n effectFragment: node,\n });\n return;\n case \"dice-pool-allocation\":\n // Player spends dice on options at runtime — each buff-bearing option\n // becomes an opt-in lever, grouped under the pool's activation cap.\n enumerateDicePool(node, source, opts, out);\n return;\n default:\n // Unknown effect — record it. Covers ability-grant, deep-strike,\n // mortal-wounds, cp-gain, movement-modifier, etc.; the buff layer\n // doesn't model these as deterministic mods to a single shot.\n out.unsupported.push({\n reason: `effect type \"${String(type)}\" is not modelled by the buff layer`,\n effectFragment: node,\n });\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Leaf translators\n// ---------------------------------------------------------------------------\n\n/**\n * Classify a node's `target` field against the perspective we're translating\n * for. Returns:\n * - `\"self\"`: the node targets the buffed unit (apply attacker-side or\n * defender-side translation, depending on perspective + stat).\n * - `\"attacker\"` / `\"defender\"`: the node targets the other party explicitly.\n * - `\"unknown\"`: missing/malformed target.\n */\nfunction classifyTarget(\n node: Record<string, unknown>,\n): \"self\" | \"attacker\" | \"defender\" | \"unknown\" {\n const target = node.target;\n if (typeof target !== \"string\") return \"unknown\";\n if (target === ATTACKER_TARGET) return \"attacker\";\n if (DEFENDER_TARGETS.has(target)) return \"defender\";\n if (SELF_TARGETS.has(target)) return \"self\";\n return \"unknown\";\n}\n\n/**\n * Does this node's target match the buffed unit under the current\n * perspective? Used for symmetric roll/keyword translations where the same\n * effect is \"self\" in either direction.\n */\nfunction appliesToBuffedUnit(\n node: Record<string, unknown>,\n perspective: TranslationPerspective,\n): boolean {\n const cls = classifyTarget(node);\n if (cls === \"self\") return true;\n if (cls === \"attacker\") return perspective === \"attacker\";\n if (cls === \"defender\") return perspective === \"target\";\n return false;\n}\n\nfunction translateReroll(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // Rerolls are inherently attacker-side (you re-roll your own hit/wound/\n // damage; save rerolls fire when *you* are the target). Apply only under\n // the matching perspective so a target-perspective walk doesn't grab the\n // attacker's reroll-failed-hits buff.\n if (opts.perspective === \"attacker\" && !appliesToBuffedUnit(node, \"attacker\")) return;\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({ reason: \"re-roll: missing modifier object\", effectFragment: node });\n return;\n }\n const narrowed = unhonorableNarrowing(modifier);\n if (narrowed) {\n out.unsupported.push({ reason: `re-roll: narrows by \"${narrowed}\" which the cruncher can't resolve here`, effectFragment: node });\n return;\n }\n const roll = modifier.roll;\n // A `value: 1` on a re-roll modifier unambiguously means \"re-roll rolls of 1\".\n // A historical migration (2026-weapon-keywords) mis-defaulted such nodes to\n // `subset: \"all-failures\"`; honor the value as the source of truth so any\n // stray data of that shape can't silently over-apply the reroll.\n const subset = modifier.value === 1 ? \"ones\" : modifier.subset;\n // Under target perspective, only \"save\" rerolls fire on the buffed unit.\n if (opts.perspective === \"target\" && roll !== \"save\") return;\n if (\n (roll === \"hit\" || roll === \"wound\" || roll === \"save\" || roll === \"damage\") &&\n (subset === \"ones\" || subset === \"all-failures\")\n ) {\n out.applied.push({ source, contribution: { type: \"reroll\", roll, subset } });\n return;\n }\n out.unsupported.push({\n reason: `re-roll on \"${String(roll)}\" (subset \"${String(subset)}\") is outside the damage path`,\n effectFragment: node,\n });\n}\n\nfunction translateRollModifier(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"roll-modifier: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const narrowed = unhonorableNarrowing(modifier);\n if (narrowed) {\n out.unsupported.push({ reason: `roll-modifier: narrows by \"${narrowed}\" which the cruncher can't resolve here`, effectFragment: node });\n return;\n }\n const value = signedValue(modifier);\n if (value === null) {\n out.unsupported.push({\n reason: `roll-modifier: operation \"${String(modifier.operation)}\" not supported`,\n effectFragment: node,\n });\n return;\n }\n const roll = modifier.roll;\n // Each roll type is intrinsically on one side. Hit / wound / damage are\n // attacker-side; save is defender-side. The perspective decides whether the\n // buffed unit's `target` is the right party for that roll type.\n if (opts.perspective === \"attacker\") {\n if (!appliesToBuffedUnit(node, \"attacker\")) return;\n if (roll === \"save\") return; // saves apply to the defender, not the attacker.\n } else {\n // target perspective: only `save` rolls on the buffed unit fire here.\n if (roll !== \"save\") return;\n if (!appliesToBuffedUnit(node, \"target\")) return;\n }\n switch (roll) {\n case \"hit\":\n out.applied.push({ source, contribution: { type: \"hit-mod\", value } });\n return;\n case \"wound\":\n out.applied.push({ source, contribution: { type: \"wound-mod\", value } });\n return;\n case \"save\":\n out.applied.push({ source, contribution: { type: \"save-mod\", value } });\n return;\n case \"damage\":\n out.applied.push({ source, contribution: { type: \"damage-mod\", value } });\n return;\n default:\n out.unsupported.push({\n reason: `roll-modifier on \"${String(roll)}\" is outside the damage path`,\n effectFragment: node,\n });\n }\n}\n\nfunction translateStatModifier(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"stat-modifier: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const narrowed = unhonorableNarrowing(modifier);\n if (narrowed) {\n out.unsupported.push({ reason: `stat-modifier: narrows by \"${narrowed}\" which the cruncher can't resolve here`, effectFragment: node });\n return;\n }\n const stat = modifier.stat;\n const isOnBuffedUnit = appliesToBuffedUnit(node, opts.perspective);\n // `attack_type: melee|ranged` scopes the mod to that attack — express it as a\n // phase gate so e.g. a melee +1 Attack doesn't fire in the shooting phase.\n const applicability = attackTypeApplicability(modifier);\n const emit = (contribution: BuffContribution): void => {\n const buff: Buff = { source, contribution };\n out.applied.push(applicability ? { ...buff, applicableWhen: applicability } : buff);\n };\n\n // AP has an inverted sign convention (stored negative; more negative = more\n // piercing) and offensive/defensive variants, so it computes its own delta\n // and routes by attacker/defender rather than going through `signedValue`.\n if (stat === \"AP\") {\n translateApModifier(node, modifier, opts, out, emit);\n return;\n }\n\n const value = signedValue(modifier);\n if (value === null) {\n out.unsupported.push({\n reason: `stat-modifier: operation \"${String(modifier.operation)}\" not supported`,\n effectFragment: node,\n });\n return;\n }\n switch (stat) {\n case \"A\":\n if (opts.perspective !== \"attacker\" || !isOnBuffedUnit) return;\n emit({ type: \"attacks-mod\", value });\n return;\n case \"S\":\n if (opts.perspective !== \"attacker\" || !isOnBuffedUnit) return;\n emit({ type: \"strength-mod\", value });\n return;\n case \"T\":\n // Defender stat. Only relevant under target perspective.\n if (opts.perspective !== \"target\") {\n out.unsupported.push({\n reason: \"stat-modifier T: defender-side stat; applies when the buffed unit is the target\",\n effectFragment: node,\n });\n return;\n }\n if (!isOnBuffedUnit) return;\n emit({ type: \"toughness-mod\", value });\n return;\n case \"Sv\":\n // Saves improve when the *defender* gets +Sv. A +1 to Sv in printed\n // rules means \"improve the save by 1\", which maps to a `save-mod` of\n // `-value` since save-mod is signed against the *needed roll*.\n // (Equivalent: a -1 Sv penalty is a +1 save-mod.) We translate\n // \"Sv add 1\" → save-mod -1 to keep the resolver's sign convention.\n if (opts.perspective !== \"target\") {\n out.unsupported.push({\n reason: \"stat-modifier Sv: defender-side stat; applies when the buffed unit is the target\",\n effectFragment: node,\n });\n return;\n }\n if (!isOnBuffedUnit) return;\n emit({ type: \"save-mod\", value: -value });\n return;\n default:\n out.unsupported.push({\n reason: `stat-modifier on \"${String(stat)}\" is outside the damage path`,\n effectFragment: node,\n });\n }\n}\n\n/**\n * Translate an `AP` stat-modifier. AP rides on the attacker's weapon profile and\n * is stored as a negative number (e.g. AP -1); more negative = more piercing.\n *\n * Two variants exist in the data:\n * - **offensive** (`target` self/unit): the buffed unit's own weapons gain AP —\n * an attacker-side `ap-mod`. `improve N` → `-N` (more piercing), `worsen N` →\n * `+N`, and the legacy `add`/`subtract` forms (which already pass a signed,\n * usually negative, value) flow through `apDelta` unchanged.\n * - **defensive** (`target: \"attacker\"`): \"enemy weapons targeting this unit\n * have AP worsened\". This applies when the buffed unit is the *target*; we do\n * not model it as an attacker-side buff (that would wrongly weaken the buffed\n * unit's own attacks), so it is surfaced as `unsupported`.\n */\nfunction translateApModifier(\n node: Record<string, unknown>,\n modifier: Record<string, unknown>,\n opts: WalkOpts,\n out: EffectTranslation,\n emit: (contribution: BuffContribution) => void,\n): void {\n if (classifyTarget(node) === \"attacker\") {\n out.unsupported.push({\n reason:\n \"stat-modifier AP on the attacker: defender-side AP reduction is not modelled by the buff layer\",\n effectFragment: node,\n });\n return;\n }\n if (opts.perspective !== \"attacker\" || !appliesToBuffedUnit(node, \"attacker\")) return;\n const delta = apDelta(modifier);\n if (delta === null) {\n out.unsupported.push({\n reason: `stat-modifier AP: operation \"${String(modifier.operation)}\" not supported`,\n effectFragment: node,\n });\n return;\n }\n emit({ type: \"ap-mod\", value: delta });\n}\n\nfunction translateFeelNoPain(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // FNP applies when the buffed unit is the *target* — it ablates incoming\n // damage. Under attacker perspective the FNP is irrelevant (the unit is\n // firing, not taking damage). Drop silently rather than as `unsupported`\n // so attacker-perspective walks don't surface a spurious diagnostic for\n // every unit that happens to have a FNP rule.\n if (opts.perspective !== \"target\") return;\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"feel-no-pain: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const threshold = Number(modifier.threshold);\n if (!Number.isFinite(threshold)) {\n out.unsupported.push({\n reason: \"feel-no-pain: threshold not numeric\",\n effectFragment: node,\n });\n return;\n }\n out.applied.push({ source, contribution: { type: \"feel-no-pain\", threshold } });\n}\n\nfunction translateKeywordGrant(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // Weapon-keyword grants ride with the attacker's profile (e.g. \"your\n // weapons gain [Sustained Hits 1]\"). Defender-perspective walks ignore\n // them — the keyword applies when the buffed unit fires, not when it's\n // shot at.\n if (opts.perspective !== \"attacker\") return;\n if (!appliesToBuffedUnit(node, \"attacker\")) return;\n const modifier = node.modifier;\n if (!isObject(modifier)) return;\n // The DSL grants keywords in two shapes: a singular `keyword` string (often\n // with a `weapon_type`) or a `keywords` array. Accept both.\n const raws = keywordGrantList(modifier);\n if (raws.length === 0) return;\n // `weapon_type: melee|ranged` scopes the grant to that attack — a melee-only\n // keyword shouldn't fire in the shooting phase. Express it as a phase gate.\n const applicability = weaponTypeApplicability(modifier);\n for (const raw of raws) {\n const ref = parseKeywordGrant(raw);\n if (!ref) {\n out.unsupported.push({\n reason: `keyword-grant: cannot parse \"${raw}\" to a catalog keyword`,\n effectFragment: { keyword: raw },\n });\n continue;\n }\n const buff: Buff = { source, contribution: { type: \"extra-keyword\", keywordRef: ref } };\n out.applied.push(applicability ? { ...buff, applicableWhen: applicability } : buff);\n }\n}\n\n/** Normalise a keyword-grant modifier's singular `keyword` and/or `keywords` array. */\nfunction keywordGrantList(modifier: Record<string, unknown>): string[] {\n const out: string[] = [];\n if (typeof modifier.keyword === \"string\") out.push(modifier.keyword);\n if (Array.isArray(modifier.keywords)) {\n for (const k of modifier.keywords) if (typeof k === \"string\") out.push(k);\n }\n return out;\n}\n\n/** Map a keyword-grant's `weapon_type` to the phase its weapons fire in. */\nfunction weaponTypeApplicability(modifier: Record<string, unknown>): BuffApplicability | undefined {\n if (modifier.weapon_type === \"melee\") return { phases: [\"fight\"] };\n if (modifier.weapon_type === \"ranged\") return { phases: [\"shooting\"] };\n return undefined;\n}\n\n/**\n * Map a stat-modifier's `attack_type` (or the equivalent `weapon_type`) to the\n * phase that attack happens in. Both spellings carry the same melee/ranged\n * intent; honoring `weapon_type` lets a \"+1 A to melee weapons\" mod phase-gate\n * correctly instead of leaking into the shooting phase.\n */\nfunction attackTypeApplicability(modifier: Record<string, unknown>): BuffApplicability | undefined {\n const kind = modifier.attack_type ?? modifier.weapon_type;\n if (kind === \"melee\") return { phases: [\"fight\"] };\n if (kind === \"ranged\") return { phases: [\"shooting\"] };\n return undefined;\n}\n\n/**\n * Narrowing keys that scope a buff to a named weapon or a model subset the\n * cruncher can't resolve at translation time (it has no weapon/model context\n * here). When present on a damage-path leaf, applying the buff unfiltered would\n * silently OVER-APPLY it, so we surface it as `unsupported` instead — the data\n * stays faithful for other consumers; the optimizer just doesn't assume it.\n * `weapon_type`/`attack_type` are NOT here — those map cleanly to a phase gate.\n */\nconst UNHONORABLE_NARROWING = [\"weapon_name\", \"weapon_profile\", \"weapon_keyword\", \"weapon_filter\", \"model_filter\", \"model_scope\"];\nfunction unhonorableNarrowing(modifier: Record<string, unknown>): string | undefined {\n return UNHONORABLE_NARROWING.find((k) => modifier[k] != null);\n}\n\nfunction translateBsModifier(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // A bs-modifier on `target: \"attacker\"` is a defender-side rule: it\n // penalises *incoming* hit rolls (e.g. Benefit of Cover). Translate it\n // as a `hit-mod` buff under target perspective so the resolver's ±1 cap\n // composes with attacker-side mods.\n if (opts.perspective !== \"target\") return;\n const cls = classifyTarget(node);\n if (cls !== \"attacker\") return; // a bs-modifier on self wouldn't make sense.\n const modifier = node.modifier;\n if (!isObject(modifier)) return;\n const value = signedValue(modifier);\n if (value === null) return;\n out.applied.push({ source, contribution: { type: \"hit-mod\", value } });\n}\n\nfunction translateConditional(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const condition = node.condition;\n const effect = node.effect;\n if (!isObject(condition)) return;\n const negated = condition.negated === true;\n const verdict = evaluateCondition(condition, opts.context);\n if (verdict === \"unknown\") {\n // A timing the player controls (e.g. \"start of phase\") isn't a wall — it's\n // an activation the player can opt into. Surface it as a lever rather than\n // dropping it. Other unevaluatable conditions stay unsupported.\n if (conditionMentionsTiming(condition)) {\n enumerateTimingGate(node, source, opts, out);\n } else {\n out.unsupported.push({\n reason: `conditional: cannot evaluate condition \"${String(condition.type)}\" against current context`,\n effectFragment: node,\n });\n }\n return;\n }\n const active = negated ? !verdict : verdict;\n if (!active) return;\n walk(effect, source, opts, out);\n}\n\n// ---------------------------------------------------------------------------\n// Activatable-lever enumeration\n//\n// Player-controlled gates — a `timing-is` the context can't pin down, each\n// `dice-pool-allocation` option, each `choice` branch — aren't walls for a\n// damage optimizer; they're the search space. Instead of dropping them to\n// `unsupported`, we descend through them and surface every buff-bearing branch\n// as an opt-in {@link ActivatableBuff}. The descent reuses the normal leaf\n// translators (so a lever applies exactly what it advertises) and turns the\n// conditions a branch still carries (target keyword, phase) into declarative\n// `applicableWhen` so the resolver gates them per-target.\n// ---------------------------------------------------------------------------\n\n/** Emit one lever per `choice` branch that yields a buff (pick exactly one). */\nfunction enumerateChoice(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const options = Array.isArray(node.options) ? node.options : [];\n options.forEach((opt, i) => {\n const buffs: Buff[] = [];\n collectGatedBuffs(opt, source, opts, {}, buffs);\n if (buffs.length === 0) return;\n out.activatable.push({\n id: `${opts.abilityId}?${i}`,\n label: labelForBuffs(buffs),\n buffs,\n group: { id: `${opts.abilityId}?choice`, maxActivations: 1 },\n });\n });\n}\n\n/** Emit one lever per buff-bearing dice-pool option, capped by `max_activations`. */\nfunction enumerateDicePool(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const options = Array.isArray(node.options) ? node.options : [];\n const maxActivations =\n typeof node.max_activations === \"number\" ? node.max_activations : options.length;\n for (const opt of options) {\n if (!isObject(opt)) continue;\n const buffs: Buff[] = [];\n collectGatedBuffs(opt.effect, source, opts, {}, buffs);\n if (buffs.length === 0) continue;\n const name = typeof opt.name === \"string\" && opt.name ? opt.name : labelForBuffs(buffs);\n out.activatable.push({\n id: `${opts.abilityId}#${name}`,\n label: name,\n buffs,\n group: { id: opts.abilityId, maxActivations },\n });\n }\n}\n\n/**\n * Surface a timing-gated activation. The timing itself is just \"when\" — opting\n * in satisfies it — so we descend into the body: an inner `dice-pool-allocation`\n * or `choice` surfaces its *own* option levers (e.g. Blessings of Khorne's\n * three keyword grants), while inner always-on buffs bundle into a single\n * timing lever. A body with no modelable combat buff (a `resurrection` or\n * `dice-gated`, like Berzerker Frenzy) yields nothing.\n */\nfunction enumerateTimingGate(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const condition = node.condition;\n if (!isObject(condition)) return;\n const sub: EffectTranslation = { applied: [], unsupported: [], activatable: [] };\n walk(node.effect, source, opts, sub);\n // Inner independent decisions (dice-pool options, choice branches) pass\n // straight through as their own levers.\n out.activatable.push(...sub.activatable);\n // Inner unconditional buffs become one lever gated only on the timing.\n if (sub.applied.length > 0) {\n const timing = extractTiming(condition) ?? \"timing\";\n out.activatable.push({\n id: `${opts.abilityId}@${timing}`,\n label: labelForBuffs(sub.applied),\n buffs: sub.applied,\n });\n }\n}\n\n/**\n * Walk the body of a player gate, collecting the buffs it would contribute.\n * Conditions are deferred to `applicableWhen` where expressible; nested\n * decisions and stochastic rolls inside an activation are not modelled.\n */\nfunction collectGatedBuffs(\n node: unknown,\n source: BuffSource,\n opts: WalkOpts,\n applicability: BuffApplicability,\n outBuffs: Buff[],\n): void {\n if (!isObject(node)) return;\n switch (node.type) {\n case \"conditional\": {\n const condition = node.condition;\n if (!isObject(condition)) return;\n const app = conditionToApplicability(condition);\n if (app === \"gate\") {\n // A nested timing gate: opting into the activation satisfies it, so\n // keep descending without adding a constraint.\n collectGatedBuffs(node.effect, source, opts, applicability, outBuffs);\n return;\n }\n if (app === \"context\") {\n // Can't express as a buff gate — fall back to the current context and\n // only descend when the condition is definitely active.\n if (evaluateCondition(condition, opts.context) === true) {\n collectGatedBuffs(node.effect, source, opts, applicability, outBuffs);\n }\n return;\n }\n collectGatedBuffs(node.effect, source, opts, combineApplicability(applicability, app), outBuffs);\n return;\n }\n case \"sequence\":\n for (const step of (node.steps as unknown[]) ?? []) {\n collectGatedBuffs(step, source, opts, applicability, outBuffs);\n }\n return;\n case \"choice\":\n case \"dice-pool-allocation\":\n case \"dice-gated\":\n // A decision (or stochastic roll) nested inside an activation. The outer\n // lever already stands for a player choice; we don't model the inner one.\n return;\n default: {\n // Leaf effect — run the normal leaf translators into a throwaway sink,\n // then attach the accumulated applicability so target/phase gating\n // defers to the resolver instead of vanishing the lever.\n const tmp: EffectTranslation = { applied: [], unsupported: [], activatable: [] };\n walk(node, source, opts, tmp);\n for (const b of tmp.applied) outBuffs.push(applyApplicability(b, applicability));\n return;\n }\n }\n}\n\n/** Does this condition (or any operand) gate on a player-controlled timing? */\nfunction conditionMentionsTiming(condition: Record<string, unknown>): boolean {\n if (condition.type === \"timing-is\") return true;\n if (typeof condition.operator === \"string\" && Array.isArray(condition.operands)) {\n return condition.operands.some((o) => isObject(o) && conditionMentionsTiming(o));\n }\n return false;\n}\n\n/** Pull the first `timing-is` timing value out of a (possibly compound) condition. */\nfunction extractTiming(condition: Record<string, unknown>): string | undefined {\n if (condition.type === \"timing-is\") {\n const t = (condition.parameters as Record<string, unknown> | undefined)?.timing;\n return typeof t === \"string\" ? t : undefined;\n }\n if (Array.isArray(condition.operands)) {\n for (const o of condition.operands) {\n if (isObject(o)) {\n const t = extractTiming(o);\n if (t) return t;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Translate a condition into a {@link BuffApplicability} the resolver can gate\n * on. Returns `\"gate\"` for a player-controlled timing (satisfied by opting in),\n * or `\"context\"` when the condition has no declarative buff representation and\n * must fall back to context evaluation.\n */\nfunction conditionToApplicability(\n condition: Record<string, unknown>,\n): BuffApplicability | \"context\" | \"gate\" {\n if (condition.negated === true) return \"context\";\n if (typeof condition.operator === \"string\" && Array.isArray(condition.operands)) {\n if (condition.operator !== \"and\") return \"context\";\n let merged: BuffApplicability = {};\n for (const operand of condition.operands) {\n if (!isObject(operand)) return \"context\";\n const a = conditionToApplicability(operand);\n if (a === \"gate\") continue; // timing operand: satisfied by opting in.\n if (a === \"context\") return \"context\";\n merged = combineApplicability(merged, a);\n }\n return merged;\n }\n const params = condition.parameters as Record<string, unknown> | undefined;\n switch (condition.type) {\n case \"timing-is\":\n return \"gate\";\n case \"phase-is\": {\n const phase = params?.phase;\n return typeof phase === \"string\" ? { phases: [phase as Phase] } : \"context\";\n }\n case \"target-has-keyword\": {\n const kw = params?.keyword;\n return typeof kw === \"string\" ? { requiresTargetKeyword: kw } : \"context\";\n }\n case \"unit-has-keyword\": {\n const kw = params?.keyword;\n return typeof kw === \"string\" ? { requiresAttackerKeyword: kw } : \"context\";\n }\n case \"attack-is-type\": {\n const t = params?.attack_type;\n if (t === \"melee\") return { phases: [\"fight\"] };\n if (t === \"ranged\") return { phases: [\"shooting\"] };\n return \"context\";\n }\n default:\n return \"context\";\n }\n}\n\n/** Merge two applicabilities; `phases` intersect, the rest narrow. */\nfunction combineApplicability(a: BuffApplicability, b: BuffApplicability): BuffApplicability {\n const out: BuffApplicability = { ...a };\n if (b.phases) {\n out.phases = a.phases ? a.phases.filter((p) => b.phases!.includes(p)) : b.phases;\n }\n if (b.rollType) out.rollType = b.rollType;\n if (b.requiresTargetKeyword) out.requiresTargetKeyword = b.requiresTargetKeyword;\n if (b.requiresAttackerKeyword) out.requiresAttackerKeyword = b.requiresAttackerKeyword;\n return out;\n}\n\n/** Attach an accumulated applicability to a buff (no-op when empty). */\nfunction applyApplicability(buff: Buff, applicability: BuffApplicability): Buff {\n if (Object.keys(applicability).length === 0) return buff;\n const merged = buff.applicableWhen\n ? combineApplicability(buff.applicableWhen, applicability)\n : applicability;\n return { ...buff, applicableWhen: merged };\n}\n\n/** A short, deduped human label summarising a lever's contributions. */\nfunction labelForBuffs(buffs: Buff[]): string {\n const seen = new Set<string>();\n const parts: string[] = [];\n for (const b of buffs) {\n const p = describeContribution(b.contribution);\n if (!seen.has(p)) {\n seen.add(p);\n parts.push(p);\n }\n }\n return parts.join(\", \") || \"buff\";\n}\n\nfunction describeContribution(c: BuffContribution): string {\n switch (c.type) {\n case \"extra-keyword\":\n return keywordLabel(c.keywordRef);\n case \"hit-mod\":\n return `${signed(c.value)} to hit`;\n case \"wound-mod\":\n return `${signed(c.value)} to wound`;\n case \"save-mod\":\n return `${signed(c.value)} to save`;\n case \"damage-mod\":\n return `${signed(c.value)} damage`;\n case \"attacks-mod\":\n return `${signed(c.value)} attacks`;\n case \"strength-mod\":\n return `${signed(c.value)} strength`;\n case \"toughness-mod\":\n return `${signed(c.value)} toughness`;\n case \"ap-mod\":\n return `AP ${c.value}`;\n case \"reroll\":\n return `re-roll ${c.roll}${c.subset === \"ones\" ? \" 1s\" : \"\"}`;\n case \"feel-no-pain\":\n return `feel no pain ${c.threshold}+`;\n case \"cover\":\n return \"cover\";\n }\n}\n\nfunction signed(n: number): string {\n return n >= 0 ? `+${n}` : `${n}`;\n}\n\n/** Render a weapon-keyword ref back to its printed form (best-effort). */\nfunction keywordLabel(ref: WeaponKeywordRef): string {\n const params = ref.parameters ?? {};\n if (ref.keyword_id === \"anti\" && typeof params.target_keyword === \"string\") {\n const th = params.threshold;\n return `Anti-${params.target_keyword}${typeof th === \"number\" ? ` ${th}+` : \"\"}`;\n }\n const base = ref.keyword_id\n .split(\"-\")\n .map((w) => (w ? w.charAt(0).toUpperCase() + w.slice(1) : w))\n .join(\" \");\n return typeof params.value === \"number\" ? `${base} ${params.value}` : base;\n}\n\n// ---------------------------------------------------------------------------\n// Condition evaluator\n// ---------------------------------------------------------------------------\n\nfunction evaluateCondition(\n condition: Record<string, unknown>,\n ctx: EngineContext,\n): boolean | \"unknown\" {\n // Compound conditions use {operator, operands} rather than {type, parameters}.\n // The schema's `condition-node` oneOf doesn't guarantee discrimination by a\n // single field, so dispatch on shape: presence of `operator` + `operands`\n // wins over the simple-condition switch below.\n if (\n typeof condition.operator === \"string\" &&\n Array.isArray(condition.operands)\n ) {\n return evaluateCompound(condition.operator, condition.operands, ctx);\n }\n switch (condition.type) {\n case \"phase-is\": {\n const wanted = (condition.parameters as Record<string, unknown> | undefined)?.phase;\n if (typeof wanted !== \"string\") return \"unknown\";\n return ctx.phase === wanted;\n }\n case \"timing-is\": {\n const wanted = (condition.parameters as Record<string, unknown> | undefined)?.timing;\n if (typeof wanted !== \"string\") return \"unknown\";\n if (ctx.timing === undefined) return \"unknown\";\n return ctx.timing === wanted;\n }\n case \"remained-stationary\":\n return ctx.attackerStationary === true;\n case \"charged-this-turn\":\n // A player-controlled context flag (did the buffed unit charge this turn?),\n // mirroring `remained-stationary`. Undefined → the caller couldn't pin it\n // down, so stay \"unknown\" and let the SPA surface the gap.\n if (ctx.attackerCharged === undefined) return \"unknown\";\n return ctx.attackerCharged;\n case \"target-has-keyword\": {\n const kw = (condition.parameters as Record<string, unknown> | undefined)?.keyword;\n if (typeof kw !== \"string\") return \"unknown\";\n return (ctx.targetKeywords ?? []).includes(kw.toLowerCase());\n }\n case \"unit-has-keyword\": {\n const kw = (condition.parameters as Record<string, unknown> | undefined)?.keyword;\n if (typeof kw !== \"string\") return \"unknown\";\n return (ctx.attackerKeywords ?? []).includes(kw.toLowerCase());\n }\n case \"is-attached\":\n case \"model-is-leader\":\n // True whenever the buffed unit is a combined (\"attached\") unit. We do\n // not thread per-member leader identity — \"attachment present\" is the\n // signal both conditions gate on. Undefined flag (caller couldn't\n // determine attachment) stays \"unknown\" so the SPA surfaces the gap.\n if (ctx.attackerAttached === undefined) return \"unknown\";\n return ctx.attackerAttached;\n default:\n return \"unknown\";\n }\n}\n\n/**\n * Kleene three-valued evaluator for compound conditions. `and` short-circuits\n * to `false` as soon as any operand is false (an unknown operand is then\n * irrelevant); `or` short-circuits to `true` symmetrically. `not` flips its\n * single operand and leaves `\"unknown\"` as `\"unknown\"`. Unknown operands that\n * don't get short-circuited propagate as `\"unknown\"` so the SPA can surface\n * the gap rather than collapsing it into a misleading false.\n */\nfunction evaluateCompound(\n operator: string,\n operands: unknown[],\n ctx: EngineContext,\n): boolean | \"unknown\" {\n if (operator === \"not\") {\n const first = operands[0];\n if (!isObject(first)) return \"unknown\";\n const v = evaluateCondition(first, ctx);\n if (v === \"unknown\") return \"unknown\";\n return !v;\n }\n if (operator !== \"and\" && operator !== \"or\") return \"unknown\";\n let sawUnknown = false;\n for (const operand of operands) {\n if (!isObject(operand)) {\n sawUnknown = true;\n continue;\n }\n const v = evaluateCondition(operand, ctx);\n if (v === \"unknown\") {\n sawUnknown = true;\n continue;\n }\n if (operator === \"and\" && v === false) return false;\n if (operator === \"or\" && v === true) return true;\n }\n if (sawUnknown) return \"unknown\";\n return operator === \"and\"; // all true for AND, all false for OR\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read a signed numeric value out of a modifier `{operation, value}` pair.\n * \"add\"/\"subtract\" become the matching sign; \"set\" / \"multiply\" / etc. return\n * `null` (translator surfaces them as unsupported).\n */\nfunction signedValue(modifier: Record<string, unknown>): number | null {\n const value = Number(modifier.value);\n if (!Number.isFinite(value)) return null;\n switch (modifier.operation) {\n case \"add\":\n return value;\n case \"subtract\":\n return -value;\n // For the symmetric stats (A/S/T) and roll-/bs-modifiers, \"improve\" moves\n // the number up (beneficial) and \"worsen\" down. AP is handled separately by\n // `apDelta`, which inverts this because AP's beneficial direction is more\n // negative.\n case \"improve\":\n return value;\n case \"worsen\":\n return -value;\n default:\n // set / halve / multiply: not a single signed delta — left unsupported.\n return null;\n }\n}\n\n/**\n * Read the AP delta out of a stat-modifier `{operation, value}` pair. AP is\n * stored negative (more negative = more piercing), so \"improve\" makes it more\n * negative and \"worsen\" less. The legacy `add`/`subtract` forms pass a signed\n * value through directly (the data already encodes the sign).\n */\nfunction apDelta(modifier: Record<string, unknown>): number | null {\n const value = Number(modifier.value);\n if (!Number.isFinite(value)) return null;\n switch (modifier.operation) {\n case \"improve\":\n return -Math.abs(value);\n case \"worsen\":\n return Math.abs(value);\n case \"add\":\n return value;\n case \"subtract\":\n return -value;\n default:\n return null;\n }\n}\n\n/**\n * Parse a printed weapon-keyword string (e.g. `\"Sustained Hits 1\"`,\n * `\"Anti-INFANTRY 4+\"`, `\"Lethal Hits\"`) into a `{keyword_id, parameters?}`\n * catalog reference, or `null` if the form is unrecognised.\n *\n * Reverses the conventions baked into the M0 catalog: kebab-case ids,\n * trailing number → `value`, embedded keyword + threshold → `target_keyword`\n * + `threshold`.\n */\nexport function parseKeywordGrant(raw: string): WeaponKeywordRef | null {\n const trimmed = raw.trim();\n if (trimmed === \"\") return null;\n\n // Anti-X N+ → { anti, target_keyword: X, threshold: N }\n const antiMatch = /^anti-([A-Z][A-Z\\s-]*)\\s+(\\d+)\\+?$/i.exec(trimmed);\n if (antiMatch) {\n return {\n keyword_id: \"anti\",\n parameters: { target_keyword: antiMatch[1].trim(), threshold: Number(antiMatch[2]) },\n };\n }\n\n // \"Lethal Hits\", \"Twin-linked\", \"Heavy\" → kebab-case lookup, no params.\n // \"Sustained Hits 1\", \"Rapid Fire 2\", \"Melta 2\" → kebab-case + value.\n const valueMatch = /^(.+?)\\s+(\\d+)$/.exec(trimmed);\n if (valueMatch) {\n return {\n keyword_id: toKebabCase(valueMatch[1]),\n parameters: { value: Number(valueMatch[2]) },\n };\n }\n return { keyword_id: toKebabCase(trimmed) };\n}\n\nfunction toKebabCase(s: string): string {\n return s\n .toLowerCase()\n .replace(/[\\s_]+/g, \"-\")\n .replace(/[^a-z0-9-]/g, \"\");\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
1
+ {"version":3,"file":"from-dsl.js","sourceRoot":"","sources":["../../src/cruncher/from-dsl.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Translate an Ability DSL `effect` tree into the {@link Buff} stack it\n * contributes (for an attacker-perspective crunch) along with a list of\n * effect fragments the translator could not auto-apply.\n *\n * The buff layer is intentionally a subset of the DSL: it covers the math the\n * cruncher's expected-value engine reads (rerolls, die-roll modifiers, S/A/T\n * stat shifts, FNP, granted weapon keywords, cover) and reports everything\n * else — choice nodes (player decisions), dice-gated effects (stochastic),\n * defender-side bs-modifier, attack-restrictions, ability grants, mortal\n * wound triggers — as `unsupported` so the SPA can surface \"this ability has\n * effects we can't auto-apply\" rather than silently dropping them.\n *\n * The walker classifies an effect's `target` against the attacker\n * perspective: `self`, `bearer`, `unit`, `attached-unit`, `attacker`, and\n * `friendly-within-aura` are all treated as \"applies to my unit\". `defender`,\n * `enemy-within-aura`, and `all-enemy` are dropped without being marked\n * unsupported — those are defender-side mods and would surface from the\n * target's perspective (M3 work), not the attacker's.\n *\n * @packageDocumentation\n */\nimport type {\n Buff,\n BuffApplicability,\n BuffContribution,\n BuffSource,\n EngineContext,\n WeaponKeywordRef,\n} from \"./buffs.js\";\nimport type { Phase } from \"../generated.js\";\n\n/** A fragment we couldn't translate. The SPA can render these as warnings. */\nexport type UnsupportedFragment = {\n reason: string;\n effectFragment: unknown;\n};\n\n/**\n * A mutually-limited pool of {@link ActivatableBuff} levers. Dice-pool\n * allocations cap how many options fire at once (`max_activations`); a `choice`\n * lets the player pick exactly one. Levers sharing a `group.id` are subject to\n * that cap — the SPA greys out further checkboxes once it's reached, and an\n * optimizer enumerates subsets within it.\n */\nexport type ActivatableGroupRef = {\n id: string;\n maxActivations: number;\n};\n\n/**\n * A buff-bearing *player decision* the cruncher can't make on its own: a\n * dice-pool option, a `choice` branch, or an activation gated on a timing the\n * player controls (e.g. \"start of phase\"). It is not auto-applied — the\n * consumer opts in (a checkbox, or an optimizer's search) and then folds\n * {@link buffs} into the crunch. Conditions the activation still carries (a\n * target keyword, a phase) ride on each buff's `applicableWhen`, so the\n * resolver gates them per-target rather than the lever vanishing.\n */\nexport type ActivatableBuff = {\n /** Stable toggle id, e.g. `\"blessings-of-khorne#Warp Blades\"`. */\n id: string;\n /** Human label for the lever (option name, or a summary of its buffs). */\n label: string;\n /** Contributions this activation adds when the player opts in (≥1). */\n buffs: Buff[];\n /** Set when the lever belongs to a mutually-limited pool. */\n group?: ActivatableGroupRef;\n};\n\nexport type EffectTranslation = {\n applied: Buff[];\n unsupported: UnsupportedFragment[];\n /** Buffs sitting behind a player decision — see {@link ActivatableBuff}. */\n activatable: ActivatableBuff[];\n};\n\n/**\n * Whose perspective the translation runs from.\n *\n * - `\"attacker\"`: the buffed unit is *firing*. `target: \"unit\"/\"self\"` etc.\n * become attacker-side mods (re-rolls, hit/wound mods, A/S shifts, granted\n * keywords). `target: \"defender\"` is silently dropped — that's incoming\n * penalty math relevant when the buffed unit is the *target*, surfaced via\n * the `\"target\"` perspective instead.\n *\n * - `\"target\"`: the buffed unit is *being shot at*. Defensive mods on the\n * buffed unit (`stat-modifier T`, `stat-modifier Sv`, `feel-no-pain`,\n * `roll-modifier save`) become defender-side buffs. Conversely, attacker-\n * only mods (re-rolls, hit/wound mods, A/S shifts) drop silently because\n * they describe what the buffed unit does when *attacking*.\n *\n * The bs-modifier effect (a -1 to incoming hit rolls, e.g. Benefit of Cover)\n * becomes a `hit-mod` buff under target perspective so it stacks correctly\n * with attacker-side modifiers in the resolver's ±1 cap.\n */\nexport type TranslationPerspective = \"attacker\" | \"target\";\n\n/** Targets that resolve to the buffed unit itself. */\nconst SELF_TARGETS = new Set([\n \"self\",\n \"bearer\",\n \"unit\",\n \"attached-unit\",\n \"friendly-within-aura\",\n \"all-friendly\",\n]);\n\n/** Aliases the DSL uses when a node specifically calls out \"the attacker\". */\nconst ATTACKER_TARGET = \"attacker\";\n/** Aliases the DSL uses when a node specifically calls out \"the defender\". */\nconst DEFENDER_TARGETS = new Set([\"defender\", \"enemy-within-aura\", \"all-enemy\"]);\n\n/**\n * Walk an ability DSL `effect` tree and produce the buff stack it contributes\n * against `context` from the given `perspective`, plus an `unsupported` list\n * naming any branches the buff layer can't express today.\n */\nexport function effectToBuffs(\n effect: unknown,\n source: BuffSource,\n context: EngineContext,\n perspective: TranslationPerspective = \"attacker\",\n): EffectTranslation {\n const out: EffectTranslation = { applied: [], unsupported: [], activatable: [] };\n const abilityId = source.kind === \"ability\" ? source.abilityId : \"effect\";\n walk(effect, source, { context, perspective, abilityId }, out);\n return out;\n}\n\ntype WalkOpts = {\n context: EngineContext;\n perspective: TranslationPerspective;\n /** Owning ability id — seeds the stable ids of activatable levers. */\n abilityId: string;\n};\n\nfunction walk(\n node: unknown,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n if (!isObject(node)) return;\n const type = node.type;\n switch (type) {\n case \"re-roll\":\n translateReroll(node, source, opts, out);\n return;\n case \"roll-modifier\":\n translateRollModifier(node, source, opts, out);\n return;\n case \"stat-modifier\":\n translateStatModifier(node, source, opts, out);\n return;\n case \"feel-no-pain\":\n translateFeelNoPain(node, source, opts, out);\n return;\n case \"keyword-grant\":\n translateKeywordGrant(node, source, opts, out);\n return;\n case \"bs-modifier\":\n translateBsModifier(node, source, opts, out);\n return;\n case \"damage-reduction\":\n translateDamageReduction(node, source, opts, out);\n return;\n case \"invulnerable-save\":\n translateInvulnerableSave(node, source, opts, out);\n return;\n case \"conditional\":\n translateConditional(node, source, opts, out);\n return;\n case \"sequence\":\n for (const step of (node.steps as unknown[]) ?? []) walk(step, source, opts, out);\n return;\n case \"choice\":\n // Player decision — each branch becomes an opt-in lever (pick one).\n enumerateChoice(node, source, opts, out);\n return;\n case \"dice-gated\":\n // Probabilistic; the buff layer is deterministic.\n out.unsupported.push({\n reason: \"dice-gated effect: stochastic; not expressible as a buff\",\n effectFragment: node,\n });\n return;\n case \"dice-pool-allocation\":\n // Player spends dice on options at runtime — each buff-bearing option\n // becomes an opt-in lever, grouped under the pool's activation cap.\n enumerateDicePool(node, source, opts, out);\n return;\n default:\n // Unknown effect — record it. Covers ability-grant, deep-strike,\n // mortal-wounds, cp-gain, movement-modifier, etc.; the buff layer\n // doesn't model these as deterministic mods to a single shot.\n out.unsupported.push({\n reason: `effect type \"${String(type)}\" is not modelled by the buff layer`,\n effectFragment: node,\n });\n return;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Leaf translators\n// ---------------------------------------------------------------------------\n\n/**\n * Classify a node's `target` field against the perspective we're translating\n * for. Returns:\n * - `\"self\"`: the node targets the buffed unit (apply attacker-side or\n * defender-side translation, depending on perspective + stat).\n * - `\"attacker\"` / `\"defender\"`: the node targets the other party explicitly.\n * - `\"unknown\"`: missing/malformed target.\n */\nfunction classifyTarget(\n node: Record<string, unknown>,\n): \"self\" | \"attacker\" | \"defender\" | \"unknown\" {\n const target = node.target;\n if (typeof target !== \"string\") return \"unknown\";\n if (target === ATTACKER_TARGET) return \"attacker\";\n if (DEFENDER_TARGETS.has(target)) return \"defender\";\n if (SELF_TARGETS.has(target)) return \"self\";\n return \"unknown\";\n}\n\n/**\n * Does this node's target match the buffed unit under the current\n * perspective? Used for symmetric roll/keyword translations where the same\n * effect is \"self\" in either direction.\n */\nfunction appliesToBuffedUnit(\n node: Record<string, unknown>,\n perspective: TranslationPerspective,\n): boolean {\n const cls = classifyTarget(node);\n if (cls === \"self\") return true;\n if (cls === \"attacker\") return perspective === \"attacker\";\n if (cls === \"defender\") return perspective === \"target\";\n return false;\n}\n\nfunction translateReroll(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // Rerolls are inherently attacker-side (you re-roll your own hit/wound/\n // damage; save rerolls fire when *you* are the target). Apply only under\n // the matching perspective so a target-perspective walk doesn't grab the\n // attacker's reroll-failed-hits buff.\n if (opts.perspective === \"attacker\" && !appliesToBuffedUnit(node, \"attacker\")) return;\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({ reason: \"re-roll: missing modifier object\", effectFragment: node });\n return;\n }\n const narrowed = unhonorableNarrowing(modifier);\n if (narrowed) {\n out.unsupported.push({ reason: `re-roll: narrows by \"${narrowed}\" which the cruncher can't resolve here`, effectFragment: node });\n return;\n }\n const roll = modifier.roll;\n // A `value: 1` on a re-roll modifier unambiguously means \"re-roll rolls of 1\".\n // A historical migration (2026-weapon-keywords) mis-defaulted such nodes to\n // `subset: \"all-failures\"`; honor the value as the source of truth so any\n // stray data of that shape can't silently over-apply the reroll.\n const subset = modifier.value === 1 ? \"ones\" : modifier.subset;\n // Under target perspective, only \"save\" rerolls fire on the buffed unit.\n if (opts.perspective === \"target\" && roll !== \"save\") return;\n if (\n (roll === \"hit\" || roll === \"wound\" || roll === \"save\" || roll === \"damage\") &&\n (subset === \"ones\" || subset === \"all-failures\")\n ) {\n out.applied.push({ source, contribution: { type: \"reroll\", roll, subset } });\n return;\n }\n out.unsupported.push({\n reason: `re-roll on \"${String(roll)}\" (subset \"${String(subset)}\") is outside the damage path`,\n effectFragment: node,\n });\n}\n\nfunction translateRollModifier(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"roll-modifier: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const narrowed = unhonorableNarrowing(modifier);\n if (narrowed) {\n out.unsupported.push({ reason: `roll-modifier: narrows by \"${narrowed}\" which the cruncher can't resolve here`, effectFragment: node });\n return;\n }\n const value = signedValue(modifier);\n if (value === null) {\n out.unsupported.push({\n reason: `roll-modifier: operation \"${String(modifier.operation)}\" not supported`,\n effectFragment: node,\n });\n return;\n }\n const roll = modifier.roll;\n // Each roll type is intrinsically on one side. Hit / wound / damage are\n // attacker-side; save is defender-side. The perspective decides whether the\n // buffed unit's `target` is the right party for that roll type.\n if (opts.perspective === \"attacker\") {\n if (!appliesToBuffedUnit(node, \"attacker\")) return;\n if (roll === \"save\") return; // saves apply to the defender, not the attacker.\n } else {\n // Target perspective accepts two shapes:\n // - `target: \"self\"/\"unit\"/...` + `roll: \"save\"` — the buffed unit's\n // own save rolls (mirrors the attacker path's reroll/stat handling).\n // - `target: \"attacker\"` + `roll: \"hit\"/\"wound\"` — a defender-side\n // rule that penalises the *incoming* attacker's hit/wound rolls (e.g.\n // \"subtract 1 from hit rolls targeting this unit\"). Functionally\n // identical to a `bs-modifier {target:\"attacker\"}` but with the\n // canonical `roll-modifier` shape; the data uses both forms.\n const cls = classifyTarget(node);\n if (cls === \"attacker\") {\n if (roll !== \"hit\" && roll !== \"wound\") return; // damage/save against the attacker make no sense here.\n } else if (cls === \"self\") {\n if (roll !== \"save\") return;\n } else {\n return;\n }\n }\n switch (roll) {\n case \"hit\":\n out.applied.push({ source, contribution: { type: \"hit-mod\", value } });\n return;\n case \"wound\":\n out.applied.push({ source, contribution: { type: \"wound-mod\", value } });\n return;\n case \"save\":\n out.applied.push({ source, contribution: { type: \"save-mod\", value } });\n return;\n case \"damage\":\n out.applied.push({ source, contribution: { type: \"damage-mod\", value } });\n return;\n default:\n out.unsupported.push({\n reason: `roll-modifier on \"${String(roll)}\" is outside the damage path`,\n effectFragment: node,\n });\n }\n}\n\nfunction translateStatModifier(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"stat-modifier: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const narrowed = unhonorableNarrowing(modifier);\n if (narrowed) {\n out.unsupported.push({ reason: `stat-modifier: narrows by \"${narrowed}\" which the cruncher can't resolve here`, effectFragment: node });\n return;\n }\n const stat = modifier.stat;\n const isOnBuffedUnit = appliesToBuffedUnit(node, opts.perspective);\n // `attack_type: melee|ranged` scopes the mod to that attack — express it as a\n // phase gate so e.g. a melee +1 Attack doesn't fire in the shooting phase.\n const applicability = attackTypeApplicability(modifier);\n const emit = (contribution: BuffContribution): void => {\n const buff: Buff = { source, contribution };\n out.applied.push(applicability ? { ...buff, applicableWhen: applicability } : buff);\n };\n\n // AP has an inverted sign convention (stored negative; more negative = more\n // piercing) and offensive/defensive variants, so it computes its own delta\n // and routes by attacker/defender rather than going through `signedValue`.\n if (stat === \"AP\") {\n translateApModifier(node, modifier, opts, out, emit);\n return;\n }\n\n const value = signedValue(modifier);\n if (value === null) {\n out.unsupported.push({\n reason: `stat-modifier: operation \"${String(modifier.operation)}\" not supported`,\n effectFragment: node,\n });\n return;\n }\n switch (stat) {\n case \"A\":\n if (opts.perspective !== \"attacker\" || !isOnBuffedUnit) return;\n emit({ type: \"attacks-mod\", value });\n return;\n case \"S\":\n if (opts.perspective !== \"attacker\" || !isOnBuffedUnit) return;\n emit({ type: \"strength-mod\", value });\n return;\n case \"T\":\n // Defender stat. Only relevant under target perspective.\n if (opts.perspective !== \"target\") {\n out.unsupported.push({\n reason: \"stat-modifier T: defender-side stat; applies when the buffed unit is the target\",\n effectFragment: node,\n });\n return;\n }\n if (!isOnBuffedUnit) return;\n emit({ type: \"toughness-mod\", value });\n return;\n case \"Sv\":\n // Saves improve when the *defender* gets +Sv. A +1 to Sv in printed\n // rules means \"improve the save by 1\", which maps to a `save-mod` of\n // `-value` since save-mod is signed against the *needed roll*.\n // (Equivalent: a -1 Sv penalty is a +1 save-mod.) We translate\n // \"Sv add 1\" → save-mod -1 to keep the resolver's sign convention.\n if (opts.perspective !== \"target\") {\n out.unsupported.push({\n reason: \"stat-modifier Sv: defender-side stat; applies when the buffed unit is the target\",\n effectFragment: node,\n });\n return;\n }\n if (!isOnBuffedUnit) return;\n emit({ type: \"save-mod\", value: -value });\n return;\n default:\n out.unsupported.push({\n reason: `stat-modifier on \"${String(stat)}\" is outside the damage path`,\n effectFragment: node,\n });\n }\n}\n\n/**\n * Translate an `AP` stat-modifier. AP rides on the attacker's weapon profile and\n * is stored as a negative number (e.g. AP -1); more negative = more piercing.\n *\n * Two variants exist in the data:\n * - **offensive** (`target` self/unit): the buffed unit's own weapons gain AP —\n * an attacker-side `ap-mod`. `improve N` → `-N` (more piercing), `worsen N` →\n * `+N`, and the legacy `add`/`subtract` forms (which already pass a signed,\n * usually negative, value) flow through `apDelta` unchanged.\n * - **defensive** (`target: \"attacker\"`): \"enemy weapons targeting this unit\n * have AP worsened\". This applies when the buffed unit is the *target*; we do\n * not model it as an attacker-side buff (that would wrongly weaken the buffed\n * unit's own attacks), so it is surfaced as `unsupported`.\n */\nfunction translateApModifier(\n node: Record<string, unknown>,\n modifier: Record<string, unknown>,\n opts: WalkOpts,\n out: EffectTranslation,\n emit: (contribution: BuffContribution) => void,\n): void {\n if (classifyTarget(node) === \"attacker\") {\n out.unsupported.push({\n reason:\n \"stat-modifier AP on the attacker: defender-side AP reduction is not modelled by the buff layer\",\n effectFragment: node,\n });\n return;\n }\n if (opts.perspective !== \"attacker\" || !appliesToBuffedUnit(node, \"attacker\")) return;\n const delta = apDelta(modifier);\n if (delta === null) {\n out.unsupported.push({\n reason: `stat-modifier AP: operation \"${String(modifier.operation)}\" not supported`,\n effectFragment: node,\n });\n return;\n }\n emit({ type: \"ap-mod\", value: delta });\n}\n\nfunction translateFeelNoPain(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // FNP applies when the buffed unit is the *target* — it ablates incoming\n // damage. Under attacker perspective the FNP is irrelevant (the unit is\n // firing, not taking damage). Drop silently rather than as `unsupported`\n // so attacker-perspective walks don't surface a spurious diagnostic for\n // every unit that happens to have a FNP rule.\n if (opts.perspective !== \"target\") return;\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"feel-no-pain: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const threshold = Number(modifier.threshold);\n if (!Number.isFinite(threshold)) {\n out.unsupported.push({\n reason: \"feel-no-pain: threshold not numeric\",\n effectFragment: node,\n });\n return;\n }\n // `modifier.scope` ∈ {\"all\", \"mortal\"} (default \"all\"). Schema's `modifier`\n // is `additionalProperties: true`, so any string lands here; we accept the\n // two documented values and route everything else to unsupported so a typo\n // (\"mortals\", \"mortal-wound\") can't silently masquerade as an all-FNP.\n const rawScope = modifier.scope;\n let scope: \"all\" | \"mortal\" = \"all\";\n if (rawScope !== undefined) {\n if (rawScope === \"all\" || rawScope === \"mortal\") {\n scope = rawScope;\n } else {\n out.unsupported.push({\n reason: `feel-no-pain: unrecognised scope \"${String(rawScope)}\" (expected \"all\" or \"mortal\")`,\n effectFragment: node,\n });\n return;\n }\n }\n const contribution =\n scope === \"mortal\"\n ? ({ type: \"feel-no-pain\", threshold, scope: \"mortal\" } as const)\n : ({ type: \"feel-no-pain\", threshold } as const);\n out.applied.push({ source, contribution });\n}\n\nfunction translateKeywordGrant(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // Weapon-keyword grants ride with the attacker's profile (e.g. \"your\n // weapons gain [Sustained Hits 1]\"). Defender-perspective walks ignore\n // them — the keyword applies when the buffed unit fires, not when it's\n // shot at.\n if (opts.perspective !== \"attacker\") return;\n if (!appliesToBuffedUnit(node, \"attacker\")) return;\n const modifier = node.modifier;\n if (!isObject(modifier)) return;\n // The DSL grants keywords in two shapes: a singular `keyword` string (often\n // with a `weapon_type`) or a `keywords` array. Accept both.\n const raws = keywordGrantList(modifier);\n if (raws.length === 0) return;\n // `weapon_type: melee|ranged` scopes the grant to that attack — a melee-only\n // keyword shouldn't fire in the shooting phase. Express it as a phase gate.\n const applicability = weaponTypeApplicability(modifier);\n for (const raw of raws) {\n const ref = parseKeywordGrant(raw);\n if (!ref) {\n out.unsupported.push({\n reason: `keyword-grant: cannot parse \"${raw}\" to a catalog keyword`,\n effectFragment: { keyword: raw },\n });\n continue;\n }\n const buff: Buff = { source, contribution: { type: \"extra-keyword\", keywordRef: ref } };\n out.applied.push(applicability ? { ...buff, applicableWhen: applicability } : buff);\n }\n}\n\n/** Normalise a keyword-grant modifier's singular `keyword` and/or `keywords` array. */\nfunction keywordGrantList(modifier: Record<string, unknown>): string[] {\n const out: string[] = [];\n if (typeof modifier.keyword === \"string\") out.push(modifier.keyword);\n if (Array.isArray(modifier.keywords)) {\n for (const k of modifier.keywords) if (typeof k === \"string\") out.push(k);\n }\n return out;\n}\n\n/** Map a keyword-grant's `weapon_type` to the phase its weapons fire in. */\nfunction weaponTypeApplicability(modifier: Record<string, unknown>): BuffApplicability | undefined {\n if (modifier.weapon_type === \"melee\") return { phases: [\"fight\"] };\n if (modifier.weapon_type === \"ranged\") return { phases: [\"shooting\"] };\n return undefined;\n}\n\n/**\n * Map a stat-modifier's `attack_type` (or the equivalent `weapon_type`) to the\n * phase that attack happens in. Both spellings carry the same melee/ranged\n * intent; honoring `weapon_type` lets a \"+1 A to melee weapons\" mod phase-gate\n * correctly instead of leaking into the shooting phase.\n */\nfunction attackTypeApplicability(modifier: Record<string, unknown>): BuffApplicability | undefined {\n const kind = modifier.attack_type ?? modifier.weapon_type;\n if (kind === \"melee\") return { phases: [\"fight\"] };\n if (kind === \"ranged\") return { phases: [\"shooting\"] };\n return undefined;\n}\n\n/**\n * Narrowing keys that scope a buff to a named weapon or a model subset the\n * cruncher can't resolve at translation time (it has no weapon/model context\n * here). When present on a damage-path leaf, applying the buff unfiltered would\n * silently OVER-APPLY it, so we surface it as `unsupported` instead — the data\n * stays faithful for other consumers; the optimizer just doesn't assume it.\n * `weapon_type`/`attack_type` are NOT here — those map cleanly to a phase gate.\n */\nconst UNHONORABLE_NARROWING = [\"weapon_name\", \"weapon_profile\", \"weapon_keyword\", \"weapon_filter\", \"model_filter\", \"model_scope\"];\nfunction unhonorableNarrowing(modifier: Record<string, unknown>): string | undefined {\n return UNHONORABLE_NARROWING.find((k) => modifier[k] != null);\n}\n\n/**\n * Defender-side damage-reduction (`{type: \"damage-reduction\", modifier:\n * {reduction: N | \"half\" | \"to-zero\"}}`). The buff layer only models the\n * additive numeric form — `\"half\"` and `\"to-zero\"` are one-use ablation\n * effects that don't fold into a deterministic expected-value crunch, so\n * they surface as `unsupported`. Attacker-perspective walks drop silently\n * (this is a defender stat).\n */\nfunction translateDamageReduction(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n if (opts.perspective !== \"target\") return;\n if (!appliesToBuffedUnit(node, \"target\")) return;\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"damage-reduction: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const reduction = modifier.reduction;\n if (typeof reduction === \"number\" && Number.isFinite(reduction) && reduction > 0) {\n out.applied.push({ source, contribution: { type: \"damage-reduction\", value: reduction } });\n return;\n }\n if (reduction === \"half\" || reduction === \"to-zero\") {\n out.unsupported.push({\n reason: `damage-reduction: \"${reduction}\" is a one-use ablation effect, not modelled by the expected-value engine`,\n effectFragment: node,\n });\n return;\n }\n out.unsupported.push({\n reason: `damage-reduction: unrecognised reduction \"${String(reduction)}\"`,\n effectFragment: node,\n });\n}\n\n/**\n * Defender-side ability-granted invulnerable save (`{type: \"invulnerable-save\",\n * modifier: {invuln_sv: N}}`). Best (lowest threshold) wins, combined with the\n * unit's printed invuln by the engine. Attacker-perspective walks drop\n * silently (this is a defender stat).\n */\nfunction translateInvulnerableSave(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n if (opts.perspective !== \"target\") return;\n if (!appliesToBuffedUnit(node, \"target\")) return;\n const modifier = node.modifier;\n if (!isObject(modifier)) {\n out.unsupported.push({\n reason: \"invulnerable-save: missing modifier object\",\n effectFragment: node,\n });\n return;\n }\n const threshold = Number(modifier.invuln_sv);\n if (!Number.isFinite(threshold) || threshold < 2 || threshold > 7) {\n out.unsupported.push({\n reason: `invulnerable-save: invuln_sv \"${String(modifier.invuln_sv)}\" is not a valid save threshold (2–7)`,\n effectFragment: node,\n });\n return;\n }\n out.applied.push({ source, contribution: { type: \"invulnerable-save\", threshold } });\n}\n\nfunction translateBsModifier(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n // A bs-modifier on `target: \"attacker\"` is a defender-side rule: it\n // penalises *incoming* hit rolls (e.g. Benefit of Cover). Translate it\n // as a `hit-mod` buff under target perspective so the resolver's ±1 cap\n // composes with attacker-side mods.\n if (opts.perspective !== \"target\") return;\n const cls = classifyTarget(node);\n if (cls !== \"attacker\") return; // a bs-modifier on self wouldn't make sense.\n const modifier = node.modifier;\n if (!isObject(modifier)) return;\n const value = signedValue(modifier);\n if (value === null) return;\n out.applied.push({ source, contribution: { type: \"hit-mod\", value } });\n}\n\nfunction translateConditional(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const condition = node.condition;\n const effect = node.effect;\n if (!isObject(condition)) return;\n const negated = condition.negated === true;\n const verdict = evaluateCondition(condition, opts.context);\n if (verdict === \"unknown\") {\n // A timing the player controls (e.g. \"start of phase\") isn't a wall — it's\n // an activation the player can opt into. Surface it as a lever rather than\n // dropping it. Other unevaluatable conditions stay unsupported.\n if (conditionMentionsTiming(condition)) {\n enumerateTimingGate(node, source, opts, out);\n } else {\n out.unsupported.push({\n reason: `conditional: cannot evaluate condition \"${String(condition.type)}\" against current context`,\n effectFragment: node,\n });\n }\n return;\n }\n const active = negated ? !verdict : verdict;\n if (!active) return;\n walk(effect, source, opts, out);\n}\n\n// ---------------------------------------------------------------------------\n// Activatable-lever enumeration\n//\n// Player-controlled gates — a `timing-is` the context can't pin down, each\n// `dice-pool-allocation` option, each `choice` branch — aren't walls for a\n// damage optimizer; they're the search space. Instead of dropping them to\n// `unsupported`, we descend through them and surface every buff-bearing branch\n// as an opt-in {@link ActivatableBuff}. The descent reuses the normal leaf\n// translators (so a lever applies exactly what it advertises) and turns the\n// conditions a branch still carries (target keyword, phase) into declarative\n// `applicableWhen` so the resolver gates them per-target.\n// ---------------------------------------------------------------------------\n\n/** Emit one lever per `choice` branch that yields a buff (pick exactly one). */\nfunction enumerateChoice(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const options = Array.isArray(node.options) ? node.options : [];\n options.forEach((opt, i) => {\n const buffs: Buff[] = [];\n collectGatedBuffs(opt, source, opts, {}, buffs);\n if (buffs.length === 0) return;\n out.activatable.push({\n id: `${opts.abilityId}?${i}`,\n label: labelForBuffs(buffs),\n buffs,\n group: { id: `${opts.abilityId}?choice`, maxActivations: 1 },\n });\n });\n}\n\n/** Emit one lever per buff-bearing dice-pool option, capped by `max_activations`. */\nfunction enumerateDicePool(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const options = Array.isArray(node.options) ? node.options : [];\n const maxActivations =\n typeof node.max_activations === \"number\" ? node.max_activations : options.length;\n for (const opt of options) {\n if (!isObject(opt)) continue;\n const buffs: Buff[] = [];\n collectGatedBuffs(opt.effect, source, opts, {}, buffs);\n if (buffs.length === 0) continue;\n const name = typeof opt.name === \"string\" && opt.name ? opt.name : labelForBuffs(buffs);\n out.activatable.push({\n id: `${opts.abilityId}#${name}`,\n label: name,\n buffs,\n group: { id: opts.abilityId, maxActivations },\n });\n }\n}\n\n/**\n * Surface a timing-gated activation. The timing itself is just \"when\" — opting\n * in satisfies it — so we descend into the body: an inner `dice-pool-allocation`\n * or `choice` surfaces its *own* option levers (e.g. Blessings of Khorne's\n * three keyword grants), while inner always-on buffs bundle into a single\n * timing lever. A body with no modelable combat buff (a `resurrection` or\n * `dice-gated`, like Berzerker Frenzy) yields nothing.\n */\nfunction enumerateTimingGate(\n node: Record<string, unknown>,\n source: BuffSource,\n opts: WalkOpts,\n out: EffectTranslation,\n): void {\n const condition = node.condition;\n if (!isObject(condition)) return;\n const sub: EffectTranslation = { applied: [], unsupported: [], activatable: [] };\n walk(node.effect, source, opts, sub);\n // Inner independent decisions (dice-pool options, choice branches) pass\n // straight through as their own levers.\n out.activatable.push(...sub.activatable);\n // Inner unconditional buffs become one lever gated only on the timing.\n if (sub.applied.length > 0) {\n const timing = extractTiming(condition) ?? \"timing\";\n out.activatable.push({\n id: `${opts.abilityId}@${timing}`,\n label: labelForBuffs(sub.applied),\n buffs: sub.applied,\n });\n }\n}\n\n/**\n * Walk the body of a player gate, collecting the buffs it would contribute.\n * Conditions are deferred to `applicableWhen` where expressible; nested\n * decisions and stochastic rolls inside an activation are not modelled.\n */\nfunction collectGatedBuffs(\n node: unknown,\n source: BuffSource,\n opts: WalkOpts,\n applicability: BuffApplicability,\n outBuffs: Buff[],\n): void {\n if (!isObject(node)) return;\n switch (node.type) {\n case \"conditional\": {\n const condition = node.condition;\n if (!isObject(condition)) return;\n const app = conditionToApplicability(condition);\n if (app === \"gate\") {\n // A nested timing gate: opting into the activation satisfies it, so\n // keep descending without adding a constraint.\n collectGatedBuffs(node.effect, source, opts, applicability, outBuffs);\n return;\n }\n if (app === \"context\") {\n // Can't express as a buff gate — fall back to the current context and\n // only descend when the condition is definitely active.\n if (evaluateCondition(condition, opts.context) === true) {\n collectGatedBuffs(node.effect, source, opts, applicability, outBuffs);\n }\n return;\n }\n collectGatedBuffs(node.effect, source, opts, combineApplicability(applicability, app), outBuffs);\n return;\n }\n case \"sequence\":\n for (const step of (node.steps as unknown[]) ?? []) {\n collectGatedBuffs(step, source, opts, applicability, outBuffs);\n }\n return;\n case \"choice\":\n case \"dice-pool-allocation\":\n case \"dice-gated\":\n // A decision (or stochastic roll) nested inside an activation. The outer\n // lever already stands for a player choice; we don't model the inner one.\n return;\n default: {\n // Leaf effect — run the normal leaf translators into a throwaway sink,\n // then attach the accumulated applicability so target/phase gating\n // defers to the resolver instead of vanishing the lever.\n const tmp: EffectTranslation = { applied: [], unsupported: [], activatable: [] };\n walk(node, source, opts, tmp);\n for (const b of tmp.applied) outBuffs.push(applyApplicability(b, applicability));\n return;\n }\n }\n}\n\n/** Does this condition (or any operand) gate on a player-controlled timing? */\nfunction conditionMentionsTiming(condition: Record<string, unknown>): boolean {\n if (condition.type === \"timing-is\") return true;\n if (typeof condition.operator === \"string\" && Array.isArray(condition.operands)) {\n return condition.operands.some((o) => isObject(o) && conditionMentionsTiming(o));\n }\n return false;\n}\n\n/** Pull the first `timing-is` timing value out of a (possibly compound) condition. */\nfunction extractTiming(condition: Record<string, unknown>): string | undefined {\n if (condition.type === \"timing-is\") {\n const t = (condition.parameters as Record<string, unknown> | undefined)?.timing;\n return typeof t === \"string\" ? t : undefined;\n }\n if (Array.isArray(condition.operands)) {\n for (const o of condition.operands) {\n if (isObject(o)) {\n const t = extractTiming(o);\n if (t) return t;\n }\n }\n }\n return undefined;\n}\n\n/**\n * Translate a condition into a {@link BuffApplicability} the resolver can gate\n * on. Returns `\"gate\"` for a player-controlled timing (satisfied by opting in),\n * or `\"context\"` when the condition has no declarative buff representation and\n * must fall back to context evaluation.\n */\nfunction conditionToApplicability(\n condition: Record<string, unknown>,\n): BuffApplicability | \"context\" | \"gate\" {\n if (condition.negated === true) return \"context\";\n if (typeof condition.operator === \"string\" && Array.isArray(condition.operands)) {\n if (condition.operator !== \"and\") return \"context\";\n let merged: BuffApplicability = {};\n for (const operand of condition.operands) {\n if (!isObject(operand)) return \"context\";\n const a = conditionToApplicability(operand);\n if (a === \"gate\") continue; // timing operand: satisfied by opting in.\n if (a === \"context\") return \"context\";\n merged = combineApplicability(merged, a);\n }\n return merged;\n }\n const params = condition.parameters as Record<string, unknown> | undefined;\n switch (condition.type) {\n case \"timing-is\":\n return \"gate\";\n case \"phase-is\": {\n const phase = params?.phase;\n return typeof phase === \"string\" ? { phases: [phase as Phase] } : \"context\";\n }\n case \"target-has-keyword\": {\n const kw = params?.keyword;\n return typeof kw === \"string\" ? { requiresTargetKeyword: kw } : \"context\";\n }\n case \"unit-has-keyword\": {\n const kw = params?.keyword;\n return typeof kw === \"string\" ? { requiresAttackerKeyword: kw } : \"context\";\n }\n case \"attack-is-type\": {\n const t = params?.attack_type;\n if (t === \"melee\") return { phases: [\"fight\"] };\n if (t === \"ranged\") return { phases: [\"shooting\"] };\n return \"context\";\n }\n default:\n return \"context\";\n }\n}\n\n/** Merge two applicabilities; `phases` intersect, the rest narrow. */\nfunction combineApplicability(a: BuffApplicability, b: BuffApplicability): BuffApplicability {\n const out: BuffApplicability = { ...a };\n if (b.phases) {\n out.phases = a.phases ? a.phases.filter((p) => b.phases!.includes(p)) : b.phases;\n }\n if (b.rollType) out.rollType = b.rollType;\n if (b.requiresTargetKeyword) out.requiresTargetKeyword = b.requiresTargetKeyword;\n if (b.requiresAttackerKeyword) out.requiresAttackerKeyword = b.requiresAttackerKeyword;\n return out;\n}\n\n/** Attach an accumulated applicability to a buff (no-op when empty). */\nfunction applyApplicability(buff: Buff, applicability: BuffApplicability): Buff {\n if (Object.keys(applicability).length === 0) return buff;\n const merged = buff.applicableWhen\n ? combineApplicability(buff.applicableWhen, applicability)\n : applicability;\n return { ...buff, applicableWhen: merged };\n}\n\n/** A short, deduped human label summarising a lever's contributions. */\nfunction labelForBuffs(buffs: Buff[]): string {\n const seen = new Set<string>();\n const parts: string[] = [];\n for (const b of buffs) {\n const p = describeContribution(b.contribution);\n if (!seen.has(p)) {\n seen.add(p);\n parts.push(p);\n }\n }\n return parts.join(\", \") || \"buff\";\n}\n\nfunction describeContribution(c: BuffContribution): string {\n switch (c.type) {\n case \"extra-keyword\":\n return keywordLabel(c.keywordRef);\n case \"hit-mod\":\n return `${signed(c.value)} to hit`;\n case \"wound-mod\":\n return `${signed(c.value)} to wound`;\n case \"save-mod\":\n return `${signed(c.value)} to save`;\n case \"damage-mod\":\n return `${signed(c.value)} damage`;\n case \"attacks-mod\":\n return `${signed(c.value)} attacks`;\n case \"strength-mod\":\n return `${signed(c.value)} strength`;\n case \"toughness-mod\":\n return `${signed(c.value)} toughness`;\n case \"ap-mod\":\n return `AP ${c.value}`;\n case \"reroll\":\n return `re-roll ${c.roll}${c.subset === \"ones\" ? \" 1s\" : \"\"}`;\n case \"feel-no-pain\":\n return c.scope === \"mortal\"\n ? `feel no pain ${c.threshold}+ vs mortals`\n : `feel no pain ${c.threshold}+`;\n case \"damage-reduction\":\n return `-${c.value} damage`;\n case \"invulnerable-save\":\n return `${c.threshold}+ invuln`;\n case \"cover\":\n return \"cover\";\n }\n}\n\nfunction signed(n: number): string {\n return n >= 0 ? `+${n}` : `${n}`;\n}\n\n/** Render a weapon-keyword ref back to its printed form (best-effort). */\nfunction keywordLabel(ref: WeaponKeywordRef): string {\n const params = ref.parameters ?? {};\n if (ref.keyword_id === \"anti\" && typeof params.target_keyword === \"string\") {\n const th = params.threshold;\n return `Anti-${params.target_keyword}${typeof th === \"number\" ? ` ${th}+` : \"\"}`;\n }\n const base = ref.keyword_id\n .split(\"-\")\n .map((w) => (w ? w.charAt(0).toUpperCase() + w.slice(1) : w))\n .join(\" \");\n return typeof params.value === \"number\" ? `${base} ${params.value}` : base;\n}\n\n// ---------------------------------------------------------------------------\n// Condition evaluator\n// ---------------------------------------------------------------------------\n\nfunction evaluateCondition(\n condition: Record<string, unknown>,\n ctx: EngineContext,\n): boolean | \"unknown\" {\n // Compound conditions use {operator, operands} rather than {type, parameters}.\n // The schema's `condition-node` oneOf doesn't guarantee discrimination by a\n // single field, so dispatch on shape: presence of `operator` + `operands`\n // wins over the simple-condition switch below.\n if (\n typeof condition.operator === \"string\" &&\n Array.isArray(condition.operands)\n ) {\n return evaluateCompound(condition.operator, condition.operands, ctx);\n }\n switch (condition.type) {\n case \"phase-is\": {\n const wanted = (condition.parameters as Record<string, unknown> | undefined)?.phase;\n if (typeof wanted !== \"string\") return \"unknown\";\n return ctx.phase === wanted;\n }\n case \"timing-is\": {\n const wanted = (condition.parameters as Record<string, unknown> | undefined)?.timing;\n if (typeof wanted !== \"string\") return \"unknown\";\n if (ctx.timing === undefined) return \"unknown\";\n return ctx.timing === wanted;\n }\n case \"remained-stationary\":\n return ctx.attackerStationary === true;\n case \"charged-this-turn\":\n // A player-controlled context flag (did the buffed unit charge this turn?),\n // mirroring `remained-stationary`. Undefined → the caller couldn't pin it\n // down, so stay \"unknown\" and let the SPA surface the gap.\n if (ctx.attackerCharged === undefined) return \"unknown\";\n return ctx.attackerCharged;\n case \"target-has-keyword\": {\n const kw = (condition.parameters as Record<string, unknown> | undefined)?.keyword;\n if (typeof kw !== \"string\") return \"unknown\";\n return (ctx.targetKeywords ?? []).includes(kw.toLowerCase());\n }\n case \"unit-has-keyword\": {\n const kw = (condition.parameters as Record<string, unknown> | undefined)?.keyword;\n if (typeof kw !== \"string\") return \"unknown\";\n return (ctx.attackerKeywords ?? []).includes(kw.toLowerCase());\n }\n case \"is-attached\":\n case \"model-is-leader\":\n // True whenever the buffed unit is a combined (\"attached\") unit. We do\n // not thread per-member leader identity — \"attachment present\" is the\n // signal both conditions gate on. Undefined flag (caller couldn't\n // determine attachment) stays \"unknown\" so the SPA surfaces the gap.\n if (ctx.attackerAttached === undefined) return \"unknown\";\n return ctx.attackerAttached;\n default:\n return \"unknown\";\n }\n}\n\n/**\n * Kleene three-valued evaluator for compound conditions. `and` short-circuits\n * to `false` as soon as any operand is false (an unknown operand is then\n * irrelevant); `or` short-circuits to `true` symmetrically. `not` flips its\n * single operand and leaves `\"unknown\"` as `\"unknown\"`. Unknown operands that\n * don't get short-circuited propagate as `\"unknown\"` so the SPA can surface\n * the gap rather than collapsing it into a misleading false.\n */\nfunction evaluateCompound(\n operator: string,\n operands: unknown[],\n ctx: EngineContext,\n): boolean | \"unknown\" {\n if (operator === \"not\") {\n const first = operands[0];\n if (!isObject(first)) return \"unknown\";\n const v = evaluateCondition(first, ctx);\n if (v === \"unknown\") return \"unknown\";\n return !v;\n }\n if (operator !== \"and\" && operator !== \"or\") return \"unknown\";\n let sawUnknown = false;\n for (const operand of operands) {\n if (!isObject(operand)) {\n sawUnknown = true;\n continue;\n }\n const v = evaluateCondition(operand, ctx);\n if (v === \"unknown\") {\n sawUnknown = true;\n continue;\n }\n if (operator === \"and\" && v === false) return false;\n if (operator === \"or\" && v === true) return true;\n }\n if (sawUnknown) return \"unknown\";\n return operator === \"and\"; // all true for AND, all false for OR\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read a signed numeric value out of a modifier `{operation, value}` pair.\n * \"add\"/\"subtract\" become the matching sign; \"set\" / \"multiply\" / etc. return\n * `null` (translator surfaces them as unsupported).\n */\nfunction signedValue(modifier: Record<string, unknown>): number | null {\n const value = Number(modifier.value);\n if (!Number.isFinite(value)) return null;\n switch (modifier.operation) {\n case \"add\":\n return value;\n case \"subtract\":\n return -value;\n // For the symmetric stats (A/S/T) and roll-/bs-modifiers, \"improve\" moves\n // the number up (beneficial) and \"worsen\" down. AP is handled separately by\n // `apDelta`, which inverts this because AP's beneficial direction is more\n // negative.\n case \"improve\":\n return value;\n case \"worsen\":\n return -value;\n default:\n // set / halve / multiply: not a single signed delta — left unsupported.\n return null;\n }\n}\n\n/**\n * Read the AP delta out of a stat-modifier `{operation, value}` pair. AP is\n * stored negative (more negative = more piercing), so \"improve\" makes it more\n * negative and \"worsen\" less. The legacy `add`/`subtract` forms pass a signed\n * value through directly (the data already encodes the sign).\n */\nfunction apDelta(modifier: Record<string, unknown>): number | null {\n const value = Number(modifier.value);\n if (!Number.isFinite(value)) return null;\n switch (modifier.operation) {\n case \"improve\":\n return -Math.abs(value);\n case \"worsen\":\n return Math.abs(value);\n case \"add\":\n return value;\n case \"subtract\":\n return -value;\n default:\n return null;\n }\n}\n\n/**\n * Parse a printed weapon-keyword string (e.g. `\"Sustained Hits 1\"`,\n * `\"Anti-INFANTRY 4+\"`, `\"Lethal Hits\"`) into a `{keyword_id, parameters?}`\n * catalog reference, or `null` if the form is unrecognised.\n *\n * Reverses the conventions baked into the M0 catalog: kebab-case ids,\n * trailing number → `value`, embedded keyword + threshold → `target_keyword`\n * + `threshold`.\n */\nexport function parseKeywordGrant(raw: string): WeaponKeywordRef | null {\n const trimmed = raw.trim();\n if (trimmed === \"\") return null;\n\n // Anti-X N+ → { anti, target_keyword: X, threshold: N }\n const antiMatch = /^anti-([A-Z][A-Z\\s-]*)\\s+(\\d+)\\+?$/i.exec(trimmed);\n if (antiMatch) {\n return {\n keyword_id: \"anti\",\n parameters: { target_keyword: antiMatch[1].trim(), threshold: Number(antiMatch[2]) },\n };\n }\n\n // \"Lethal Hits\", \"Twin-linked\", \"Heavy\" → kebab-case lookup, no params.\n // \"Sustained Hits 1\", \"Rapid Fire 2\", \"Melta 2\" → kebab-case + value.\n const valueMatch = /^(.+?)\\s+(\\d+)$/.exec(trimmed);\n if (valueMatch) {\n return {\n keyword_id: toKebabCase(valueMatch[1]),\n parameters: { value: Number(valueMatch[2]) },\n };\n }\n return { keyword_id: toKebabCase(trimmed) };\n}\n\nfunction toKebabCase(s: string): string {\n return s\n .toLowerCase()\n .replace(/[\\s_]+/g, \"-\")\n .replace(/[^a-z0-9-]/g, \"\");\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}