@alpaca-software/40kdc-data 0.2.0 → 0.3.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.
Files changed (41) hide show
  1. package/dist/author-input.d.ts +20 -1
  2. package/dist/author-input.d.ts.map +1 -1
  3. package/dist/author-input.js +64 -8
  4. package/dist/author-input.js.map +1 -1
  5. package/dist/author-seed.d.ts +62 -0
  6. package/dist/author-seed.d.ts.map +1 -0
  7. package/dist/author-seed.js +194 -0
  8. package/dist/author-seed.js.map +1 -0
  9. package/dist/commands/translate.d.ts.map +1 -1
  10. package/dist/commands/translate.js +6 -68
  11. package/dist/commands/translate.js.map +1 -1
  12. package/dist/data/bundle.generated.js +1 -1
  13. package/dist/data/bundle.generated.js.map +1 -1
  14. package/dist/gen-conformance.js +22 -1
  15. package/dist/gen-conformance.js.map +1 -1
  16. package/dist/generated.d.ts +181 -146
  17. package/dist/generated.d.ts.map +1 -1
  18. package/dist/generated.js.map +1 -1
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/runner.d.ts.map +1 -1
  24. package/dist/runner.js +16 -0
  25. package/dist/runner.js.map +1 -1
  26. package/dist/translate/condition.d.ts +26 -0
  27. package/dist/translate/condition.d.ts.map +1 -0
  28. package/dist/translate/condition.js +171 -0
  29. package/dist/translate/condition.js.map +1 -0
  30. package/dist/translate/index.d.ts +9 -0
  31. package/dist/translate/index.d.ts.map +1 -0
  32. package/dist/translate/index.js +9 -0
  33. package/dist/translate/index.js.map +1 -0
  34. package/dist/translate/scoring.d.ts +38 -0
  35. package/dist/translate/scoring.d.ts.map +1 -0
  36. package/dist/translate/scoring.js +80 -0
  37. package/dist/translate/scoring.js.map +1 -0
  38. package/package.json +2 -1
  39. package/schemas/core/secondary-card.schema.json +50 -28
  40. package/schemas/enrichment/ability-dsl/condition.schema.json +5 -2
  41. package/schemas/enrichment/ability-dsl/effect.schema.json +2 -1
@@ -6,79 +6,17 @@
6
6
  */
7
7
  import { readFileSync } from "node:fs";
8
8
  import { resolve } from "node:path";
9
- // ─── Condition translator ────────────────────────────────────────────
10
- function translateCondition(c) {
11
- const negate = c.negated ? "not " : "";
12
- // Compound conditions
13
- if (c.operator === "and" && c.operands) {
14
- const parts = c.operands.map(translateCondition);
15
- return parts.join(" AND ");
16
- }
17
- if (c.operator === "or" && c.operands) {
18
- const parts = c.operands.map(translateCondition);
19
- return parts.join(" OR ");
20
- }
21
- if (c.operator === "not" && c.operands) {
22
- return `NOT (${c.operands.map(translateCondition).join(", ")})`;
23
- }
24
- const p = c.parameters ?? {};
25
- switch (c.type) {
26
- case "phase-is":
27
- return `${negate}during the ${p.phase} phase`;
28
- case "timing-is":
29
- return `${negate}at ${formatTiming(p.timing)}`;
30
- case "player-turn-is":
31
- return `${negate}in ${p.turn === "your-turn" ? "your" : p.turn === "opponent-turn" ? "opponent's" : "either player's"} turn`;
32
- case "charged-this-turn":
33
- return `${negate}unit charged this turn`;
34
- case "advanced-this-turn":
35
- return `${negate}unit advanced this turn`;
36
- case "remained-stationary":
37
- return `${negate}unit remained stationary`;
38
- case "unit-below-starting-strength":
39
- return `${negate}target is below starting strength`;
40
- case "unit-below-half-strength":
41
- return `${negate}target is below half strength`;
42
- case "unit-has-keyword":
43
- return `${negate}unit has "${p.keyword}"`;
44
- case "target-has-keyword":
45
- return `${negate}target has "${p.keyword}"`;
46
- case "model-is-leader":
47
- return `${negate}model is leading a unit`;
48
- case "is-attached":
49
- return `${negate}attached to a ${p.keyword ?? ""} unit`;
50
- case "attack-is-type":
51
- return `${negate}for ${p.attack_type} attacks`;
52
- case "is-battle-shocked":
53
- return `${negate}unit is battle-shocked`;
54
- case "has-lost-wounds":
55
- return `${negate}model has lost wounds`;
56
- case "opponent-unit-within-range":
57
- return `${negate}enemy unit within ${p.range === "engagement" ? "engagement range" : p.range + '"'}`;
58
- case "unit-within-range-of":
59
- return `${negate}within ${p.range}" of ${p.target_type ?? "target"}${p.keyword ? ` (${p.keyword})` : ""}`;
60
- case "within-range-of-objective":
61
- return `${negate}within range of an objective`;
62
- case "controls-objective":
63
- return `${negate}controlling an objective`;
64
- case "has-fought-this-phase":
65
- return `${negate}has fought this phase`;
66
- case "destroyed-by-attack-type":
67
- return `${negate}destroyed by ${p.attack_type} attack`;
68
- default:
69
- return `${negate}[${c.type ?? "unknown"}]`;
70
- }
71
- }
72
- function formatTiming(t) {
73
- return t.replace(/-/g, " ");
74
- }
9
+ import { describeCondition } from "../translate/condition.js";
75
10
  // ─── Effect translator ───────────────────────────────────────────────
11
+ //
12
+ // Condition humanization lives in ../translate/condition.ts (shared with the
13
+ // scoring-card translator and pinned by the conformance corpus).
76
14
  function translateEffect(e, depth = 0) {
77
15
  const indent = " ".repeat(depth);
78
16
  const arrow = depth > 0 ? "→ " : "";
79
17
  switch (e.type) {
80
18
  case "conditional":
81
- return (`${indent}If ${translateCondition(e.condition)}:\n` +
19
+ return (`${indent}If ${describeCondition(e.condition)}:\n` +
82
20
  translateEffect(e.effect, depth + 1));
83
21
  case "sequence":
84
22
  return e
@@ -177,7 +115,7 @@ function translateEffectInline(e) {
177
115
  return `modify OC of ${target} by ${m.value}`;
178
116
  // Container types — recurse
179
117
  case "conditional":
180
- return `if ${translateCondition(e.condition)}: ${translateEffectInline(e.effect)}`;
118
+ return `if ${describeCondition(e.condition)}: ${translateEffectInline(e.effect)}`;
181
119
  case "sequence":
182
120
  return e.steps.map(translateEffectInline).join("; ");
183
121
  case "dice-gated": {
@@ -1 +1 @@
1
- {"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/commands/translate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Translates ability DSL entries into plain English descriptions.\n *\n * Recursively walks the effect/condition tree and produces human-readable\n * text purely from the structured data — no external text sources needed.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n// ─── Types (minimal, matching schema shapes) ─────────────────────────\n\ninterface Condition {\n type?: string;\n operator?: string;\n operands?: Condition[];\n parameters?: Record<string, unknown>;\n negated?: boolean;\n}\n\ninterface Effect {\n type: string;\n target?: string;\n modifier?: Record<string, unknown>;\n condition?: Condition;\n effect?: Effect;\n steps?: Effect[];\n options?: (Effect & { name?: string; requirement?: Record<string, unknown>; choice_label?: string })[];\n choice_label?: string;\n dice?: string;\n threshold?: number | string;\n comparison?: string;\n on_success?: Effect | null;\n on_fail?: Effect | null;\n pool?: { count: number; die: string };\n max_activations?: number;\n}\n\ninterface Ability {\n ability_id: string;\n name: string;\n ability_type?: string;\n behavior?: string;\n detachment_id?: string | null;\n faction_id?: string | null;\n unit_ids?: string[];\n effect: Effect;\n scope?: { range: string; duration: string; range_inches?: number };\n}\n\n// ─── Condition translator ────────────────────────────────────────────\n\nfunction translateCondition(c: Condition): string {\n const negate = c.negated ? \"not \" : \"\";\n\n // Compound conditions\n if (c.operator === \"and\" && c.operands) {\n const parts = c.operands.map(translateCondition);\n return parts.join(\" AND \");\n }\n if (c.operator === \"or\" && c.operands) {\n const parts = c.operands.map(translateCondition);\n return parts.join(\" OR \");\n }\n if (c.operator === \"not\" && c.operands) {\n return `NOT (${c.operands.map(translateCondition).join(\", \")})`;\n }\n\n const p = c.parameters ?? {};\n\n switch (c.type) {\n case \"phase-is\":\n return `${negate}during the ${p.phase} phase`;\n case \"timing-is\":\n return `${negate}at ${formatTiming(p.timing as string)}`;\n case \"player-turn-is\":\n return `${negate}in ${p.turn === \"your-turn\" ? \"your\" : p.turn === \"opponent-turn\" ? \"opponent's\" : \"either player's\"} turn`;\n case \"charged-this-turn\":\n return `${negate}unit charged this turn`;\n case \"advanced-this-turn\":\n return `${negate}unit advanced this turn`;\n case \"remained-stationary\":\n return `${negate}unit remained stationary`;\n case \"unit-below-starting-strength\":\n return `${negate}target is below starting strength`;\n case \"unit-below-half-strength\":\n return `${negate}target is below half strength`;\n case \"unit-has-keyword\":\n return `${negate}unit has \"${p.keyword}\"`;\n case \"target-has-keyword\":\n return `${negate}target has \"${p.keyword}\"`;\n case \"model-is-leader\":\n return `${negate}model is leading a unit`;\n case \"is-attached\":\n return `${negate}attached to a ${p.keyword ?? \"\"} unit`;\n case \"attack-is-type\":\n return `${negate}for ${p.attack_type} attacks`;\n case \"is-battle-shocked\":\n return `${negate}unit is battle-shocked`;\n case \"has-lost-wounds\":\n return `${negate}model has lost wounds`;\n case \"opponent-unit-within-range\":\n return `${negate}enemy unit within ${p.range === \"engagement\" ? \"engagement range\" : p.range + '\"'}`;\n case \"unit-within-range-of\":\n return `${negate}within ${p.range}\" of ${p.target_type ?? \"target\"}${p.keyword ? ` (${p.keyword})` : \"\"}`;\n case \"within-range-of-objective\":\n return `${negate}within range of an objective`;\n case \"controls-objective\":\n return `${negate}controlling an objective`;\n case \"has-fought-this-phase\":\n return `${negate}has fought this phase`;\n case \"destroyed-by-attack-type\":\n return `${negate}destroyed by ${p.attack_type} attack`;\n default:\n return `${negate}[${c.type ?? \"unknown\"}]`;\n }\n}\n\nfunction formatTiming(t: string): string {\n return t.replace(/-/g, \" \");\n}\n\n// ─── Effect translator ───────────────────────────────────────────────\n\nfunction translateEffect(e: Effect, depth: number = 0): string {\n const indent = \" \".repeat(depth);\n const arrow = depth > 0 ? \"→ \" : \"\";\n\n switch (e.type) {\n case \"conditional\":\n return (\n `${indent}If ${translateCondition(e.condition!)}:\\n` +\n translateEffect(e.effect!, depth + 1)\n );\n\n case \"sequence\":\n return e\n .steps!.map((s) => translateEffect(s, depth))\n .join(\"\\n\");\n\n case \"choice\":\n return (\n `${indent}${arrow}Choose one${e.choice_label ? ` (${e.choice_label})` : \"\"}:\\n` +\n e\n .options!.map((o, i) => `${indent} ${i + 1}. ${translateEffectInline(o)}`)\n .join(\"\\n\")\n );\n\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n const success = e.on_success\n ? translateEffectInline(e.on_success)\n : \"nothing\";\n const fail = e.on_fail\n ? `, otherwise ${translateEffectInline(e.on_fail)}`\n : \"\";\n return `${indent}${arrow}Roll ${e.dice}: on ${comp}, ${success}${fail}`;\n }\n\n case \"dice-pool-allocation\": {\n const poolStr = `${e.pool!.count}${e.pool!.die}`;\n const lines = [`${indent}${arrow}Roll ${poolStr} (max ${e.max_activations} activations):`];\n for (const opt of e.options!) {\n const req = opt.requirement!;\n lines.push(\n `${indent} - ${opt.name}: need ${req.type} of ${req.min_value}+ → ${translateEffectInline(opt.effect!)}`\n );\n }\n return lines.join(\"\\n\");\n }\n\n default:\n return `${indent}${arrow}${translateEffectInline(e)}`;\n }\n}\n\n/** Single-line translation for leaf effects. */\nfunction translateEffectInline(e: Effect): string {\n const m = e.modifier ?? {};\n const target = formatTarget(e.target);\n\n switch (e.type) {\n case \"stat-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : m.operation === \"subtract\" ? \"-\" : `${m.operation} `;\n const scope = m.attack_type ? ` (${m.attack_type})` : \"\";\n return `${op}${m.value} ${m.stat}${scope} for ${target}`;\n }\n case \"roll-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : \"-\";\n return `${op}${m.value} to ${m.roll} rolls for ${target}`;\n }\n case \"re-roll\":\n return `re-roll ${m.roll}${m.value ? ` (${m.value}s)` : \"\"} for ${target}`;\n case \"mortal-wounds\":\n return `deal ${m.amount ?? m.amount_table ? \"variable\" : \"?\"} mortal wounds to ${target}`;\n case \"feel-no-pain\":\n return `${target} gains Feel No Pain ${m.threshold}+`;\n case \"keyword-grant\": {\n // Grants come as singular `keyword` or the (dominant) `keywords` array.\n const kw = Array.isArray(m.keywords) ? m.keywords.join(\", \") : (m.keyword ?? \"keywords\");\n return `${target}'s ${m.weapon_type ?? \"all\"} weapons gain ${kw}`;\n }\n case \"ability-grant\":\n return `${target} gains ${formatGrantType((m.grant_type ?? m.ability_id) as string | undefined)}`;\n case \"movement-modifier\":\n return `${target} gains ${m.move_type}${m.value ? ` ${m.value}\"` : \"\"}`;\n case \"damage-reduction\":\n return `reduce incoming damage to ${target} by ${m.amount}`;\n case \"resurrection\":\n return `return ${m.count ?? 1} model(s) to ${target} with ${m.wounds_remaining ?? \"full\"} wounds`;\n case \"model-destruction\":\n return `destroy ${m.count} non-leader model(s) from ${target}`;\n case \"cp-gain\":\n return `gain ${m.amount} CP`;\n case \"cp-refund\":\n return `refund ${m.amount} CP`;\n case \"resource-gain\":\n return `gain ${m.amount} to ${m.pool_id}`;\n case \"resource-spend\":\n return `spend ${m.amount} from ${m.pool_id}`;\n case \"invulnerable-save\":\n return `${target} gains ${m.value}+ invulnerable save`;\n case \"leadership-modifier\":\n return `force battle-shock test on ${target}`;\n case \"fight-on-death\":\n return `${target} fights on death`;\n case \"shoot-on-death\":\n return `${target} shoots on death`;\n case \"fight-first\":\n return `${target} fights first`;\n case \"fight-last\":\n return `${target} fights last`;\n case \"deep-strike\":\n return `${target} can deep strike`;\n case \"fallback-and-act\":\n return `${target} can fall back and act`;\n case \"attack-restriction\":\n return `${target}: ${m.restriction_type ?? \"restriction\"} (max ${m.max_models ?? \"?\"} models)`;\n case \"objective-control-modifier\":\n return `modify OC of ${target} by ${m.value}`;\n\n // Container types — recurse\n case \"conditional\":\n return `if ${translateCondition(e.condition!)}: ${translateEffectInline(e.effect!)}`;\n case \"sequence\":\n return e.steps!.map(translateEffectInline).join(\"; \");\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n return `roll ${e.dice} (${comp}): ${e.on_success ? translateEffectInline(e.on_success) : \"nothing\"}`;\n }\n\n default:\n return `[${e.type}]`;\n }\n}\n\nfunction formatTarget(t?: string): string {\n if (!t) return \"target\";\n return t.replace(/-/g, \" \");\n}\n\nfunction formatGrantType(g: string | undefined): string {\n // ability-grant carries either `grant_type` or `ability_id`; an unauthored\n // stub may carry neither. Don't crash the whole translation on a missing key.\n return g ? g.replace(/-/g, \" \") : \"an ability\";\n}\n\nfunction formatComparison(comp: string, threshold: number | string): string {\n const thStr = typeof threshold === \"string\" ? threshold : `${threshold}`;\n switch (comp) {\n case \"gte\": return `${thStr}+`;\n case \"lte\": return `${thStr} or less`;\n case \"gt\": return `greater than ${thStr}`;\n case \"lt\": return `less than ${thStr}`;\n case \"eq\": return `exactly ${thStr}`;\n default: return `${thStr}+`;\n }\n}\n\n// ─── Scope translator ────────────────────────────────────────────────\n\nfunction translateScope(s?: { range: string; duration: string; range_inches?: number }): string {\n if (!s) return \"\";\n const range = s.range.replace(/-/g, \" \");\n const duration = s.duration.replace(/-/g, \" \");\n return `Scope: ${range}${s.range_inches ? ` (${s.range_inches}\")` : \"\"}. Duration: ${duration}.`;\n}\n\n// ─── Main command ────────────────────────────────────────────────────\n\nexport async function translateCommand(\n path?: string\n): Promise<void> {\n const filePath = resolve(\n process.cwd(),\n path ?? \"../data/enrichment/world-eaters/abilities.json\"\n );\n const abilities: Ability[] = JSON.parse(readFileSync(filePath, \"utf-8\"));\n\n for (const a of abilities) {\n const meta: string[] = [];\n if (a.ability_type) meta.push(a.ability_type);\n if (a.behavior) meta.push(a.behavior);\n if (a.detachment_id) meta.push(`detachment: ${a.detachment_id}`);\n if (a.unit_ids?.length) meta.push(`units: ${a.unit_ids.join(\", \")}`);\n\n console.log(`\\n═══ ${a.name} [${a.ability_id}] ═══`);\n if (meta.length) console.log(` ${meta.join(\" | \")}`);\n console.log(translateEffect(a.effect));\n const scope = translateScope(a.scope);\n if (scope) console.log(scope);\n }\n\n console.log(`\\n── ${abilities.length} abilities translated ──`);\n}\n"]}
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/commands/translate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Translates ability DSL entries into plain English descriptions.\n *\n * Recursively walks the effect/condition tree and produces human-readable\n * text purely from the structured data — no external text sources needed.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { describeCondition, type Condition } from \"../translate/condition.js\";\n\n// ─── Types (minimal, matching schema shapes) ─────────────────────────\n\ninterface Effect {\n type: string;\n target?: string;\n modifier?: Record<string, unknown>;\n condition?: Condition;\n effect?: Effect;\n steps?: Effect[];\n options?: (Effect & { name?: string; requirement?: Record<string, unknown>; choice_label?: string })[];\n choice_label?: string;\n dice?: string;\n threshold?: number | string;\n comparison?: string;\n on_success?: Effect | null;\n on_fail?: Effect | null;\n pool?: { count: number; die: string };\n max_activations?: number;\n}\n\ninterface Ability {\n ability_id: string;\n name: string;\n ability_type?: string;\n behavior?: string;\n detachment_id?: string | null;\n faction_id?: string | null;\n unit_ids?: string[];\n effect: Effect;\n scope?: { range: string; duration: string; range_inches?: number };\n}\n\n// ─── Effect translator ───────────────────────────────────────────────\n//\n// Condition humanization lives in ../translate/condition.ts (shared with the\n// scoring-card translator and pinned by the conformance corpus).\n\nfunction translateEffect(e: Effect, depth: number = 0): string {\n const indent = \" \".repeat(depth);\n const arrow = depth > 0 ? \"→ \" : \"\";\n\n switch (e.type) {\n case \"conditional\":\n return (\n `${indent}If ${describeCondition(e.condition!)}:\\n` +\n translateEffect(e.effect!, depth + 1)\n );\n\n case \"sequence\":\n return e\n .steps!.map((s) => translateEffect(s, depth))\n .join(\"\\n\");\n\n case \"choice\":\n return (\n `${indent}${arrow}Choose one${e.choice_label ? ` (${e.choice_label})` : \"\"}:\\n` +\n e\n .options!.map((o, i) => `${indent} ${i + 1}. ${translateEffectInline(o)}`)\n .join(\"\\n\")\n );\n\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n const success = e.on_success\n ? translateEffectInline(e.on_success)\n : \"nothing\";\n const fail = e.on_fail\n ? `, otherwise ${translateEffectInline(e.on_fail)}`\n : \"\";\n return `${indent}${arrow}Roll ${e.dice}: on ${comp}, ${success}${fail}`;\n }\n\n case \"dice-pool-allocation\": {\n const poolStr = `${e.pool!.count}${e.pool!.die}`;\n const lines = [`${indent}${arrow}Roll ${poolStr} (max ${e.max_activations} activations):`];\n for (const opt of e.options!) {\n const req = opt.requirement!;\n lines.push(\n `${indent} - ${opt.name}: need ${req.type} of ${req.min_value}+ → ${translateEffectInline(opt.effect!)}`\n );\n }\n return lines.join(\"\\n\");\n }\n\n default:\n return `${indent}${arrow}${translateEffectInline(e)}`;\n }\n}\n\n/** Single-line translation for leaf effects. */\nfunction translateEffectInline(e: Effect): string {\n const m = e.modifier ?? {};\n const target = formatTarget(e.target);\n\n switch (e.type) {\n case \"stat-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : m.operation === \"subtract\" ? \"-\" : `${m.operation} `;\n const scope = m.attack_type ? ` (${m.attack_type})` : \"\";\n return `${op}${m.value} ${m.stat}${scope} for ${target}`;\n }\n case \"roll-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : \"-\";\n return `${op}${m.value} to ${m.roll} rolls for ${target}`;\n }\n case \"re-roll\":\n return `re-roll ${m.roll}${m.value ? ` (${m.value}s)` : \"\"} for ${target}`;\n case \"mortal-wounds\":\n return `deal ${m.amount ?? m.amount_table ? \"variable\" : \"?\"} mortal wounds to ${target}`;\n case \"feel-no-pain\":\n return `${target} gains Feel No Pain ${m.threshold}+`;\n case \"keyword-grant\": {\n // Grants come as singular `keyword` or the (dominant) `keywords` array.\n const kw = Array.isArray(m.keywords) ? m.keywords.join(\", \") : (m.keyword ?? \"keywords\");\n return `${target}'s ${m.weapon_type ?? \"all\"} weapons gain ${kw}`;\n }\n case \"ability-grant\":\n return `${target} gains ${formatGrantType((m.grant_type ?? m.ability_id) as string | undefined)}`;\n case \"movement-modifier\":\n return `${target} gains ${m.move_type}${m.value ? ` ${m.value}\"` : \"\"}`;\n case \"damage-reduction\":\n return `reduce incoming damage to ${target} by ${m.amount}`;\n case \"resurrection\":\n return `return ${m.count ?? 1} model(s) to ${target} with ${m.wounds_remaining ?? \"full\"} wounds`;\n case \"model-destruction\":\n return `destroy ${m.count} non-leader model(s) from ${target}`;\n case \"cp-gain\":\n return `gain ${m.amount} CP`;\n case \"cp-refund\":\n return `refund ${m.amount} CP`;\n case \"resource-gain\":\n return `gain ${m.amount} to ${m.pool_id}`;\n case \"resource-spend\":\n return `spend ${m.amount} from ${m.pool_id}`;\n case \"invulnerable-save\":\n return `${target} gains ${m.value}+ invulnerable save`;\n case \"leadership-modifier\":\n return `force battle-shock test on ${target}`;\n case \"fight-on-death\":\n return `${target} fights on death`;\n case \"shoot-on-death\":\n return `${target} shoots on death`;\n case \"fight-first\":\n return `${target} fights first`;\n case \"fight-last\":\n return `${target} fights last`;\n case \"deep-strike\":\n return `${target} can deep strike`;\n case \"fallback-and-act\":\n return `${target} can fall back and act`;\n case \"attack-restriction\":\n return `${target}: ${m.restriction_type ?? \"restriction\"} (max ${m.max_models ?? \"?\"} models)`;\n case \"objective-control-modifier\":\n return `modify OC of ${target} by ${m.value}`;\n\n // Container types — recurse\n case \"conditional\":\n return `if ${describeCondition(e.condition!)}: ${translateEffectInline(e.effect!)}`;\n case \"sequence\":\n return e.steps!.map(translateEffectInline).join(\"; \");\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n return `roll ${e.dice} (${comp}): ${e.on_success ? translateEffectInline(e.on_success) : \"nothing\"}`;\n }\n\n default:\n return `[${e.type}]`;\n }\n}\n\nfunction formatTarget(t?: string): string {\n if (!t) return \"target\";\n return t.replace(/-/g, \" \");\n}\n\nfunction formatGrantType(g: string | undefined): string {\n // ability-grant carries either `grant_type` or `ability_id`; an unauthored\n // stub may carry neither. Don't crash the whole translation on a missing key.\n return g ? g.replace(/-/g, \" \") : \"an ability\";\n}\n\nfunction formatComparison(comp: string, threshold: number | string): string {\n const thStr = typeof threshold === \"string\" ? threshold : `${threshold}`;\n switch (comp) {\n case \"gte\": return `${thStr}+`;\n case \"lte\": return `${thStr} or less`;\n case \"gt\": return `greater than ${thStr}`;\n case \"lt\": return `less than ${thStr}`;\n case \"eq\": return `exactly ${thStr}`;\n default: return `${thStr}+`;\n }\n}\n\n// ─── Scope translator ────────────────────────────────────────────────\n\nfunction translateScope(s?: { range: string; duration: string; range_inches?: number }): string {\n if (!s) return \"\";\n const range = s.range.replace(/-/g, \" \");\n const duration = s.duration.replace(/-/g, \" \");\n return `Scope: ${range}${s.range_inches ? ` (${s.range_inches}\")` : \"\"}. Duration: ${duration}.`;\n}\n\n// ─── Main command ────────────────────────────────────────────────────\n\nexport async function translateCommand(\n path?: string\n): Promise<void> {\n const filePath = resolve(\n process.cwd(),\n path ?? \"../data/enrichment/world-eaters/abilities.json\"\n );\n const abilities: Ability[] = JSON.parse(readFileSync(filePath, \"utf-8\"));\n\n for (const a of abilities) {\n const meta: string[] = [];\n if (a.ability_type) meta.push(a.ability_type);\n if (a.behavior) meta.push(a.behavior);\n if (a.detachment_id) meta.push(`detachment: ${a.detachment_id}`);\n if (a.unit_ids?.length) meta.push(`units: ${a.unit_ids.join(\", \")}`);\n\n console.log(`\\n═══ ${a.name} [${a.ability_id}] ═══`);\n if (meta.length) console.log(` ${meta.join(\" | \")}`);\n console.log(translateEffect(a.effect));\n const scope = translateScope(a.scope);\n if (scope) console.log(scope);\n }\n\n console.log(`\\n── ${abilities.length} abilities translated ──`);\n}\n"]}