@alpaca-software/40kdc-data 0.5.11 → 0.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +10 -0
  2. package/dist/codegen-data.js +1 -0
  3. package/dist/codegen-data.js.map +1 -1
  4. package/dist/commands/import.d.ts.map +1 -1
  5. package/dist/commands/import.js +3 -1
  6. package/dist/commands/import.js.map +1 -1
  7. package/dist/convert-faction.d.ts.map +1 -1
  8. package/dist/convert-faction.js +22 -2
  9. package/dist/convert-faction.js.map +1 -1
  10. package/dist/data/bundle.generated.js +1 -1
  11. package/dist/data/bundle.generated.js.map +1 -1
  12. package/dist/data/dataset.d.ts +2 -1
  13. package/dist/data/dataset.d.ts.map +1 -1
  14. package/dist/data/dataset.js +2 -0
  15. package/dist/data/dataset.js.map +1 -1
  16. package/dist/data/entities.d.ts +22 -0
  17. package/dist/data/entities.d.ts.map +1 -1
  18. package/dist/data/entities.js +28 -0
  19. package/dist/data/entities.js.map +1 -1
  20. package/dist/data/index.d.ts +2 -0
  21. package/dist/data/index.d.ts.map +1 -1
  22. package/dist/data/index.js +2 -0
  23. package/dist/data/index.js.map +1 -1
  24. package/dist/data/types.d.ts +3 -1
  25. package/dist/data/types.d.ts.map +1 -1
  26. package/dist/data/types.js +1 -0
  27. package/dist/data/types.js.map +1 -1
  28. package/dist/export/newrecruit-json.d.ts.map +1 -1
  29. package/dist/export/newrecruit-json.js +4 -3
  30. package/dist/export/newrecruit-json.js.map +1 -1
  31. package/dist/export/newrecruit-simple.js +2 -2
  32. package/dist/export/newrecruit-simple.js.map +1 -1
  33. package/dist/export/newrecruit-wtc.d.ts.map +1 -1
  34. package/dist/export/newrecruit-wtc.js +3 -1
  35. package/dist/export/newrecruit-wtc.js.map +1 -1
  36. package/dist/export/rosterizer.d.ts.map +1 -1
  37. package/dist/export/rosterizer.js +6 -7
  38. package/dist/export/rosterizer.js.map +1 -1
  39. package/dist/extract-faction-pack.d.ts +56 -0
  40. package/dist/extract-faction-pack.d.ts.map +1 -0
  41. package/dist/extract-faction-pack.js +374 -0
  42. package/dist/extract-faction-pack.js.map +1 -0
  43. package/dist/gen-conformance.js +37 -4
  44. package/dist/gen-conformance.js.map +1 -1
  45. package/dist/generated.d.ts +42 -2
  46. package/dist/generated.d.ts.map +1 -1
  47. package/dist/generated.js.map +1 -1
  48. package/dist/import/gw.js +1 -1
  49. package/dist/import/gw.js.map +1 -1
  50. package/dist/import/listforge-text.js +1 -1
  51. package/dist/import/listforge-text.js.map +1 -1
  52. package/dist/import/listforge.d.ts.map +1 -1
  53. package/dist/import/listforge.js +18 -3
  54. package/dist/import/listforge.js.map +1 -1
  55. package/dist/import/newrecruit-json.d.ts.map +1 -1
  56. package/dist/import/newrecruit-json.js +21 -3
  57. package/dist/import/newrecruit-json.js.map +1 -1
  58. package/dist/import/newrecruit-simple.js +1 -1
  59. package/dist/import/newrecruit-simple.js.map +1 -1
  60. package/dist/import/newrecruit-wtc.js +1 -1
  61. package/dist/import/newrecruit-wtc.js.map +1 -1
  62. package/dist/import/resolve.d.ts.map +1 -1
  63. package/dist/import/resolve.js +46 -18
  64. package/dist/import/resolve.js.map +1 -1
  65. package/dist/import/roster-json.d.ts +5 -3
  66. package/dist/import/roster-json.d.ts.map +1 -1
  67. package/dist/import/roster-json.js +8 -4
  68. package/dist/import/roster-json.js.map +1 -1
  69. package/dist/import/rosterizer.js +3 -3
  70. package/dist/import/rosterizer.js.map +1 -1
  71. package/dist/import/types.d.ts +15 -4
  72. package/dist/import/types.d.ts.map +1 -1
  73. package/dist/import/types.js.map +1 -1
  74. package/dist/index.d.ts +1 -0
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +4 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/rube-goldberg.js +1 -1
  79. package/dist/rube-goldberg.js.map +1 -1
  80. package/dist/runner.d.ts.map +1 -1
  81. package/dist/runner.js +33 -1
  82. package/dist/runner.js.map +1 -1
  83. package/dist/scope.d.ts +31 -0
  84. package/dist/scope.d.ts.map +1 -0
  85. package/dist/scope.js +34 -0
  86. package/dist/scope.js.map +1 -0
  87. package/dist/translate/effect.d.ts +17 -3
  88. package/dist/translate/effect.d.ts.map +1 -1
  89. package/dist/translate/effect.js +23 -4
  90. package/dist/translate/effect.js.map +1 -1
  91. package/dist/translate/index.d.ts +1 -1
  92. package/dist/translate/index.d.ts.map +1 -1
  93. package/dist/translate/index.js +1 -1
  94. package/dist/translate/index.js.map +1 -1
  95. package/dist/validate.d.ts.map +1 -1
  96. package/dist/validate.js +1 -0
  97. package/dist/validate.js.map +1 -1
  98. package/package.json +2 -1
  99. package/schemas/core/hull-shape.schema.json +30 -0
  100. package/schemas/core/roster.schema.json +24 -8
  101. package/schemas/core/stratagem.schema.json +2 -2
  102. package/schemas/core/unit-composition.schema.json +7 -0
  103. package/schemas/enrichment/ability-dsl/ability.schema.json +14 -0
package/README.md CHANGED
@@ -61,6 +61,16 @@ as `normalizeName` so you can reproduce it in your own search UI.
61
61
 
62
62
  Auto-generated from the source: [`docs/api/`](docs/api/README.md).
63
63
 
64
+ ## See it in action
65
+
66
+ Open-source example apps built on this package:
67
+
68
+ - [List Builder](https://list-builder.alpacasoft.dev) — build an army list and export to ListForge / NewRecruit.
69
+ - [Salvo](https://salvo.alpacasoft.dev) — damage calculator over the cruncher and abilities-resolver.
70
+ - [Mission Matrix](https://mission-matrix.alpacasoft.dev) — the 11th-edition Force Disposition matchup grid.
71
+ - [Layout Editor](https://layout-editor.alpacasoft.dev) — author terrain layouts on a board; exports terrain-layout JSON.
72
+ - [Hull Tracer](https://hull-tracer.alpacasoft.dev) — trace a model's collision hull from a top-down photo; exports geometry-only hull-shape JSON.
73
+
64
74
  ## Also: schema validation
65
75
 
66
76
  This package also ships the canonical JSON Schemas and an AJV-based validator
@@ -45,6 +45,7 @@ const FILE_TO_COLLECTION = {
45
45
  "force-dispositions": "forceDispositions",
46
46
  "terrain-templates": "terrainTemplates",
47
47
  "terrain-layouts": "terrainLayouts",
48
+ "hull-shapes": "hullShapes",
48
49
  "resource-pools": "resourcePools",
49
50
  "timing-flags": "timingFlags",
50
51
  "interaction-flags": "interactionFlags",
@@ -1 +1 @@
1
- {"version":3,"file":"codegen-data.js","sourceRoot":"","sources":["../src/codegen-data.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bundles every authored data file under `data/` into a single embedded module,\n * `src/data/bundle.generated.ts`.\n *\n * The bundle is inlined as an escaped JSON *string* that is `JSON.parse`d at load\n * time (mirroring the Rust crate's `include_str!`): tsc typechecks it instantly\n * (it is just a string), it parses once at import, and it compiles into `dist`\n * with no runtime filesystem access — so the published package works in Node,\n * bundlers, and browsers alike, where `data/` is not shipped.\n *\n * Run via `npm run codegen:data`. The output is gitignored and regenerated on\n * build/test/pack.\n */\nimport { readdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { emptyRawData, type RawData } from \"./data/types.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst REPO_ROOT = resolve(__dirname, \"../..\");\nconst DATA_ROOTS = [join(REPO_ROOT, \"data\", \"core\"), join(REPO_ROOT, \"data\", \"enrichment\")];\nconst OUT_FILE = join(__dirname, \"data\", \"bundle.generated.ts\");\n\n/** Directory names that hold examples/scratch data and must never be bundled. */\nconst EXCLUDED_DIRS = new Set([\"_example\", \"_port-audit\"]);\n\n/** Map a data file's base name (sans `.json`) to its `RawData` collection key. */\nconst FILE_TO_COLLECTION: Record<string, keyof RawData> = {\n units: \"units\",\n \"target-profiles\": \"targetProfiles\",\n weapons: \"weapons\",\n \"weapon-keywords\": \"weaponKeywords\",\n factions: \"factions\",\n abilities: \"abilities\",\n \"phase-mappings\": \"phaseMappings\",\n detachments: \"detachments\",\n stratagems: \"stratagems\",\n enhancements: \"enhancements\",\n \"leader-attachments\": \"leaderAttachments\",\n \"unit-compositions\": \"unitCompositions\",\n \"wargear-options\": \"wargearOptions\",\n wargear: \"wargear\",\n \"game-versions\": \"gameVersions\",\n missions: \"missions\",\n \"mission-matchups\": \"missionMatchups\",\n \"secondary-cards\": \"missionCards\",\n \"deployment-patterns\": \"deploymentPatterns\",\n \"force-dispositions\": \"forceDispositions\",\n \"terrain-templates\": \"terrainTemplates\",\n \"terrain-layouts\": \"terrainLayouts\",\n \"resource-pools\": \"resourcePools\",\n \"timing-flags\": \"timingFlags\",\n \"interaction-flags\": \"interactionFlags\",\n};\n\n/** The id-bearing key for a collection, used only for duplicate-id reporting. */\nconst ID_KEY: Partial<Record<keyof RawData, string>> = {\n abilities: \"ability_id\",\n};\n\n/** Recursively collect bundleable `.json` files, skipping excluded dirs/examples. */\nfunction collectFiles(dir: string): string[] {\n const out: string[] = [];\n for (const entry of readdirSync(dir)) {\n if (EXCLUDED_DIRS.has(entry)) continue;\n const full = join(dir, entry);\n if (statSync(full).isDirectory()) {\n out.push(...collectFiles(full));\n } else if (entry.endsWith(\".json\") && !entry.endsWith(\".example.json\")) {\n out.push(full);\n }\n }\n return out;\n}\n\nfunction baseName(file: string): string {\n return file.slice(file.lastIndexOf(\"/\") + 1, -\".json\".length);\n}\n\nfunction build(): RawData {\n const data = emptyRawData();\n for (const root of DATA_ROOTS) {\n for (const file of collectFiles(root)) {\n const collection = FILE_TO_COLLECTION[baseName(file)];\n if (!collection) continue; // schema/scratch json we don't bundle\n const parsed = JSON.parse(readFileSync(file, \"utf-8\")) as unknown;\n if (!Array.isArray(parsed)) {\n throw new Error(`expected a JSON array in ${file}`);\n }\n (data[collection] as unknown[]).push(...parsed);\n }\n }\n return data;\n}\n\n/** Warn (do not fail) on duplicate primary ids — a data-hygiene signal. */\nfunction reportDuplicateIds(data: RawData): void {\n for (const [collection, key] of Object.entries(ID_KEY) as [keyof RawData, string][]) {\n const seen = new Set<string>();\n const dupes = new Set<string>();\n for (const item of data[collection] as Record<string, unknown>[]) {\n const id = item[key] as string | undefined;\n if (id === undefined) continue;\n if (seen.has(id)) dupes.add(id);\n else seen.add(id);\n }\n if (dupes.size > 0) {\n console.warn(` ⚠ ${collection}: ${dupes.size} duplicate ${key}(s), e.g. ${[...dupes].slice(0, 3).join(\", \")}`);\n }\n }\n}\n\nfunction emit(data: RawData): string {\n // JSON.stringify of the JSON text yields a valid, fully-escaped JS string\n // literal — safe to drop straight into the generated source.\n const jsonText = JSON.stringify(data);\n const literal = JSON.stringify(jsonText);\n return `/* GENERATED by 'npm run codegen:data' from the repository's data/ tree. DO NOT EDIT BY HAND. */\nimport type { RawData } from \"./types.js\";\n\nconst JSON_TEXT = ${literal};\n\n/** The full 40kdc dataset, embedded at build time and parsed once at load. */\nexport const RAW_DATA: RawData = JSON.parse(JSON_TEXT) as RawData;\n`;\n}\n\nfunction main(): void {\n const data = build();\n reportDuplicateIds(data);\n writeFileSync(OUT_FILE, emit(data));\n const counts = (Object.keys(data) as (keyof RawData)[])\n .map((k) => `${k}=${data[k].length}`)\n .join(\", \");\n console.log(`Wrote ${OUT_FILE}\\n ${counts}`);\n}\n\nmain();\n"]}
1
+ {"version":3,"file":"codegen-data.js","sourceRoot":"","sources":["../src/codegen-data.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bundles every authored data file under `data/` into a single embedded module,\n * `src/data/bundle.generated.ts`.\n *\n * The bundle is inlined as an escaped JSON *string* that is `JSON.parse`d at load\n * time (mirroring the Rust crate's `include_str!`): tsc typechecks it instantly\n * (it is just a string), it parses once at import, and it compiles into `dist`\n * with no runtime filesystem access — so the published package works in Node,\n * bundlers, and browsers alike, where `data/` is not shipped.\n *\n * Run via `npm run codegen:data`. The output is gitignored and regenerated on\n * build/test/pack.\n */\nimport { readdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { emptyRawData, type RawData } from \"./data/types.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst REPO_ROOT = resolve(__dirname, \"../..\");\nconst DATA_ROOTS = [join(REPO_ROOT, \"data\", \"core\"), join(REPO_ROOT, \"data\", \"enrichment\")];\nconst OUT_FILE = join(__dirname, \"data\", \"bundle.generated.ts\");\n\n/** Directory names that hold examples/scratch data and must never be bundled. */\nconst EXCLUDED_DIRS = new Set([\"_example\", \"_port-audit\"]);\n\n/** Map a data file's base name (sans `.json`) to its `RawData` collection key. */\nconst FILE_TO_COLLECTION: Record<string, keyof RawData> = {\n units: \"units\",\n \"target-profiles\": \"targetProfiles\",\n weapons: \"weapons\",\n \"weapon-keywords\": \"weaponKeywords\",\n factions: \"factions\",\n abilities: \"abilities\",\n \"phase-mappings\": \"phaseMappings\",\n detachments: \"detachments\",\n stratagems: \"stratagems\",\n enhancements: \"enhancements\",\n \"leader-attachments\": \"leaderAttachments\",\n \"unit-compositions\": \"unitCompositions\",\n \"wargear-options\": \"wargearOptions\",\n wargear: \"wargear\",\n \"game-versions\": \"gameVersions\",\n missions: \"missions\",\n \"mission-matchups\": \"missionMatchups\",\n \"secondary-cards\": \"missionCards\",\n \"deployment-patterns\": \"deploymentPatterns\",\n \"force-dispositions\": \"forceDispositions\",\n \"terrain-templates\": \"terrainTemplates\",\n \"terrain-layouts\": \"terrainLayouts\",\n \"hull-shapes\": \"hullShapes\",\n \"resource-pools\": \"resourcePools\",\n \"timing-flags\": \"timingFlags\",\n \"interaction-flags\": \"interactionFlags\",\n};\n\n/** The id-bearing key for a collection, used only for duplicate-id reporting. */\nconst ID_KEY: Partial<Record<keyof RawData, string>> = {\n abilities: \"ability_id\",\n};\n\n/** Recursively collect bundleable `.json` files, skipping excluded dirs/examples. */\nfunction collectFiles(dir: string): string[] {\n const out: string[] = [];\n for (const entry of readdirSync(dir)) {\n if (EXCLUDED_DIRS.has(entry)) continue;\n const full = join(dir, entry);\n if (statSync(full).isDirectory()) {\n out.push(...collectFiles(full));\n } else if (entry.endsWith(\".json\") && !entry.endsWith(\".example.json\")) {\n out.push(full);\n }\n }\n return out;\n}\n\nfunction baseName(file: string): string {\n return file.slice(file.lastIndexOf(\"/\") + 1, -\".json\".length);\n}\n\nfunction build(): RawData {\n const data = emptyRawData();\n for (const root of DATA_ROOTS) {\n for (const file of collectFiles(root)) {\n const collection = FILE_TO_COLLECTION[baseName(file)];\n if (!collection) continue; // schema/scratch json we don't bundle\n const parsed = JSON.parse(readFileSync(file, \"utf-8\")) as unknown;\n if (!Array.isArray(parsed)) {\n throw new Error(`expected a JSON array in ${file}`);\n }\n (data[collection] as unknown[]).push(...parsed);\n }\n }\n return data;\n}\n\n/** Warn (do not fail) on duplicate primary ids — a data-hygiene signal. */\nfunction reportDuplicateIds(data: RawData): void {\n for (const [collection, key] of Object.entries(ID_KEY) as [keyof RawData, string][]) {\n const seen = new Set<string>();\n const dupes = new Set<string>();\n for (const item of data[collection] as Record<string, unknown>[]) {\n const id = item[key] as string | undefined;\n if (id === undefined) continue;\n if (seen.has(id)) dupes.add(id);\n else seen.add(id);\n }\n if (dupes.size > 0) {\n console.warn(` ⚠ ${collection}: ${dupes.size} duplicate ${key}(s), e.g. ${[...dupes].slice(0, 3).join(\", \")}`);\n }\n }\n}\n\nfunction emit(data: RawData): string {\n // JSON.stringify of the JSON text yields a valid, fully-escaped JS string\n // literal — safe to drop straight into the generated source.\n const jsonText = JSON.stringify(data);\n const literal = JSON.stringify(jsonText);\n return `/* GENERATED by 'npm run codegen:data' from the repository's data/ tree. DO NOT EDIT BY HAND. */\nimport type { RawData } from \"./types.js\";\n\nconst JSON_TEXT = ${literal};\n\n/** The full 40kdc dataset, embedded at build time and parsed once at load. */\nexport const RAW_DATA: RawData = JSON.parse(JSON_TEXT) as RawData;\n`;\n}\n\nfunction main(): void {\n const data = build();\n reportDuplicateIds(data);\n writeFileSync(OUT_FILE, emit(data));\n const counts = (Object.keys(data) as (keyof RawData)[])\n .map((k) => `${k}=${data[k].length}`)\n .join(\", \");\n console.log(`Wrote ${OUT_FILE}\\n ${counts}`);\n}\n\nmain();\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/commands/import.ts"],"names":[],"mappings":"AAkBA,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAqDD,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC,CAsCf"}
1
+ {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/commands/import.ts"],"names":[],"mappings":"AAkBA,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA2DD,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC,CAsCf"}
@@ -34,7 +34,9 @@ function formatPretty(roster) {
34
34
  const lines = [];
35
35
  lines.push(`Roster: ${roster.name}`);
36
36
  lines.push(` Faction: ${roster.faction_id ?? "(unresolved)"}`);
37
- lines.push(` Detachment: ${roster.detachment_id ?? "(none/unresolved)"}`);
37
+ lines.push(` Detachments: ${roster.detachments
38
+ .map((d) => `${d.ref.id ?? d.ref.raw_name}${d.dp_cost != null ? ` (${d.dp_cost} DP)` : ""}`)
39
+ .join(", ") || "(none/unresolved)"}`);
38
40
  lines.push(` Battle size: ${roster.battle_size ?? "(unmapped)"}`);
39
41
  lines.push(` Points: computed ${roster.points.total_computed}` +
40
42
  (roster.points.total_reported !== null ? `, reported ${roster.points.total_reported}` : "") +
@@ -1 +1 @@
1
- {"version":3,"file":"import.js","sourceRoot":"","sources":["../../src/commands/import.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `import` command: turn a ListForge army-list export into a 40kdc roster.\n *\n * Input may be a ListForge URL, a bare base64 segment, an already-decoded JSON\n * string, or a path to a file containing any of those (or `-`/omitted for stdin).\n * The resolved roster is validated against `roster.schema.json` before output —\n * a guard that the importer only ever emits schema-valid rosters.\n *\n * The import is lenient: unresolved entries do not fail the command (exit 0).\n * Only a decode failure or schema-invalid output is fatal (exit 1).\n */\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { importListForge } from \"../import/index.js\";\nimport { createValidator } from \"../schema-loader.js\";\nimport type { Roster } from \"../import/index.js\";\n\nconst ROSTER_SCHEMA_ID = \"https://40kdc.dev/schemas/core/roster.schema.json\";\n\ninterface ImportOpts {\n reporter: string;\n out?: string;\n}\n\nfunction readStdin(): string {\n try {\n return readFileSync(0, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\n/** Resolve the command's input source to a raw payload string. */\nfunction resolveInput(input: string | undefined): string {\n if (!input || input === \"-\") return readStdin().trim();\n if (existsSync(input)) return readFileSync(input, \"utf8\").trim();\n return input;\n}\n\nfunction formatPretty(roster: Roster): string {\n const d = roster.diagnostics;\n const lines: string[] = [];\n lines.push(`Roster: ${roster.name}`);\n lines.push(` Faction: ${roster.faction_id ?? \"(unresolved)\"}`);\n lines.push(` Detachment: ${roster.detachment_id ?? \"(none/unresolved)\"}`);\n lines.push(` Battle size: ${roster.battle_size ?? \"(unmapped)\"}`);\n lines.push(\n ` Points: computed ${roster.points.total_computed}` +\n (roster.points.total_reported !== null ? `, reported ${roster.points.total_reported}` : \"\") +\n (roster.points.declared_limit !== null ? `, limit ${roster.points.declared_limit}` : \"\"),\n );\n lines.push(` Units (${roster.units.length}):`);\n for (const u of roster.units) {\n const mark = u.ref.resolved ? \"✓\" : \"✗\";\n const id = u.ref.resolved ? u.ref.id : `${u.ref.raw_name} → unresolved`;\n const extras: string[] = [];\n if (u.is_warlord) extras.push(\"warlord\");\n if (u.enhancement) extras.push(`enh:${u.enhancement.resolved ? u.enhancement.id : \"?\"}`);\n if (u.leader_attachment) extras.push(`leads:${u.leader_attachment.bodyguard_ref.id}?`);\n const suffix = extras.length ? ` [${extras.join(\", \")}]` : \"\";\n lines.push(` ${mark} ${id} ×${u.model_count}${u.points !== null ? ` (${u.points}pts)` : \"\"}${suffix}`);\n }\n lines.push(\n ` Resolved: ${d.resolved_units} units / ${d.resolved_weapons} weapons; ` +\n `unresolved: ${d.unresolved_units} units / ${d.unresolved_weapons} weapons`,\n );\n if (d.warnings.length) {\n lines.push(` Warnings (${d.warnings.length}):`);\n for (const w of d.warnings) {\n lines.push(` - [${w.code}]${w.raw_name ? ` \"${w.raw_name}\":` : \"\"} ${w.message}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\nexport async function importCommand(\n input: string | undefined,\n opts: ImportOpts,\n): Promise<void> {\n const payload = resolveInput(input);\n if (!payload) {\n console.error(\"import: no input (provide a URL/base64/JSON argument, a file path, or pipe via stdin)\");\n process.exit(1);\n }\n\n let roster: Roster;\n try {\n roster = importListForge(payload);\n } catch (err) {\n console.error(`import: failed to decode/parse payload: ${(err as Error).message}`);\n process.exit(1);\n }\n\n // Guard: our own output must be schema-valid.\n const validate = createValidator().getSchema(ROSTER_SCHEMA_ID);\n if (!validate) {\n console.error(`import: roster schema not found (${ROSTER_SCHEMA_ID})`);\n process.exit(1);\n }\n if (!validate(roster)) {\n console.error(\"import: produced roster failed schema validation:\");\n console.error(JSON.stringify(validate.errors, null, 2));\n process.exit(1);\n }\n\n const json = JSON.stringify(roster, null, 2);\n if (opts.out) {\n writeFileSync(opts.out, json + \"\\n\", \"utf8\");\n console.error(`Wrote roster → ${opts.out}`);\n }\n\n if (opts.reporter === \"pretty\") {\n console.log(formatPretty(roster));\n } else if (!opts.out) {\n console.log(json);\n }\n}\n"]}
1
+ {"version":3,"file":"import.js","sourceRoot":"","sources":["../../src/commands/import.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `import` command: turn a ListForge army-list export into a 40kdc roster.\n *\n * Input may be a ListForge URL, a bare base64 segment, an already-decoded JSON\n * string, or a path to a file containing any of those (or `-`/omitted for stdin).\n * The resolved roster is validated against `roster.schema.json` before output —\n * a guard that the importer only ever emits schema-valid rosters.\n *\n * The import is lenient: unresolved entries do not fail the command (exit 0).\n * Only a decode failure or schema-invalid output is fatal (exit 1).\n */\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { importListForge } from \"../import/index.js\";\nimport { createValidator } from \"../schema-loader.js\";\nimport type { Roster } from \"../import/index.js\";\n\nconst ROSTER_SCHEMA_ID = \"https://40kdc.dev/schemas/core/roster.schema.json\";\n\ninterface ImportOpts {\n reporter: string;\n out?: string;\n}\n\nfunction readStdin(): string {\n try {\n return readFileSync(0, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\n/** Resolve the command's input source to a raw payload string. */\nfunction resolveInput(input: string | undefined): string {\n if (!input || input === \"-\") return readStdin().trim();\n if (existsSync(input)) return readFileSync(input, \"utf8\").trim();\n return input;\n}\n\nfunction formatPretty(roster: Roster): string {\n const d = roster.diagnostics;\n const lines: string[] = [];\n lines.push(`Roster: ${roster.name}`);\n lines.push(` Faction: ${roster.faction_id ?? \"(unresolved)\"}`);\n lines.push(\n ` Detachments: ${\n roster.detachments\n .map((d) => `${d.ref.id ?? d.ref.raw_name}${d.dp_cost != null ? ` (${d.dp_cost} DP)` : \"\"}`)\n .join(\", \") || \"(none/unresolved)\"\n }`,\n );\n lines.push(` Battle size: ${roster.battle_size ?? \"(unmapped)\"}`);\n lines.push(\n ` Points: computed ${roster.points.total_computed}` +\n (roster.points.total_reported !== null ? `, reported ${roster.points.total_reported}` : \"\") +\n (roster.points.declared_limit !== null ? `, limit ${roster.points.declared_limit}` : \"\"),\n );\n lines.push(` Units (${roster.units.length}):`);\n for (const u of roster.units) {\n const mark = u.ref.resolved ? \"✓\" : \"✗\";\n const id = u.ref.resolved ? u.ref.id : `${u.ref.raw_name} → unresolved`;\n const extras: string[] = [];\n if (u.is_warlord) extras.push(\"warlord\");\n if (u.enhancement) extras.push(`enh:${u.enhancement.resolved ? u.enhancement.id : \"?\"}`);\n if (u.leader_attachment) extras.push(`leads:${u.leader_attachment.bodyguard_ref.id}?`);\n const suffix = extras.length ? ` [${extras.join(\", \")}]` : \"\";\n lines.push(` ${mark} ${id} ×${u.model_count}${u.points !== null ? ` (${u.points}pts)` : \"\"}${suffix}`);\n }\n lines.push(\n ` Resolved: ${d.resolved_units} units / ${d.resolved_weapons} weapons; ` +\n `unresolved: ${d.unresolved_units} units / ${d.unresolved_weapons} weapons`,\n );\n if (d.warnings.length) {\n lines.push(` Warnings (${d.warnings.length}):`);\n for (const w of d.warnings) {\n lines.push(` - [${w.code}]${w.raw_name ? ` \"${w.raw_name}\":` : \"\"} ${w.message}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\nexport async function importCommand(\n input: string | undefined,\n opts: ImportOpts,\n): Promise<void> {\n const payload = resolveInput(input);\n if (!payload) {\n console.error(\"import: no input (provide a URL/base64/JSON argument, a file path, or pipe via stdin)\");\n process.exit(1);\n }\n\n let roster: Roster;\n try {\n roster = importListForge(payload);\n } catch (err) {\n console.error(`import: failed to decode/parse payload: ${(err as Error).message}`);\n process.exit(1);\n }\n\n // Guard: our own output must be schema-valid.\n const validate = createValidator().getSchema(ROSTER_SCHEMA_ID);\n if (!validate) {\n console.error(`import: roster schema not found (${ROSTER_SCHEMA_ID})`);\n process.exit(1);\n }\n if (!validate(roster)) {\n console.error(\"import: produced roster failed schema validation:\");\n console.error(JSON.stringify(validate.errors, null, 2));\n process.exit(1);\n }\n\n const json = JSON.stringify(roster, null, 2);\n if (opts.out) {\n writeFileSync(opts.out, json + \"\\n\", \"utf8\");\n console.error(`Wrote roster → ${opts.out}`);\n }\n\n if (opts.reporter === \"pretty\") {\n console.log(formatPretty(roster));\n } else if (!opts.out) {\n console.log(json);\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"convert-faction.d.ts","sourceRoot":"","sources":["../src/convert-faction.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,EAAE,KAAK,aAAa,EAAkC,MAAM,gCAAgC,CAAC;AAGpG,OAAO,sCAAsC,CAAC;AAC9C,OAAO,2CAA2C,CAAC;AACnD,OAAO,uCAAuC,CAAC;AAC/C,OAAO,0CAA0C,CAAC;AAClD,OAAO,2CAA2C,CAAC;AACnD,OAAO,kCAAkC,CAAC;AAC1C,OAAO,2CAA2C,CAAC;AACnD,OAAO,sCAAsC,CAAC;AAC9C,OAAO,uCAAuC,CAAC;AAC/C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,0CAA0C,CAAC;AAClD,OAAO,0CAA0C,CAAC;AAClD,OAAO,gDAAgD,CAAC;AACxD,OAAO,4CAA4C,CAAC;AACpD,OAAO,oCAAoC,CAAC;AAC5C,OAAO,kCAAkC,CAAC;AAC1C,OAAO,iCAAiC,CAAC;AACzC,OAAO,uCAAuC,CAAC;AAC/C,OAAO,8BAA8B,CAAC;AACtC,OAAO,iCAAiC,CAAC;AACzC,OAAO,6CAA6C,CAAC;AACrD,OAAO,yCAAyC,CAAC;AACjD,OAAO,0CAA0C,CAAC;AAClD,OAAO,sCAAsC,CAAC;AAC9C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,sCAAsC,CAAC;AAC9C,OAAO,wCAAwC,CAAC;AAChD,OAAO,oCAAoC,CAAC;AAC5C,OAAO,sCAAsC,CAAC;AAC9C,OAAO,wCAAwC,CAAC;AAChD,OAAO,uCAAuC,CAAC;AAC/C,OAAO,oCAAoC,CAAC;AAC5C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAqJ7C,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,IAAI,CA6dN"}
1
+ {"version":3,"file":"convert-faction.d.ts","sourceRoot":"","sources":["../src/convert-faction.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,EAAE,KAAK,aAAa,EAAkC,MAAM,gCAAgC,CAAC;AAGpG,OAAO,sCAAsC,CAAC;AAC9C,OAAO,2CAA2C,CAAC;AACnD,OAAO,uCAAuC,CAAC;AAC/C,OAAO,0CAA0C,CAAC;AAClD,OAAO,2CAA2C,CAAC;AACnD,OAAO,kCAAkC,CAAC;AAC1C,OAAO,2CAA2C,CAAC;AACnD,OAAO,sCAAsC,CAAC;AAC9C,OAAO,uCAAuC,CAAC;AAC/C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,0CAA0C,CAAC;AAClD,OAAO,0CAA0C,CAAC;AAClD,OAAO,gDAAgD,CAAC;AACxD,OAAO,4CAA4C,CAAC;AACpD,OAAO,oCAAoC,CAAC;AAC5C,OAAO,kCAAkC,CAAC;AAC1C,OAAO,iCAAiC,CAAC;AACzC,OAAO,uCAAuC,CAAC;AAC/C,OAAO,8BAA8B,CAAC;AACtC,OAAO,iCAAiC,CAAC;AACzC,OAAO,6CAA6C,CAAC;AACrD,OAAO,yCAAyC,CAAC;AACjD,OAAO,0CAA0C,CAAC;AAClD,OAAO,sCAAsC,CAAC;AAC9C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,sCAAsC,CAAC;AAC9C,OAAO,wCAAwC,CAAC;AAChD,OAAO,oCAAoC,CAAC;AAC5C,OAAO,sCAAsC,CAAC;AAC9C,OAAO,wCAAwC,CAAC;AAChD,OAAO,uCAAuC,CAAC;AAC/C,OAAO,oCAAoC,CAAC;AAC5C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAqJ7C,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,IAAI,CAmfN"}
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Faction configs are registered via side-effect imports from ./converters/configs/.
8
8
  */
9
- import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
9
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
10
10
  import { resolve, dirname } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { nameToId, parseStratagemType, parsePlayerTurn, mapPhases } from "./converters/id-generator.js";
@@ -265,6 +265,18 @@ export function convertFaction(config, options = {}) {
265
265
  factionEnhancements = factionEnhancements.filter((e) => allowedDetachments.has(e.detachment));
266
266
  factionStratagems = factionStratagems.filter((s) => allowedDetachments.has(s.detachment));
267
267
  }
268
+ // `detachment_points` (11e DP cost) and `force_dispositions` are maintained
269
+ // by hand — army-assist doesn't carry them — so a re-run must NOT drop them.
270
+ // Carry both forward by id from the existing on-disk file.
271
+ const existingDetPath = resolve(ROOT, `data/core/${factionId}/detachments.json`);
272
+ const preservedById = new Map();
273
+ if (existsSync(existingDetPath)) {
274
+ const existing = JSON.parse(readFileSync(existingDetPath, "utf-8"));
275
+ for (const d of existing) {
276
+ if (typeof d.id === "string")
277
+ preservedById.set(d.id, d);
278
+ }
279
+ }
268
280
  const detachments = detachmentNames.map((detName) => {
269
281
  const detId = nameToId(detName);
270
282
  const detEnhIds = factionEnhancements
@@ -273,7 +285,7 @@ export function convertFaction(config, options = {}) {
273
285
  const detStratIds = factionStratagems
274
286
  .filter((s) => s.detachment === detName)
275
287
  .map((s) => nameToId(s.name));
276
- return {
288
+ const record = {
277
289
  id: detId,
278
290
  name: detName,
279
291
  faction_id: factionId,
@@ -282,6 +294,14 @@ export function convertFaction(config, options = {}) {
282
294
  stratagem_ids: detStratIds,
283
295
  game_version: GAME_VERSION,
284
296
  };
297
+ const preserved = preservedById.get(detId);
298
+ if (preserved && "detachment_points" in preserved) {
299
+ record.detachment_points = preserved.detachment_points;
300
+ }
301
+ if (preserved && "force_dispositions" in preserved) {
302
+ record.force_dispositions = preserved.force_dispositions;
303
+ }
304
+ return record;
285
305
  });
286
306
  // ─── Build enhancements ───
287
307
  console.log("Converting enhancements...");
@@ -1 +1 @@
1
- {"version":3,"file":"convert-faction.js","sourceRoot":"","sources":["../src/convert-faction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generic faction converter: army-assist → 40kdc-data format.\n *\n * Usage: npx tsx tools/src/convert-faction.ts <faction-id>\n * Example: npx tsx tools/src/convert-faction.ts emperors-children\n *\n * Faction configs are registered via side-effect imports from ./converters/configs/.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { nameToId, parseStratagemType, parsePlayerTurn, mapPhases } from \"./converters/id-generator.js\";\nimport { parseMove, parseTargetNumber, parseIntStat, parseInvuln } from \"./converters/stat-parser.js\";\nimport { findFactionViewIndex, getViewEntries, getPointsForView, splitIntoViews, type SourceAbility } from \"./converters/view-selector.js\";\nimport { buildWeaponRegistry, type SourceWargear } from \"./converters/weapon-dedup.js\";\nimport { buildWargearOptions } from \"./converters/wargear-options.js\";\nimport { getKeywordsForFaction, type SourceKeyword } from \"./converters/keyword-filter.js\";\nimport { type FactionConfig, getFactionConfig, listFactions } from \"./converters/faction-config.js\";\n\n// Register all faction configs\nimport \"./converters/configs/world-eaters.js\";\nimport \"./converters/configs/emperors-children.js\";\nimport \"./converters/configs/chaos-knights.js\";\nimport \"./converters/configs/imperial-knights.js\";\nimport \"./converters/configs/leagues-of-votann.js\";\nimport \"./converters/configs/drukhari.js\";\nimport \"./converters/configs/genestealer-cults.js\";\nimport \"./converters/configs/grey-knights.js\";\nimport \"./converters/configs/thousand-sons.js\";\nimport \"./converters/configs/death-guard.js\";\nimport \"./converters/configs/adeptus-custodes.js\";\nimport \"./converters/configs/adepta-sororitas.js\";\nimport \"./converters/configs/agents-of-the-imperium.js\";\nimport \"./converters/configs/adeptus-mechanicus.js\";\nimport \"./converters/configs/tau-empire.js\";\nimport \"./converters/configs/tyranids.js\";\nimport \"./converters/configs/necrons.js\";\nimport \"./converters/configs/chaos-daemons.js\";\nimport \"./converters/configs/orks.js\";\nimport \"./converters/configs/aeldari.js\";\nimport \"./converters/configs/chaos-space-marines.js\";\nimport \"./converters/configs/astra-militarum.js\";\nimport \"./converters/configs/adeptus-astartes.js\";\nimport \"./converters/configs/blood-angels.js\";\nimport \"./converters/configs/dark-angels.js\";\nimport \"./converters/configs/space-wolves.js\";\nimport \"./converters/configs/black-templars.js\";\nimport \"./converters/configs/deathwatch.js\";\nimport \"./converters/configs/ultramarines.js\";\nimport \"./converters/configs/imperial-fists.js\";\nimport \"./converters/configs/crimson-fists.js\";\nimport \"./converters/configs/iron-hands.js\";\nimport \"./converters/configs/raven-guard.js\";\nimport \"./converters/configs/salamanders.js\";\nimport \"./converters/configs/white-scars.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst ROOT = resolve(__dirname, \"../..\");\nconst SOURCE = resolve(process.env.HOME!, \"army-assist/src/assets/json\");\nconst GAME_VERSION = { edition: \"10th\", dataslate: \"2025-q3\" };\n\n// ─── Source data types ───────────────────────────────────────────────\n\ninterface SourceDatasheet {\n id: string;\n name: string;\n faction_id: string;\n loadout: string;\n transport: string;\n role: string;\n damaged_w: string;\n damaged_description: string;\n}\n\ninterface SourceModel {\n datasheet_id: string;\n line: string;\n name: string;\n M: string;\n T: string;\n Sv: string;\n inv_sv: string;\n inv_sv_descr: string;\n W: string;\n Ld: string;\n OC: string;\n base_size: string;\n base_size_descr: string;\n}\n\ninterface SourcePoints {\n datasheet_id: string;\n models: string;\n cost: string;\n}\n\ninterface SourceLeader {\n leader_id: string;\n attached_id: string;\n}\n\ninterface SourceEnhancement {\n id: string;\n name: string;\n faction_id: string;\n cost: string;\n detachment: string;\n phases: string[];\n}\n\ninterface SourceStratagem {\n id: string;\n name: string;\n faction_id: string;\n type: string;\n cp_cost: string;\n turn: string;\n phase: string;\n detachment: string;\n phases: string[];\n}\n\ninterface SourceDetachmentAbility {\n name: string;\n faction_id: string;\n detachment: string;\n phases: string[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction readJSON<T>(filename: string): T {\n return JSON.parse(readFileSync(resolve(SOURCE, filename), \"utf-8\"));\n}\n\nfunction writeOutput(relPath: string, data: unknown): void {\n const outPath = resolve(ROOT, relPath);\n writeFileSync(outPath, JSON.stringify(data, null, 2) + \"\\n\");\n console.log(` ✓ ${relPath} (${Array.isArray(data) ? data.length : 1} entries)`);\n}\n\n/** Determine unit role from keywords and abilities. */\nfunction deriveRole(\n keywords: string[],\n abilities: SourceAbility[],\n name: string\n): string | undefined {\n const kw = new Set(keywords.map((k) => k.toLowerCase()));\n if (kw.has(\"epic hero\")) return \"epic-hero\";\n\n if (kw.has(\"character\")) return \"character\";\n const hasLeader = abilities.some(\n (a) => a.type === \"Core\" && a.name === \"Leader\"\n );\n if (hasLeader) return \"character\";\n\n if (kw.has(\"battleline\")) return \"battleline\";\n\n if (\n name.toLowerCase().includes(\"rhino\") ||\n kw.has(\"dedicated transport\")\n )\n return \"dedicated-transport\";\n\n return undefined;\n}\n\n/** Parse base size string. \"32mm\" → { shape: \"round\", diameter: 32 } */\nfunction parseBaseSize(\n s: string\n): { shape: string; diameter?: number; width?: number; length?: number } | undefined {\n if (!s || s.trim() === \"\") return undefined;\n const round = s.match(/(\\d+)\\s*mm/i);\n if (round) return { shape: \"round\", diameter: parseInt(round[1], 10) };\n const oval = s.match(/(\\d+)\\s*x\\s*(\\d+)/i);\n if (oval)\n return {\n shape: \"oval\",\n width: parseInt(oval[1], 10),\n length: parseInt(oval[2], 10),\n };\n return undefined;\n}\n\n/** Parse transport capacity from transport text. */\nfunction parseTransport(\n s: string\n): { capacity: number; keyword_restrictions?: string[]; exclusion_keywords?: string[] } | undefined {\n if (!s || s.trim() === \"\") return undefined;\n // Match \"capacity of N\" or \"N <FACTION> INFANTRY\"\n const capMatch = s.match(/capacity\\s*(?:of\\s*)?(\\d+)/i) || s.match(/(\\d+)\\s+\\S[\\S\\s]*?\\s+(?:infantry|model)/i);\n if (!capMatch) return undefined;\n const capacity = parseInt(capMatch[1], 10);\n const result: { capacity: number; keyword_restrictions?: string[]; exclusion_keywords?: string[] } = { capacity };\n\n if (/jump pack/i.test(s) && /cannot/i.test(s)) {\n result.exclusion_keywords = [\"Jump Pack\"];\n }\n return result;\n}\n\n// ─── Main conversion ─────────────────────────────────────────────────\n\nexport function convertFaction(\n config: FactionConfig,\n options: { wargearOnly?: boolean } = {},\n): void {\n const { sourceFactionId, factionId, factionName, factionAbilityName } = config;\n\n console.log(`Converting ${factionName} (${sourceFactionId} → ${factionId})...`);\n console.log(\"Loading source data from army-assist...\");\n\n const datasheets = readJSON<SourceDatasheet[]>(\"Datasheets.json\");\n const allModels = readJSON<SourceModel[]>(\"Datasheets_models.json\");\n const allWargear = readJSON<SourceWargear[]>(\"Datasheets_wargear.json\");\n const allOptions = readJSON<\n { datasheet_id: string; line: string; description: string | null }[]\n >(\"Datasheets_options.json\");\n const allComposition = readJSON<\n { datasheet_id: string; line: string; description: string }[]\n >(\"Datasheets_unit_composition.json\");\n const allAbilities = readJSON<SourceAbility[]>(\"Datasheets_abilities.json\");\n const allKeywords = readJSON<SourceKeyword[]>(\"Datasheets_keywords.json\");\n const allPoints = readJSON<SourcePoints[]>(\"Datasheets_points.json\");\n const allLeaders = readJSON<SourceLeader[]>(\"Datasheets_leader.json\");\n const enhancements = readJSON<SourceEnhancement[]>(\"Enhancements.json\");\n const stratagems = readJSON<SourceStratagem[]>(\"Stratagems.json\");\n const detachmentAbilities = readJSON<SourceDetachmentAbility[]>(\n \"Detachment_abilities.json\"\n );\n\n // Filter to target faction, skip datasheets with no model data (metadata entries)\n const modelDatasheetIds = new Set(allModels.map((m) => m.datasheet_id));\n const factionDatasheets = datasheets.filter(\n (d) => d.faction_id === sourceFactionId && modelDatasheetIds.has(d.id)\n );\n const factionIds = new Set(factionDatasheets.map((d) => d.id));\n const idToName = new Map(factionDatasheets.map((d) => [d.id, d.name]));\n\n console.log(`Found ${factionDatasheets.length} ${factionName} datasheets\\n`);\n\n // ─── Determine view indices for shared units ───\n const viewIndices = new Map<string, number>();\n for (const ds of factionDatasheets) {\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n viewIndices.set(ds.id, findFactionViewIndex(dsAbilities, factionAbilityName));\n }\n\n // ─── Build units ───\n console.log(\"Converting units...\");\n const units: Record<string, unknown>[] = [];\n\n const unitWargearMap = new Map<string, SourceWargear[]>();\n const unitAbilityNames = new Map<string, string[]>();\n\n for (const ds of factionDatasheets) {\n const viewIdx = viewIndices.get(ds.id)!;\n\n // Models\n const dsModels = allModels.filter((m) => m.datasheet_id === ds.id);\n const viewModels = getViewEntries(dsModels, viewIdx);\n\n // Abilities (for role derivation and ability name collection)\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n const viewAbilities = getViewEntries(dsAbilities, viewIdx);\n\n const abilityNames = viewAbilities\n .filter((a) => a.type !== \"Faction\")\n .map((a) => a.name);\n unitAbilityNames.set(ds.id, [...new Set(abilityNames)]);\n\n // Keywords — use faction-aware filtering for shared units\n const dsKeywords = allKeywords.filter(\n (k) => k.datasheet_id === ds.id\n );\n const { factionKeywords, regularKeywords } = getKeywordsForFaction(\n dsKeywords,\n factionName\n );\n\n // Points — select the correct view for shared units\n const allDsPoints = allPoints.filter((p) => p.datasheet_id === ds.id);\n const dsAbilityViews = splitIntoViews(\n allAbilities.filter((a) => a.datasheet_id === ds.id)\n );\n const numViews = dsAbilityViews.length;\n const viewPoints = getPointsForView(allDsPoints, viewIdx, numViews);\n const dsPoints = viewPoints\n .map((p) => ({\n models: parseInt(p.models, 10),\n cost: parseInt(p.cost, 10),\n }))\n .sort((a, b) => a.models - b.models);\n\n // Wargear for this unit's view\n const dsWargear = allWargear.filter(\n (w) => w.datasheet_id === ds.id\n );\n const viewWargear = getViewEntries(dsWargear, viewIdx);\n unitWargearMap.set(ds.id, viewWargear);\n\n // Build stat profiles\n const profiles = viewModels.map((m) => {\n const profile: Record<string, unknown> = {\n name: m.name,\n M: parseMove(m.M),\n T: parseIntStat(m.T),\n W: parseIntStat(m.W),\n Sv: parseTargetNumber(m.Sv)!,\n invuln_sv: parseInvuln(m.inv_sv),\n Ld: parseTargetNumber(m.Ld)!,\n OC: parseIntStat(m.OC),\n };\n return profile;\n });\n\n const role = deriveRole(regularKeywords, viewAbilities, ds.name);\n const baseSize = parseBaseSize(\n viewModels[0]?.base_size ?? \"\"\n );\n const transport = parseTransport(ds.transport);\n\n const modelMin = dsPoints.length > 0 ? dsPoints[0].models : 1;\n const modelMax =\n dsPoints.length > 0 ? dsPoints[dsPoints.length - 1].models : 1;\n\n const unitId = nameToId(ds.name);\n const unit: Record<string, unknown> = {\n id: unitId,\n name: ds.name,\n faction_id: factionId,\n ...(role ? { role } : {}),\n profiles,\n points: dsPoints,\n keywords: regularKeywords,\n faction_keywords: factionKeywords,\n ...(baseSize ? { base_size_mm: baseSize } : {}),\n model_count: { min: modelMin, max: modelMax },\n weapon_ids: [],\n ability_ids: [],\n ...(transport ? { transport_capacity: transport } : {}),\n game_version: GAME_VERSION,\n is_legend: false,\n };\n\n units.push(unit);\n }\n\n // ─── Build weapons ───\n console.log(\"Converting weapons...\");\n const { weapons, unitWeaponIds } = buildWeaponRegistry(\n unitWargearMap,\n GAME_VERSION\n );\n\n // Wire weapon_ids into units\n for (const unit of units) {\n const dsId = factionDatasheets.find((d) => d.name === (unit as { name: string }).name)!.id;\n const weaponIds = unitWeaponIds.get(dsId);\n if (weaponIds) {\n (unit as { weapon_ids: string[] }).weapon_ids = [...weaponIds].sort();\n }\n }\n\n // ─── Build wargear options + non-weapon wargear ───\n console.log(\"Converting wargear options...\");\n const globalWeaponIds = new Set(weapons.map((w) => w.id));\n const {\n wargearOptions,\n wargear: wargearItems,\n unparsed: unparsedOptions,\n } = buildWargearOptions(\n factionDatasheets.map((d) => ({ id: d.id, name: d.name })),\n allModels,\n allOptions,\n allComposition,\n unitWeaponIds,\n globalWeaponIds,\n GAME_VERSION\n );\n\n // ─── Build leader attachments ───\n console.log(\"Converting leader attachments...\");\n const leaderMap = new Map<string, Set<string>>();\n\n for (const l of allLeaders) {\n if (factionIds.has(l.leader_id) && factionIds.has(l.attached_id)) {\n const leaderId = nameToId(idToName.get(l.leader_id)!);\n const attachedId = nameToId(idToName.get(l.attached_id)!);\n if (!leaderMap.has(leaderId)) {\n leaderMap.set(leaderId, new Set());\n }\n leaderMap.get(leaderId)!.add(attachedId);\n }\n }\n\n const leaderAttachments = [...leaderMap.entries()].map(\n ([leaderId, bodyguards]) => ({\n leader_id: leaderId,\n eligible_bodyguard_ids: [...bodyguards].sort(),\n max_leaders_per_unit: 1,\n game_version: GAME_VERSION,\n })\n );\n // ─── Build detachments ───\n console.log(\"Converting detachments...\");\n const factionDetAbilities = detachmentAbilities.filter(\n (d) => d.faction_id === sourceFactionId\n );\n let factionEnhancements = enhancements.filter(\n (e) => e.faction_id === sourceFactionId\n );\n let factionStratagems = stratagems.filter(\n (s) => s.faction_id === sourceFactionId\n );\n\n // Deduplicate detachments (some have multiple ability entries per detachment)\n let detachmentNames = [...new Set(factionDetAbilities.map((da) => da.detachment))];\n\n // Apply detachment filter for subfactions\n if (config.detachmentFilter) {\n detachmentNames = detachmentNames.filter((d) =>\n config.detachmentFilter!.includes(d)\n );\n // Also filter enhancements and stratagems to matching detachments\n const allowedDetachments = new Set(config.detachmentFilter);\n factionEnhancements = factionEnhancements.filter((e) => allowedDetachments.has(e.detachment));\n factionStratagems = factionStratagems.filter((s) => allowedDetachments.has(s.detachment));\n }\n\n const detachments = detachmentNames.map((detName) => {\n const detId = nameToId(detName);\n const detEnhIds = factionEnhancements\n .filter((e) => e.detachment === detName)\n .map((e) => nameToId(e.name));\n const detStratIds = factionStratagems\n .filter((s) => s.detachment === detName)\n .map((s) => nameToId(s.name));\n\n return {\n id: detId,\n name: detName,\n faction_id: factionId,\n detachment_rule_id: null as string | null,\n enhancement_ids: detEnhIds,\n stratagem_ids: detStratIds,\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Build enhancements ───\n console.log(\"Converting enhancements...\");\n const enhancementEntities = factionEnhancements.map((e) => ({\n id: nameToId(e.name),\n name: e.name,\n detachment_id: nameToId(e.detachment),\n cost: parseInt(e.cost, 10),\n keyword_restrictions: [factionName],\n ability_id: null as string | null,\n is_unique: true,\n game_version: GAME_VERSION,\n }));\n\n // ─── Build stratagems ───\n console.log(\"Converting stratagems...\");\n const stratagemEntities = factionStratagems.map((s) => {\n const { type } = parseStratagemType(s.type);\n const phases = mapPhases(s.phases);\n const playerTurn = parsePlayerTurn(s.turn);\n\n return {\n id: nameToId(s.name),\n name: s.name,\n category: \"detachment\" as const,\n type,\n detachment_id: nameToId(s.detachment),\n cp_cost: parseInt(s.cp_cost, 10),\n phases,\n player_turn: playerTurn,\n timing: \"once-per-phase\" as const,\n target_restrictions: null as null,\n ability_id: null as string | null,\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Build phase mappings from source ability phases ───\n console.log(\"Converting phase mappings...\");\n const phaseMappings: Record<string, unknown>[] = [];\n\n // Unit abilities\n for (const ds of factionDatasheets) {\n const viewIdx = viewIndices.get(ds.id)!;\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n const viewAbilities = getViewEntries(dsAbilities, viewIdx);\n const seen = new Set<string>();\n\n for (const a of viewAbilities) {\n if (a.type === \"Faction\") continue;\n const sourceId = nameToId(a.name);\n if (seen.has(sourceId)) continue;\n seen.add(sourceId);\n\n const sourceType =\n a.type === \"Core\" ? \"ability\" :\n a.type === \"Wargear\" ? \"ability\" :\n \"ability\";\n\n const phases = mapPhases(a.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: sourceId,\n source_type: sourceType,\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n }\n\n // Stratagem phase mappings\n for (const s of factionStratagems) {\n const phases = mapPhases(s.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(s.name),\n source_type: \"stratagem\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Enhancement phase mappings\n for (const e of factionEnhancements) {\n const phases = mapPhases(e.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(e.name),\n source_type: \"enhancement\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Detachment rule phase mappings\n for (const da of factionDetAbilities) {\n const phases = mapPhases(da.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(da.name),\n source_type: \"detachment-rule\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Deduplicate phase mappings\n const dedupedPhaseMappings = [\n ...new Map(\n phaseMappings.map((pm) => [\n `${(pm as { source_id: string }).source_id}|${(pm as { source_type: string }).source_type}`,\n pm,\n ])\n ).values(),\n ];\n\n // ─── Build unit compositions ───\n console.log(\"Generating unit compositions...\");\n\n const unitCompositions = units.map((u) => {\n const unitId = (u as { id: string }).id;\n const modelCount = (u as { model_count: { min: number; max: number } }).model_count;\n\n const override = config.compositionOverrides[unitId];\n if (override) {\n return {\n unit_id: unitId,\n models: override,\n game_version: GAME_VERSION,\n };\n }\n\n // Single-model unit (vehicles, characters, monsters)\n const profileName = (u as { profiles: { name: string }[] }).profiles[0]?.name;\n return {\n unit_id: unitId,\n models: [\n {\n name: profileName || (u as { name: string }).name,\n min: modelCount.min,\n max: modelCount.max,\n is_leader_model: false,\n },\n ],\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Generate factions.json ───\n const factionEntity = [\n {\n id: factionId,\n name: factionName,\n parent_faction_id: config.parentFactionId,\n game_version: GAME_VERSION,\n keywords: config.factionKeywords,\n aliases: config.aliases,\n faction_rule_id: config.factionRuleId,\n },\n ];\n\n // ─── Write output ──────────────────────────────────────────────────\n const coreDir = `data/core/${factionId}`;\n const enrichDir = `data/enrichment/${factionId}`;\n mkdirSync(resolve(ROOT, coreDir), { recursive: true });\n mkdirSync(resolve(ROOT, enrichDir), { recursive: true });\n\n console.log(\"\\nWriting output files...\");\n\n // `wargearOnly` adds just the wargear data: the rest of the converter's output\n // has drifted from the committed dataset (e.g. weapon keywords are object refs\n // there, strings here), so a full rewrite would regress those files. Adding\n // wargear must not touch them.\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/factions.json`, factionEntity);\n }\n if (!config.skipUnits) {\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/units.json`, units);\n writeOutput(`${coreDir}/weapons.json`, weapons);\n writeOutput(`${coreDir}/leader-attachments.json`, leaderAttachments);\n writeOutput(`${coreDir}/unit-compositions.json`, unitCompositions);\n }\n writeOutput(`${coreDir}/wargear-options.json`, wargearOptions);\n if (wargearItems.length > 0) {\n writeOutput(`${coreDir}/wargear.json`, wargearItems);\n }\n if (unparsedOptions.length > 0) {\n // Underscore-prefixed: a report for manual review, skipped by validation\n // and not bundled into the dataset.\n writeOutput(`${coreDir}/_wargear-options.unparsed.json`, unparsedOptions);\n }\n }\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/detachments.json`, detachments);\n writeOutput(`${coreDir}/enhancements.json`, enhancementEntities);\n writeOutput(`${coreDir}/stratagems.json`, stratagemEntities);\n if (!config.skipUnits) {\n writeOutput(`${enrichDir}/phase-mappings.json`, dedupedPhaseMappings);\n }\n }\n\n // ─── Summary ───\n console.log(`\\n── ${factionName} Summary ──`);\n if (!config.skipUnits) {\n console.log(` Units: ${units.length}`);\n console.log(` Weapons: ${weapons.length}`);\n console.log(` Leader attachments: ${leaderAttachments.length}`);\n console.log(` Unit compositions: ${unitCompositions.length}`);\n console.log(` Wargear options: ${wargearOptions.length}`);\n console.log(` Wargear items: ${wargearItems.length}`);\n console.log(` Unparsed options: ${unparsedOptions.length}`);\n }\n console.log(` Detachments: ${detachments.length}`);\n console.log(` Enhancements: ${enhancementEntities.length}`);\n console.log(` Stratagems: ${stratagemEntities.length}`);\n if (!config.skipUnits) {\n console.log(` Phase mappings: ${dedupedPhaseMappings.length}`);\n }\n console.log(\"\\nDone. Run 'npm run validate' to check output.\");\n}\n\n// ─── CLI entry point ─────────────────────────────────────────────────\n\n// Only run CLI when this module is the entry point (not when imported)\nconst isMain = process.argv[1] &&\n resolve(process.argv[1]).replace(/\\.\\w+$/, \"\") ===\n fileURLToPath(import.meta.url).replace(/\\.\\w+$/, \"\");\n\nif (isMain) {\n const args = process.argv.slice(2);\n const wargearOnly = args.includes(\"--wargear-only\");\n const positional = args.filter((a) => !a.startsWith(\"--\"));\n\n if (positional.length === 0 || args[0] === \"--help\") {\n console.log(\n \"Usage: npx tsx tools/src/convert-faction.ts <faction-id|all> [--wargear-only]\",\n );\n console.log(`Available factions: ${listFactions().join(\", \")}`);\n process.exit(args[0] === \"--help\" ? 0 : 1);\n }\n\n const target = positional[0];\n const factionIds = target === \"all\" ? listFactions() : [target];\n for (const id of factionIds) {\n convertFaction(getFactionConfig(id), { wargearOnly });\n }\n}\n"]}
1
+ {"version":3,"file":"convert-faction.js","sourceRoot":"","sources":["../src/convert-faction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generic faction converter: army-assist → 40kdc-data format.\n *\n * Usage: npx tsx tools/src/convert-faction.ts <faction-id>\n * Example: npx tsx tools/src/convert-faction.ts emperors-children\n *\n * Faction configs are registered via side-effect imports from ./converters/configs/.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { nameToId, parseStratagemType, parsePlayerTurn, mapPhases } from \"./converters/id-generator.js\";\nimport { parseMove, parseTargetNumber, parseIntStat, parseInvuln } from \"./converters/stat-parser.js\";\nimport { findFactionViewIndex, getViewEntries, getPointsForView, splitIntoViews, type SourceAbility } from \"./converters/view-selector.js\";\nimport { buildWeaponRegistry, type SourceWargear } from \"./converters/weapon-dedup.js\";\nimport { buildWargearOptions } from \"./converters/wargear-options.js\";\nimport { getKeywordsForFaction, type SourceKeyword } from \"./converters/keyword-filter.js\";\nimport { type FactionConfig, getFactionConfig, listFactions } from \"./converters/faction-config.js\";\n\n// Register all faction configs\nimport \"./converters/configs/world-eaters.js\";\nimport \"./converters/configs/emperors-children.js\";\nimport \"./converters/configs/chaos-knights.js\";\nimport \"./converters/configs/imperial-knights.js\";\nimport \"./converters/configs/leagues-of-votann.js\";\nimport \"./converters/configs/drukhari.js\";\nimport \"./converters/configs/genestealer-cults.js\";\nimport \"./converters/configs/grey-knights.js\";\nimport \"./converters/configs/thousand-sons.js\";\nimport \"./converters/configs/death-guard.js\";\nimport \"./converters/configs/adeptus-custodes.js\";\nimport \"./converters/configs/adepta-sororitas.js\";\nimport \"./converters/configs/agents-of-the-imperium.js\";\nimport \"./converters/configs/adeptus-mechanicus.js\";\nimport \"./converters/configs/tau-empire.js\";\nimport \"./converters/configs/tyranids.js\";\nimport \"./converters/configs/necrons.js\";\nimport \"./converters/configs/chaos-daemons.js\";\nimport \"./converters/configs/orks.js\";\nimport \"./converters/configs/aeldari.js\";\nimport \"./converters/configs/chaos-space-marines.js\";\nimport \"./converters/configs/astra-militarum.js\";\nimport \"./converters/configs/adeptus-astartes.js\";\nimport \"./converters/configs/blood-angels.js\";\nimport \"./converters/configs/dark-angels.js\";\nimport \"./converters/configs/space-wolves.js\";\nimport \"./converters/configs/black-templars.js\";\nimport \"./converters/configs/deathwatch.js\";\nimport \"./converters/configs/ultramarines.js\";\nimport \"./converters/configs/imperial-fists.js\";\nimport \"./converters/configs/crimson-fists.js\";\nimport \"./converters/configs/iron-hands.js\";\nimport \"./converters/configs/raven-guard.js\";\nimport \"./converters/configs/salamanders.js\";\nimport \"./converters/configs/white-scars.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst ROOT = resolve(__dirname, \"../..\");\nconst SOURCE = resolve(process.env.HOME!, \"army-assist/src/assets/json\");\nconst GAME_VERSION = { edition: \"10th\", dataslate: \"2025-q3\" };\n\n// ─── Source data types ───────────────────────────────────────────────\n\ninterface SourceDatasheet {\n id: string;\n name: string;\n faction_id: string;\n loadout: string;\n transport: string;\n role: string;\n damaged_w: string;\n damaged_description: string;\n}\n\ninterface SourceModel {\n datasheet_id: string;\n line: string;\n name: string;\n M: string;\n T: string;\n Sv: string;\n inv_sv: string;\n inv_sv_descr: string;\n W: string;\n Ld: string;\n OC: string;\n base_size: string;\n base_size_descr: string;\n}\n\ninterface SourcePoints {\n datasheet_id: string;\n models: string;\n cost: string;\n}\n\ninterface SourceLeader {\n leader_id: string;\n attached_id: string;\n}\n\ninterface SourceEnhancement {\n id: string;\n name: string;\n faction_id: string;\n cost: string;\n detachment: string;\n phases: string[];\n}\n\ninterface SourceStratagem {\n id: string;\n name: string;\n faction_id: string;\n type: string;\n cp_cost: string;\n turn: string;\n phase: string;\n detachment: string;\n phases: string[];\n}\n\ninterface SourceDetachmentAbility {\n name: string;\n faction_id: string;\n detachment: string;\n phases: string[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction readJSON<T>(filename: string): T {\n return JSON.parse(readFileSync(resolve(SOURCE, filename), \"utf-8\"));\n}\n\nfunction writeOutput(relPath: string, data: unknown): void {\n const outPath = resolve(ROOT, relPath);\n writeFileSync(outPath, JSON.stringify(data, null, 2) + \"\\n\");\n console.log(` ✓ ${relPath} (${Array.isArray(data) ? data.length : 1} entries)`);\n}\n\n/** Determine unit role from keywords and abilities. */\nfunction deriveRole(\n keywords: string[],\n abilities: SourceAbility[],\n name: string\n): string | undefined {\n const kw = new Set(keywords.map((k) => k.toLowerCase()));\n if (kw.has(\"epic hero\")) return \"epic-hero\";\n\n if (kw.has(\"character\")) return \"character\";\n const hasLeader = abilities.some(\n (a) => a.type === \"Core\" && a.name === \"Leader\"\n );\n if (hasLeader) return \"character\";\n\n if (kw.has(\"battleline\")) return \"battleline\";\n\n if (\n name.toLowerCase().includes(\"rhino\") ||\n kw.has(\"dedicated transport\")\n )\n return \"dedicated-transport\";\n\n return undefined;\n}\n\n/** Parse base size string. \"32mm\" → { shape: \"round\", diameter: 32 } */\nfunction parseBaseSize(\n s: string\n): { shape: string; diameter?: number; width?: number; length?: number } | undefined {\n if (!s || s.trim() === \"\") return undefined;\n const round = s.match(/(\\d+)\\s*mm/i);\n if (round) return { shape: \"round\", diameter: parseInt(round[1], 10) };\n const oval = s.match(/(\\d+)\\s*x\\s*(\\d+)/i);\n if (oval)\n return {\n shape: \"oval\",\n width: parseInt(oval[1], 10),\n length: parseInt(oval[2], 10),\n };\n return undefined;\n}\n\n/** Parse transport capacity from transport text. */\nfunction parseTransport(\n s: string\n): { capacity: number; keyword_restrictions?: string[]; exclusion_keywords?: string[] } | undefined {\n if (!s || s.trim() === \"\") return undefined;\n // Match \"capacity of N\" or \"N <FACTION> INFANTRY\"\n const capMatch = s.match(/capacity\\s*(?:of\\s*)?(\\d+)/i) || s.match(/(\\d+)\\s+\\S[\\S\\s]*?\\s+(?:infantry|model)/i);\n if (!capMatch) return undefined;\n const capacity = parseInt(capMatch[1], 10);\n const result: { capacity: number; keyword_restrictions?: string[]; exclusion_keywords?: string[] } = { capacity };\n\n if (/jump pack/i.test(s) && /cannot/i.test(s)) {\n result.exclusion_keywords = [\"Jump Pack\"];\n }\n return result;\n}\n\n// ─── Main conversion ─────────────────────────────────────────────────\n\nexport function convertFaction(\n config: FactionConfig,\n options: { wargearOnly?: boolean } = {},\n): void {\n const { sourceFactionId, factionId, factionName, factionAbilityName } = config;\n\n console.log(`Converting ${factionName} (${sourceFactionId} → ${factionId})...`);\n console.log(\"Loading source data from army-assist...\");\n\n const datasheets = readJSON<SourceDatasheet[]>(\"Datasheets.json\");\n const allModels = readJSON<SourceModel[]>(\"Datasheets_models.json\");\n const allWargear = readJSON<SourceWargear[]>(\"Datasheets_wargear.json\");\n const allOptions = readJSON<\n { datasheet_id: string; line: string; description: string | null }[]\n >(\"Datasheets_options.json\");\n const allComposition = readJSON<\n { datasheet_id: string; line: string; description: string }[]\n >(\"Datasheets_unit_composition.json\");\n const allAbilities = readJSON<SourceAbility[]>(\"Datasheets_abilities.json\");\n const allKeywords = readJSON<SourceKeyword[]>(\"Datasheets_keywords.json\");\n const allPoints = readJSON<SourcePoints[]>(\"Datasheets_points.json\");\n const allLeaders = readJSON<SourceLeader[]>(\"Datasheets_leader.json\");\n const enhancements = readJSON<SourceEnhancement[]>(\"Enhancements.json\");\n const stratagems = readJSON<SourceStratagem[]>(\"Stratagems.json\");\n const detachmentAbilities = readJSON<SourceDetachmentAbility[]>(\n \"Detachment_abilities.json\"\n );\n\n // Filter to target faction, skip datasheets with no model data (metadata entries)\n const modelDatasheetIds = new Set(allModels.map((m) => m.datasheet_id));\n const factionDatasheets = datasheets.filter(\n (d) => d.faction_id === sourceFactionId && modelDatasheetIds.has(d.id)\n );\n const factionIds = new Set(factionDatasheets.map((d) => d.id));\n const idToName = new Map(factionDatasheets.map((d) => [d.id, d.name]));\n\n console.log(`Found ${factionDatasheets.length} ${factionName} datasheets\\n`);\n\n // ─── Determine view indices for shared units ───\n const viewIndices = new Map<string, number>();\n for (const ds of factionDatasheets) {\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n viewIndices.set(ds.id, findFactionViewIndex(dsAbilities, factionAbilityName));\n }\n\n // ─── Build units ───\n console.log(\"Converting units...\");\n const units: Record<string, unknown>[] = [];\n\n const unitWargearMap = new Map<string, SourceWargear[]>();\n const unitAbilityNames = new Map<string, string[]>();\n\n for (const ds of factionDatasheets) {\n const viewIdx = viewIndices.get(ds.id)!;\n\n // Models\n const dsModels = allModels.filter((m) => m.datasheet_id === ds.id);\n const viewModels = getViewEntries(dsModels, viewIdx);\n\n // Abilities (for role derivation and ability name collection)\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n const viewAbilities = getViewEntries(dsAbilities, viewIdx);\n\n const abilityNames = viewAbilities\n .filter((a) => a.type !== \"Faction\")\n .map((a) => a.name);\n unitAbilityNames.set(ds.id, [...new Set(abilityNames)]);\n\n // Keywords — use faction-aware filtering for shared units\n const dsKeywords = allKeywords.filter(\n (k) => k.datasheet_id === ds.id\n );\n const { factionKeywords, regularKeywords } = getKeywordsForFaction(\n dsKeywords,\n factionName\n );\n\n // Points — select the correct view for shared units\n const allDsPoints = allPoints.filter((p) => p.datasheet_id === ds.id);\n const dsAbilityViews = splitIntoViews(\n allAbilities.filter((a) => a.datasheet_id === ds.id)\n );\n const numViews = dsAbilityViews.length;\n const viewPoints = getPointsForView(allDsPoints, viewIdx, numViews);\n const dsPoints = viewPoints\n .map((p) => ({\n models: parseInt(p.models, 10),\n cost: parseInt(p.cost, 10),\n }))\n .sort((a, b) => a.models - b.models);\n\n // Wargear for this unit's view\n const dsWargear = allWargear.filter(\n (w) => w.datasheet_id === ds.id\n );\n const viewWargear = getViewEntries(dsWargear, viewIdx);\n unitWargearMap.set(ds.id, viewWargear);\n\n // Build stat profiles\n const profiles = viewModels.map((m) => {\n const profile: Record<string, unknown> = {\n name: m.name,\n M: parseMove(m.M),\n T: parseIntStat(m.T),\n W: parseIntStat(m.W),\n Sv: parseTargetNumber(m.Sv)!,\n invuln_sv: parseInvuln(m.inv_sv),\n Ld: parseTargetNumber(m.Ld)!,\n OC: parseIntStat(m.OC),\n };\n return profile;\n });\n\n const role = deriveRole(regularKeywords, viewAbilities, ds.name);\n const baseSize = parseBaseSize(\n viewModels[0]?.base_size ?? \"\"\n );\n const transport = parseTransport(ds.transport);\n\n const modelMin = dsPoints.length > 0 ? dsPoints[0].models : 1;\n const modelMax =\n dsPoints.length > 0 ? dsPoints[dsPoints.length - 1].models : 1;\n\n const unitId = nameToId(ds.name);\n const unit: Record<string, unknown> = {\n id: unitId,\n name: ds.name,\n faction_id: factionId,\n ...(role ? { role } : {}),\n profiles,\n points: dsPoints,\n keywords: regularKeywords,\n faction_keywords: factionKeywords,\n ...(baseSize ? { base_size_mm: baseSize } : {}),\n model_count: { min: modelMin, max: modelMax },\n weapon_ids: [],\n ability_ids: [],\n ...(transport ? { transport_capacity: transport } : {}),\n game_version: GAME_VERSION,\n is_legend: false,\n };\n\n units.push(unit);\n }\n\n // ─── Build weapons ───\n console.log(\"Converting weapons...\");\n const { weapons, unitWeaponIds } = buildWeaponRegistry(\n unitWargearMap,\n GAME_VERSION\n );\n\n // Wire weapon_ids into units\n for (const unit of units) {\n const dsId = factionDatasheets.find((d) => d.name === (unit as { name: string }).name)!.id;\n const weaponIds = unitWeaponIds.get(dsId);\n if (weaponIds) {\n (unit as { weapon_ids: string[] }).weapon_ids = [...weaponIds].sort();\n }\n }\n\n // ─── Build wargear options + non-weapon wargear ───\n console.log(\"Converting wargear options...\");\n const globalWeaponIds = new Set(weapons.map((w) => w.id));\n const {\n wargearOptions,\n wargear: wargearItems,\n unparsed: unparsedOptions,\n } = buildWargearOptions(\n factionDatasheets.map((d) => ({ id: d.id, name: d.name })),\n allModels,\n allOptions,\n allComposition,\n unitWeaponIds,\n globalWeaponIds,\n GAME_VERSION\n );\n\n // ─── Build leader attachments ───\n console.log(\"Converting leader attachments...\");\n const leaderMap = new Map<string, Set<string>>();\n\n for (const l of allLeaders) {\n if (factionIds.has(l.leader_id) && factionIds.has(l.attached_id)) {\n const leaderId = nameToId(idToName.get(l.leader_id)!);\n const attachedId = nameToId(idToName.get(l.attached_id)!);\n if (!leaderMap.has(leaderId)) {\n leaderMap.set(leaderId, new Set());\n }\n leaderMap.get(leaderId)!.add(attachedId);\n }\n }\n\n const leaderAttachments = [...leaderMap.entries()].map(\n ([leaderId, bodyguards]) => ({\n leader_id: leaderId,\n eligible_bodyguard_ids: [...bodyguards].sort(),\n max_leaders_per_unit: 1,\n game_version: GAME_VERSION,\n })\n );\n // ─── Build detachments ───\n console.log(\"Converting detachments...\");\n const factionDetAbilities = detachmentAbilities.filter(\n (d) => d.faction_id === sourceFactionId\n );\n let factionEnhancements = enhancements.filter(\n (e) => e.faction_id === sourceFactionId\n );\n let factionStratagems = stratagems.filter(\n (s) => s.faction_id === sourceFactionId\n );\n\n // Deduplicate detachments (some have multiple ability entries per detachment)\n let detachmentNames = [...new Set(factionDetAbilities.map((da) => da.detachment))];\n\n // Apply detachment filter for subfactions\n if (config.detachmentFilter) {\n detachmentNames = detachmentNames.filter((d) =>\n config.detachmentFilter!.includes(d)\n );\n // Also filter enhancements and stratagems to matching detachments\n const allowedDetachments = new Set(config.detachmentFilter);\n factionEnhancements = factionEnhancements.filter((e) => allowedDetachments.has(e.detachment));\n factionStratagems = factionStratagems.filter((s) => allowedDetachments.has(s.detachment));\n }\n\n // `detachment_points` (11e DP cost) and `force_dispositions` are maintained\n // by hand — army-assist doesn't carry them — so a re-run must NOT drop them.\n // Carry both forward by id from the existing on-disk file.\n const existingDetPath = resolve(ROOT, `data/core/${factionId}/detachments.json`);\n const preservedById = new Map<string, Record<string, unknown>>();\n if (existsSync(existingDetPath)) {\n const existing = JSON.parse(readFileSync(existingDetPath, \"utf-8\")) as Array<\n Record<string, unknown>\n >;\n for (const d of existing) {\n if (typeof d.id === \"string\") preservedById.set(d.id, d);\n }\n }\n\n const detachments = detachmentNames.map((detName) => {\n const detId = nameToId(detName);\n const detEnhIds = factionEnhancements\n .filter((e) => e.detachment === detName)\n .map((e) => nameToId(e.name));\n const detStratIds = factionStratagems\n .filter((s) => s.detachment === detName)\n .map((s) => nameToId(s.name));\n\n const record: Record<string, unknown> = {\n id: detId,\n name: detName,\n faction_id: factionId,\n detachment_rule_id: null as string | null,\n enhancement_ids: detEnhIds,\n stratagem_ids: detStratIds,\n game_version: GAME_VERSION,\n };\n const preserved = preservedById.get(detId);\n if (preserved && \"detachment_points\" in preserved) {\n record.detachment_points = preserved.detachment_points;\n }\n if (preserved && \"force_dispositions\" in preserved) {\n record.force_dispositions = preserved.force_dispositions;\n }\n return record;\n });\n\n // ─── Build enhancements ───\n console.log(\"Converting enhancements...\");\n const enhancementEntities = factionEnhancements.map((e) => ({\n id: nameToId(e.name),\n name: e.name,\n detachment_id: nameToId(e.detachment),\n cost: parseInt(e.cost, 10),\n keyword_restrictions: [factionName],\n ability_id: null as string | null,\n is_unique: true,\n game_version: GAME_VERSION,\n }));\n\n // ─── Build stratagems ───\n console.log(\"Converting stratagems...\");\n const stratagemEntities = factionStratagems.map((s) => {\n const { type } = parseStratagemType(s.type);\n const phases = mapPhases(s.phases);\n const playerTurn = parsePlayerTurn(s.turn);\n\n return {\n id: nameToId(s.name),\n name: s.name,\n category: \"detachment\" as const,\n type,\n detachment_id: nameToId(s.detachment),\n cp_cost: parseInt(s.cp_cost, 10),\n phases,\n player_turn: playerTurn,\n timing: \"once-per-phase\" as const,\n target_restrictions: null as null,\n ability_id: null as string | null,\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Build phase mappings from source ability phases ───\n console.log(\"Converting phase mappings...\");\n const phaseMappings: Record<string, unknown>[] = [];\n\n // Unit abilities\n for (const ds of factionDatasheets) {\n const viewIdx = viewIndices.get(ds.id)!;\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n const viewAbilities = getViewEntries(dsAbilities, viewIdx);\n const seen = new Set<string>();\n\n for (const a of viewAbilities) {\n if (a.type === \"Faction\") continue;\n const sourceId = nameToId(a.name);\n if (seen.has(sourceId)) continue;\n seen.add(sourceId);\n\n const sourceType =\n a.type === \"Core\" ? \"ability\" :\n a.type === \"Wargear\" ? \"ability\" :\n \"ability\";\n\n const phases = mapPhases(a.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: sourceId,\n source_type: sourceType,\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n }\n\n // Stratagem phase mappings\n for (const s of factionStratagems) {\n const phases = mapPhases(s.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(s.name),\n source_type: \"stratagem\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Enhancement phase mappings\n for (const e of factionEnhancements) {\n const phases = mapPhases(e.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(e.name),\n source_type: \"enhancement\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Detachment rule phase mappings\n for (const da of factionDetAbilities) {\n const phases = mapPhases(da.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(da.name),\n source_type: \"detachment-rule\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Deduplicate phase mappings\n const dedupedPhaseMappings = [\n ...new Map(\n phaseMappings.map((pm) => [\n `${(pm as { source_id: string }).source_id}|${(pm as { source_type: string }).source_type}`,\n pm,\n ])\n ).values(),\n ];\n\n // ─── Build unit compositions ───\n console.log(\"Generating unit compositions...\");\n\n const unitCompositions = units.map((u) => {\n const unitId = (u as { id: string }).id;\n const modelCount = (u as { model_count: { min: number; max: number } }).model_count;\n\n const override = config.compositionOverrides[unitId];\n if (override) {\n return {\n unit_id: unitId,\n models: override,\n game_version: GAME_VERSION,\n };\n }\n\n // Single-model unit (vehicles, characters, monsters)\n const profileName = (u as { profiles: { name: string }[] }).profiles[0]?.name;\n return {\n unit_id: unitId,\n models: [\n {\n name: profileName || (u as { name: string }).name,\n min: modelCount.min,\n max: modelCount.max,\n is_leader_model: false,\n },\n ],\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Generate factions.json ───\n const factionEntity = [\n {\n id: factionId,\n name: factionName,\n parent_faction_id: config.parentFactionId,\n game_version: GAME_VERSION,\n keywords: config.factionKeywords,\n aliases: config.aliases,\n faction_rule_id: config.factionRuleId,\n },\n ];\n\n // ─── Write output ──────────────────────────────────────────────────\n const coreDir = `data/core/${factionId}`;\n const enrichDir = `data/enrichment/${factionId}`;\n mkdirSync(resolve(ROOT, coreDir), { recursive: true });\n mkdirSync(resolve(ROOT, enrichDir), { recursive: true });\n\n console.log(\"\\nWriting output files...\");\n\n // `wargearOnly` adds just the wargear data: the rest of the converter's output\n // has drifted from the committed dataset (e.g. weapon keywords are object refs\n // there, strings here), so a full rewrite would regress those files. Adding\n // wargear must not touch them.\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/factions.json`, factionEntity);\n }\n if (!config.skipUnits) {\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/units.json`, units);\n writeOutput(`${coreDir}/weapons.json`, weapons);\n writeOutput(`${coreDir}/leader-attachments.json`, leaderAttachments);\n writeOutput(`${coreDir}/unit-compositions.json`, unitCompositions);\n }\n writeOutput(`${coreDir}/wargear-options.json`, wargearOptions);\n if (wargearItems.length > 0) {\n writeOutput(`${coreDir}/wargear.json`, wargearItems);\n }\n if (unparsedOptions.length > 0) {\n // Underscore-prefixed: a report for manual review, skipped by validation\n // and not bundled into the dataset.\n writeOutput(`${coreDir}/_wargear-options.unparsed.json`, unparsedOptions);\n }\n }\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/detachments.json`, detachments);\n writeOutput(`${coreDir}/enhancements.json`, enhancementEntities);\n writeOutput(`${coreDir}/stratagems.json`, stratagemEntities);\n if (!config.skipUnits) {\n writeOutput(`${enrichDir}/phase-mappings.json`, dedupedPhaseMappings);\n }\n }\n\n // ─── Summary ───\n console.log(`\\n── ${factionName} Summary ──`);\n if (!config.skipUnits) {\n console.log(` Units: ${units.length}`);\n console.log(` Weapons: ${weapons.length}`);\n console.log(` Leader attachments: ${leaderAttachments.length}`);\n console.log(` Unit compositions: ${unitCompositions.length}`);\n console.log(` Wargear options: ${wargearOptions.length}`);\n console.log(` Wargear items: ${wargearItems.length}`);\n console.log(` Unparsed options: ${unparsedOptions.length}`);\n }\n console.log(` Detachments: ${detachments.length}`);\n console.log(` Enhancements: ${enhancementEntities.length}`);\n console.log(` Stratagems: ${stratagemEntities.length}`);\n if (!config.skipUnits) {\n console.log(` Phase mappings: ${dedupedPhaseMappings.length}`);\n }\n console.log(\"\\nDone. Run 'npm run validate' to check output.\");\n}\n\n// ─── CLI entry point ─────────────────────────────────────────────────\n\n// Only run CLI when this module is the entry point (not when imported)\nconst isMain = process.argv[1] &&\n resolve(process.argv[1]).replace(/\\.\\w+$/, \"\") ===\n fileURLToPath(import.meta.url).replace(/\\.\\w+$/, \"\");\n\nif (isMain) {\n const args = process.argv.slice(2);\n const wargearOnly = args.includes(\"--wargear-only\");\n const positional = args.filter((a) => !a.startsWith(\"--\"));\n\n if (positional.length === 0 || args[0] === \"--help\") {\n console.log(\n \"Usage: npx tsx tools/src/convert-faction.ts <faction-id|all> [--wargear-only]\",\n );\n console.log(`Available factions: ${listFactions().join(\", \")}`);\n process.exit(args[0] === \"--help\" ? 0 : 1);\n }\n\n const target = positional[0];\n const factionIds = target === \"all\" ? listFactions() : [target];\n for (const id of factionIds) {\n convertFaction(getFactionConfig(id), { wargearOnly });\n }\n}\n"]}