@alpaca-software/40kdc-data 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abilities-resolver/index.d.ts +9 -0
- package/dist/abilities-resolver/index.d.ts.map +1 -0
- package/dist/abilities-resolver/index.js +9 -0
- package/dist/abilities-resolver/index.js.map +1 -0
- package/dist/abilities-resolver/resolver.d.ts +64 -0
- package/dist/abilities-resolver/resolver.d.ts.map +1 -0
- package/dist/abilities-resolver/resolver.js +135 -0
- package/dist/abilities-resolver/resolver.js.map +1 -0
- package/dist/bundle-schemas.d.ts +1 -0
- package/dist/bundle-schemas.d.ts.map +1 -0
- package/dist/bundle-schemas.js +1 -0
- package/dist/bundle-schemas.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen-data.d.ts +1 -0
- package/dist/codegen-data.d.ts.map +1 -0
- package/dist/codegen-data.js +2 -0
- package/dist/codegen-data.js.map +1 -0
- package/dist/commands/import.d.ts +1 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +1 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/translate.d.ts +1 -0
- package/dist/commands/translate.d.ts.map +1 -0
- package/dist/commands/translate.js +1 -0
- package/dist/commands/translate.js.map +1 -0
- package/dist/commands/validate-all.d.ts +1 -0
- package/dist/commands/validate-all.d.ts.map +1 -0
- package/dist/commands/validate-all.js +1 -0
- package/dist/commands/validate-all.js.map +1 -0
- package/dist/commands/validate-core.d.ts +1 -0
- package/dist/commands/validate-core.d.ts.map +1 -0
- package/dist/commands/validate-core.js +1 -0
- package/dist/commands/validate-core.js.map +1 -0
- package/dist/commands/validate-enrichment.d.ts +1 -0
- package/dist/commands/validate-enrichment.d.ts.map +1 -0
- package/dist/commands/validate-enrichment.js +1 -0
- package/dist/commands/validate-enrichment.js.map +1 -0
- package/dist/convert-faction.d.ts +1 -0
- package/dist/convert-faction.d.ts.map +1 -0
- package/dist/convert-faction.js +1 -0
- package/dist/convert-faction.js.map +1 -0
- package/dist/converters/configs/adepta-sororitas.d.ts +1 -0
- package/dist/converters/configs/adepta-sororitas.d.ts.map +1 -0
- package/dist/converters/configs/adepta-sororitas.js +1 -0
- package/dist/converters/configs/adepta-sororitas.js.map +1 -0
- package/dist/converters/configs/adeptus-astartes.d.ts +1 -0
- package/dist/converters/configs/adeptus-astartes.d.ts.map +1 -0
- package/dist/converters/configs/adeptus-astartes.js +1 -0
- package/dist/converters/configs/adeptus-astartes.js.map +1 -0
- package/dist/converters/configs/adeptus-custodes.d.ts +1 -0
- package/dist/converters/configs/adeptus-custodes.d.ts.map +1 -0
- package/dist/converters/configs/adeptus-custodes.js +1 -0
- package/dist/converters/configs/adeptus-custodes.js.map +1 -0
- package/dist/converters/configs/adeptus-mechanicus.d.ts +1 -0
- package/dist/converters/configs/adeptus-mechanicus.d.ts.map +1 -0
- package/dist/converters/configs/adeptus-mechanicus.js +1 -0
- package/dist/converters/configs/adeptus-mechanicus.js.map +1 -0
- package/dist/converters/configs/aeldari.d.ts +1 -0
- package/dist/converters/configs/aeldari.d.ts.map +1 -0
- package/dist/converters/configs/aeldari.js +1 -0
- package/dist/converters/configs/aeldari.js.map +1 -0
- package/dist/converters/configs/agents-of-the-imperium.d.ts +1 -0
- package/dist/converters/configs/agents-of-the-imperium.d.ts.map +1 -0
- package/dist/converters/configs/agents-of-the-imperium.js +1 -0
- package/dist/converters/configs/agents-of-the-imperium.js.map +1 -0
- package/dist/converters/configs/astra-militarum.d.ts +1 -0
- package/dist/converters/configs/astra-militarum.d.ts.map +1 -0
- package/dist/converters/configs/astra-militarum.js +1 -0
- package/dist/converters/configs/astra-militarum.js.map +1 -0
- package/dist/converters/configs/black-templars.d.ts +1 -0
- package/dist/converters/configs/black-templars.d.ts.map +1 -0
- package/dist/converters/configs/black-templars.js +1 -0
- package/dist/converters/configs/black-templars.js.map +1 -0
- package/dist/converters/configs/blood-angels.d.ts +1 -0
- package/dist/converters/configs/blood-angels.d.ts.map +1 -0
- package/dist/converters/configs/blood-angels.js +1 -0
- package/dist/converters/configs/blood-angels.js.map +1 -0
- package/dist/converters/configs/chaos-daemons.d.ts +1 -0
- package/dist/converters/configs/chaos-daemons.d.ts.map +1 -0
- package/dist/converters/configs/chaos-daemons.js +1 -0
- package/dist/converters/configs/chaos-daemons.js.map +1 -0
- package/dist/converters/configs/chaos-knights.d.ts +1 -0
- package/dist/converters/configs/chaos-knights.d.ts.map +1 -0
- package/dist/converters/configs/chaos-knights.js +1 -0
- package/dist/converters/configs/chaos-knights.js.map +1 -0
- package/dist/converters/configs/chaos-space-marines.d.ts +1 -0
- package/dist/converters/configs/chaos-space-marines.d.ts.map +1 -0
- package/dist/converters/configs/chaos-space-marines.js +1 -0
- package/dist/converters/configs/chaos-space-marines.js.map +1 -0
- package/dist/converters/configs/crimson-fists.d.ts +1 -0
- package/dist/converters/configs/crimson-fists.d.ts.map +1 -0
- package/dist/converters/configs/crimson-fists.js +1 -0
- package/dist/converters/configs/crimson-fists.js.map +1 -0
- package/dist/converters/configs/dark-angels.d.ts +1 -0
- package/dist/converters/configs/dark-angels.d.ts.map +1 -0
- package/dist/converters/configs/dark-angels.js +1 -0
- package/dist/converters/configs/dark-angels.js.map +1 -0
- package/dist/converters/configs/death-guard.d.ts +1 -0
- package/dist/converters/configs/death-guard.d.ts.map +1 -0
- package/dist/converters/configs/death-guard.js +1 -0
- package/dist/converters/configs/death-guard.js.map +1 -0
- package/dist/converters/configs/deathwatch.d.ts +1 -0
- package/dist/converters/configs/deathwatch.d.ts.map +1 -0
- package/dist/converters/configs/deathwatch.js +1 -0
- package/dist/converters/configs/deathwatch.js.map +1 -0
- package/dist/converters/configs/drukhari.d.ts +1 -0
- package/dist/converters/configs/drukhari.d.ts.map +1 -0
- package/dist/converters/configs/drukhari.js +1 -0
- package/dist/converters/configs/drukhari.js.map +1 -0
- package/dist/converters/configs/emperors-children.d.ts +1 -0
- package/dist/converters/configs/emperors-children.d.ts.map +1 -0
- package/dist/converters/configs/emperors-children.js +1 -0
- package/dist/converters/configs/emperors-children.js.map +1 -0
- package/dist/converters/configs/genestealer-cults.d.ts +1 -0
- package/dist/converters/configs/genestealer-cults.d.ts.map +1 -0
- package/dist/converters/configs/genestealer-cults.js +1 -0
- package/dist/converters/configs/genestealer-cults.js.map +1 -0
- package/dist/converters/configs/grey-knights.d.ts +1 -0
- package/dist/converters/configs/grey-knights.d.ts.map +1 -0
- package/dist/converters/configs/grey-knights.js +1 -0
- package/dist/converters/configs/grey-knights.js.map +1 -0
- package/dist/converters/configs/imperial-fists.d.ts +1 -0
- package/dist/converters/configs/imperial-fists.d.ts.map +1 -0
- package/dist/converters/configs/imperial-fists.js +1 -0
- package/dist/converters/configs/imperial-fists.js.map +1 -0
- package/dist/converters/configs/imperial-knights.d.ts +1 -0
- package/dist/converters/configs/imperial-knights.d.ts.map +1 -0
- package/dist/converters/configs/imperial-knights.js +1 -0
- package/dist/converters/configs/imperial-knights.js.map +1 -0
- package/dist/converters/configs/iron-hands.d.ts +1 -0
- package/dist/converters/configs/iron-hands.d.ts.map +1 -0
- package/dist/converters/configs/iron-hands.js +1 -0
- package/dist/converters/configs/iron-hands.js.map +1 -0
- package/dist/converters/configs/leagues-of-votann.d.ts +1 -0
- package/dist/converters/configs/leagues-of-votann.d.ts.map +1 -0
- package/dist/converters/configs/leagues-of-votann.js +1 -0
- package/dist/converters/configs/leagues-of-votann.js.map +1 -0
- package/dist/converters/configs/necrons.d.ts +1 -0
- package/dist/converters/configs/necrons.d.ts.map +1 -0
- package/dist/converters/configs/necrons.js +1 -0
- package/dist/converters/configs/necrons.js.map +1 -0
- package/dist/converters/configs/orks.d.ts +1 -0
- package/dist/converters/configs/orks.d.ts.map +1 -0
- package/dist/converters/configs/orks.js +1 -0
- package/dist/converters/configs/orks.js.map +1 -0
- package/dist/converters/configs/raven-guard.d.ts +1 -0
- package/dist/converters/configs/raven-guard.d.ts.map +1 -0
- package/dist/converters/configs/raven-guard.js +1 -0
- package/dist/converters/configs/raven-guard.js.map +1 -0
- package/dist/converters/configs/salamanders.d.ts +1 -0
- package/dist/converters/configs/salamanders.d.ts.map +1 -0
- package/dist/converters/configs/salamanders.js +1 -0
- package/dist/converters/configs/salamanders.js.map +1 -0
- package/dist/converters/configs/space-wolves.d.ts +1 -0
- package/dist/converters/configs/space-wolves.d.ts.map +1 -0
- package/dist/converters/configs/space-wolves.js +1 -0
- package/dist/converters/configs/space-wolves.js.map +1 -0
- package/dist/converters/configs/tau-empire.d.ts +1 -0
- package/dist/converters/configs/tau-empire.d.ts.map +1 -0
- package/dist/converters/configs/tau-empire.js +1 -0
- package/dist/converters/configs/tau-empire.js.map +1 -0
- package/dist/converters/configs/thousand-sons.d.ts +1 -0
- package/dist/converters/configs/thousand-sons.d.ts.map +1 -0
- package/dist/converters/configs/thousand-sons.js +1 -0
- package/dist/converters/configs/thousand-sons.js.map +1 -0
- package/dist/converters/configs/tyranids.d.ts +1 -0
- package/dist/converters/configs/tyranids.d.ts.map +1 -0
- package/dist/converters/configs/tyranids.js +1 -0
- package/dist/converters/configs/tyranids.js.map +1 -0
- package/dist/converters/configs/ultramarines.d.ts +1 -0
- package/dist/converters/configs/ultramarines.d.ts.map +1 -0
- package/dist/converters/configs/ultramarines.js +1 -0
- package/dist/converters/configs/ultramarines.js.map +1 -0
- package/dist/converters/configs/white-scars.d.ts +1 -0
- package/dist/converters/configs/white-scars.d.ts.map +1 -0
- package/dist/converters/configs/white-scars.js +1 -0
- package/dist/converters/configs/white-scars.js.map +1 -0
- package/dist/converters/configs/world-eaters.d.ts +1 -0
- package/dist/converters/configs/world-eaters.d.ts.map +1 -0
- package/dist/converters/configs/world-eaters.js +1 -0
- package/dist/converters/configs/world-eaters.js.map +1 -0
- package/dist/converters/faction-config.d.ts +1 -0
- package/dist/converters/faction-config.d.ts.map +1 -0
- package/dist/converters/faction-config.js +1 -0
- package/dist/converters/faction-config.js.map +1 -0
- package/dist/converters/id-generator.d.ts +1 -0
- package/dist/converters/id-generator.d.ts.map +1 -0
- package/dist/converters/id-generator.js +1 -0
- package/dist/converters/id-generator.js.map +1 -0
- package/dist/converters/keyword-filter.d.ts +1 -0
- package/dist/converters/keyword-filter.d.ts.map +1 -0
- package/dist/converters/keyword-filter.js +1 -0
- package/dist/converters/keyword-filter.js.map +1 -0
- package/dist/converters/stat-parser.d.ts +1 -0
- package/dist/converters/stat-parser.d.ts.map +1 -0
- package/dist/converters/stat-parser.js +1 -0
- package/dist/converters/stat-parser.js.map +1 -0
- package/dist/converters/view-selector.d.ts +1 -0
- package/dist/converters/view-selector.d.ts.map +1 -0
- package/dist/converters/view-selector.js +1 -0
- package/dist/converters/view-selector.js.map +1 -0
- package/dist/converters/weapon-dedup.d.ts +1 -0
- package/dist/converters/weapon-dedup.d.ts.map +1 -0
- package/dist/converters/weapon-dedup.js +1 -0
- package/dist/converters/weapon-dedup.js.map +1 -0
- package/dist/cruncher/buffs.d.ts +184 -0
- package/dist/cruncher/buffs.d.ts.map +1 -0
- package/dist/cruncher/buffs.js +150 -0
- package/dist/cruncher/buffs.js.map +1 -0
- package/dist/cruncher/engine.d.ts +50 -0
- package/dist/cruncher/engine.d.ts.map +1 -0
- package/dist/cruncher/engine.js +312 -0
- package/dist/cruncher/engine.js.map +1 -0
- package/dist/cruncher/from-dsl.d.ts +69 -0
- package/dist/cruncher/from-dsl.d.ts.map +1 -0
- package/dist/cruncher/from-dsl.js +523 -0
- package/dist/cruncher/from-dsl.js.map +1 -0
- package/dist/cruncher/from-keyword.d.ts +35 -0
- package/dist/cruncher/from-keyword.d.ts.map +1 -0
- package/dist/cruncher/from-keyword.js +159 -0
- package/dist/cruncher/from-keyword.js.map +1 -0
- package/dist/cruncher/get-buffs.d.ts +12 -0
- package/dist/cruncher/get-buffs.d.ts.map +1 -0
- package/dist/cruncher/get-buffs.js +7 -0
- package/dist/cruncher/get-buffs.js.map +1 -0
- package/dist/cruncher/index.d.ts +11 -0
- package/dist/cruncher/index.d.ts.map +1 -0
- package/dist/cruncher/index.js +11 -0
- package/dist/cruncher/index.js.map +1 -0
- package/dist/data/bundle.generated.d.ts +1 -0
- package/dist/data/bundle.generated.d.ts.map +1 -0
- package/dist/data/bundle.generated.js +2 -1
- package/dist/data/bundle.generated.js.map +1 -0
- package/dist/data/collection.d.ts +1 -0
- package/dist/data/collection.d.ts.map +1 -0
- package/dist/data/collection.js +1 -0
- package/dist/data/collection.js.map +1 -0
- package/dist/data/dataset.d.ts +54 -2
- package/dist/data/dataset.d.ts.map +1 -0
- package/dist/data/dataset.js +111 -1
- package/dist/data/dataset.js.map +1 -0
- package/dist/data/entities.d.ts +70 -2
- package/dist/data/entities.d.ts.map +1 -0
- package/dist/data/entities.js +122 -0
- package/dist/data/entities.js.map +1 -0
- package/dist/data/index.d.ts +9 -1
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +14 -1
- package/dist/data/index.js.map +1 -0
- package/dist/data/normalize.d.ts +1 -0
- package/dist/data/normalize.d.ts.map +1 -0
- package/dist/data/normalize.js +1 -0
- package/dist/data/normalize.js.map +1 -0
- package/dist/data/roster-resolve.d.ts +33 -0
- package/dist/data/roster-resolve.d.ts.map +1 -0
- package/dist/data/roster-resolve.js +36 -0
- package/dist/data/roster-resolve.js.map +1 -0
- package/dist/data/types.d.ts +4 -1
- package/dist/data/types.d.ts.map +1 -0
- package/dist/data/types.js +2 -0
- package/dist/data/types.js.map +1 -0
- package/dist/export/helpers.d.ts +33 -0
- package/dist/export/helpers.d.ts.map +1 -0
- package/dist/export/helpers.js +57 -0
- package/dist/export/helpers.js.map +1 -0
- package/dist/export/index.d.ts +21 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +25 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/newrecruit-json.d.ts +3 -0
- package/dist/export/newrecruit-json.d.ts.map +1 -0
- package/dist/export/newrecruit-json.js +140 -0
- package/dist/export/newrecruit-json.js.map +1 -0
- package/dist/export/newrecruit-simple.d.ts +3 -0
- package/dist/export/newrecruit-simple.d.ts.map +1 -0
- package/dist/export/newrecruit-simple.js +76 -0
- package/dist/export/newrecruit-simple.js.map +1 -0
- package/dist/export/newrecruit-wtc.d.ts +4 -0
- package/dist/export/newrecruit-wtc.d.ts.map +1 -0
- package/dist/export/newrecruit-wtc.js +142 -0
- package/dist/export/newrecruit-wtc.js.map +1 -0
- package/dist/export/roster-json.d.ts +3 -0
- package/dist/export/roster-json.d.ts.map +1 -0
- package/dist/export/roster-json.js +8 -0
- package/dist/export/roster-json.js.map +1 -0
- package/dist/export/serializer.d.ts +27 -0
- package/dist/export/serializer.d.ts.map +1 -0
- package/dist/export/serializer.js +2 -0
- package/dist/export/serializer.js.map +1 -0
- package/dist/gen-conformance.d.ts +1 -0
- package/dist/gen-conformance.d.ts.map +1 -0
- package/dist/gen-conformance.js +73 -12
- package/dist/gen-conformance.js.map +1 -0
- package/dist/generated.d.ts +194 -118
- package/dist/generated.d.ts.map +1 -0
- package/dist/generated.js +1 -0
- package/dist/generated.js.map +1 -0
- package/dist/import/adapter.d.ts +4 -3
- package/dist/import/adapter.d.ts.map +1 -0
- package/dist/import/adapter.js +1 -0
- package/dist/import/adapter.js.map +1 -0
- package/dist/import/decode.d.ts +1 -0
- package/dist/import/decode.d.ts.map +1 -0
- package/dist/import/decode.js +1 -0
- package/dist/import/decode.js.map +1 -0
- package/dist/import/import-roster.d.ts +35 -0
- package/dist/import/import-roster.d.ts.map +1 -0
- package/dist/import/import-roster.js +97 -0
- package/dist/import/import-roster.js.map +1 -0
- package/dist/import/index.d.ts +7 -3
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +5 -1
- package/dist/import/index.js.map +1 -0
- package/dist/import/listforge.d.ts +1 -0
- package/dist/import/listforge.d.ts.map +1 -0
- package/dist/import/listforge.js +7 -1
- package/dist/import/listforge.js.map +1 -0
- package/dist/import/newrecruit-json.d.ts +31 -0
- package/dist/import/newrecruit-json.d.ts.map +1 -0
- package/dist/import/newrecruit-json.js +224 -0
- package/dist/import/newrecruit-json.js.map +1 -0
- package/dist/import/newrecruit-simple.d.ts +29 -0
- package/dist/import/newrecruit-simple.d.ts.map +1 -0
- package/dist/import/newrecruit-simple.js +200 -0
- package/dist/import/newrecruit-simple.js.map +1 -0
- package/dist/import/newrecruit-text.d.ts +48 -0
- package/dist/import/newrecruit-text.d.ts.map +1 -0
- package/dist/import/newrecruit-text.js +96 -0
- package/dist/import/newrecruit-text.js.map +1 -0
- package/dist/import/newrecruit-wtc.d.ts +36 -0
- package/dist/import/newrecruit-wtc.d.ts.map +1 -0
- package/dist/import/newrecruit-wtc.js +334 -0
- package/dist/import/newrecruit-wtc.js.map +1 -0
- package/dist/import/resolve.d.ts +3 -2
- package/dist/import/resolve.d.ts.map +1 -0
- package/dist/import/resolve.js +5 -2
- package/dist/import/resolve.js.map +1 -0
- package/dist/import/types.d.ts +11 -1
- package/dist/import/types.d.ts.map +1 -0
- package/dist/import/types.js +1 -0
- package/dist/import/types.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -0
- package/dist/known-support-10e.d.ts +1 -0
- package/dist/known-support-10e.d.ts.map +1 -0
- package/dist/known-support-10e.js +1 -0
- package/dist/known-support-10e.js.map +1 -0
- package/dist/link-abilities.d.ts +41 -0
- package/dist/link-abilities.d.ts.map +1 -0
- package/dist/link-abilities.js +159 -0
- package/dist/link-abilities.js.map +1 -0
- package/dist/migrations/2026-weapon-keywords.d.ts +2 -0
- package/dist/migrations/2026-weapon-keywords.d.ts.map +1 -0
- package/dist/migrations/2026-weapon-keywords.js +243 -0
- package/dist/migrations/2026-weapon-keywords.js.map +1 -0
- package/dist/port-10e-faction.d.ts +1 -0
- package/dist/port-10e-faction.d.ts.map +1 -0
- package/dist/port-10e-faction.js +1 -0
- package/dist/port-10e-faction.js.map +1 -0
- package/dist/report.d.ts +1 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +1 -0
- package/dist/report.js.map +1 -0
- package/dist/rube-goldberg.d.ts +3 -0
- package/dist/rube-goldberg.d.ts.map +1 -0
- package/dist/rube-goldberg.js +109 -0
- package/dist/rube-goldberg.js.map +1 -0
- package/dist/schema-loader.d.ts +1 -0
- package/dist/schema-loader.d.ts.map +1 -0
- package/dist/schema-loader.js +1 -0
- package/dist/schema-loader.js.map +1 -0
- package/dist/validate.d.ts +1 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +2 -0
- package/dist/validate.js.map +1 -0
- package/package.json +7 -2
- package/schemas/core/roster.schema.json +17 -4
- package/schemas/core/weapon-keyword.schema.json +31 -0
- package/schemas/core/weapon.schema.json +22 -1
- package/schemas/enrichment/ability-dsl/effect.schema.json +23 -1
- package/dist/import/import-listforge.d.ts +0 -23
- package/dist/import/import-listforge.js +0 -32
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The eligible-abilities resolver: given a unit (plus optional detachment,
|
|
3
|
+
* leader, supporting units), enumerate every ability that could apply to it
|
|
4
|
+
* in a chosen phase, tagged by source so the SPA can group them in the UI.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
export { resolveEligibleAbilities, type EligibilityInput, type EligibleAbility, type EligibleAbilitySource, } from "./resolver.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/abilities-resolver/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,GAC3B,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The eligible-abilities resolver: given a unit (plus optional detachment,
|
|
3
|
+
* leader, supporting units), enumerate every ability that could apply to it
|
|
4
|
+
* in a chosen phase, tagged by source so the SPA can group them in the UI.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
export { resolveEligibleAbilities, } from "./resolver.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/abilities-resolver/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,wBAAwB,GAIzB,MAAM,eAAe,CAAC","sourcesContent":["/**\n * The eligible-abilities resolver: given a unit (plus optional detachment,\n * leader, supporting units), enumerate every ability that could apply to it\n * in a chosen phase, tagged by source so the SPA can group them in the UI.\n *\n * @packageDocumentation\n */\nexport {\n resolveEligibleAbilities,\n type EligibilityInput,\n type EligibleAbility,\n type EligibleAbilitySource,\n} from \"./resolver.js\";\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walks the dataset for every ability that could apply to a chosen unit in a
|
|
3
|
+
* chosen phase. The SPA passes the result through to the buff layer (each
|
|
4
|
+
* {@link EligibleAbility} carries `.getBuffs()`, the source is pre-tagged).
|
|
5
|
+
*
|
|
6
|
+
* Resolution order — stable for snapshot tests:
|
|
7
|
+
*
|
|
8
|
+
* 1. **army** — faction-scoped abilities whose `ability_type` is `"faction"`
|
|
9
|
+
* and whose `faction_id` matches the input.
|
|
10
|
+
* 2. **detachment** — abilities authored against the detachment
|
|
11
|
+
* (`ability_type` is `"detachment"`, `detachment_id` matches).
|
|
12
|
+
* 3. **detachment-stratagem** — stratagems on the detachment, each yielding
|
|
13
|
+
* the ability referenced by `stratagem.ability_id` (if any).
|
|
14
|
+
* 4. **unit** — abilities listed in `unit.ability_ids`.
|
|
15
|
+
* 5. **leader** — abilities listed in the attached leader's `ability_ids`.
|
|
16
|
+
* 6. **support** — abilities on supporting units whose scope range is an
|
|
17
|
+
* aura (not `self` / `unit`).
|
|
18
|
+
*
|
|
19
|
+
* Each step phase-filters via the existing `Dataset.phasesFor` index. The
|
|
20
|
+
* resolver collects abilities first, *then* filters by phase, so the SPA can
|
|
21
|
+
* also ask "what abilities are eligible across all phases?" by passing every
|
|
22
|
+
* phase (today the API requires a single phase; if the SPA wants the wide
|
|
23
|
+
* view it can call the resolver once per phase).
|
|
24
|
+
*/
|
|
25
|
+
import type { Phase } from "../generated.js";
|
|
26
|
+
import type { Dataset } from "../data/dataset.js";
|
|
27
|
+
import type { AbilityView } from "../data/entities.js";
|
|
28
|
+
export type EligibleAbilitySource = {
|
|
29
|
+
kind: "army";
|
|
30
|
+
} | {
|
|
31
|
+
kind: "detachment";
|
|
32
|
+
detachmentId: string;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "detachment-stratagem";
|
|
35
|
+
stratagemId: string;
|
|
36
|
+
cpCost: number;
|
|
37
|
+
} | {
|
|
38
|
+
kind: "unit";
|
|
39
|
+
unitId: string;
|
|
40
|
+
} | {
|
|
41
|
+
kind: "leader";
|
|
42
|
+
leaderId: string;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "support";
|
|
45
|
+
sourceUnitId: string;
|
|
46
|
+
};
|
|
47
|
+
export type EligibilityInput = {
|
|
48
|
+
unitId: string;
|
|
49
|
+
/** Overrides the unit's own `faction_id` when given (for inheritance cases). */
|
|
50
|
+
factionId?: string;
|
|
51
|
+
detachmentId?: string;
|
|
52
|
+
attachedLeaderId?: string;
|
|
53
|
+
/** Friendly units whose auras could apply (M2 walks only their aura-ranged abilities). */
|
|
54
|
+
supportingUnitIds?: string[];
|
|
55
|
+
};
|
|
56
|
+
export type EligibleAbility = {
|
|
57
|
+
ability: AbilityView;
|
|
58
|
+
source: EligibleAbilitySource;
|
|
59
|
+
/** The subset of `ability.phases` that intersect the requested phase. */
|
|
60
|
+
phases: Phase[];
|
|
61
|
+
};
|
|
62
|
+
/** Compute the sorted-by-source eligible-ability list for one (unit, phase). */
|
|
63
|
+
export declare function resolveEligibleAbilities(dataset: Dataset, input: EligibilityInput, phase: Phase): EligibleAbility[];
|
|
64
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/abilities-resolver/resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0FAA0F;IAC1F,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,yEAAyE;IACzE,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,gBAAgB,EACvB,KAAK,EAAE,KAAK,GACX,eAAe,EAAE,CA8FnB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Compute the sorted-by-source eligible-ability list for one (unit, phase). */
|
|
2
|
+
export function resolveEligibleAbilities(dataset, input, phase) {
|
|
3
|
+
const unit = dataset.units.get(input.unitId);
|
|
4
|
+
if (!unit)
|
|
5
|
+
return [];
|
|
6
|
+
const factionId = input.factionId ?? unit.raw.faction_id;
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
const out = [];
|
|
9
|
+
// 1. Army — faction-scoped abilities (faction rule + any other faction-typed).
|
|
10
|
+
for (const ability of dataset.abilities.byFaction(factionId)) {
|
|
11
|
+
if (ability.raw.ability_type !== "faction")
|
|
12
|
+
continue;
|
|
13
|
+
if (!phaseMatches(ability, phase))
|
|
14
|
+
continue;
|
|
15
|
+
pushUnique(out, seen, { ability, source: { kind: "army" }, phases: intersect(ability.phases, phase) });
|
|
16
|
+
}
|
|
17
|
+
// 2. Detachment abilities — abilities whose detachment_id matches.
|
|
18
|
+
if (input.detachmentId) {
|
|
19
|
+
for (const ability of dataset.abilities) {
|
|
20
|
+
if (ability.raw.ability_type !== "detachment")
|
|
21
|
+
continue;
|
|
22
|
+
if (ability.raw.detachment_id !== input.detachmentId)
|
|
23
|
+
continue;
|
|
24
|
+
if (!phaseMatches(ability, phase))
|
|
25
|
+
continue;
|
|
26
|
+
pushUnique(out, seen, {
|
|
27
|
+
ability,
|
|
28
|
+
source: { kind: "detachment", detachmentId: input.detachmentId },
|
|
29
|
+
phases: intersect(ability.phases, phase),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// 3. Detachment stratagems.
|
|
33
|
+
const detachment = dataset.detachments.get(input.detachmentId);
|
|
34
|
+
if (detachment) {
|
|
35
|
+
for (const stratId of detachment.stratagem_ids ?? []) {
|
|
36
|
+
const stratagem = dataset.stratagems.get(stratId);
|
|
37
|
+
if (!stratagem)
|
|
38
|
+
continue;
|
|
39
|
+
if (!stratagemPhaseMatches(stratagem, phase))
|
|
40
|
+
continue;
|
|
41
|
+
const ability = stratagem.ability_id !== null && stratagem.ability_id !== undefined
|
|
42
|
+
? dataset.abilities.get(stratagem.ability_id)
|
|
43
|
+
: undefined;
|
|
44
|
+
if (!ability)
|
|
45
|
+
continue;
|
|
46
|
+
pushUnique(out, seen, {
|
|
47
|
+
ability,
|
|
48
|
+
source: {
|
|
49
|
+
kind: "detachment-stratagem",
|
|
50
|
+
stratagemId: stratagem.id,
|
|
51
|
+
cpCost: stratagem.cp_cost,
|
|
52
|
+
},
|
|
53
|
+
phases: [phase], // the stratagem's printed phase governs eligibility.
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// 4. Unit's own abilities.
|
|
59
|
+
for (const ability of unit.abilities) {
|
|
60
|
+
if (!phaseMatches(ability, phase))
|
|
61
|
+
continue;
|
|
62
|
+
pushUnique(out, seen, {
|
|
63
|
+
ability,
|
|
64
|
+
source: { kind: "unit", unitId: input.unitId },
|
|
65
|
+
phases: intersect(ability.phases, phase),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// 5. Attached leader.
|
|
69
|
+
if (input.attachedLeaderId) {
|
|
70
|
+
const leader = dataset.units.get(input.attachedLeaderId);
|
|
71
|
+
if (leader) {
|
|
72
|
+
for (const ability of leader.abilities) {
|
|
73
|
+
if (!phaseMatches(ability, phase))
|
|
74
|
+
continue;
|
|
75
|
+
pushUnique(out, seen, {
|
|
76
|
+
ability,
|
|
77
|
+
source: { kind: "leader", leaderId: input.attachedLeaderId },
|
|
78
|
+
phases: intersect(ability.phases, phase),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// 6. Supporting units — only aura-scoped abilities (otherwise the buff
|
|
84
|
+
// would describe a self-target effect that doesn't reach the input unit).
|
|
85
|
+
for (const supportId of input.supportingUnitIds ?? []) {
|
|
86
|
+
const supporter = dataset.units.get(supportId);
|
|
87
|
+
if (!supporter)
|
|
88
|
+
continue;
|
|
89
|
+
for (const ability of supporter.abilities) {
|
|
90
|
+
if (!phaseMatches(ability, phase))
|
|
91
|
+
continue;
|
|
92
|
+
if (!isAuraScope(ability.raw.scope?.range))
|
|
93
|
+
continue;
|
|
94
|
+
pushUnique(out, seen, {
|
|
95
|
+
ability,
|
|
96
|
+
source: { kind: "support", sourceUnitId: supportId },
|
|
97
|
+
phases: intersect(ability.phases, phase),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Helpers
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
function phaseMatches(ability, phase) {
|
|
107
|
+
const phases = ability.phases;
|
|
108
|
+
// An ability with no phase-mapping is permissive — surface it everywhere so
|
|
109
|
+
// the SPA can decide. M2's translator already gates conditional-on-phase
|
|
110
|
+
// effects internally, so this stays generous on purpose.
|
|
111
|
+
if (phases.length === 0)
|
|
112
|
+
return true;
|
|
113
|
+
return phases.includes(phase);
|
|
114
|
+
}
|
|
115
|
+
function stratagemPhaseMatches(stratagem, phase) {
|
|
116
|
+
if (!stratagem.phases || stratagem.phases.length === 0)
|
|
117
|
+
return false;
|
|
118
|
+
return stratagem.phases.includes(phase);
|
|
119
|
+
}
|
|
120
|
+
function intersect(phases, phase) {
|
|
121
|
+
return phases.includes(phase) ? [phase] : phases;
|
|
122
|
+
}
|
|
123
|
+
function isAuraScope(range) {
|
|
124
|
+
if (typeof range !== "string")
|
|
125
|
+
return false;
|
|
126
|
+
return range.startsWith("aura-") || range === "any-on-battlefield" || range === "any-visible";
|
|
127
|
+
}
|
|
128
|
+
function pushUnique(out, seen, entry) {
|
|
129
|
+
const key = `${entry.source.kind}::${entry.ability.id}`;
|
|
130
|
+
if (seen.has(key))
|
|
131
|
+
return;
|
|
132
|
+
seen.add(key);
|
|
133
|
+
out.push(entry);
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/abilities-resolver/resolver.ts"],"names":[],"mappings":"AAqDA,gFAAgF;AAChF,MAAM,UAAU,wBAAwB,CACtC,OAAgB,EAChB,KAAuB,EACvB,KAAY;IAEZ,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;IACzD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAsB,EAAE,CAAC;IAElC,+EAA+E;IAC/E,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7D,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,SAAS;YAAE,SAAS;QACrD,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC;YAAE,SAAS;QAC5C,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IACzG,CAAC;IAED,mEAAmE;IACnE,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACvB,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACxC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,YAAY;gBAAE,SAAS;YACxD,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,KAAK,CAAC,YAAY;gBAAE,SAAS;YAC/D,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC;gBAAE,SAAS;YAC5C,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE;gBACpB,OAAO;gBACP,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE;gBAChE,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;aACzC,CAAC,CAAC;QACL,CAAC;QAED,4BAA4B;QAC5B,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC/D,IAAI,UAAU,EAAE,CAAC;YACf,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;gBACrD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAClD,IAAI,CAAC,SAAS;oBAAE,SAAS;gBACzB,IAAI,CAAC,qBAAqB,CAAC,SAAS,EAAE,KAAK,CAAC;oBAAE,SAAS;gBACvD,MAAM,OAAO,GACX,SAAS,CAAC,UAAU,KAAK,IAAI,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS;oBACjE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC;oBAC7C,CAAC,CAAC,SAAS,CAAC;gBAChB,IAAI,CAAC,OAAO;oBAAE,SAAS;gBACvB,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE;oBACpB,OAAO;oBACP,MAAM,EAAE;wBACN,IAAI,EAAE,sBAAsB;wBAC5B,WAAW,EAAE,SAAS,CAAC,EAAE;wBACzB,MAAM,EAAE,SAAS,CAAC,OAAO;qBAC1B;oBACD,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,qDAAqD;iBACvE,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,2BAA2B;IAC3B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACrC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC;YAAE,SAAS;QAC5C,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE;YACpB,OAAO;YACP,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;YAC9C,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;SACzC,CAAC,CAAC;IACL,CAAC;IAED,sBAAsB;IACtB,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACzD,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACvC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC;oBAAE,SAAS;gBAC5C,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE;oBACpB,OAAO;oBACP,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,gBAAgB,EAAE;oBAC5D,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;iBACzC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,0EAA0E;IAC1E,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,EAAE,CAAC;QACtD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC/C,IAAI,CAAC,SAAS;YAAE,SAAS;QACzB,KAAK,MAAM,OAAO,IAAI,SAAS,CAAC,SAAS,EAAE,CAAC;YAC1C,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC;gBAAE,SAAS;YAC5C,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC;gBAAE,SAAS;YACrD,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE;gBACpB,OAAO;gBACP,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE;gBACpD,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;aACzC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,YAAY,CAAC,OAAoB,EAAE,KAAY;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,yEAAyE;IACzE,yDAAyD;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,qBAAqB,CAAC,SAAoB,EAAE,KAAY;IAC/D,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,SAAS,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,OAAQ,SAAS,CAAC,MAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,SAAS,CAAC,MAAe,EAAE,KAAY;IAC9C,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,KAAK,oBAAoB,IAAI,KAAK,KAAK,aAAa,CAAC;AAChG,CAAC;AAED,SAAS,UAAU,CACjB,GAAsB,EACtB,IAAiB,EACjB,KAAsB;IAEtB,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;IACxD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACd,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Walks the dataset for every ability that could apply to a chosen unit in a\n * chosen phase. The SPA passes the result through to the buff layer (each\n * {@link EligibleAbility} carries `.getBuffs()`, the source is pre-tagged).\n *\n * Resolution order — stable for snapshot tests:\n *\n * 1. **army** — faction-scoped abilities whose `ability_type` is `\"faction\"`\n * and whose `faction_id` matches the input.\n * 2. **detachment** — abilities authored against the detachment\n * (`ability_type` is `\"detachment\"`, `detachment_id` matches).\n * 3. **detachment-stratagem** — stratagems on the detachment, each yielding\n * the ability referenced by `stratagem.ability_id` (if any).\n * 4. **unit** — abilities listed in `unit.ability_ids`.\n * 5. **leader** — abilities listed in the attached leader's `ability_ids`.\n * 6. **support** — abilities on supporting units whose scope range is an\n * aura (not `self` / `unit`).\n *\n * Each step phase-filters via the existing `Dataset.phasesFor` index. The\n * resolver collects abilities first, *then* filters by phase, so the SPA can\n * also ask \"what abilities are eligible across all phases?\" by passing every\n * phase (today the API requires a single phase; if the SPA wants the wide\n * view it can call the resolver once per phase).\n */\nimport type { Phase, Stratagem } from \"../generated.js\";\nimport type { Dataset } from \"../data/dataset.js\";\nimport type { AbilityView } from \"../data/entities.js\";\n\nexport type EligibleAbilitySource =\n | { kind: \"army\" }\n | { kind: \"detachment\"; detachmentId: string }\n | { kind: \"detachment-stratagem\"; stratagemId: string; cpCost: number }\n | { kind: \"unit\"; unitId: string }\n | { kind: \"leader\"; leaderId: string }\n | { kind: \"support\"; sourceUnitId: string };\n\nexport type EligibilityInput = {\n unitId: string;\n /** Overrides the unit's own `faction_id` when given (for inheritance cases). */\n factionId?: string;\n detachmentId?: string;\n attachedLeaderId?: string;\n /** Friendly units whose auras could apply (M2 walks only their aura-ranged abilities). */\n supportingUnitIds?: string[];\n};\n\nexport type EligibleAbility = {\n ability: AbilityView;\n source: EligibleAbilitySource;\n /** The subset of `ability.phases` that intersect the requested phase. */\n phases: Phase[];\n};\n\n/** Compute the sorted-by-source eligible-ability list for one (unit, phase). */\nexport function resolveEligibleAbilities(\n dataset: Dataset,\n input: EligibilityInput,\n phase: Phase,\n): EligibleAbility[] {\n const unit = dataset.units.get(input.unitId);\n if (!unit) return [];\n const factionId = input.factionId ?? unit.raw.faction_id;\n const seen = new Set<string>();\n const out: EligibleAbility[] = [];\n\n // 1. Army — faction-scoped abilities (faction rule + any other faction-typed).\n for (const ability of dataset.abilities.byFaction(factionId)) {\n if (ability.raw.ability_type !== \"faction\") continue;\n if (!phaseMatches(ability, phase)) continue;\n pushUnique(out, seen, { ability, source: { kind: \"army\" }, phases: intersect(ability.phases, phase) });\n }\n\n // 2. Detachment abilities — abilities whose detachment_id matches.\n if (input.detachmentId) {\n for (const ability of dataset.abilities) {\n if (ability.raw.ability_type !== \"detachment\") continue;\n if (ability.raw.detachment_id !== input.detachmentId) continue;\n if (!phaseMatches(ability, phase)) continue;\n pushUnique(out, seen, {\n ability,\n source: { kind: \"detachment\", detachmentId: input.detachmentId },\n phases: intersect(ability.phases, phase),\n });\n }\n\n // 3. Detachment stratagems.\n const detachment = dataset.detachments.get(input.detachmentId);\n if (detachment) {\n for (const stratId of detachment.stratagem_ids ?? []) {\n const stratagem = dataset.stratagems.get(stratId);\n if (!stratagem) continue;\n if (!stratagemPhaseMatches(stratagem, phase)) continue;\n const ability =\n stratagem.ability_id !== null && stratagem.ability_id !== undefined\n ? dataset.abilities.get(stratagem.ability_id)\n : undefined;\n if (!ability) continue;\n pushUnique(out, seen, {\n ability,\n source: {\n kind: \"detachment-stratagem\",\n stratagemId: stratagem.id,\n cpCost: stratagem.cp_cost,\n },\n phases: [phase], // the stratagem's printed phase governs eligibility.\n });\n }\n }\n }\n\n // 4. Unit's own abilities.\n for (const ability of unit.abilities) {\n if (!phaseMatches(ability, phase)) continue;\n pushUnique(out, seen, {\n ability,\n source: { kind: \"unit\", unitId: input.unitId },\n phases: intersect(ability.phases, phase),\n });\n }\n\n // 5. Attached leader.\n if (input.attachedLeaderId) {\n const leader = dataset.units.get(input.attachedLeaderId);\n if (leader) {\n for (const ability of leader.abilities) {\n if (!phaseMatches(ability, phase)) continue;\n pushUnique(out, seen, {\n ability,\n source: { kind: \"leader\", leaderId: input.attachedLeaderId },\n phases: intersect(ability.phases, phase),\n });\n }\n }\n }\n\n // 6. Supporting units — only aura-scoped abilities (otherwise the buff\n // would describe a self-target effect that doesn't reach the input unit).\n for (const supportId of input.supportingUnitIds ?? []) {\n const supporter = dataset.units.get(supportId);\n if (!supporter) continue;\n for (const ability of supporter.abilities) {\n if (!phaseMatches(ability, phase)) continue;\n if (!isAuraScope(ability.raw.scope?.range)) continue;\n pushUnique(out, seen, {\n ability,\n source: { kind: \"support\", sourceUnitId: supportId },\n phases: intersect(ability.phases, phase),\n });\n }\n }\n\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction phaseMatches(ability: AbilityView, phase: Phase): boolean {\n const phases = ability.phases;\n // An ability with no phase-mapping is permissive — surface it everywhere so\n // the SPA can decide. M2's translator already gates conditional-on-phase\n // effects internally, so this stays generous on purpose.\n if (phases.length === 0) return true;\n return phases.includes(phase);\n}\n\nfunction stratagemPhaseMatches(stratagem: Stratagem, phase: Phase): boolean {\n if (!stratagem.phases || stratagem.phases.length === 0) return false;\n return (stratagem.phases as Phase[]).includes(phase);\n}\n\nfunction intersect(phases: Phase[], phase: Phase): Phase[] {\n return phases.includes(phase) ? [phase] : phases;\n}\n\nfunction isAuraScope(range: unknown): boolean {\n if (typeof range !== \"string\") return false;\n return range.startsWith(\"aura-\") || range === \"any-on-battlefield\" || range === \"any-visible\";\n}\n\nfunction pushUnique(\n out: EligibleAbility[],\n seen: Set<string>,\n entry: EligibleAbility,\n): void {\n const key = `${entry.source.kind}::${entry.ability.id}`;\n if (seen.has(key)) return;\n seen.add(key);\n out.push(entry);\n}\n"]}
|
package/dist/bundle-schemas.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-schemas.d.ts","sourceRoot":"","sources":["../src/bundle-schemas.ts"],"names":[],"mappings":"AA2CA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA2E1C,wBAAgB,MAAM,IAAI,UAAU,CAsCnC"}
|
package/dist/bundle-schemas.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-schemas.js","sourceRoot":"","sources":["../src/bundle-schemas.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { findSchemaFiles, SCHEMAS_ROOT } from \"./schema-loader.js\";\n\n/**\n * Flattens the multi-file schema set into a single self-contained\n * draft-2020-12 document, written to crates/wh40kdc/schemas/bundled.schema.json.\n *\n * Why a bespoke flattener rather than a ref-parser bundle: the Rust codegen\n * (typify) wants one document where every type lives in a single flat `$defs`\n * map — it resolves `$ref`s as `#/$defs/<name>` and does NOT traverse nested\n * `$defs` paths. Generic bundlers also anchor a reused subschema to its first-use\n * location (e.g. `#/$defs/faction/properties/id`), yielding junk Rust type names.\n *\n * So this pass hoists EVERY definition — `common.schema.json`'s shared defs, each\n * entity schema (keyed by its filename stem), and every entity's local `$defs` —\n * flat into one top-level `$defs`. All such names are globally unique across the\n * schema set (asserted at build time), so no prefixing is needed and type names\n * track the names authors actually chose.\n *\n * Refs are resolved against each schema's `$id` URL — not its filesystem path —\n * because that is how the refs are authored (e.g. `../defs/common.schema.json`\n * targets the `$id` `.../schemas/defs/...`, while the file lives in `$defs/`).\n */\n\nconst COMMON_ID = \"https://40kdc.dev/schemas/defs/common.schema.json\";\n\n/**\n * Schemas excluded from the codegen bundle (still loaded for AJV validation).\n * The roster schema describes importer *output* — a tool-side artifact, not a\n * dataset entity the Rust crate serves — so it is intentionally kept out of the\n * generated types. Its TS types are hand-authored in `src/import/types.ts`.\n */\nconst CODEGEN_EXCLUDED_IDS = new Set([\n \"https://40kdc.dev/schemas/core/roster.schema.json\",\n]);\nconst OUTPUT_PATH = resolve(\n SCHEMAS_ROOT,\n \"../crates/wh40kdc/schemas/bundled.schema.json\",\n);\nconst BUNDLE_ID = \"https://40kdc.dev/schemas/bundled.schema.json\";\n\ntype JsonObject = Record<string, unknown>;\n\nfunction stemOfId(id: string): string {\n return basename(new URL(id).pathname).replace(/\\.schema\\.json$/, \"\");\n}\n\n/**\n * Rewrite a single `$ref` (resolved against the `$id` of the file it appears in)\n * to a flat pointer into the bundle's top-level `$defs`.\n *\n * - any `#/$defs/<name>` pointer (file-local, common, or cross-file into a hoisted\n * local def) stays `#/$defs/<name>` — every such name is now top-level.\n * - a whole-file ref (`effect.schema.json`) maps to that file's stem: `#/$defs/effect`.\n */\nfunction rewriteRef(ref: string, sourceId: string): string {\n const hashIndex = ref.indexOf(\"#\");\n const filePart = hashIndex === -1 ? ref : ref.slice(0, hashIndex);\n const pointer = hashIndex === -1 ? \"\" : ref.slice(hashIndex + 1); // e.g. \"/$defs/entity-id\"\n\n if (pointer) {\n if (!pointer.startsWith(\"/$defs/\")) {\n throw new Error(\n `unexpected non-$defs JSON pointer in $ref ${JSON.stringify(ref)} (source ${sourceId})`,\n );\n }\n return `#${pointer}`;\n }\n\n // Whole-file ref: resolve to the target's $id and key by its stem.\n const targetId = filePart ? new URL(filePart, sourceId).href : sourceId;\n return `#/$defs/${stemOfId(targetId)}`;\n}\n\n/**\n * Strip JSON Schema conditional applicators (`if`/`then`/`else`) from the codegen\n * bundle. typify cannot model them — they express \"field X is required when field\n * Y has value Z\", which has no Rust-type representation. The constraints are still\n * enforced at data-validation time by ajv against the real (un-stripped) schemas;\n * dropping them here only makes the affected fields optional in the generated Rust\n * types, which is correct for deserializing any valid document.\n */\nfunction stripConditionals(node: unknown): unknown {\n if (Array.isArray(node)) {\n return node.map(stripConditionals);\n }\n if (node && typeof node === \"object\") {\n const out: JsonObject = {};\n for (const [key, value] of Object.entries(node as JsonObject)) {\n if (key === \"if\" || key === \"then\" || key === \"else\") continue;\n out[key] = stripConditionals(value);\n }\n return out;\n }\n return node;\n}\n\n/** Recursively rewrite every `$ref` in `node`, knowing the source schema's `$id`. */\nfunction rewriteRefs(node: unknown, sourceId: string): unknown {\n if (Array.isArray(node)) {\n return node.map((item) => rewriteRefs(item, sourceId));\n }\n if (node && typeof node === \"object\") {\n const out: JsonObject = {};\n for (const [key, value] of Object.entries(node as JsonObject)) {\n if (key === \"$ref\" && typeof value === \"string\") {\n out[key] = rewriteRef(value, sourceId);\n } else {\n out[key] = rewriteRefs(value, sourceId);\n }\n }\n return out;\n }\n return node;\n}\n\nexport function bundle(): JsonObject {\n const files = findSchemaFiles(SCHEMAS_ROOT).sort();\n const defs: JsonObject = {};\n\n const place = (name: string, def: unknown, sourceId: string): void => {\n if (name in defs) {\n throw new Error(`definition name collision: ${name} (from ${sourceId})`);\n }\n defs[name] = stripConditionals(rewriteRefs(def, sourceId));\n };\n\n for (const file of files) {\n const raw = JSON.parse(readFileSync(file, \"utf-8\")) as JsonObject;\n const id = raw.$id as string;\n if (!id) throw new Error(`schema missing $id: ${file}`);\n if (CODEGEN_EXCLUDED_IDS.has(id)) continue;\n const { $id: _id, $schema: _schema, $defs: localDefs, ...body } = raw;\n\n // Hoist this file's local $defs flat to the top level (names are globally\n // unique across the schema set; collisions throw above).\n for (const [name, def] of Object.entries((localDefs ?? {}) as JsonObject)) {\n place(name, def, id);\n }\n\n // common is purely a $defs bag — it contributes no stem-keyed entity.\n if (id !== COMMON_ID) {\n place(stemOfId(id), body, id);\n }\n }\n\n return {\n $schema: \"https://json-schema.org/draft/2020-12/schema\",\n $id: BUNDLE_ID,\n title: \"40kdc Bundled Schemas\",\n description:\n \"Auto-generated by tools/src/bundle-schemas.ts. Single self-contained schema for Rust codegen — do not edit by hand.\",\n $defs: defs,\n };\n}\n\nfunction main(): void {\n const result = bundle();\n mkdirSync(dirname(OUTPUT_PATH), { recursive: true });\n writeFileSync(OUTPUT_PATH, JSON.stringify(result, null, 2) + \"\\n\", \"utf-8\");\n const count = Object.keys(result.$defs as JsonObject).length;\n console.log(`Bundled ${count} definitions → ${OUTPUT_PATH}`);\n}\n\nif (import.meta.url === pathToFileURL(process.argv[1]).href) {\n main();\n}\n"]}
|
package/dist/cli.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import { Command } from "commander";
|
|
2
3
|
import { validateCoreCommand } from "./commands/validate-core.js";
|
|
3
4
|
import { validateEnrichmentCommand } from "./commands/validate-enrichment.js";
|
|
@@ -37,3 +38,4 @@ program
|
|
|
37
38
|
.option("--out <file>", "Write roster JSON to a file instead of stdout")
|
|
38
39
|
.action(importCommand);
|
|
39
40
|
program.parse();
|
|
41
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,gBAAgB,CAAC;KACtB,WAAW,CAAC,2CAA2C,CAAC;KACxD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,0BAA0B,CAAC;KACvC,MAAM,CAAC,mBAAmB,EAAE,+BAA+B,EAAE,QAAQ,CAAC;KACtE,MAAM,CAAC,mBAAmB,CAAC,CAAC;AAE/B,OAAO;KACJ,OAAO,CAAC,qBAAqB,CAAC;KAC9B,WAAW,CAAC,gCAAgC,CAAC;KAC7C,MAAM,CAAC,mBAAmB,EAAE,+BAA+B,EAAE,QAAQ,CAAC;KACtE,MAAM,CAAC,yBAAyB,CAAC,CAAC;AAErC,OAAO;KACJ,OAAO,CAAC,cAAc,CAAC;KACvB,WAAW,CAAC,yBAAyB,CAAC;KACtC,MAAM,CAAC,mBAAmB,EAAE,+BAA+B,EAAE,QAAQ,CAAC;KACtE,MAAM,CAAC,kBAAkB,CAAC,CAAC;AAE9B,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,wCAAwC,CAAC;KACrD,QAAQ,CAAC,QAAQ,EAAE,6BAA6B,CAAC;KACjD,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAE5B,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,yDAAyD,CAAC;KACtE,QAAQ,CAAC,SAAS,EAAE,wEAAwE,CAAC;KAC7F,MAAM,CAAC,mBAAmB,EAAE,+BAA+B,EAAE,MAAM,CAAC;KACpE,MAAM,CAAC,cAAc,EAAE,+CAA+C,CAAC;KACvE,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO,CAAC,KAAK,EAAE,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { validateCoreCommand } from \"./commands/validate-core.js\";\nimport { validateEnrichmentCommand } from \"./commands/validate-enrichment.js\";\nimport { validateAllCommand } from \"./commands/validate-all.js\";\nimport { translateCommand } from \"./commands/translate.js\";\nimport { importCommand } from \"./commands/import.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"40kdc-validate\")\n .description(\"Validate 40kdc data files against schemas\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"validate-core\")\n .description(\"Validate core data files\")\n .option(\"--reporter <mode>\", \"Output format: pretty or json\", \"pretty\")\n .action(validateCoreCommand);\n\nprogram\n .command(\"validate-enrichment\")\n .description(\"Validate enrichment data files\")\n .option(\"--reporter <mode>\", \"Output format: pretty or json\", \"pretty\")\n .action(validateEnrichmentCommand);\n\nprogram\n .command(\"validate-all\")\n .description(\"Validate all data files\")\n .option(\"--reporter <mode>\", \"Output format: pretty or json\", \"pretty\")\n .action(validateAllCommand);\n\nprogram\n .command(\"translate\")\n .description(\"Translate ability DSL to plain English\")\n .argument(\"[path]\", \"Path to abilities.json file\")\n .action(translateCommand);\n\nprogram\n .command(\"import\")\n .description(\"Import a ListForge army-list export into a 40kdc roster\")\n .argument(\"[input]\", \"ListForge URL, base64 segment, JSON, or file path (omit/'-' for stdin)\")\n .option(\"--reporter <mode>\", \"Output format: json or pretty\", \"json\")\n .option(\"--out <file>\", \"Write roster JSON to a file instead of stdout\")\n .action(importCommand);\n\nprogram.parse();\n"]}
|
package/dist/codegen-data.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen-data.d.ts","sourceRoot":"","sources":["../src/codegen-data.ts"],"names":[],"mappings":""}
|
package/dist/codegen-data.js
CHANGED
|
@@ -25,6 +25,7 @@ const EXCLUDED_DIRS = new Set(["_example", "_port-audit"]);
|
|
|
25
25
|
const FILE_TO_COLLECTION = {
|
|
26
26
|
units: "units",
|
|
27
27
|
weapons: "weapons",
|
|
28
|
+
"weapon-keywords": "weaponKeywords",
|
|
28
29
|
factions: "factions",
|
|
29
30
|
abilities: "abilities",
|
|
30
31
|
"phase-mappings": "phaseMappings",
|
|
@@ -126,3 +127,4 @@ function main() {
|
|
|
126
127
|
console.log(`Wrote ${OUT_FILE}\n ${counts}`);
|
|
127
128
|
}
|
|
128
129
|
main();
|
|
130
|
+
//# sourceMappingURL=codegen-data.js.map
|
|
@@ -0,0 +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 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 \"game-versions\": \"gameVersions\",\n missions: \"missions\",\n \"mission-matchups\": \"missionMatchups\",\n \"secondary-cards\": \"secondaryCards\",\n \"deployment-patterns\": \"deploymentPatterns\",\n \"force-dispositions\": \"forceDispositions\",\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"]}
|
|
@@ -0,0 +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"}
|
package/dist/commands/import.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import.js","sourceRoot":"","sources":["../../src/commands/import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtD,MAAM,gBAAgB,GAAG,mDAAmD,CAAC;AAO7E,SAAS,SAAS;IAChB,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,SAAS,YAAY,CAAC,KAAyB;IAC7C,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACjE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAAc;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;IAC7B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACrC,KAAK,CAAC,IAAI,CAAC,iBAAiB,MAAM,CAAC,UAAU,IAAI,cAAc,EAAE,CAAC,CAAC;IACnE,KAAK,CAAC,IAAI,CAAC,iBAAiB,MAAM,CAAC,aAAa,IAAI,mBAAmB,EAAE,CAAC,CAAC;IAC3E,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,WAAW,IAAI,YAAY,EAAE,CAAC,CAAC;IACnE,KAAK,CAAC,IAAI,CACR,sBAAsB,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE;QAClD,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,cAAc,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3F,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC3F,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAChD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QACxC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,eAAe,CAAC;QACxE,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,CAAC,CAAC,UAAU;YAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,CAAC,CAAC,WAAW;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACzF,IAAI,CAAC,CAAC,iBAAiB;YAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,iBAAiB,CAAC,aAAa,CAAC,EAAE,GAAG,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IAC5G,CAAC;IACD,KAAK,CAAC,IAAI,CACR,eAAe,CAAC,CAAC,cAAc,YAAY,CAAC,CAAC,gBAAgB,YAAY;QACvE,eAAe,CAAC,CAAC,gBAAgB,YAAY,CAAC,CAAC,kBAAkB,UAAU,CAC9E,CAAC;IACF,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;QACjD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAyB,EACzB,IAAgB;IAEhB,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,uFAAuF,CAAC,CAAC;QACvG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,2CAA4C,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,8CAA8C;IAC9C,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IAC/D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,oCAAoC,gBAAgB,GAAG,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACnE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7C,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,kBAAkB,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IACpC,CAAC;SAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;AACH,CAAC","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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../src/commands/translate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwRH,wBAAsB,gBAAgB,CACpC,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/commands/translate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0CpC,wEAAwE;AAExE,SAAS,kBAAkB,CAAC,CAAY;IACtC,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvC,sBAAsB;IACtB,IAAI,CAAC,CAAC,QAAQ,KAAK,KAAK,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACjD,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACjD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,CAAC,CAAC,QAAQ,KAAK,KAAK,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QACvC,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IAClE,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC;IAE7B,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,UAAU;YACb,OAAO,GAAG,MAAM,cAAc,CAAC,CAAC,KAAK,QAAQ,CAAC;QAChD,KAAK,WAAW;YACd,OAAO,GAAG,MAAM,MAAM,YAAY,CAAC,CAAC,CAAC,MAAgB,CAAC,EAAE,CAAC;QAC3D,KAAK,gBAAgB;YACnB,OAAO,GAAG,MAAM,MAAM,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,iBAAiB,OAAO,CAAC;QAC/H,KAAK,mBAAmB;YACtB,OAAO,GAAG,MAAM,wBAAwB,CAAC;QAC3C,KAAK,oBAAoB;YACvB,OAAO,GAAG,MAAM,yBAAyB,CAAC;QAC5C,KAAK,qBAAqB;YACxB,OAAO,GAAG,MAAM,0BAA0B,CAAC;QAC7C,KAAK,8BAA8B;YACjC,OAAO,GAAG,MAAM,mCAAmC,CAAC;QACtD,KAAK,0BAA0B;YAC7B,OAAO,GAAG,MAAM,+BAA+B,CAAC;QAClD,KAAK,kBAAkB;YACrB,OAAO,GAAG,MAAM,aAAa,CAAC,CAAC,OAAO,GAAG,CAAC;QAC5C,KAAK,oBAAoB;YACvB,OAAO,GAAG,MAAM,eAAe,CAAC,CAAC,OAAO,GAAG,CAAC;QAC9C,KAAK,iBAAiB;YACpB,OAAO,GAAG,MAAM,yBAAyB,CAAC;QAC5C,KAAK,aAAa;YAChB,OAAO,GAAG,MAAM,iBAAiB,CAAC,CAAC,OAAO,IAAI,EAAE,OAAO,CAAC;QAC1D,KAAK,gBAAgB;YACnB,OAAO,GAAG,MAAM,OAAO,CAAC,CAAC,WAAW,UAAU,CAAC;QACjD,KAAK,mBAAmB;YACtB,OAAO,GAAG,MAAM,wBAAwB,CAAC;QAC3C,KAAK,iBAAiB;YACpB,OAAO,GAAG,MAAM,uBAAuB,CAAC;QAC1C,KAAK,4BAA4B;YAC/B,OAAO,GAAG,MAAM,qBAAqB,CAAC,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC;QACvG,KAAK,sBAAsB;YACzB,OAAO,GAAG,MAAM,UAAU,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,WAAW,IAAI,QAAQ,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC5G,KAAK,2BAA2B;YAC9B,OAAO,GAAG,MAAM,8BAA8B,CAAC;QACjD,KAAK,oBAAoB;YACvB,OAAO,GAAG,MAAM,0BAA0B,CAAC;QAC7C,KAAK,uBAAuB;YAC1B,OAAO,GAAG,MAAM,uBAAuB,CAAC;QAC1C,KAAK,0BAA0B;YAC7B,OAAO,GAAG,MAAM,gBAAgB,CAAC,CAAC,WAAW,SAAS,CAAC;QACzD;YACE,OAAO,GAAG,MAAM,IAAI,CAAC,CAAC,IAAI,IAAI,SAAS,GAAG,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED,wEAAwE;AAExE,SAAS,eAAe,CAAC,CAAS,EAAE,QAAgB,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAEpC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,aAAa;YAChB,OAAO,CACL,GAAG,MAAM,MAAM,kBAAkB,CAAC,CAAC,CAAC,SAAU,CAAC,KAAK;gBACpD,eAAe,CAAC,CAAC,CAAC,MAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CACtC,CAAC;QAEJ,KAAK,UAAU;YACb,OAAO,CAAC;iBACL,KAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;iBAC5C,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhB,KAAK,QAAQ;YACX,OAAO,CACL,GAAG,MAAM,GAAG,KAAK,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK;gBAC/E,CAAC;qBACE,OAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,KAAK,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC1E,IAAI,CAAC,IAAI,CAAC,CACd,CAAC;QAEJ,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC,UAAU,IAAI,KAAK,EAAE,CAAC,CAAC,SAAU,CAAC,CAAC;YACnE,MAAM,OAAO,GAAG,CAAC,CAAC,UAAU;gBAC1B,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,UAAU,CAAC;gBACrC,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO;gBACpB,CAAC,CAAC,eAAe,qBAAqB,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE;gBACnD,CAAC,CAAC,EAAE,CAAC;YACP,OAAO,GAAG,MAAM,GAAG,KAAK,QAAQ,CAAC,CAAC,IAAI,QAAQ,IAAI,KAAK,OAAO,GAAG,IAAI,EAAE,CAAC;QAC1E,CAAC;QAED,KAAK,sBAAsB,CAAC,CAAC,CAAC;YAC5B,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,IAAK,CAAC,KAAK,GAAG,CAAC,CAAC,IAAK,CAAC,GAAG,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,GAAG,KAAK,QAAQ,OAAO,SAAS,CAAC,CAAC,eAAe,gBAAgB,CAAC,CAAC;YAC3F,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,OAAQ,EAAE,CAAC;gBAC7B,MAAM,GAAG,GAAG,GAAG,CAAC,WAAY,CAAC;gBAC7B,KAAK,CAAC,IAAI,CACR,GAAG,MAAM,OAAO,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,SAAS,OAAO,qBAAqB,CAAC,GAAG,CAAC,MAAO,CAAC,EAAE,CAC1G,CAAC;YACJ,CAAC;YACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QAED;YACE,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1D,CAAC;AACH,CAAC;AAED,gDAAgD;AAChD,SAAS,qBAAqB,CAAC,CAAS;IACtC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAEtC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,MAAM,EAAE,GAAG,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,CAAC;YAC9F,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,GAAG,KAAK,QAAQ,MAAM,EAAE,CAAC;QAC3D,CAAC;QACD,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,MAAM,EAAE,GAAG,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAC7C,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,IAAI,cAAc,MAAM,EAAE,CAAC;QAC5D,CAAC;QACD,KAAK,SAAS;YACZ,OAAO,WAAW,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,MAAM,EAAE,CAAC;QAC7E,KAAK,eAAe;YAClB,OAAO,QAAQ,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,qBAAqB,MAAM,EAAE,CAAC;QAC5F,KAAK,cAAc;YACjB,OAAO,GAAG,MAAM,uBAAuB,CAAC,CAAC,SAAS,GAAG,CAAC;QACxD,KAAK,eAAe;YAClB,OAAO,GAAG,MAAM,MAAM,CAAC,CAAC,WAAW,IAAI,KAAK,iBAAiB,CAAC,CAAC,OAAO,EAAE,CAAC;QAC3E,KAAK,eAAe;YAClB,OAAO,GAAG,MAAM,UAAU,eAAe,CAAC,CAAC,CAAC,UAAoB,CAAC,EAAE,CAAC;QACtE,KAAK,mBAAmB;YACtB,OAAO,GAAG,MAAM,UAAU,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC1E,KAAK,kBAAkB;YACrB,OAAO,6BAA6B,MAAM,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;QAC9D,KAAK,cAAc;YACjB,OAAO,UAAU,CAAC,CAAC,KAAK,IAAI,CAAC,gBAAgB,MAAM,SAAS,CAAC,CAAC,gBAAgB,IAAI,MAAM,SAAS,CAAC;QACpG,KAAK,mBAAmB;YACtB,OAAO,WAAW,CAAC,CAAC,KAAK,6BAA6B,MAAM,EAAE,CAAC;QACjE,KAAK,SAAS;YACZ,OAAO,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC;QAC/B,KAAK,WAAW;YACd,OAAO,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC;QACjC,KAAK,eAAe;YAClB,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;QAC5C,KAAK,gBAAgB;YACnB,OAAO,SAAS,CAAC,CAAC,MAAM,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;QAC/C,KAAK,mBAAmB;YACtB,OAAO,GAAG,MAAM,UAAU,CAAC,CAAC,KAAK,qBAAqB,CAAC;QACzD,KAAK,qBAAqB;YACxB,OAAO,8BAA8B,MAAM,EAAE,CAAC;QAChD,KAAK,gBAAgB;YACnB,OAAO,GAAG,MAAM,kBAAkB,CAAC;QACrC,KAAK,gBAAgB;YACnB,OAAO,GAAG,MAAM,kBAAkB,CAAC;QACrC,KAAK,aAAa;YAChB,OAAO,GAAG,MAAM,eAAe,CAAC;QAClC,KAAK,YAAY;YACf,OAAO,GAAG,MAAM,cAAc,CAAC;QACjC,KAAK,aAAa;YAChB,OAAO,GAAG,MAAM,kBAAkB,CAAC;QACrC,KAAK,kBAAkB;YACrB,OAAO,GAAG,MAAM,wBAAwB,CAAC;QAC3C,KAAK,oBAAoB;YACvB,OAAO,GAAG,MAAM,KAAK,CAAC,CAAC,gBAAgB,IAAI,aAAa,SAAS,CAAC,CAAC,UAAU,IAAI,GAAG,UAAU,CAAC;QACjG,KAAK,4BAA4B;YAC/B,OAAO,gBAAgB,MAAM,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC;QAEhD,4BAA4B;QAC5B,KAAK,aAAa;YAChB,OAAO,MAAM,kBAAkB,CAAC,CAAC,CAAC,SAAU,CAAC,KAAK,qBAAqB,CAAC,CAAC,CAAC,MAAO,CAAC,EAAE,CAAC;QACvF,KAAK,UAAU;YACb,OAAO,CAAC,CAAC,KAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC,UAAU,IAAI,KAAK,EAAE,CAAC,CAAC,SAAU,CAAC,CAAC;YACnE,OAAO,QAAQ,CAAC,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QACvG,CAAC;QAED;YACE,OAAO,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC;IACzB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,CAAU;IAC9B,IAAI,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IACxB,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,eAAe,CAAC,CAAS;IAChC,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,SAA0B;IAChE,MAAM,KAAK,GAAG,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,EAAE,CAAC;IACzE,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,KAAK,CAAC,CAAC,OAAO,GAAG,KAAK,GAAG,CAAC;QAC/B,KAAK,KAAK,CAAC,CAAC,OAAO,GAAG,KAAK,UAAU,CAAC;QACtC,KAAK,IAAI,CAAC,CAAC,OAAO,gBAAgB,KAAK,EAAE,CAAC;QAC1C,KAAK,IAAI,CAAC,CAAC,OAAO,aAAa,KAAK,EAAE,CAAC;QACvC,KAAK,IAAI,CAAC,CAAC,OAAO,WAAW,KAAK,EAAE,CAAC;QACrC,OAAO,CAAC,CAAC,OAAO,GAAG,KAAK,GAAG,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,wEAAwE;AAExE,SAAS,cAAc,CAAC,CAA8D;IACpF,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAClB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/C,OAAO,UAAU,KAAK,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,EAAE,eAAe,QAAQ,GAAG,CAAC;AACnG,CAAC;AAED,wEAAwE;AAExE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAa;IAEb,MAAM,QAAQ,GAAG,OAAO,CACtB,OAAO,CAAC,GAAG,EAAE,EACb,IAAI,IAAI,gDAAgD,CACzD,CAAC;IACF,MAAM,SAAS,GAAc,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAEzE,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,YAAY;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QAC9C,IAAI,CAAC,CAAC,QAAQ;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,CAAC,CAAC,aAAa;YAAE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM;YAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAErE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,UAAU,OAAO,CAAC,CAAC;QACrD,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,KAAK;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,QAAQ,SAAS,CAAC,MAAM,0BAA0B,CAAC,CAAC;AAClE,CAAC","sourcesContent":["/**\n * Translates ability DSL entries into plain English descriptions.\n *\n * Recursively walks the effect/condition tree and produces human-readable\n * text purely from the structured data — no external text sources needed.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n// ─── Types (minimal, matching schema shapes) ─────────────────────────\n\ninterface Condition {\n type?: string;\n operator?: string;\n operands?: Condition[];\n parameters?: Record<string, unknown>;\n negated?: boolean;\n}\n\ninterface Effect {\n type: string;\n target?: string;\n modifier?: Record<string, unknown>;\n condition?: Condition;\n effect?: Effect;\n steps?: Effect[];\n options?: (Effect & { name?: string; requirement?: Record<string, unknown>; choice_label?: string })[];\n choice_label?: string;\n dice?: string;\n threshold?: number | string;\n comparison?: string;\n on_success?: Effect | null;\n on_fail?: Effect | null;\n pool?: { count: number; die: string };\n max_activations?: number;\n}\n\ninterface Ability {\n ability_id: string;\n name: string;\n ability_type?: string;\n behavior?: string;\n detachment_id?: string | null;\n faction_id?: string | null;\n unit_ids?: string[];\n effect: Effect;\n scope?: { range: string; duration: string; range_inches?: number };\n}\n\n// ─── Condition translator ────────────────────────────────────────────\n\nfunction translateCondition(c: Condition): string {\n const negate = c.negated ? \"not \" : \"\";\n\n // Compound conditions\n if (c.operator === \"and\" && c.operands) {\n const parts = c.operands.map(translateCondition);\n return parts.join(\" AND \");\n }\n if (c.operator === \"or\" && c.operands) {\n const parts = c.operands.map(translateCondition);\n return parts.join(\" OR \");\n }\n if (c.operator === \"not\" && c.operands) {\n return `NOT (${c.operands.map(translateCondition).join(\", \")})`;\n }\n\n const p = c.parameters ?? {};\n\n switch (c.type) {\n case \"phase-is\":\n return `${negate}during the ${p.phase} phase`;\n case \"timing-is\":\n return `${negate}at ${formatTiming(p.timing as string)}`;\n case \"player-turn-is\":\n return `${negate}in ${p.turn === \"your-turn\" ? \"your\" : p.turn === \"opponent-turn\" ? \"opponent's\" : \"either player's\"} turn`;\n case \"charged-this-turn\":\n return `${negate}unit charged this turn`;\n case \"advanced-this-turn\":\n return `${negate}unit advanced this turn`;\n case \"remained-stationary\":\n return `${negate}unit remained stationary`;\n case \"unit-below-starting-strength\":\n return `${negate}target is below starting strength`;\n case \"unit-below-half-strength\":\n return `${negate}target is below half strength`;\n case \"unit-has-keyword\":\n return `${negate}unit has \"${p.keyword}\"`;\n case \"target-has-keyword\":\n return `${negate}target has \"${p.keyword}\"`;\n case \"model-is-leader\":\n return `${negate}model is leading a unit`;\n case \"is-attached\":\n return `${negate}attached to a ${p.keyword ?? \"\"} unit`;\n case \"attack-is-type\":\n return `${negate}for ${p.attack_type} attacks`;\n case \"is-battle-shocked\":\n return `${negate}unit is battle-shocked`;\n case \"has-lost-wounds\":\n return `${negate}model has lost wounds`;\n case \"opponent-unit-within-range\":\n return `${negate}enemy unit within ${p.range === \"engagement\" ? \"engagement range\" : p.range + '\"'}`;\n case \"unit-within-range-of\":\n return `${negate}within ${p.range}\" of ${p.target_type ?? \"target\"}${p.keyword ? ` (${p.keyword})` : \"\"}`;\n case \"within-range-of-objective\":\n return `${negate}within range of an objective`;\n case \"controls-objective\":\n return `${negate}controlling an objective`;\n case \"has-fought-this-phase\":\n return `${negate}has fought this phase`;\n case \"destroyed-by-attack-type\":\n return `${negate}destroyed by ${p.attack_type} attack`;\n default:\n return `${negate}[${c.type ?? \"unknown\"}]`;\n }\n}\n\nfunction formatTiming(t: string): string {\n return t.replace(/-/g, \" \");\n}\n\n// ─── Effect translator ───────────────────────────────────────────────\n\nfunction translateEffect(e: Effect, depth: number = 0): string {\n const indent = \" \".repeat(depth);\n const arrow = depth > 0 ? \"→ \" : \"\";\n\n switch (e.type) {\n case \"conditional\":\n return (\n `${indent}If ${translateCondition(e.condition!)}:\\n` +\n translateEffect(e.effect!, depth + 1)\n );\n\n case \"sequence\":\n return e\n .steps!.map((s) => translateEffect(s, depth))\n .join(\"\\n\");\n\n case \"choice\":\n return (\n `${indent}${arrow}Choose one${e.choice_label ? ` (${e.choice_label})` : \"\"}:\\n` +\n e\n .options!.map((o, i) => `${indent} ${i + 1}. ${translateEffectInline(o)}`)\n .join(\"\\n\")\n );\n\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n const success = e.on_success\n ? translateEffectInline(e.on_success)\n : \"nothing\";\n const fail = e.on_fail\n ? `, otherwise ${translateEffectInline(e.on_fail)}`\n : \"\";\n return `${indent}${arrow}Roll ${e.dice}: on ${comp}, ${success}${fail}`;\n }\n\n case \"dice-pool-allocation\": {\n const poolStr = `${e.pool!.count}${e.pool!.die}`;\n const lines = [`${indent}${arrow}Roll ${poolStr} (max ${e.max_activations} activations):`];\n for (const opt of e.options!) {\n const req = opt.requirement!;\n lines.push(\n `${indent} - ${opt.name}: need ${req.type} of ${req.min_value}+ → ${translateEffectInline(opt.effect!)}`\n );\n }\n return lines.join(\"\\n\");\n }\n\n default:\n return `${indent}${arrow}${translateEffectInline(e)}`;\n }\n}\n\n/** Single-line translation for leaf effects. */\nfunction translateEffectInline(e: Effect): string {\n const m = e.modifier ?? {};\n const target = formatTarget(e.target);\n\n switch (e.type) {\n case \"stat-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : m.operation === \"subtract\" ? \"-\" : `${m.operation} `;\n const scope = m.attack_type ? ` (${m.attack_type})` : \"\";\n return `${op}${m.value} ${m.stat}${scope} for ${target}`;\n }\n case \"roll-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : \"-\";\n return `${op}${m.value} to ${m.roll} rolls for ${target}`;\n }\n case \"re-roll\":\n return `re-roll ${m.roll}${m.value ? ` (${m.value}s)` : \"\"} for ${target}`;\n case \"mortal-wounds\":\n return `deal ${m.amount ?? m.amount_table ? \"variable\" : \"?\"} mortal wounds to ${target}`;\n case \"feel-no-pain\":\n return `${target} gains Feel No Pain ${m.threshold}+`;\n case \"keyword-grant\":\n return `${target}'s ${m.weapon_type ?? \"all\"} weapons gain ${m.keyword}`;\n case \"ability-grant\":\n return `${target} gains ${formatGrantType(m.grant_type as string)}`;\n case \"movement-modifier\":\n return `${target} gains ${m.move_type}${m.value ? ` ${m.value}\"` : \"\"}`;\n case \"damage-reduction\":\n return `reduce incoming damage to ${target} by ${m.amount}`;\n case \"resurrection\":\n return `return ${m.count ?? 1} model(s) to ${target} with ${m.wounds_remaining ?? \"full\"} wounds`;\n case \"model-destruction\":\n return `destroy ${m.count} non-leader model(s) from ${target}`;\n case \"cp-gain\":\n return `gain ${m.amount} CP`;\n case \"cp-refund\":\n return `refund ${m.amount} CP`;\n case \"resource-gain\":\n return `gain ${m.amount} to ${m.pool_id}`;\n case \"resource-spend\":\n return `spend ${m.amount} from ${m.pool_id}`;\n case \"invulnerable-save\":\n return `${target} gains ${m.value}+ invulnerable save`;\n case \"leadership-modifier\":\n return `force battle-shock test on ${target}`;\n case \"fight-on-death\":\n return `${target} fights on death`;\n case \"shoot-on-death\":\n return `${target} shoots on death`;\n case \"fight-first\":\n return `${target} fights first`;\n case \"fight-last\":\n return `${target} fights last`;\n case \"deep-strike\":\n return `${target} can deep strike`;\n case \"fallback-and-act\":\n return `${target} can fall back and act`;\n case \"attack-restriction\":\n return `${target}: ${m.restriction_type ?? \"restriction\"} (max ${m.max_models ?? \"?\"} models)`;\n case \"objective-control-modifier\":\n return `modify OC of ${target} by ${m.value}`;\n\n // Container types — recurse\n case \"conditional\":\n return `if ${translateCondition(e.condition!)}: ${translateEffectInline(e.effect!)}`;\n case \"sequence\":\n return e.steps!.map(translateEffectInline).join(\"; \");\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n return `roll ${e.dice} (${comp}): ${e.on_success ? translateEffectInline(e.on_success) : \"nothing\"}`;\n }\n\n default:\n return `[${e.type}]`;\n }\n}\n\nfunction formatTarget(t?: string): string {\n if (!t) return \"target\";\n return t.replace(/-/g, \" \");\n}\n\nfunction formatGrantType(g: string): string {\n return g.replace(/-/g, \" \");\n}\n\nfunction formatComparison(comp: string, threshold: number | string): string {\n const thStr = typeof threshold === \"string\" ? threshold : `${threshold}`;\n switch (comp) {\n case \"gte\": return `${thStr}+`;\n case \"lte\": return `${thStr} or less`;\n case \"gt\": return `greater than ${thStr}`;\n case \"lt\": return `less than ${thStr}`;\n case \"eq\": return `exactly ${thStr}`;\n default: return `${thStr}+`;\n }\n}\n\n// ─── Scope translator ────────────────────────────────────────────────\n\nfunction translateScope(s?: { range: string; duration: string; range_inches?: number }): string {\n if (!s) return \"\";\n const range = s.range.replace(/-/g, \" \");\n const duration = s.duration.replace(/-/g, \" \");\n return `Scope: ${range}${s.range_inches ? ` (${s.range_inches}\")` : \"\"}. Duration: ${duration}.`;\n}\n\n// ─── Main command ────────────────────────────────────────────────────\n\nexport async function translateCommand(\n path?: string\n): Promise<void> {\n const filePath = resolve(\n process.cwd(),\n path ?? \"../data/enrichment/world-eaters/abilities.json\"\n );\n const abilities: Ability[] = JSON.parse(readFileSync(filePath, \"utf-8\"));\n\n for (const a of abilities) {\n const meta: string[] = [];\n if (a.ability_type) meta.push(a.ability_type);\n if (a.behavior) meta.push(a.behavior);\n if (a.detachment_id) meta.push(`detachment: ${a.detachment_id}`);\n if (a.unit_ids?.length) meta.push(`units: ${a.unit_ids.join(\", \")}`);\n\n console.log(`\\n═══ ${a.name} [${a.ability_id}] ═══`);\n if (meta.length) console.log(` ${meta.join(\" | \")}`);\n console.log(translateEffect(a.effect));\n const scope = translateScope(a.scope);\n if (scope) console.log(scope);\n }\n\n console.log(`\\n── ${abilities.length} abilities translated ──`);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-all.d.ts","sourceRoot":"","sources":["../../src/commands/validate-all.ts"],"names":[],"mappings":"AAKA,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBlF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-all.js","sourceRoot":"","sources":["../../src/commands/validate-all.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAqB,MAAM,cAAc,CAAC;AAG/D,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAA0B;IACjE,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAE9B,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAC9D,MAAM,gBAAgB,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC;IAE1E,MAAM,QAAQ,GAAqB;QACjC,UAAU,EAAE,UAAU,CAAC,UAAU,GAAG,gBAAgB,CAAC,UAAU;QAC/D,UAAU,EAAE,UAAU,CAAC,UAAU,GAAG,gBAAgB,CAAC,UAAU;QAC/D,MAAM,EAAE,UAAU,CAAC,MAAM,GAAG,gBAAgB,CAAC,MAAM;QACnD,MAAM,EAAE,UAAU,CAAC,MAAM,GAAG,gBAAgB,CAAC,MAAM;QACnD,MAAM,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,GAAG,gBAAgB,CAAC,MAAM,CAAC;KAC3D,CAAC;IAEF,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAwB,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import { createValidator } from \"../schema-loader.js\";\nimport { validateFiles } from \"../validate.js\";\nimport { formatReport, type ReporterMode } from \"../report.js\";\nimport type { ValidationResult } from \"../validate.js\";\n\nexport async function validateAllCommand(opts: { reporter: string }): Promise<void> {\n const ajv = createValidator();\n\n const coreResult = await validateFiles(ajv, \"core/**/*.json\");\n const enrichmentResult = await validateFiles(ajv, \"enrichment/**/*.json\");\n\n const combined: ValidationResult = {\n totalFiles: coreResult.totalFiles + enrichmentResult.totalFiles,\n totalItems: coreResult.totalItems + enrichmentResult.totalItems,\n passed: coreResult.passed + enrichmentResult.passed,\n failed: coreResult.failed + enrichmentResult.failed,\n errors: [...coreResult.errors, ...enrichmentResult.errors],\n };\n\n const output = formatReport(combined, opts.reporter as ReporterMode);\n console.log(output);\n if (combined.failed > 0) {\n process.exit(1);\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-core.d.ts","sourceRoot":"","sources":["../../src/commands/validate-core.ts"],"names":[],"mappings":"AAIA,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQnF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-core.js","sourceRoot":"","sources":["../../src/commands/validate-core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAqB,MAAM,cAAc,CAAC;AAE/D,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAA0B;IAClE,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,QAAwB,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import { createValidator } from \"../schema-loader.js\";\nimport { validateFiles } from \"../validate.js\";\nimport { formatReport, type ReporterMode } from \"../report.js\";\n\nexport async function validateCoreCommand(opts: { reporter: string }): Promise<void> {\n const ajv = createValidator();\n const result = await validateFiles(ajv, \"core/**/*.json\");\n const output = formatReport(result, opts.reporter as ReporterMode);\n console.log(output);\n if (result.failed > 0) {\n process.exit(1);\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-enrichment.d.ts","sourceRoot":"","sources":["../../src/commands/validate-enrichment.ts"],"names":[],"mappings":"AAIA,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-enrichment.js","sourceRoot":"","sources":["../../src/commands/validate-enrichment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAqB,MAAM,cAAc,CAAC;AAE/D,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,IAA0B;IACxE,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,QAAwB,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC","sourcesContent":["import { createValidator } from \"../schema-loader.js\";\nimport { validateFiles } from \"../validate.js\";\nimport { formatReport, type ReporterMode } from \"../report.js\";\n\nexport async function validateEnrichmentCommand(opts: { reporter: string }): Promise<void> {\n const ajv = createValidator();\n const result = await validateFiles(ajv, \"enrichment/**/*.json\");\n const output = formatReport(result, opts.reporter as ReporterMode);\n console.log(output);\n if (result.failed > 0) {\n process.exit(1);\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert-faction.d.ts","sourceRoot":"","sources":["../src/convert-faction.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,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,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAgb1D"}
|
package/dist/convert-faction.js
CHANGED