@alpaca-software/40kdc-data 0.4.16 → 0.5.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.
- package/dist/author-batch.d.ts.map +1 -1
- package/dist/author-batch.js +3 -2
- package/dist/author-batch.js.map +1 -1
- package/dist/commands/translate.d.ts +3 -2
- package/dist/commands/translate.d.ts.map +1 -1
- package/dist/commands/translate.js +6 -154
- package/dist/commands/translate.js.map +1 -1
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/entities.d.ts +7 -0
- package/dist/data/entities.d.ts.map +1 -1
- package/dist/data/entities.js +10 -0
- package/dist/data/entities.js.map +1 -1
- package/dist/gen-conformance.js +57 -1
- package/dist/gen-conformance.js.map +1 -1
- package/dist/generated.d.ts +1 -1
- package/dist/generated.d.ts.map +1 -1
- package/dist/generated.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +16 -1
- package/dist/runner.js.map +1 -1
- package/dist/translate/condition.d.ts.map +1 -1
- package/dist/translate/condition.js +11 -0
- package/dist/translate/condition.js.map +1 -1
- package/dist/translate/effect.d.ts +72 -0
- package/dist/translate/effect.d.ts.map +1 -0
- package/dist/translate/effect.js +241 -0
- package/dist/translate/effect.js.map +1 -0
- package/dist/translate/index.d.ts +7 -4
- package/dist/translate/index.d.ts.map +1 -1
- package/dist/translate/index.js +7 -4
- package/dist/translate/index.js.map +1 -1
- package/package.json +2 -2
- package/schemas/enrichment/ability-dsl/condition.schema.json +2 -2
|
@@ -1,161 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Translates ability DSL entries into plain English descriptions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Thin CLI shell over the shared `translate/effect.ts` describer (the
|
|
5
|
+
* conformance-pinned `ability.print()`); this file only handles file loading
|
|
6
|
+
* and per-ability presentation.
|
|
6
7
|
*/
|
|
7
8
|
import { readFileSync } from "node:fs";
|
|
8
9
|
import { resolve } from "node:path";
|
|
9
|
-
import {
|
|
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).
|
|
14
|
-
function translateEffect(e, depth = 0) {
|
|
15
|
-
const indent = " ".repeat(depth);
|
|
16
|
-
const arrow = depth > 0 ? "→ " : "";
|
|
17
|
-
switch (e.type) {
|
|
18
|
-
case "conditional":
|
|
19
|
-
return (`${indent}If ${describeCondition(e.condition)}:\n` +
|
|
20
|
-
translateEffect(e.effect, depth + 1));
|
|
21
|
-
case "sequence":
|
|
22
|
-
return e
|
|
23
|
-
.steps.map((s) => translateEffect(s, depth))
|
|
24
|
-
.join("\n");
|
|
25
|
-
case "choice":
|
|
26
|
-
return (`${indent}${arrow}Choose one${e.choice_label ? ` (${e.choice_label})` : ""}:\n` +
|
|
27
|
-
e
|
|
28
|
-
.options.map((o, i) => `${indent} ${i + 1}. ${translateEffectInline(o)}`)
|
|
29
|
-
.join("\n"));
|
|
30
|
-
case "dice-gated": {
|
|
31
|
-
const comp = formatComparison(e.comparison ?? "gte", e.threshold);
|
|
32
|
-
const success = e.on_success
|
|
33
|
-
? translateEffectInline(e.on_success)
|
|
34
|
-
: "nothing";
|
|
35
|
-
const fail = e.on_fail
|
|
36
|
-
? `, otherwise ${translateEffectInline(e.on_fail)}`
|
|
37
|
-
: "";
|
|
38
|
-
return `${indent}${arrow}Roll ${e.dice}: on ${comp}, ${success}${fail}`;
|
|
39
|
-
}
|
|
40
|
-
case "dice-pool-allocation": {
|
|
41
|
-
const poolStr = `${e.pool.count}${e.pool.die}`;
|
|
42
|
-
const lines = [`${indent}${arrow}Roll ${poolStr} (max ${e.max_activations} activations):`];
|
|
43
|
-
for (const opt of e.options) {
|
|
44
|
-
const req = opt.requirement;
|
|
45
|
-
lines.push(`${indent} - ${opt.name}: need ${req.type} of ${req.min_value}+ → ${translateEffectInline(opt.effect)}`);
|
|
46
|
-
}
|
|
47
|
-
return lines.join("\n");
|
|
48
|
-
}
|
|
49
|
-
default:
|
|
50
|
-
return `${indent}${arrow}${translateEffectInline(e)}`;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/** Single-line translation for leaf effects. */
|
|
54
|
-
function translateEffectInline(e) {
|
|
55
|
-
const m = e.modifier ?? {};
|
|
56
|
-
const target = formatTarget(e.target);
|
|
57
|
-
switch (e.type) {
|
|
58
|
-
case "stat-modifier": {
|
|
59
|
-
const op = m.operation === "add" ? "+" : m.operation === "subtract" ? "-" : `${m.operation} `;
|
|
60
|
-
const scope = m.attack_type ? ` (${m.attack_type})` : "";
|
|
61
|
-
return `${op}${m.value} ${m.stat}${scope} for ${target}`;
|
|
62
|
-
}
|
|
63
|
-
case "roll-modifier": {
|
|
64
|
-
const op = m.operation === "add" ? "+" : "-";
|
|
65
|
-
return `${op}${m.value} to ${m.roll} rolls for ${target}`;
|
|
66
|
-
}
|
|
67
|
-
case "re-roll":
|
|
68
|
-
return `re-roll ${m.roll}${m.value ? ` (${m.value}s)` : ""} for ${target}`;
|
|
69
|
-
case "mortal-wounds":
|
|
70
|
-
return `deal ${m.amount ?? m.amount_table ? "variable" : "?"} mortal wounds to ${target}`;
|
|
71
|
-
case "feel-no-pain":
|
|
72
|
-
return `${target} gains Feel No Pain ${m.threshold}+`;
|
|
73
|
-
case "keyword-grant": {
|
|
74
|
-
// Grants come as singular `keyword` or the (dominant) `keywords` array.
|
|
75
|
-
const kw = Array.isArray(m.keywords) ? m.keywords.join(", ") : (m.keyword ?? "keywords");
|
|
76
|
-
return `${target}'s ${m.weapon_type ?? "all"} weapons gain ${kw}`;
|
|
77
|
-
}
|
|
78
|
-
case "ability-grant":
|
|
79
|
-
return `${target} gains ${formatGrantType((m.grant_type ?? m.ability_id))}`;
|
|
80
|
-
case "movement-modifier":
|
|
81
|
-
return `${target} gains ${m.move_type}${m.value ? ` ${m.value}"` : ""}`;
|
|
82
|
-
case "damage-reduction":
|
|
83
|
-
return `reduce incoming damage to ${target} by ${m.amount}`;
|
|
84
|
-
case "resurrection":
|
|
85
|
-
return `return ${m.count ?? 1} model(s) to ${target} with ${m.wounds_remaining ?? "full"} wounds`;
|
|
86
|
-
case "model-destruction":
|
|
87
|
-
return `destroy ${m.count} non-leader model(s) from ${target}`;
|
|
88
|
-
case "cp-gain":
|
|
89
|
-
return `gain ${m.amount} CP`;
|
|
90
|
-
case "cp-refund":
|
|
91
|
-
return `refund ${m.amount} CP`;
|
|
92
|
-
case "resource-gain":
|
|
93
|
-
return `gain ${m.amount} to ${m.pool_id}`;
|
|
94
|
-
case "resource-spend":
|
|
95
|
-
return `spend ${m.amount} from ${m.pool_id}`;
|
|
96
|
-
case "invulnerable-save":
|
|
97
|
-
return `${target} gains ${m.value}+ invulnerable save`;
|
|
98
|
-
case "leadership-modifier":
|
|
99
|
-
return `force battle-shock test on ${target}`;
|
|
100
|
-
case "fight-on-death":
|
|
101
|
-
return `${target} fights on death`;
|
|
102
|
-
case "shoot-on-death":
|
|
103
|
-
return `${target} shoots on death`;
|
|
104
|
-
case "fight-first":
|
|
105
|
-
return `${target} fights first`;
|
|
106
|
-
case "fight-last":
|
|
107
|
-
return `${target} fights last`;
|
|
108
|
-
case "deep-strike":
|
|
109
|
-
return `${target} can deep strike`;
|
|
110
|
-
case "fallback-and-act":
|
|
111
|
-
return `${target} can fall back and act`;
|
|
112
|
-
case "attack-restriction":
|
|
113
|
-
return `${target}: ${m.restriction_type ?? "restriction"} (max ${m.max_models ?? "?"} models)`;
|
|
114
|
-
case "objective-control-modifier":
|
|
115
|
-
return `modify OC of ${target} by ${m.value}`;
|
|
116
|
-
// Container types — recurse
|
|
117
|
-
case "conditional":
|
|
118
|
-
return `if ${describeCondition(e.condition)}: ${translateEffectInline(e.effect)}`;
|
|
119
|
-
case "sequence":
|
|
120
|
-
return e.steps.map(translateEffectInline).join("; ");
|
|
121
|
-
case "dice-gated": {
|
|
122
|
-
const comp = formatComparison(e.comparison ?? "gte", e.threshold);
|
|
123
|
-
return `roll ${e.dice} (${comp}): ${e.on_success ? translateEffectInline(e.on_success) : "nothing"}`;
|
|
124
|
-
}
|
|
125
|
-
default:
|
|
126
|
-
return `[${e.type}]`;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
function formatTarget(t) {
|
|
130
|
-
if (!t)
|
|
131
|
-
return "target";
|
|
132
|
-
return t.replace(/-/g, " ");
|
|
133
|
-
}
|
|
134
|
-
function formatGrantType(g) {
|
|
135
|
-
// ability-grant carries either `grant_type` or `ability_id`; an unauthored
|
|
136
|
-
// stub may carry neither. Don't crash the whole translation on a missing key.
|
|
137
|
-
return g ? g.replace(/-/g, " ") : "an ability";
|
|
138
|
-
}
|
|
139
|
-
function formatComparison(comp, threshold) {
|
|
140
|
-
const thStr = typeof threshold === "string" ? threshold : `${threshold}`;
|
|
141
|
-
switch (comp) {
|
|
142
|
-
case "gte": return `${thStr}+`;
|
|
143
|
-
case "lte": return `${thStr} or less`;
|
|
144
|
-
case "gt": return `greater than ${thStr}`;
|
|
145
|
-
case "lt": return `less than ${thStr}`;
|
|
146
|
-
case "eq": return `exactly ${thStr}`;
|
|
147
|
-
default: return `${thStr}+`;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
// ─── Scope translator ────────────────────────────────────────────────
|
|
151
|
-
function translateScope(s) {
|
|
152
|
-
if (!s)
|
|
153
|
-
return "";
|
|
154
|
-
const range = s.range.replace(/-/g, " ");
|
|
155
|
-
const duration = s.duration.replace(/-/g, " ");
|
|
156
|
-
return `Scope: ${range}${s.range_inches ? ` (${s.range_inches}")` : ""}. Duration: ${duration}.`;
|
|
157
|
-
}
|
|
158
|
-
// ─── Main command ────────────────────────────────────────────────────
|
|
10
|
+
import { describeEffect, describeScope } from "../translate/effect.js";
|
|
159
11
|
export async function translateCommand(path) {
|
|
160
12
|
const filePath = resolve(process.cwd(), path ?? "../data/enrichment/world-eaters/abilities.json");
|
|
161
13
|
const abilities = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
@@ -172,8 +24,8 @@ export async function translateCommand(path) {
|
|
|
172
24
|
console.log(`\n═══ ${a.name} [${a.ability_id}] ═══`);
|
|
173
25
|
if (meta.length)
|
|
174
26
|
console.log(` ${meta.join(" | ")}`);
|
|
175
|
-
console.log(
|
|
176
|
-
const scope =
|
|
27
|
+
console.log(describeEffect(a.effect));
|
|
28
|
+
const scope = describeScope(a.scope);
|
|
177
29
|
if (scope)
|
|
178
30
|
console.log(scope);
|
|
179
31
|
}
|
|
@@ -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\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"]}
|
|
1
|
+
{"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/commands/translate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAkC,MAAM,wBAAwB,CAAC;AAcvG,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAa;IAEb,MAAM,QAAQ,GAAG,OAAO,CACtB,OAAO,CAAC,GAAG,EAAE,EACb,IAAI,IAAI,gDAAgD,CACzD,CAAC;IACF,MAAM,SAAS,GAAc,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAEzE,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,YAAY;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QAC9C,IAAI,CAAC,CAAC,QAAQ;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,CAAC,CAAC,aAAa;YAAE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM;YAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAErE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,UAAU,OAAO,CAAC,CAAC;QACrD,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,QAAQ,SAAS,CAAC,MAAM,0BAA0B,CAAC,CAAC;AAClE,CAAC","sourcesContent":["/**\n * Translates ability DSL entries into plain English descriptions.\n *\n * Thin CLI shell over the shared `translate/effect.ts` describer (the\n * conformance-pinned `ability.print()`); this file only handles file loading\n * and per-ability presentation.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { describeEffect, describeScope, type Effect, type AbilityScope } from \"../translate/effect.js\";\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?: AbilityScope;\n}\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(describeEffect(a.effect));\n const scope = describeScope(a.scope);\n if (scope) console.log(scope);\n }\n\n console.log(`\\n── ${abilities.length} abilities translated ──`);\n}\n"]}
|