@alpaca-software/40kdc-data 0.5.11 → 0.5.14
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/README.md +10 -0
- package/dist/author-input-pack.d.ts +27 -0
- package/dist/author-input-pack.d.ts.map +1 -0
- package/dist/author-input-pack.js +198 -0
- package/dist/author-input-pack.js.map +1 -0
- package/dist/codegen-data.js +1 -0
- package/dist/codegen-data.js.map +1 -1
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +3 -1
- package/dist/commands/import.js.map +1 -1
- package/dist/convert-faction.d.ts.map +1 -1
- package/dist/convert-faction.js +22 -2
- package/dist/convert-faction.js.map +1 -1
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/dataset.d.ts +2 -1
- package/dist/data/dataset.d.ts.map +1 -1
- package/dist/data/dataset.js +2 -0
- package/dist/data/dataset.js.map +1 -1
- package/dist/data/entities.d.ts +22 -0
- package/dist/data/entities.d.ts.map +1 -1
- package/dist/data/entities.js +28 -0
- package/dist/data/entities.js.map +1 -1
- package/dist/data/index.d.ts +3 -1
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +3 -1
- package/dist/data/index.js.map +1 -1
- package/dist/data/roster-resolve.d.ts +16 -1
- package/dist/data/roster-resolve.d.ts.map +1 -1
- package/dist/data/roster-resolve.js +19 -0
- package/dist/data/roster-resolve.js.map +1 -1
- package/dist/data/types.d.ts +3 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/data/types.js +1 -0
- package/dist/data/types.js.map +1 -1
- package/dist/export/newrecruit-json.d.ts.map +1 -1
- package/dist/export/newrecruit-json.js +4 -3
- package/dist/export/newrecruit-json.js.map +1 -1
- package/dist/export/newrecruit-simple.js +2 -2
- package/dist/export/newrecruit-simple.js.map +1 -1
- package/dist/export/newrecruit-wtc.d.ts.map +1 -1
- package/dist/export/newrecruit-wtc.js +3 -1
- package/dist/export/newrecruit-wtc.js.map +1 -1
- package/dist/export/rosterizer.d.ts.map +1 -1
- package/dist/export/rosterizer.js +6 -7
- package/dist/export/rosterizer.js.map +1 -1
- package/dist/extract-faction-pack.d.ts +61 -0
- package/dist/extract-faction-pack.d.ts.map +1 -0
- package/dist/extract-faction-pack.js +318 -0
- package/dist/extract-faction-pack.js.map +1 -0
- package/dist/gen-conformance.js +37 -4
- package/dist/gen-conformance.js.map +1 -1
- package/dist/generated.d.ts +46 -2
- package/dist/generated.d.ts.map +1 -1
- package/dist/generated.js.map +1 -1
- package/dist/import/gw-headerless.d.ts +70 -0
- package/dist/import/gw-headerless.d.ts.map +1 -0
- package/dist/import/gw-headerless.js +343 -0
- package/dist/import/gw-headerless.js.map +1 -0
- package/dist/import/gw.js +1 -1
- package/dist/import/gw.js.map +1 -1
- package/dist/import/import-roster.d.ts.map +1 -1
- package/dist/import/import-roster.js +8 -0
- package/dist/import/import-roster.js.map +1 -1
- package/dist/import/listforge-text.js +1 -1
- package/dist/import/listforge-text.js.map +1 -1
- package/dist/import/listforge.d.ts.map +1 -1
- package/dist/import/listforge.js +18 -3
- package/dist/import/listforge.js.map +1 -1
- package/dist/import/newrecruit-json.d.ts.map +1 -1
- package/dist/import/newrecruit-json.js +21 -3
- package/dist/import/newrecruit-json.js.map +1 -1
- package/dist/import/newrecruit-simple.js +1 -1
- package/dist/import/newrecruit-simple.js.map +1 -1
- package/dist/import/newrecruit-wtc.js +1 -1
- package/dist/import/newrecruit-wtc.js.map +1 -1
- package/dist/import/resolve.d.ts.map +1 -1
- package/dist/import/resolve.js +46 -18
- package/dist/import/resolve.js.map +1 -1
- package/dist/import/roster-json.d.ts +5 -3
- package/dist/import/roster-json.d.ts.map +1 -1
- package/dist/import/roster-json.js +8 -4
- package/dist/import/roster-json.js.map +1 -1
- package/dist/import/rosterizer.js +3 -3
- package/dist/import/rosterizer.js.map +1 -1
- package/dist/import/types.d.ts +15 -4
- package/dist/import/types.d.ts.map +1 -1
- package/dist/import/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/merge-faction-pack.d.ts +2 -0
- package/dist/merge-faction-pack.d.ts.map +1 -0
- package/dist/merge-faction-pack.js +157 -0
- package/dist/merge-faction-pack.js.map +1 -0
- package/dist/pack-blocks.d.ts +32 -0
- package/dist/pack-blocks.d.ts.map +1 -0
- package/dist/pack-blocks.js +99 -0
- package/dist/pack-blocks.js.map +1 -0
- package/dist/rube-goldberg.js +1 -1
- package/dist/rube-goldberg.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +33 -1
- package/dist/runner.js.map +1 -1
- package/dist/scope.d.ts +31 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +34 -0
- package/dist/scope.js.map +1 -0
- package/dist/seed-detachment-abilities.d.ts +2 -0
- package/dist/seed-detachment-abilities.d.ts.map +1 -0
- package/dist/seed-detachment-abilities.js +137 -0
- package/dist/seed-detachment-abilities.js.map +1 -0
- package/dist/translate/effect.d.ts +17 -3
- package/dist/translate/effect.d.ts.map +1 -1
- package/dist/translate/effect.js +23 -4
- package/dist/translate/effect.js.map +1 -1
- package/dist/translate/index.d.ts +1 -1
- package/dist/translate/index.d.ts.map +1 -1
- package/dist/translate/index.js +1 -1
- package/dist/translate/index.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +1 -0
- package/dist/validate.js.map +1 -1
- package/package.json +3 -1
- package/schemas/core/detachment.schema.json +6 -0
- package/schemas/core/hull-shape.schema.json +30 -0
- package/schemas/core/roster.schema.json +24 -8
- package/schemas/core/stratagem.schema.json +2 -2
- package/schemas/core/unit-composition.schema.json +7 -0
- package/schemas/enrichment/ability-dsl/ability.schema.json +14 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed empty-modifier DSL stubs for new-detachment abilities — the detachment
|
|
3
|
+
* rule, plus every stratagem and enhancement — so they flow through the normal
|
|
4
|
+
* `author-input → author-batch propose/apply` authoring workflow.
|
|
5
|
+
*
|
|
6
|
+
* The authoring pipeline only fleshes stubs that already exist (it iterates
|
|
7
|
+
* `hasEmptyModifier` entries). The faction-pack merge created the *core*
|
|
8
|
+
* stratagem/enhancement entities with `ability_id: null` and left detachment
|
|
9
|
+
* rules unmodelled, so nothing was authorable. This tool closes that gap:
|
|
10
|
+
*
|
|
11
|
+
* - one stub per core stratagem/enhancement (id = the entity id), and the core
|
|
12
|
+
* entity's `ability_id` is wired to it.
|
|
13
|
+
* - one `ability_type:"detachment"` stub per detachment rule (id = a slug of the
|
|
14
|
+
* `detachment_rule_name` from staging), and `detachment.detachment_rule_id`
|
|
15
|
+
* is wired to it.
|
|
16
|
+
*
|
|
17
|
+
* Scope is the faction-pack staging (`faction-pack-input/<faction>.json`, already
|
|
18
|
+
* trimmed to the new detachments). Idempotent: an ability id that already exists
|
|
19
|
+
* is left untouched (so hand-authored rules survive), and an already-wired core
|
|
20
|
+
* entity is skipped. Real mechanics are authored downstream — the stub effect is
|
|
21
|
+
* the empty-modifier placeholder the pipeline recognises. No GW prose is written.
|
|
22
|
+
*
|
|
23
|
+
* Usage: tsx tools/src/seed-detachment-abilities.ts <faction-id>… | --all-xenos
|
|
24
|
+
*/
|
|
25
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
26
|
+
import { resolve } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
29
|
+
const DATA_ROOT = resolve(__dirname, "../../data");
|
|
30
|
+
const XENOS = [
|
|
31
|
+
"orks", "aeldari", "drukhari", "tyranids",
|
|
32
|
+
"genestealer-cults", "necrons", "leagues-of-votann", "tau-empire",
|
|
33
|
+
];
|
|
34
|
+
const GAME_VERSION = { edition: "11th", dataslate: "pre-launch-provisional" };
|
|
35
|
+
const readJson = (p) => JSON.parse(readFileSync(p, "utf-8"));
|
|
36
|
+
const writeJson = (p, v) => writeFileSync(p, JSON.stringify(v, null, 2) + "\n");
|
|
37
|
+
/** kebab-case id slug matching `^[a-z0-9][a-z0-9-]*[a-z0-9]$`. */
|
|
38
|
+
const slug = (s) => s.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "")
|
|
39
|
+
.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
40
|
+
function emptyStub(abilityId, name, abilityType, factionId, detachmentId) {
|
|
41
|
+
return {
|
|
42
|
+
ability_id: abilityId,
|
|
43
|
+
name,
|
|
44
|
+
authored_by: "40kdc-community",
|
|
45
|
+
game_version: { ...GAME_VERSION },
|
|
46
|
+
version: "2025-q3",
|
|
47
|
+
supersedes: null,
|
|
48
|
+
unit_ids: [],
|
|
49
|
+
faction_id: factionId,
|
|
50
|
+
detachment_id: detachmentId,
|
|
51
|
+
ability_type: abilityType,
|
|
52
|
+
behavior: "passive",
|
|
53
|
+
effect: { type: "stat-modifier", target: "unit", modifier: {} },
|
|
54
|
+
scope: { range: "unit", duration: "permanent" },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function seedFaction(faction) {
|
|
58
|
+
const stagingPath = resolve(DATA_ROOT, "_audit", "faction-pack-input", `${faction}.json`);
|
|
59
|
+
if (!existsSync(stagingPath)) {
|
|
60
|
+
console.log(`${faction}: no staging file, skipped`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const staging = readJson(stagingPath);
|
|
64
|
+
const dir = resolve(DATA_ROOT, "core", faction);
|
|
65
|
+
const detachments = readJson(resolve(dir, "detachments.json"));
|
|
66
|
+
const stratagems = readJson(resolve(dir, "stratagems.json"));
|
|
67
|
+
const enhancements = readJson(resolve(dir, "enhancements.json"));
|
|
68
|
+
const abilitiesPath = resolve(DATA_ROOT, "enrichment", faction, "abilities.json");
|
|
69
|
+
const abilities = readJson(abilitiesPath);
|
|
70
|
+
const detById = new Map(detachments.map((d) => [d.id, d]));
|
|
71
|
+
const abilityIds = new Set(abilities.map((a) => a.ability_id));
|
|
72
|
+
const stratById = new Map(stratagems.map((s) => [s.id, s]));
|
|
73
|
+
const enhById = new Map(enhancements.map((e) => [e.id, e]));
|
|
74
|
+
let added = 0;
|
|
75
|
+
let wired = 0;
|
|
76
|
+
const ensureStub = (id, name, type, detId) => {
|
|
77
|
+
if (abilityIds.has(id))
|
|
78
|
+
return; // already authored or seeded
|
|
79
|
+
abilities.push(emptyStub(id, name, type, faction, detId));
|
|
80
|
+
abilityIds.add(id);
|
|
81
|
+
added++;
|
|
82
|
+
};
|
|
83
|
+
for (const sd of staging.detachments) {
|
|
84
|
+
const det = detById.get(sd.id);
|
|
85
|
+
if (!det)
|
|
86
|
+
continue;
|
|
87
|
+
// Detachment rule → ability_type "detachment".
|
|
88
|
+
if (sd.detachment_rule_name) {
|
|
89
|
+
let ruleId = slug(sd.detachment_rule_name);
|
|
90
|
+
if (abilityIds.has(ruleId) && det.detachment_rule_id !== ruleId)
|
|
91
|
+
ruleId = `${sd.id}-${ruleId}`;
|
|
92
|
+
ensureStub(ruleId, sd.detachment_rule_name, "detachment", sd.id);
|
|
93
|
+
if (!det.detachment_rule_id) {
|
|
94
|
+
det.detachment_rule_id = ruleId;
|
|
95
|
+
wired++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Stratagems + enhancements → stub per core entity, wire its ability_id.
|
|
99
|
+
for (const s of sd.stratagems ?? []) {
|
|
100
|
+
const core = stratById.get(s.id);
|
|
101
|
+
if (!core)
|
|
102
|
+
continue;
|
|
103
|
+
ensureStub(s.id, core.name, "stratagem", sd.id);
|
|
104
|
+
if (core.ability_id == null) {
|
|
105
|
+
core.ability_id = s.id;
|
|
106
|
+
wired++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const e of sd.enhancements ?? []) {
|
|
110
|
+
const core = enhById.get(e.id);
|
|
111
|
+
if (!core)
|
|
112
|
+
continue;
|
|
113
|
+
ensureStub(e.id, core.name, "enhancement", sd.id);
|
|
114
|
+
if (core.ability_id == null) {
|
|
115
|
+
core.ability_id = e.id;
|
|
116
|
+
wired++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
writeJson(abilitiesPath, abilities);
|
|
121
|
+
writeJson(resolve(dir, "detachments.json"), detachments);
|
|
122
|
+
writeJson(resolve(dir, "stratagems.json"), stratagems);
|
|
123
|
+
writeJson(resolve(dir, "enhancements.json"), enhancements);
|
|
124
|
+
console.log(`${faction}: +${added} stub abilities, wired ${wired} link(s)`);
|
|
125
|
+
}
|
|
126
|
+
function main() {
|
|
127
|
+
const args = process.argv.slice(2);
|
|
128
|
+
const factions = args.includes("--all-xenos") ? XENOS : args;
|
|
129
|
+
if (factions.length === 0) {
|
|
130
|
+
console.error("usage: seed-detachment-abilities.ts <faction-id>… | --all-xenos");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
for (const f of factions)
|
|
134
|
+
seedFaction(f);
|
|
135
|
+
}
|
|
136
|
+
main();
|
|
137
|
+
//# sourceMappingURL=seed-detachment-abilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed-detachment-abilities.js","sourceRoot":"","sources":["../src/seed-detachment-abilities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Seed empty-modifier DSL stubs for new-detachment abilities — the detachment\n * rule, plus every stratagem and enhancement — so they flow through the normal\n * `author-input → author-batch propose/apply` authoring workflow.\n *\n * The authoring pipeline only fleshes stubs that already exist (it iterates\n * `hasEmptyModifier` entries). The faction-pack merge created the *core*\n * stratagem/enhancement entities with `ability_id: null` and left detachment\n * rules unmodelled, so nothing was authorable. This tool closes that gap:\n *\n * - one stub per core stratagem/enhancement (id = the entity id), and the core\n * entity's `ability_id` is wired to it.\n * - one `ability_type:\"detachment\"` stub per detachment rule (id = a slug of the\n * `detachment_rule_name` from staging), and `detachment.detachment_rule_id`\n * is wired to it.\n *\n * Scope is the faction-pack staging (`faction-pack-input/<faction>.json`, already\n * trimmed to the new detachments). Idempotent: an ability id that already exists\n * is left untouched (so hand-authored rules survive), and an already-wired core\n * entity is skipped. Real mechanics are authored downstream — the stub effect is\n * the empty-modifier placeholder the pipeline recognises. No GW prose is written.\n *\n * Usage: tsx tools/src/seed-detachment-abilities.ts <faction-id>… | --all-xenos\n */\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\nconst DATA_ROOT = resolve(__dirname, \"../../data\");\n\nconst XENOS = [\n \"orks\", \"aeldari\", \"drukhari\", \"tyranids\",\n \"genestealer-cults\", \"necrons\", \"leagues-of-votann\", \"tau-empire\",\n];\n\nconst GAME_VERSION = { edition: \"11th\", dataslate: \"pre-launch-provisional\" } as const;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Json = any;\nconst readJson = (p: string): Json => JSON.parse(readFileSync(p, \"utf-8\"));\nconst writeJson = (p: string, v: unknown): void => writeFileSync(p, JSON.stringify(v, null, 2) + \"\\n\");\n\n/** kebab-case id slug matching `^[a-z0-9][a-z0-9-]*[a-z0-9]$`. */\nconst slug = (s: string): string =>\n s.toLowerCase().normalize(\"NFKD\").replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n\nfunction emptyStub(\n abilityId: string,\n name: string,\n abilityType: string,\n factionId: string,\n detachmentId: string,\n): Json {\n return {\n ability_id: abilityId,\n name,\n authored_by: \"40kdc-community\",\n game_version: { ...GAME_VERSION },\n version: \"2025-q3\",\n supersedes: null,\n unit_ids: [],\n faction_id: factionId,\n detachment_id: detachmentId,\n ability_type: abilityType,\n behavior: \"passive\",\n effect: { type: \"stat-modifier\", target: \"unit\", modifier: {} },\n scope: { range: \"unit\", duration: \"permanent\" },\n };\n}\n\nfunction seedFaction(faction: string): void {\n const stagingPath = resolve(DATA_ROOT, \"_audit\", \"faction-pack-input\", `${faction}.json`);\n if (!existsSync(stagingPath)) {\n console.log(`${faction}: no staging file, skipped`);\n return;\n }\n const staging = readJson(stagingPath);\n const dir = resolve(DATA_ROOT, \"core\", faction);\n const detachments = readJson(resolve(dir, \"detachments.json\"));\n const stratagems = readJson(resolve(dir, \"stratagems.json\"));\n const enhancements = readJson(resolve(dir, \"enhancements.json\"));\n const abilitiesPath = resolve(DATA_ROOT, \"enrichment\", faction, \"abilities.json\");\n const abilities = readJson(abilitiesPath);\n\n const detById = new Map<string, Json>(detachments.map((d: Json) => [d.id, d]));\n const abilityIds = new Set(abilities.map((a: Json) => a.ability_id as string));\n const stratById = new Map<string, Json>(stratagems.map((s: Json) => [s.id, s]));\n const enhById = new Map<string, Json>(enhancements.map((e: Json) => [e.id, e]));\n\n let added = 0;\n let wired = 0;\n\n const ensureStub = (id: string, name: string, type: string, detId: string): void => {\n if (abilityIds.has(id)) return; // already authored or seeded\n abilities.push(emptyStub(id, name, type, faction, detId));\n abilityIds.add(id);\n added++;\n };\n\n for (const sd of staging.detachments) {\n const det = detById.get(sd.id);\n if (!det) continue;\n\n // Detachment rule → ability_type \"detachment\".\n if (sd.detachment_rule_name) {\n let ruleId = slug(sd.detachment_rule_name);\n if (abilityIds.has(ruleId) && det.detachment_rule_id !== ruleId) ruleId = `${sd.id}-${ruleId}`;\n ensureStub(ruleId, sd.detachment_rule_name, \"detachment\", sd.id);\n if (!det.detachment_rule_id) {\n det.detachment_rule_id = ruleId;\n wired++;\n }\n }\n\n // Stratagems + enhancements → stub per core entity, wire its ability_id.\n for (const s of sd.stratagems ?? []) {\n const core = stratById.get(s.id);\n if (!core) continue;\n ensureStub(s.id, core.name, \"stratagem\", sd.id);\n if (core.ability_id == null) {\n core.ability_id = s.id;\n wired++;\n }\n }\n for (const e of sd.enhancements ?? []) {\n const core = enhById.get(e.id);\n if (!core) continue;\n ensureStub(e.id, core.name, \"enhancement\", sd.id);\n if (core.ability_id == null) {\n core.ability_id = e.id;\n wired++;\n }\n }\n }\n\n writeJson(abilitiesPath, abilities);\n writeJson(resolve(dir, \"detachments.json\"), detachments);\n writeJson(resolve(dir, \"stratagems.json\"), stratagems);\n writeJson(resolve(dir, \"enhancements.json\"), enhancements);\n console.log(`${faction}: +${added} stub abilities, wired ${wired} link(s)`);\n}\n\nfunction main(): void {\n const args = process.argv.slice(2);\n const factions = args.includes(\"--all-xenos\") ? XENOS : args;\n if (factions.length === 0) {\n console.error(\"usage: seed-detachment-abilities.ts <faction-id>… | --all-xenos\");\n process.exit(1);\n }\n for (const f of factions) seedFaction(f);\n}\n\nmain();\n"]}
|
|
@@ -48,11 +48,17 @@ export interface AbilityScope {
|
|
|
48
48
|
duration?: string;
|
|
49
49
|
range_inches?: number;
|
|
50
50
|
}
|
|
51
|
+
/** Curated keyword filter naming which units an ability benefits. */
|
|
52
|
+
export interface AbilityAppliesTo {
|
|
53
|
+
required_keywords?: string[];
|
|
54
|
+
excluded_keywords?: string[];
|
|
55
|
+
}
|
|
51
56
|
/** Minimal ability view for `describeAbility`. */
|
|
52
57
|
export interface AbilityLike {
|
|
53
58
|
name?: string;
|
|
54
59
|
effect?: Effect;
|
|
55
60
|
scope?: AbilityScope;
|
|
61
|
+
applies_to?: AbilityAppliesTo | null;
|
|
56
62
|
}
|
|
57
63
|
/** Single-clause translation for leaf effects (and inline container forms). */
|
|
58
64
|
export declare function describeEffectInline(e: Effect): string;
|
|
@@ -64,9 +70,17 @@ export declare function describeEffect(e: Effect, depth?: number): string;
|
|
|
64
70
|
/** `Scope: aura (6"). Duration: phase.` — empty string when absent. */
|
|
65
71
|
export declare function describeScope(s?: AbilityScope): string;
|
|
66
72
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* carries no
|
|
73
|
+
* `Applies to: units with Possessed.` — the roster-highlighting audience named
|
|
74
|
+
* by a curated `applies_to` filter. Empty string when the filter is absent or
|
|
75
|
+
* carries no keywords (nothing to say). `required_keywords` reads as an AND set;
|
|
76
|
+
* `excluded_keywords` render as a trailing `(excluding …)`.
|
|
77
|
+
*/
|
|
78
|
+
export declare function describeAppliesTo(a?: AbilityAppliesTo | null): string;
|
|
79
|
+
/**
|
|
80
|
+
* Full generated text for an ability: the effect tree, a trailing scope line,
|
|
81
|
+
* and a trailing `Applies to:` line when the ability carries a curated
|
|
82
|
+
* `applies_to` filter. This is the `ability.print()` consumers render when the
|
|
83
|
+
* dataset carries no rules prose.
|
|
70
84
|
*/
|
|
71
85
|
export declare function describeAbility(a: AbilityLike): string;
|
|
72
86
|
//# sourceMappingURL=effect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../../src/translate/effect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,MAAM,GAAG;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACvC,CAAC,EAAE,CAAC;IACL,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,+DAA+D;AAC/D,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"effect.d.ts","sourceRoot":"","sources":["../../src/translate/effect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,MAAM,GAAG;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACvC,CAAC,EAAE,CAAC;IACL,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,+DAA+D;AAC/D,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,UAAU,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACtC;AAoCD,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAmItD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,MAAM,CAmCnE;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAMtD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,CAAC,EAAE,gBAAgB,GAAG,IAAI,GAAG,MAAM,CAQrE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAKtD"}
|
package/dist/translate/effect.js
CHANGED
|
@@ -229,13 +229,32 @@ export function describeScope(s) {
|
|
|
229
229
|
return `Scope: ${range}${inches}. Duration: ${duration}.`;
|
|
230
230
|
}
|
|
231
231
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
* carries no
|
|
232
|
+
* `Applies to: units with Possessed.` — the roster-highlighting audience named
|
|
233
|
+
* by a curated `applies_to` filter. Empty string when the filter is absent or
|
|
234
|
+
* carries no keywords (nothing to say). `required_keywords` reads as an AND set;
|
|
235
|
+
* `excluded_keywords` render as a trailing `(excluding …)`.
|
|
236
|
+
*/
|
|
237
|
+
export function describeAppliesTo(a) {
|
|
238
|
+
if (!a)
|
|
239
|
+
return "";
|
|
240
|
+
const required = a.required_keywords ?? [];
|
|
241
|
+
const excluded = a.excluded_keywords ?? [];
|
|
242
|
+
if (required.length === 0 && excluded.length === 0)
|
|
243
|
+
return "";
|
|
244
|
+
const base = required.length ? `units with ${required.join(", ")}` : "all units";
|
|
245
|
+
const exc = excluded.length ? ` (excluding ${excluded.join(", ")})` : "";
|
|
246
|
+
return `Applies to: ${base}${exc}.`;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Full generated text for an ability: the effect tree, a trailing scope line,
|
|
250
|
+
* and a trailing `Applies to:` line when the ability carries a curated
|
|
251
|
+
* `applies_to` filter. This is the `ability.print()` consumers render when the
|
|
252
|
+
* dataset carries no rules prose.
|
|
235
253
|
*/
|
|
236
254
|
export function describeAbility(a) {
|
|
237
255
|
const effect = a.effect ? describeEffect(a.effect) : "";
|
|
238
256
|
const scope = describeScope(a.scope);
|
|
239
|
-
|
|
257
|
+
const applies = describeAppliesTo(a.applies_to);
|
|
258
|
+
return [effect, scope, applies].filter(Boolean).join("\n");
|
|
240
259
|
}
|
|
241
260
|
//# sourceMappingURL=effect.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../src/translate/effect.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Humanize an Ability-DSL `effect` tree into plain English — the\n * `ability.print()` of the dataset. Output is an *approximation* generated\n * purely from the structured data (no external rules text), ASCII-only, with\n * a fixed clause order: it is pinned byte-for-byte across the TS and Rust\n * ports by the `conformance/effect-translation` corpus, so any phrasing\n * change here is a semantic corpus change (bump `conformance/SPEC_VERSION`).\n *\n * Container nodes (`sequence`, `conditional`, `choice`, `dice-gated`,\n * `dice-pool-allocation`) render block-style with two-space indentation and\n * an ASCII `-> ` arrow; leaves render as single clauses. Unknown leaf types\n * and unrecognized modifier shapes degrade to a deterministic bracketed form\n * (`[the-type]`) rather than failing — coverage improves as authoring does.\n */\n\nimport { describeCondition, dekebab, type Condition } from \"./condition.js\";\n\n/**\n * Minimal structural view of an effect node. Matches the ability-dsl effect\n * schema: a single effect carries `type` + `target` + `modifier`; containers\n * carry their own shape (`steps`, `options`, `condition`/`effect`, dice\n * fields).\n */\nexport interface Effect {\n type?: string;\n target?: string;\n modifier?: Record<string, unknown>;\n condition?: Condition;\n effect?: Effect;\n steps?: Effect[];\n options?: (Effect & {\n name?: string;\n requirement?: Record<string, unknown>;\n })[];\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\n/** Ability scope, as carried on enrichment ability entries. */\nexport interface AbilityScope {\n range?: string;\n duration?: string;\n range_inches?: number;\n}\n\n/** Minimal ability view for `describeAbility`. */\nexport interface AbilityLike {\n name?: string;\n effect?: Effect;\n scope?: AbilityScope;\n}\n\n/** JS-template stringification (numbers print without trailing `.0`). */\nfunction jstr(v: unknown): string {\n if (v == null) return \"?\";\n if (Array.isArray(v)) return v.map(jstr).join(\", \");\n return String(v);\n}\n\nfunction formatTarget(t?: string): string {\n return t ? dekebab(t) : \"target\";\n}\n\nfunction signed(operation: unknown, value: unknown): string {\n const op = operation === \"add\" || operation === \"improve\" ? \"+\" : \"-\";\n return `${op}${jstr(value)}`;\n}\n\nfunction formatComparison(comp: string, threshold: unknown): string {\n const th = jstr(threshold);\n switch (comp) {\n case \"gte\":\n return `${th}+`;\n case \"lte\":\n return `${th} or less`;\n case \"gt\":\n return `greater than ${th}`;\n case \"lt\":\n return `less than ${th}`;\n case \"eq\":\n return `exactly ${th}`;\n default:\n return `${th}+`;\n }\n}\n\n/** Single-clause translation for leaf effects (and inline container forms). */\nexport function describeEffectInline(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 scope = m.attack_type ? ` (${jstr(m.attack_type)})` : \"\";\n if (m.stat == null) return `modify stats for ${target}`;\n if (m.operation === \"set\") return `set ${jstr(m.stat)} to ${jstr(m.value)}${scope} for ${target}`;\n return `${signed(m.operation, m.value)} ${jstr(m.stat)}${scope} for ${target}`;\n }\n case \"roll-modifier\": {\n const ctx = m.context ? ` (${jstr(m.context)})` : \"\";\n if (m.value == null) return `${dekebab(jstr(m.operation))} ${jstr(m.roll)} rolls${ctx} for ${target}`;\n return `${signed(m.operation, m.value)} to ${jstr(m.roll)} rolls${ctx} for ${target}`;\n }\n case \"re-roll\": {\n const subset = m.subset ? ` (${dekebab(jstr(m.subset))})` : \"\";\n const atk = m.attack_type ? ` (${jstr(m.attack_type)})` : \"\";\n return `re-roll ${jstr(m.roll)} rolls${subset}${atk} for ${target}`;\n }\n case \"mortal-wounds\": {\n const amount = m.count ?? m.amount ?? (m.amount_table ? \"variable\" : \"?\");\n const range = m.range ?? m.range_inches;\n const within = range != null ? ` (within ${jstr(range)}\")` : \"\";\n return `deal ${jstr(amount)} mortal wounds to ${target}${within}`;\n }\n case \"feel-no-pain\":\n return `${target} gains Feel No Pain ${jstr(m.threshold)}+`;\n case \"ward\":\n return `${target} gains Ward ${jstr(m.threshold ?? m.value)}+`;\n case \"invulnerable-save\":\n return `${target} gains a ${jstr(m.value)}+ invulnerable save`;\n case \"keyword-grant\": {\n const kw = Array.isArray(m.keywords) ? m.keywords.map(jstr).join(\", \") : jstr(m.keyword ?? \"keywords\");\n if (m.weapon_name != null) return `${target}'s ${jstr(m.weapon_name)} gains ${kw}`;\n if (m.weapon_type != null) return `${target}'s ${jstr(m.weapon_type)} weapons gain ${kw}`;\n return `${target}'s weapons gain ${kw}`;\n }\n case \"ability-grant\": {\n const grant = m.grant_type ?? m.ability_id;\n const cap = m.capacity != null ? ` (${jstr(m.capacity)})` : \"\";\n return `${target} gains ${grant != null ? dekebab(jstr(grant)) : \"an ability\"}${cap}`;\n }\n case \"movement-modifier\": {\n const kind = m.move_type ?? m.type;\n const dist = m.distance ?? m.value;\n const inches = dist != null ? ` ${jstr(dist)}\"` : \"\";\n return `${target} gains ${kind != null ? dekebab(jstr(kind)) : \"a movement effect\"}${inches}`;\n }\n case \"damage-reduction\":\n return `reduce incoming damage to ${target} by ${jstr(m.amount ?? m.value)}`;\n case \"resurrection\":\n return `return ${jstr(m.count ?? 1)} model(s) to ${target} with ${jstr(m.wounds_remaining ?? \"full\")} wounds`;\n case \"model-destruction\":\n return `destroy ${jstr(m.count)} non-leader model(s) from ${target}`;\n case \"cp-gain\":\n return `gain ${jstr(m.amount)} CP`;\n case \"cp-refund\":\n return `refund ${jstr(m.amount)} CP`;\n case \"resource-gain\":\n return `gain ${jstr(m.amount)} to ${jstr(m.pool_id)}`;\n case \"resource-spend\":\n return `spend ${jstr(m.amount)} from ${jstr(m.pool_id)}`;\n case \"leadership-modifier\": {\n if (m.test != null && m.operation == null) return `force a ${dekebab(jstr(m.test))} test on ${target}`;\n if (m.test != null) return `${dekebab(jstr(m.operation))} ${dekebab(jstr(m.test))} tests for ${target}`;\n if (m.operation != null) return `${signed(m.operation, m.value)} Leadership for ${target}`;\n return `modify Leadership for ${target}`;\n }\n case \"fight-first\":\n return `${target} fights first`;\n case \"fight-last\":\n return `${target} fights last`;\n case \"fight-on-death\":\n return `${target} fights on death`;\n case \"shoot-on-death\":\n return `${target} shoots on death`;\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 const what = m.restriction ?? m.restriction_type;\n const range = m.range != null ? ` (within ${jstr(m.range)}\")` : \"\";\n const max = m.max_models != null ? ` (max ${jstr(m.max_models)} models)` : \"\";\n return `${target}: ${what != null ? dekebab(jstr(what)) : \"attack restriction\"}${range}${max}`;\n }\n case \"objective-control-modifier\": {\n if (m.operation != null) return `${signed(m.operation, m.value)} OC for ${target}`;\n return `modify OC of ${target} by ${jstr(m.value)}`;\n }\n case \"bs-modifier\":\n return `${signed(m.operation, m.value)} BS for ${target}`;\n case \"charge-roll-modifier\":\n return `${signed(m.operation, m.value)} to charge rolls for ${target}`;\n case \"engagement-passthrough\":\n return `${target} can move through engagement range`;\n case \"terrain-area-tag\":\n return `tag the terrain area as ${dekebab(jstr(m.tag))}`;\n case \"objective-tag\":\n return `tag the objective as ${dekebab(jstr(m.tag))}`;\n case \"unit-tag\":\n return `tag ${target} as ${dekebab(jstr(m.tag))}`;\n\n // Container types — inline forms.\n case \"conditional\":\n return `if ${describeCondition(e.condition ?? {})}: ${describeEffectInline(e.effect ?? {})}`;\n case \"sequence\":\n return (e.steps ?? []).map(describeEffectInline).join(\"; \");\n case \"choice\": {\n const label = e.choice_label ? ` (${e.choice_label})` : \"\";\n return `choose one${label}: ${(e.options ?? []).map(describeEffectInline).join(\" / \")}`;\n }\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold);\n const success = e.on_success ? describeEffectInline(e.on_success) : \"nothing\";\n const fail = e.on_fail ? `, otherwise ${describeEffectInline(e.on_fail)}` : \"\";\n return `roll ${jstr(e.dice)}: on ${comp}, ${success}${fail}`;\n }\n case \"dice-pool-allocation\": {\n const pool = e.pool ? `${jstr(e.pool.count)}${jstr(e.pool.die)}` : \"?\";\n const opts = (e.options ?? [])\n .map((o) => `${jstr(o.name)} (${jstr(o.requirement?.min_value)}+): ${describeEffectInline(o.effect ?? {})}`)\n .join(\" / \");\n return `roll ${pool}: ${opts}`;\n }\n\n default:\n return `[${e.type ?? \"unknown\"}]`;\n }\n}\n\n/**\n * Block translation of an effect tree. Containers expand over multiple lines\n * with two-space indentation; leaves delegate to `describeEffectInline`.\n */\nexport function describeEffect(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 `${indent}If ${describeCondition(e.condition ?? {})}:\\n` + describeEffect(e.effect ?? {}, depth + 1);\n case \"sequence\":\n return (e.steps ?? []).map((s) => describeEffect(s, depth)).join(\"\\n\");\n case \"choice\": {\n const label = e.choice_label ? ` (${e.choice_label})` : \"\";\n return (\n `${indent}${arrow}Choose one${label}:\\n` +\n (e.options ?? []).map((o, i) => `${indent} ${i + 1}. ${describeEffectInline(o)}`).join(\"\\n\")\n );\n }\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold);\n const success = e.on_success ? describeEffectInline(e.on_success) : \"nothing\";\n const fail = e.on_fail ? `, otherwise ${describeEffectInline(e.on_fail)}` : \"\";\n return `${indent}${arrow}Roll ${jstr(e.dice)}: on ${comp}, ${success}${fail}`;\n }\n case \"dice-pool-allocation\": {\n const pool = e.pool ? `${jstr(e.pool.count)}${jstr(e.pool.die)}` : \"?\";\n const lines = [`${indent}${arrow}Roll ${pool} (max ${jstr(e.max_activations)} activations):`];\n for (const opt of e.options ?? []) {\n lines.push(\n `${indent} - ${jstr(opt.name)}: need ${jstr(opt.requirement?.type)} of ${jstr(opt.requirement?.min_value)}+ -> ${describeEffectInline(opt.effect ?? {})}`\n );\n }\n return lines.join(\"\\n\");\n }\n default:\n return `${indent}${arrow}${describeEffectInline(e)}`;\n }\n}\n\n/** `Scope: aura (6\"). Duration: phase.` — empty string when absent. */\nexport function describeScope(s?: AbilityScope): string {\n if (!s || (!s.range && !s.duration)) return \"\";\n const range = dekebab(s.range ?? \"\");\n const inches = s.range_inches != null ? ` (${jstr(s.range_inches)}\")` : \"\";\n const duration = dekebab(s.duration ?? \"\");\n return `Scope: ${range}${inches}. Duration: ${duration}.`;\n}\n\n/**\n * Full generated text for an ability: the effect tree plus a trailing scope\n * line. This is the `ability.print()` consumers render when the dataset\n * carries no rules prose.\n */\nexport function describeAbility(a: AbilityLike): string {\n const effect = a.effect ? describeEffect(a.effect) : \"\";\n const scope = describeScope(a.scope);\n return scope ? (effect ? `${effect}\\n${scope}` : scope) : effect;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"effect.js","sourceRoot":"","sources":["../../src/translate/effect.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Humanize an Ability-DSL `effect` tree into plain English — the\n * `ability.print()` of the dataset. Output is an *approximation* generated\n * purely from the structured data (no external rules text), ASCII-only, with\n * a fixed clause order: it is pinned byte-for-byte across the TS and Rust\n * ports by the `conformance/effect-translation` corpus, so any phrasing\n * change here is a semantic corpus change (bump `conformance/SPEC_VERSION`).\n *\n * Container nodes (`sequence`, `conditional`, `choice`, `dice-gated`,\n * `dice-pool-allocation`) render block-style with two-space indentation and\n * an ASCII `-> ` arrow; leaves render as single clauses. Unknown leaf types\n * and unrecognized modifier shapes degrade to a deterministic bracketed form\n * (`[the-type]`) rather than failing — coverage improves as authoring does.\n */\n\nimport { describeCondition, dekebab, type Condition } from \"./condition.js\";\n\n/**\n * Minimal structural view of an effect node. Matches the ability-dsl effect\n * schema: a single effect carries `type` + `target` + `modifier`; containers\n * carry their own shape (`steps`, `options`, `condition`/`effect`, dice\n * fields).\n */\nexport interface Effect {\n type?: string;\n target?: string;\n modifier?: Record<string, unknown>;\n condition?: Condition;\n effect?: Effect;\n steps?: Effect[];\n options?: (Effect & {\n name?: string;\n requirement?: Record<string, unknown>;\n })[];\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\n/** Ability scope, as carried on enrichment ability entries. */\nexport interface AbilityScope {\n range?: string;\n duration?: string;\n range_inches?: number;\n}\n\n/** Curated keyword filter naming which units an ability benefits. */\nexport interface AbilityAppliesTo {\n required_keywords?: string[];\n excluded_keywords?: string[];\n}\n\n/** Minimal ability view for `describeAbility`. */\nexport interface AbilityLike {\n name?: string;\n effect?: Effect;\n scope?: AbilityScope;\n applies_to?: AbilityAppliesTo | null;\n}\n\n/** JS-template stringification (numbers print without trailing `.0`). */\nfunction jstr(v: unknown): string {\n if (v == null) return \"?\";\n if (Array.isArray(v)) return v.map(jstr).join(\", \");\n return String(v);\n}\n\nfunction formatTarget(t?: string): string {\n return t ? dekebab(t) : \"target\";\n}\n\nfunction signed(operation: unknown, value: unknown): string {\n const op = operation === \"add\" || operation === \"improve\" ? \"+\" : \"-\";\n return `${op}${jstr(value)}`;\n}\n\nfunction formatComparison(comp: string, threshold: unknown): string {\n const th = jstr(threshold);\n switch (comp) {\n case \"gte\":\n return `${th}+`;\n case \"lte\":\n return `${th} or less`;\n case \"gt\":\n return `greater than ${th}`;\n case \"lt\":\n return `less than ${th}`;\n case \"eq\":\n return `exactly ${th}`;\n default:\n return `${th}+`;\n }\n}\n\n/** Single-clause translation for leaf effects (and inline container forms). */\nexport function describeEffectInline(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 scope = m.attack_type ? ` (${jstr(m.attack_type)})` : \"\";\n if (m.stat == null) return `modify stats for ${target}`;\n if (m.operation === \"set\") return `set ${jstr(m.stat)} to ${jstr(m.value)}${scope} for ${target}`;\n return `${signed(m.operation, m.value)} ${jstr(m.stat)}${scope} for ${target}`;\n }\n case \"roll-modifier\": {\n const ctx = m.context ? ` (${jstr(m.context)})` : \"\";\n if (m.value == null) return `${dekebab(jstr(m.operation))} ${jstr(m.roll)} rolls${ctx} for ${target}`;\n return `${signed(m.operation, m.value)} to ${jstr(m.roll)} rolls${ctx} for ${target}`;\n }\n case \"re-roll\": {\n const subset = m.subset ? ` (${dekebab(jstr(m.subset))})` : \"\";\n const atk = m.attack_type ? ` (${jstr(m.attack_type)})` : \"\";\n return `re-roll ${jstr(m.roll)} rolls${subset}${atk} for ${target}`;\n }\n case \"mortal-wounds\": {\n const amount = m.count ?? m.amount ?? (m.amount_table ? \"variable\" : \"?\");\n const range = m.range ?? m.range_inches;\n const within = range != null ? ` (within ${jstr(range)}\")` : \"\";\n return `deal ${jstr(amount)} mortal wounds to ${target}${within}`;\n }\n case \"feel-no-pain\":\n return `${target} gains Feel No Pain ${jstr(m.threshold)}+`;\n case \"ward\":\n return `${target} gains Ward ${jstr(m.threshold ?? m.value)}+`;\n case \"invulnerable-save\":\n return `${target} gains a ${jstr(m.value)}+ invulnerable save`;\n case \"keyword-grant\": {\n const kw = Array.isArray(m.keywords) ? m.keywords.map(jstr).join(\", \") : jstr(m.keyword ?? \"keywords\");\n if (m.weapon_name != null) return `${target}'s ${jstr(m.weapon_name)} gains ${kw}`;\n if (m.weapon_type != null) return `${target}'s ${jstr(m.weapon_type)} weapons gain ${kw}`;\n return `${target}'s weapons gain ${kw}`;\n }\n case \"ability-grant\": {\n const grant = m.grant_type ?? m.ability_id;\n const cap = m.capacity != null ? ` (${jstr(m.capacity)})` : \"\";\n return `${target} gains ${grant != null ? dekebab(jstr(grant)) : \"an ability\"}${cap}`;\n }\n case \"movement-modifier\": {\n const kind = m.move_type ?? m.type;\n const dist = m.distance ?? m.value;\n const inches = dist != null ? ` ${jstr(dist)}\"` : \"\";\n return `${target} gains ${kind != null ? dekebab(jstr(kind)) : \"a movement effect\"}${inches}`;\n }\n case \"damage-reduction\":\n return `reduce incoming damage to ${target} by ${jstr(m.amount ?? m.value)}`;\n case \"resurrection\":\n return `return ${jstr(m.count ?? 1)} model(s) to ${target} with ${jstr(m.wounds_remaining ?? \"full\")} wounds`;\n case \"model-destruction\":\n return `destroy ${jstr(m.count)} non-leader model(s) from ${target}`;\n case \"cp-gain\":\n return `gain ${jstr(m.amount)} CP`;\n case \"cp-refund\":\n return `refund ${jstr(m.amount)} CP`;\n case \"resource-gain\":\n return `gain ${jstr(m.amount)} to ${jstr(m.pool_id)}`;\n case \"resource-spend\":\n return `spend ${jstr(m.amount)} from ${jstr(m.pool_id)}`;\n case \"leadership-modifier\": {\n if (m.test != null && m.operation == null) return `force a ${dekebab(jstr(m.test))} test on ${target}`;\n if (m.test != null) return `${dekebab(jstr(m.operation))} ${dekebab(jstr(m.test))} tests for ${target}`;\n if (m.operation != null) return `${signed(m.operation, m.value)} Leadership for ${target}`;\n return `modify Leadership for ${target}`;\n }\n case \"fight-first\":\n return `${target} fights first`;\n case \"fight-last\":\n return `${target} fights last`;\n case \"fight-on-death\":\n return `${target} fights on death`;\n case \"shoot-on-death\":\n return `${target} shoots on death`;\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 const what = m.restriction ?? m.restriction_type;\n const range = m.range != null ? ` (within ${jstr(m.range)}\")` : \"\";\n const max = m.max_models != null ? ` (max ${jstr(m.max_models)} models)` : \"\";\n return `${target}: ${what != null ? dekebab(jstr(what)) : \"attack restriction\"}${range}${max}`;\n }\n case \"objective-control-modifier\": {\n if (m.operation != null) return `${signed(m.operation, m.value)} OC for ${target}`;\n return `modify OC of ${target} by ${jstr(m.value)}`;\n }\n case \"bs-modifier\":\n return `${signed(m.operation, m.value)} BS for ${target}`;\n case \"charge-roll-modifier\":\n return `${signed(m.operation, m.value)} to charge rolls for ${target}`;\n case \"engagement-passthrough\":\n return `${target} can move through engagement range`;\n case \"terrain-area-tag\":\n return `tag the terrain area as ${dekebab(jstr(m.tag))}`;\n case \"objective-tag\":\n return `tag the objective as ${dekebab(jstr(m.tag))}`;\n case \"unit-tag\":\n return `tag ${target} as ${dekebab(jstr(m.tag))}`;\n\n // Container types — inline forms.\n case \"conditional\":\n return `if ${describeCondition(e.condition ?? {})}: ${describeEffectInline(e.effect ?? {})}`;\n case \"sequence\":\n return (e.steps ?? []).map(describeEffectInline).join(\"; \");\n case \"choice\": {\n const label = e.choice_label ? ` (${e.choice_label})` : \"\";\n return `choose one${label}: ${(e.options ?? []).map(describeEffectInline).join(\" / \")}`;\n }\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold);\n const success = e.on_success ? describeEffectInline(e.on_success) : \"nothing\";\n const fail = e.on_fail ? `, otherwise ${describeEffectInline(e.on_fail)}` : \"\";\n return `roll ${jstr(e.dice)}: on ${comp}, ${success}${fail}`;\n }\n case \"dice-pool-allocation\": {\n const pool = e.pool ? `${jstr(e.pool.count)}${jstr(e.pool.die)}` : \"?\";\n const opts = (e.options ?? [])\n .map((o) => `${jstr(o.name)} (${jstr(o.requirement?.min_value)}+): ${describeEffectInline(o.effect ?? {})}`)\n .join(\" / \");\n return `roll ${pool}: ${opts}`;\n }\n\n default:\n return `[${e.type ?? \"unknown\"}]`;\n }\n}\n\n/**\n * Block translation of an effect tree. Containers expand over multiple lines\n * with two-space indentation; leaves delegate to `describeEffectInline`.\n */\nexport function describeEffect(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 `${indent}If ${describeCondition(e.condition ?? {})}:\\n` + describeEffect(e.effect ?? {}, depth + 1);\n case \"sequence\":\n return (e.steps ?? []).map((s) => describeEffect(s, depth)).join(\"\\n\");\n case \"choice\": {\n const label = e.choice_label ? ` (${e.choice_label})` : \"\";\n return (\n `${indent}${arrow}Choose one${label}:\\n` +\n (e.options ?? []).map((o, i) => `${indent} ${i + 1}. ${describeEffectInline(o)}`).join(\"\\n\")\n );\n }\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold);\n const success = e.on_success ? describeEffectInline(e.on_success) : \"nothing\";\n const fail = e.on_fail ? `, otherwise ${describeEffectInline(e.on_fail)}` : \"\";\n return `${indent}${arrow}Roll ${jstr(e.dice)}: on ${comp}, ${success}${fail}`;\n }\n case \"dice-pool-allocation\": {\n const pool = e.pool ? `${jstr(e.pool.count)}${jstr(e.pool.die)}` : \"?\";\n const lines = [`${indent}${arrow}Roll ${pool} (max ${jstr(e.max_activations)} activations):`];\n for (const opt of e.options ?? []) {\n lines.push(\n `${indent} - ${jstr(opt.name)}: need ${jstr(opt.requirement?.type)} of ${jstr(opt.requirement?.min_value)}+ -> ${describeEffectInline(opt.effect ?? {})}`\n );\n }\n return lines.join(\"\\n\");\n }\n default:\n return `${indent}${arrow}${describeEffectInline(e)}`;\n }\n}\n\n/** `Scope: aura (6\"). Duration: phase.` — empty string when absent. */\nexport function describeScope(s?: AbilityScope): string {\n if (!s || (!s.range && !s.duration)) return \"\";\n const range = dekebab(s.range ?? \"\");\n const inches = s.range_inches != null ? ` (${jstr(s.range_inches)}\")` : \"\";\n const duration = dekebab(s.duration ?? \"\");\n return `Scope: ${range}${inches}. Duration: ${duration}.`;\n}\n\n/**\n * `Applies to: units with Possessed.` — the roster-highlighting audience named\n * by a curated `applies_to` filter. Empty string when the filter is absent or\n * carries no keywords (nothing to say). `required_keywords` reads as an AND set;\n * `excluded_keywords` render as a trailing `(excluding …)`.\n */\nexport function describeAppliesTo(a?: AbilityAppliesTo | null): string {\n if (!a) return \"\";\n const required = a.required_keywords ?? [];\n const excluded = a.excluded_keywords ?? [];\n if (required.length === 0 && excluded.length === 0) return \"\";\n const base = required.length ? `units with ${required.join(\", \")}` : \"all units\";\n const exc = excluded.length ? ` (excluding ${excluded.join(\", \")})` : \"\";\n return `Applies to: ${base}${exc}.`;\n}\n\n/**\n * Full generated text for an ability: the effect tree, a trailing scope line,\n * and a trailing `Applies to:` line when the ability carries a curated\n * `applies_to` filter. This is the `ability.print()` consumers render when the\n * dataset carries no rules prose.\n */\nexport function describeAbility(a: AbilityLike): string {\n const effect = a.effect ? describeEffect(a.effect) : \"\";\n const scope = describeScope(a.scope);\n const applies = describeAppliesTo(a.applies_to);\n return [effect, scope, applies].filter(Boolean).join(\"\\n\");\n}\n"]}
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { describeCondition, dekebab, type Condition } from "./condition.js";
|
|
10
10
|
export { describeTrigger, describeAward, describeScoringCard, type ScoringTrigger, type ScoringAward, type ScoringMode, } from "./scoring.js";
|
|
11
|
-
export { describeEffect, describeEffectInline, describeAbility, describeScope, type Effect, type AbilityScope, type AbilityLike, } from "./effect.js";
|
|
11
|
+
export { describeEffect, describeEffectInline, describeAbility, describeAppliesTo, describeScope, type Effect, type AbilityScope, type AbilityAppliesTo, type AbilityLike, } from "./effect.js";
|
|
12
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/translate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/translate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC"}
|
package/dist/translate/index.js
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { describeCondition, dekebab } from "./condition.js";
|
|
10
10
|
export { describeTrigger, describeAward, describeScoringCard, } from "./scoring.js";
|
|
11
|
-
export { describeEffect, describeEffectInline, describeAbility, describeScope, } from "./effect.js";
|
|
11
|
+
export { describeEffect, describeEffectInline, describeAbility, describeAppliesTo, describeScope, } from "./effect.js";
|
|
12
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/translate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAkB,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,aAAa,EACb,mBAAmB,GAIpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/translate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAkB,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,aAAa,EACb,mBAAmB,GAIpB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,aAAa,GAKd,MAAM,aAAa,CAAC","sourcesContent":["/**\n * Plain-English translation of structured game data — the `secondary-card`\n * scoring `awards` (mission \"how to play\" readouts), the shared Ability-DSL\n * condition humanizer, and the Ability-DSL effect describer (\"ability.print()\").\n * Output is ASCII-only and pinned across language ports by the\n * `conformance/scoring-translation` and `conformance/effect-translation`\n * corpora.\n */\nexport { describeCondition, dekebab, type Condition } from \"./condition.js\";\nexport {\n describeTrigger,\n describeAward,\n describeScoringCard,\n type ScoringTrigger,\n type ScoringAward,\n type ScoringMode,\n} from \"./scoring.js\";\nexport {\n describeEffect,\n describeEffectInline,\n describeAbility,\n describeAppliesTo,\n describeScope,\n type Effect,\n type AbilityScope,\n type AbilityAppliesTo,\n type AbilityLike,\n} from \"./effect.js\";\n"]}
|
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAS3B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAS3B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAkDD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,CAAC,CAkF3B"}
|
package/dist/validate.js
CHANGED
|
@@ -28,6 +28,7 @@ const SCHEMA_MAP = {
|
|
|
28
28
|
"secondary-cards": "https://40kdc.dev/schemas/core/secondary-card.schema.json",
|
|
29
29
|
"terrain-templates": "https://40kdc.dev/schemas/core/terrain-template.schema.json",
|
|
30
30
|
"terrain-layouts": "https://40kdc.dev/schemas/core/terrain-layout.schema.json",
|
|
31
|
+
"hull-shapes": "https://40kdc.dev/schemas/core/hull-shape.schema.json",
|
|
31
32
|
"phase-mappings": "https://40kdc.dev/schemas/enrichment/phase-mapping.schema.json",
|
|
32
33
|
"timing-flags": "https://40kdc.dev/schemas/enrichment/timing-flag.schema.json",
|
|
33
34
|
"interaction-flags": "https://40kdc.dev/schemas/enrichment/interaction-flag.schema.json",
|
package/dist/validate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import type Ajv from \"ajv\";\nimport { readFileSync } from \"node:fs\";\nimport { glob } from \"glob\";\nimport { resolve, basename } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\nconst DATA_ROOT = resolve(__dirname, \"../../data\");\n\nexport interface ValidationError {\n file: string;\n index: number;\n errors: Array<{ path: string; message: string }>;\n}\n\nexport interface ValidationResult {\n totalFiles: number;\n totalItems: number;\n passed: number;\n failed: number;\n errors: ValidationError[];\n}\n\n/**\n * Map from data file base-name prefix to schema $id.\n */\nconst SCHEMA_MAP: Record<string, string> = {\n factions: \"https://40kdc.dev/schemas/core/faction.schema.json\",\n units: \"https://40kdc.dev/schemas/core/unit.schema.json\",\n \"target-profiles\": \"https://40kdc.dev/schemas/core/target-profile.schema.json\",\n weapons: \"https://40kdc.dev/schemas/core/weapon.schema.json\",\n \"weapon-keywords\": \"https://40kdc.dev/schemas/core/weapon-keyword.schema.json\",\n \"game-versions\": \"https://40kdc.dev/schemas/core/game-version.schema.json\",\n detachments: \"https://40kdc.dev/schemas/core/detachment.schema.json\",\n enhancements: \"https://40kdc.dev/schemas/core/enhancement.schema.json\",\n stratagems: \"https://40kdc.dev/schemas/core/stratagem.schema.json\",\n \"wargear-options\": \"https://40kdc.dev/schemas/core/wargear-option.schema.json\",\n wargear: \"https://40kdc.dev/schemas/core/wargear.schema.json\",\n \"leader-attachments\": \"https://40kdc.dev/schemas/core/leader-attachment.schema.json\",\n \"unit-compositions\": \"https://40kdc.dev/schemas/core/unit-composition.schema.json\",\n \"force-dispositions\": \"https://40kdc.dev/schemas/core/force-disposition.schema.json\",\n \"deployment-patterns\": \"https://40kdc.dev/schemas/core/deployment-pattern.schema.json\",\n \"mission-matchups\": \"https://40kdc.dev/schemas/core/mission-matchup.schema.json\",\n missions: \"https://40kdc.dev/schemas/core/mission.schema.json\",\n \"secondary-cards\": \"https://40kdc.dev/schemas/core/secondary-card.schema.json\",\n \"terrain-templates\": \"https://40kdc.dev/schemas/core/terrain-template.schema.json\",\n \"terrain-layouts\": \"https://40kdc.dev/schemas/core/terrain-layout.schema.json\",\n \"phase-mappings\": \"https://40kdc.dev/schemas/enrichment/phase-mapping.schema.json\",\n \"timing-flags\": \"https://40kdc.dev/schemas/enrichment/timing-flag.schema.json\",\n \"interaction-flags\": \"https://40kdc.dev/schemas/enrichment/interaction-flag.schema.json\",\n abilities: \"https://40kdc.dev/schemas/enrichment/ability-dsl/ability.schema.json\",\n \"resource-pools\": \"https://40kdc.dev/schemas/enrichment/resource-pool.schema.json\",\n};\n\n/**\n * Determine which schema $id to use for a given data file path.\n * Convention: the file's base name starts with a SCHEMA_MAP prefix (real data is\n * `<prefix>.json`; test fixtures are `<prefix>-good.json` / `<prefix>-bad.json`).\n * Prefixes are tried longest-first so `wargear-options.json` resolves to the\n * wargear-option schema rather than the shorter `wargear` key (distinct entities).\n */\nconst SCHEMA_PREFIXES = Object.keys(SCHEMA_MAP).sort((a, b) => b.length - a.length);\nfunction resolveSchemaId(filePath: string): string | null {\n const base = basename(filePath);\n for (const prefix of SCHEMA_PREFIXES) {\n if (base.startsWith(prefix)) return SCHEMA_MAP[prefix];\n }\n return null;\n}\n\n/**\n * Validate all data files matching the given glob pattern.\n * Each data file is expected to be a JSON array; each element is validated individually.\n */\nexport async function validateFiles(\n ajv: Ajv,\n pattern: string,\n cwd?: string,\n): Promise<ValidationResult> {\n const root = cwd ?? DATA_ROOT;\n const files = await glob(pattern, { cwd: root, absolute: true });\n\n const result: ValidationResult = {\n totalFiles: files.length,\n totalItems: 0,\n passed: 0,\n failed: 0,\n errors: [],\n };\n\n for (const file of files) {\n // Underscore-prefixed files are scratch/reports (e.g. the converter's\n // `_wargear-options.unparsed.json`), not dataset entities — skip them.\n if (basename(file).startsWith(\"_\")) continue;\n const schemaId = resolveSchemaId(file);\n if (!schemaId) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: `No schema mapping found for file: ${basename(file)}` }],\n });\n result.failed++;\n continue;\n }\n\n const validate = ajv.getSchema(schemaId);\n if (!validate) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: `Schema not found: ${schemaId}` }],\n });\n result.failed++;\n continue;\n }\n\n let data: unknown;\n try {\n const raw = readFileSync(file, \"utf-8\");\n data = JSON.parse(raw);\n } catch (err) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: `Failed to parse JSON: ${(err as Error).message}` }],\n });\n result.failed++;\n continue;\n }\n\n if (!Array.isArray(data)) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: \"Data file must be a JSON array\" }],\n });\n result.failed++;\n continue;\n }\n\n for (let i = 0; i < data.length; i++) {\n result.totalItems++;\n const valid = validate(data[i]);\n if (valid) {\n result.passed++;\n } else {\n result.failed++;\n result.errors.push({\n file,\n index: i,\n errors: (validate.errors ?? []).map((e) => ({\n path: e.instancePath || \"/\",\n message: e.message ?? \"Unknown validation error\",\n })),\n });\n }\n }\n }\n\n return result;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import type Ajv from \"ajv\";\nimport { readFileSync } from \"node:fs\";\nimport { glob } from \"glob\";\nimport { resolve, basename } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\nconst DATA_ROOT = resolve(__dirname, \"../../data\");\n\nexport interface ValidationError {\n file: string;\n index: number;\n errors: Array<{ path: string; message: string }>;\n}\n\nexport interface ValidationResult {\n totalFiles: number;\n totalItems: number;\n passed: number;\n failed: number;\n errors: ValidationError[];\n}\n\n/**\n * Map from data file base-name prefix to schema $id.\n */\nconst SCHEMA_MAP: Record<string, string> = {\n factions: \"https://40kdc.dev/schemas/core/faction.schema.json\",\n units: \"https://40kdc.dev/schemas/core/unit.schema.json\",\n \"target-profiles\": \"https://40kdc.dev/schemas/core/target-profile.schema.json\",\n weapons: \"https://40kdc.dev/schemas/core/weapon.schema.json\",\n \"weapon-keywords\": \"https://40kdc.dev/schemas/core/weapon-keyword.schema.json\",\n \"game-versions\": \"https://40kdc.dev/schemas/core/game-version.schema.json\",\n detachments: \"https://40kdc.dev/schemas/core/detachment.schema.json\",\n enhancements: \"https://40kdc.dev/schemas/core/enhancement.schema.json\",\n stratagems: \"https://40kdc.dev/schemas/core/stratagem.schema.json\",\n \"wargear-options\": \"https://40kdc.dev/schemas/core/wargear-option.schema.json\",\n wargear: \"https://40kdc.dev/schemas/core/wargear.schema.json\",\n \"leader-attachments\": \"https://40kdc.dev/schemas/core/leader-attachment.schema.json\",\n \"unit-compositions\": \"https://40kdc.dev/schemas/core/unit-composition.schema.json\",\n \"force-dispositions\": \"https://40kdc.dev/schemas/core/force-disposition.schema.json\",\n \"deployment-patterns\": \"https://40kdc.dev/schemas/core/deployment-pattern.schema.json\",\n \"mission-matchups\": \"https://40kdc.dev/schemas/core/mission-matchup.schema.json\",\n missions: \"https://40kdc.dev/schemas/core/mission.schema.json\",\n \"secondary-cards\": \"https://40kdc.dev/schemas/core/secondary-card.schema.json\",\n \"terrain-templates\": \"https://40kdc.dev/schemas/core/terrain-template.schema.json\",\n \"terrain-layouts\": \"https://40kdc.dev/schemas/core/terrain-layout.schema.json\",\n \"hull-shapes\": \"https://40kdc.dev/schemas/core/hull-shape.schema.json\",\n \"phase-mappings\": \"https://40kdc.dev/schemas/enrichment/phase-mapping.schema.json\",\n \"timing-flags\": \"https://40kdc.dev/schemas/enrichment/timing-flag.schema.json\",\n \"interaction-flags\": \"https://40kdc.dev/schemas/enrichment/interaction-flag.schema.json\",\n abilities: \"https://40kdc.dev/schemas/enrichment/ability-dsl/ability.schema.json\",\n \"resource-pools\": \"https://40kdc.dev/schemas/enrichment/resource-pool.schema.json\",\n};\n\n/**\n * Determine which schema $id to use for a given data file path.\n * Convention: the file's base name starts with a SCHEMA_MAP prefix (real data is\n * `<prefix>.json`; test fixtures are `<prefix>-good.json` / `<prefix>-bad.json`).\n * Prefixes are tried longest-first so `wargear-options.json` resolves to the\n * wargear-option schema rather than the shorter `wargear` key (distinct entities).\n */\nconst SCHEMA_PREFIXES = Object.keys(SCHEMA_MAP).sort((a, b) => b.length - a.length);\nfunction resolveSchemaId(filePath: string): string | null {\n const base = basename(filePath);\n for (const prefix of SCHEMA_PREFIXES) {\n if (base.startsWith(prefix)) return SCHEMA_MAP[prefix];\n }\n return null;\n}\n\n/**\n * Validate all data files matching the given glob pattern.\n * Each data file is expected to be a JSON array; each element is validated individually.\n */\nexport async function validateFiles(\n ajv: Ajv,\n pattern: string,\n cwd?: string,\n): Promise<ValidationResult> {\n const root = cwd ?? DATA_ROOT;\n const files = await glob(pattern, { cwd: root, absolute: true });\n\n const result: ValidationResult = {\n totalFiles: files.length,\n totalItems: 0,\n passed: 0,\n failed: 0,\n errors: [],\n };\n\n for (const file of files) {\n // Underscore-prefixed files are scratch/reports (e.g. the converter's\n // `_wargear-options.unparsed.json`), not dataset entities — skip them.\n if (basename(file).startsWith(\"_\")) continue;\n const schemaId = resolveSchemaId(file);\n if (!schemaId) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: `No schema mapping found for file: ${basename(file)}` }],\n });\n result.failed++;\n continue;\n }\n\n const validate = ajv.getSchema(schemaId);\n if (!validate) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: `Schema not found: ${schemaId}` }],\n });\n result.failed++;\n continue;\n }\n\n let data: unknown;\n try {\n const raw = readFileSync(file, \"utf-8\");\n data = JSON.parse(raw);\n } catch (err) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: `Failed to parse JSON: ${(err as Error).message}` }],\n });\n result.failed++;\n continue;\n }\n\n if (!Array.isArray(data)) {\n result.errors.push({\n file,\n index: -1,\n errors: [{ path: \"\", message: \"Data file must be a JSON array\" }],\n });\n result.failed++;\n continue;\n }\n\n for (let i = 0; i < data.length; i++) {\n result.totalItems++;\n const valid = validate(data[i]);\n if (valid) {\n result.passed++;\n } else {\n result.failed++;\n result.errors.push({\n file,\n index: i,\n errors: (validate.errors ?? []).map((e) => ({\n path: e.instancePath || \"/\",\n message: e.message ?? \"Unknown validation error\",\n })),\n });\n }\n }\n }\n\n return result;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpaca-software/40kdc-data",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The 40kdc Warhammer 40K dataset behind a linked, typed API — find units, follow them to their weapons, abilities, phases, and factions. Also validates data against the canonical JSON Schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "npm run codegen:data && tsc",
|
|
56
|
+
"postbuild": "npm run audit:coverage",
|
|
56
57
|
"codegen:data": "tsx src/codegen-data.ts",
|
|
57
58
|
"gen:conformance": "tsx src/gen-conformance.ts",
|
|
58
59
|
"docs:api": "typedoc",
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"translate": "tsx src/cli.ts translate",
|
|
65
66
|
"audit:coverage": "tsx src/cli.ts audit-coverage --write",
|
|
66
67
|
"scrub:defensive-flag": "tsx src/scrub-defensive-flag.ts",
|
|
68
|
+
"extract:faction-pack": "tsx src/extract-faction-pack.ts",
|
|
67
69
|
"author:input": "tsx src/author-input.ts",
|
|
68
70
|
"author:seed": "tsx src/author-seed.ts",
|
|
69
71
|
"author:propose": "tsx src/author-batch.ts propose",
|
|
@@ -27,6 +27,12 @@
|
|
|
27
27
|
"uniqueItems": true,
|
|
28
28
|
"description": "11e: ids of the Force Disposition entities this detachment grants. Empty until assigned."
|
|
29
29
|
},
|
|
30
|
+
"tags": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": { "type": "string" },
|
|
33
|
+
"uniqueItems": true,
|
|
34
|
+
"description": "11e: detachment-type tags (e.g. 'dynasty', 'kabal'). A roster may include at most one detachment per shared tag — the 'you can only take one of X type of detachment' rule. Empty when the detachment carries no UNIQUE tag."
|
|
35
|
+
},
|
|
30
36
|
"enhancement_ids": {
|
|
31
37
|
"type": "array",
|
|
32
38
|
"items": { "$ref": "../defs/common.schema.json#/$defs/entity-id" }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://40kdc.dev/schemas/core/hull-shape.schema.json",
|
|
4
|
+
"title": "Hull Shape",
|
|
5
|
+
"description": "A model's 2D collision footprint as an explicit polygon, used in place of a circular/oval base for vehicles and other hull-based models. Points are authored in local inches (y-down); a consumer re-centers the polygon on its area centroid before placement, so the local origin does not affect where the model lands — only its shape matters (mirrors the terrain-template footprint convention). A hull shape is faction-agnostic and reusable: one outline (e.g. a Rhino chassis) is authored once and referenced by `hull_shape_id` from every model that shares that hull, across factions. This entity stores geometry only — never an image, image URL, or any source-asset metadata.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"id": { "$ref": "../defs/common.schema.json#/$defs/entity-id" },
|
|
9
|
+
"name": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
10
|
+
"points": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"description": "Polygon vertices in local inches (y-down), in order around the outline. A hull is always a polygon (never a rectangle/right-triangle), so the points are given directly rather than via the shared `footprint` oneOf.",
|
|
13
|
+
"items": { "$ref": "../defs/common.schema.json#/$defs/vec2" },
|
|
14
|
+
"minItems": 3
|
|
15
|
+
},
|
|
16
|
+
"bounds_width_in": {
|
|
17
|
+
"type": "number",
|
|
18
|
+
"exclusiveMinimum": 0,
|
|
19
|
+
"description": "Cached axis-aligned bounding-box width in inches (max x − min x). Derived from `points`; recorded so consumers can size/scale without recomputing."
|
|
20
|
+
},
|
|
21
|
+
"bounds_height_in": {
|
|
22
|
+
"type": "number",
|
|
23
|
+
"exclusiveMinimum": 0,
|
|
24
|
+
"description": "Cached axis-aligned bounding-box height in inches (max y − min y). Derived from `points`."
|
|
25
|
+
},
|
|
26
|
+
"game_version": { "$ref": "../defs/game-version-ref.schema.json" }
|
|
27
|
+
},
|
|
28
|
+
"required": ["id", "name", "points", "bounds_width_in", "bounds_height_in", "game_version"],
|
|
29
|
+
"additionalProperties": false
|
|
30
|
+
}
|