@alpaca-software/40kdc-data 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/abilities-resolver/resolver.d.ts +13 -4
- package/dist/abilities-resolver/resolver.d.ts.map +1 -1
- package/dist/abilities-resolver/resolver.js +22 -15
- package/dist/abilities-resolver/resolver.js.map +1 -1
- package/dist/audit-coverage.d.ts +78 -0
- package/dist/audit-coverage.d.ts.map +1 -0
- package/dist/audit-coverage.js +341 -0
- package/dist/audit-coverage.js.map +1 -0
- package/dist/author-batch.d.ts +147 -0
- package/dist/author-batch.d.ts.map +1 -0
- package/dist/author-batch.js +675 -0
- package/dist/author-batch.js.map +1 -0
- package/dist/author-input.d.ts +37 -0
- package/dist/author-input.d.ts.map +1 -0
- package/dist/author-input.js +162 -0
- package/dist/author-input.js.map +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/translate.d.ts.map +1 -1
- package/dist/commands/translate.js +9 -4
- package/dist/commands/translate.js.map +1 -1
- package/dist/cruncher/attribution.d.ts +66 -0
- package/dist/cruncher/attribution.d.ts.map +1 -0
- package/dist/cruncher/attribution.js +88 -0
- package/dist/cruncher/attribution.js.map +1 -0
- package/dist/cruncher/buffs.d.ts +80 -2
- package/dist/cruncher/buffs.d.ts.map +1 -1
- package/dist/cruncher/buffs.js +33 -4
- package/dist/cruncher/buffs.js.map +1 -1
- package/dist/cruncher/engine.d.ts.map +1 -1
- package/dist/cruncher/engine.js +50 -15
- package/dist/cruncher/engine.js.map +1 -1
- package/dist/cruncher/from-dsl.d.ts +32 -0
- package/dist/cruncher/from-dsl.d.ts.map +1 -1
- package/dist/cruncher/from-dsl.js +605 -45
- package/dist/cruncher/from-dsl.js.map +1 -1
- package/dist/cruncher/index.d.ts +1 -0
- package/dist/cruncher/index.d.ts.map +1 -1
- package/dist/cruncher/index.js +1 -0
- package/dist/cruncher/index.js.map +1 -1
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/collection.d.ts +9 -0
- package/dist/data/collection.d.ts.map +1 -1
- package/dist/data/collection.js +14 -0
- package/dist/data/collection.js.map +1 -1
- package/dist/data/dataset.d.ts +80 -2
- package/dist/data/dataset.d.ts.map +1 -1
- package/dist/data/dataset.js +143 -6
- package/dist/data/dataset.js.map +1 -1
- package/dist/data/entities.d.ts +2 -5
- package/dist/data/entities.d.ts.map +1 -1
- package/dist/data/entities.js.map +1 -1
- package/dist/data/index.d.ts +3 -2
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +1 -1
- package/dist/data/index.js.map +1 -1
- package/dist/data/normalize.d.ts.map +1 -1
- package/dist/data/normalize.js +8 -1
- package/dist/data/normalize.js.map +1 -1
- package/dist/data/roster-resolve.d.ts +26 -1
- package/dist/data/roster-resolve.d.ts.map +1 -1
- package/dist/data/roster-resolve.js +46 -0
- package/dist/data/roster-resolve.js.map +1 -1
- package/dist/export/index.d.ts +1 -0
- package/dist/export/index.d.ts.map +1 -1
- package/dist/export/index.js +3 -0
- package/dist/export/index.js.map +1 -1
- package/dist/export/rosterizer.d.ts +3 -0
- package/dist/export/rosterizer.d.ts.map +1 -0
- package/dist/export/rosterizer.js +144 -0
- package/dist/export/rosterizer.js.map +1 -0
- package/dist/export/serializer.d.ts +1 -1
- package/dist/export/serializer.d.ts.map +1 -1
- package/dist/export/serializer.js.map +1 -1
- package/dist/gen-conformance.js +212 -11
- package/dist/gen-conformance.js.map +1 -1
- package/dist/import/gw.d.ts +69 -0
- package/dist/import/gw.d.ts.map +1 -0
- package/dist/import/gw.js +245 -0
- package/dist/import/gw.js.map +1 -0
- package/dist/import/import-roster.d.ts +52 -3
- package/dist/import/import-roster.d.ts.map +1 -1
- package/dist/import/import-roster.js +114 -4
- package/dist/import/import-roster.js.map +1 -1
- package/dist/import/index.d.ts +2 -2
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -1
- package/dist/import/index.js.map +1 -1
- package/dist/import/listforge.d.ts.map +1 -1
- package/dist/import/listforge.js +15 -1
- package/dist/import/listforge.js.map +1 -1
- package/dist/import/newrecruit-text.d.ts +3 -0
- package/dist/import/newrecruit-text.d.ts.map +1 -1
- package/dist/import/newrecruit-text.js +6 -0
- package/dist/import/newrecruit-text.js.map +1 -1
- package/dist/import/newrecruit-wtc.d.ts.map +1 -1
- package/dist/import/newrecruit-wtc.js +10 -7
- package/dist/import/newrecruit-wtc.js.map +1 -1
- package/dist/import/rosterizer.d.ts +70 -0
- package/dist/import/rosterizer.d.ts.map +1 -0
- package/dist/import/rosterizer.js +348 -0
- package/dist/import/rosterizer.js.map +1 -0
- package/dist/import/types.d.ts +1 -1
- package/dist/import/types.d.ts.map +1 -1
- package/dist/import/types.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/migrations/2026-weapon-keywords.js +4 -0
- package/dist/migrations/2026-weapon-keywords.js.map +1 -1
- package/dist/runner.d.ts +38 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +537 -0
- package/dist/runner.js.map +1 -0
- package/dist/scrub-defensive-flag.d.ts +23 -0
- package/dist/scrub-defensive-flag.d.ts.map +1 -0
- package/dist/scrub-defensive-flag.js +149 -0
- package/dist/scrub-defensive-flag.js.map +1 -0
- package/dist/scrub-ip.d.ts +14 -0
- package/dist/scrub-ip.d.ts.map +1 -0
- package/dist/scrub-ip.js +88 -0
- package/dist/scrub-ip.js.map +1 -0
- package/package.json +10 -2
- package/schemas/core/roster.schema.json +3 -1
- package/schemas/enrichment/ability-dsl/effect.schema.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entities.js","sourceRoot":"","sources":["../../src/data/entities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Linked views over the richly-connected entity types. Each wraps a raw\n * generated record and resolves its relationships lazily against the owning\n * {@link Dataset}; the full underlying record is always available via `.raw`.\n *\n * @packageDocumentation\n */\nimport type {\n AbilityDSLEntry,\n Faction,\n Phase,\n Unit,\n Weapon,\n WeaponKeyword,\n} from \"../generated.js\";\nimport type { Buff, BuffSource, EngineContext } from \"../cruncher/buffs.js\";\nimport { buffsFromKeyword } from \"../cruncher/from-keyword.js\";\nimport {\n effectToBuffs,\n type TranslationPerspective,\n type UnsupportedFragment,\n} from \"../cruncher/from-dsl.js\";\nimport type { Dataset } from \"./dataset.js\";\n\n/** A unit, linked to its faction, weapons, and abilities. */\nexport class UnitView {\n constructor(\n /** The full generated `Unit` record. */\n readonly raw: Unit,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** The unit's faction, or `undefined` if its `faction_id` is unknown. */\n get faction(): FactionView | undefined {\n return this.ds.factions.get(this.raw.faction_id);\n }\n\n /** Weapons referenced by `weapon_ids`; unresolved ids are skipped. */\n get weapons(): WeaponView[] {\n return resolveAll(this.raw.weapon_ids, (id) => this.ds.weapons.get(id));\n }\n\n /** Abilities referenced by `ability_ids`; unresolved ids are skipped. */\n get abilities(): AbilityView[] {\n return resolveAll(this.raw.ability_ids, (id) => this.ds.abilities.get(id));\n }\n\n /**\n * The stat profile at index `i` (default 0). Returns the schema-generated\n * profile object directly so callers can feed it straight to the engine\n * without an intermediate wrapper.\n */\n profileAt(i = 0): Unit[\"profiles\"][number] {\n const profile = this.raw.profiles[i];\n if (profile === undefined) {\n throw new RangeError(\n `UnitView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`,\n );\n }\n return profile;\n }\n}\n\n/**\n * An ability, linked to the phases it acts in and the units that have it.\n *\n * Phases are not stored on the ability — they live in `phase-mappings` records.\n *\n * @example\n * units.find(\"Kharn\")!.abilities\n * .filter(a => a.phases.includes(\"shooting\"));\n */\nexport class AbilityView {\n constructor(\n /** The full generated ability record. */\n readonly raw: AbilityDSLEntry,\n private readonly ds: Dataset,\n ) {}\n\n /** The ability's id (`ability_id` in the raw record). */\n get id(): string {\n return this.raw.ability_id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Game phases this ability acts in, unioned across its phase-mappings. */\n get phases(): Phase[] {\n return this.ds.phasesFor(\"ability\", this.raw.ability_id);\n }\n\n /** Units that list this ability in their `ability_ids`. */\n get units(): UnitView[] {\n return this.ds.unitsWithAbility(this.raw.ability_id);\n }\n\n /**\n * Buff stack this ability contributes against `context`, with provenance\n * tagged via `source` (the caller knows whether this ability is being read\n * as army, detachment, unit, leader, etc.). DSL branches the buff layer\n * can't auto-apply are dropped here; call {@link describeBuffs} if you\n * also want the diagnostics. `perspective` defaults to `\"attacker\"`; pass\n * `\"target\"` to translate the ability as a defensive buff (FNP, T/Sv\n * stat-mods, save rerolls, incoming hit penalties).\n */\n getBuffs(\n source: BuffSource,\n context?: EngineContext,\n perspective: TranslationPerspective = \"attacker\",\n ): Buff[] {\n return this.describeBuffs(source, context, perspective).applied;\n }\n\n /**\n * Full DSL→Buff translation, including the `unsupported` list of effect\n * fragments the buff layer can't model. The SPA renders these as warnings\n * so users see which abilities have effects that need a manual toggle.\n */\n describeBuffs(\n source: BuffSource,\n context?: EngineContext,\n perspective: TranslationPerspective = \"attacker\",\n ): { applied: Buff[]; unsupported: UnsupportedFragment[] } {\n const ctx: EngineContext = context ?? { phase: \"shooting\" };\n return effectToBuffs(this.raw.effect, source, ctx, perspective);\n }\n}\n\n/** A weapon, linked to the units that carry it. */\nexport class WeaponView {\n constructor(\n /** The full generated `Weapon` record. */\n readonly raw: Weapon,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Units that list this weapon in their `weapon_ids`. */\n get units(): UnitView[] {\n return this.ds.unitsWithWeapon(this.raw.id);\n }\n\n /** The stat profile at index `i` (default 0). */\n profileAt(i = 0): Weapon[\"profiles\"][number] {\n const profile = this.raw.profiles[i];\n if (profile === undefined) {\n throw new RangeError(\n `WeaponView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`,\n );\n }\n return profile;\n }\n\n /**\n * Catalog views for each keyword referenced by profile `i`, paired with the\n * reference-site parameters. Unresolved keyword ids are skipped.\n */\n keywordsAt(\n i = 0,\n ): { keyword: WeaponKeywordView; parameters: Record<string, unknown> | undefined }[] {\n const profile = this.profileAt(i);\n const refs = profile.keywords ?? [];\n const out: { keyword: WeaponKeywordView; parameters: Record<string, unknown> | undefined }[] = [];\n for (const ref of refs) {\n const view = this.ds.weaponKeywords.get(ref.keyword_id);\n if (!view) continue;\n out.push({\n keyword: view,\n parameters: ref.parameters as Record<string, unknown> | undefined,\n });\n }\n return out;\n }\n\n /**\n * Buffs contributed by profile `i`'s intrinsic keywords against `context` —\n * the natural \"what does this profile bring on its own?\" call the engine\n * makes automatically before adding ability/manual buffs.\n */\n profileBuffs(i: number | undefined, context: EngineContext): Buff[] {\n const index = i ?? 0;\n const out: Buff[] = [];\n for (const { keyword, parameters } of this.keywordsAt(index)) {\n out.push(\n ...buffsFromKeyword({\n keywordId: keyword.id,\n weaponId: this.raw.id,\n effect: keyword.raw.effect,\n ...(parameters !== undefined ? { parameters } : {}),\n context,\n }),\n );\n }\n return out;\n }\n}\n\n/**\n * A weapon-keyword catalog entry, linked to the weapons whose profiles\n * reference it. Exposes the keyword's mechanical effect as a buff stack\n * via {@link getBuffs}.\n */\nexport class WeaponKeywordView {\n constructor(\n /** The full generated `WeaponKeyword` record. */\n readonly raw: WeaponKeyword,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Weapons whose profiles reference this keyword id. */\n get weapons(): WeaponView[] {\n return this.ds.weaponsWithKeyword(this.raw.id);\n }\n\n /**\n * Buff contributions from this catalog entry, for one reference site:\n * pass the keyword's `parameters` (e.g. `{ value: 1 }` for Sustained Hits 1)\n * along with the `weaponId` that's carrying it (used as the buff source)\n * and the engine `context` (e.g. attacker stationary?).\n */\n getBuffs(\n parameters: Record<string, unknown> | undefined,\n weaponId: string,\n context: EngineContext,\n ): Buff[] {\n return buffsFromKeyword({\n keywordId: this.raw.id,\n weaponId,\n effect: this.raw.effect,\n ...(parameters !== undefined ? { parameters } : {}),\n context,\n });\n }\n}\n\n/** A faction, linked to its units and the records scoped to it. */\nexport class FactionView {\n constructor(\n /** The full generated `Faction` record. */\n readonly raw: Faction,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Units whose `faction_id` is this faction (may be empty for successors). */\n get units(): UnitView[] {\n return this.ds.units.byFaction(this.raw.id);\n }\n\n /** Faction-scoped abilities (abilities whose `faction_id` is this faction). */\n get abilities(): AbilityView[] {\n return this.ds.abilities.byFaction(this.raw.id);\n }\n\n /** Distinct weapons carried by this faction's units. */\n get weapons(): WeaponView[] {\n const seen = new Set<string>();\n const out: WeaponView[] = [];\n for (const unit of this.units) {\n for (const weapon of unit.weapons) {\n if (seen.has(weapon.id)) continue;\n seen.add(weapon.id);\n out.push(weapon);\n }\n }\n return out;\n }\n}\n\n/** Resolve a list of ids, dropping any that don't resolve. */\nfunction resolveAll<V>(ids: string[] | undefined, get: (id: string) => V | undefined): V[] {\n const out: V[] = [];\n for (const id of ids ?? []) {\n const v = get(id);\n if (v !== undefined) out.push(v);\n }\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entities.js","sourceRoot":"","sources":["../../src/data/entities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Linked views over the richly-connected entity types. Each wraps a raw\n * generated record and resolves its relationships lazily against the owning\n * {@link Dataset}; the full underlying record is always available via `.raw`.\n *\n * @packageDocumentation\n */\nimport type {\n AbilityDSLEntry,\n Faction,\n Phase,\n Unit,\n Weapon,\n WeaponKeyword,\n} from \"../generated.js\";\nimport type { Buff, BuffSource, EngineContext } from \"../cruncher/buffs.js\";\nimport { buffsFromKeyword } from \"../cruncher/from-keyword.js\";\nimport {\n effectToBuffs,\n type EffectTranslation,\n type TranslationPerspective,\n} from \"../cruncher/from-dsl.js\";\nimport type { Dataset } from \"./dataset.js\";\n\n/** A unit, linked to its faction, weapons, and abilities. */\nexport class UnitView {\n constructor(\n /** The full generated `Unit` record. */\n readonly raw: Unit,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** The unit's faction, or `undefined` if its `faction_id` is unknown. */\n get faction(): FactionView | undefined {\n return this.ds.factions.get(this.raw.faction_id);\n }\n\n /** Weapons referenced by `weapon_ids`; unresolved ids are skipped. */\n get weapons(): WeaponView[] {\n return resolveAll(this.raw.weapon_ids, (id) => this.ds.weapons.get(id));\n }\n\n /** Abilities referenced by `ability_ids`; unresolved ids are skipped. */\n get abilities(): AbilityView[] {\n return resolveAll(this.raw.ability_ids, (id) => this.ds.abilities.get(id));\n }\n\n /**\n * The stat profile at index `i` (default 0). Returns the schema-generated\n * profile object directly so callers can feed it straight to the engine\n * without an intermediate wrapper.\n */\n profileAt(i = 0): Unit[\"profiles\"][number] {\n const profile = this.raw.profiles[i];\n if (profile === undefined) {\n throw new RangeError(\n `UnitView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`,\n );\n }\n return profile;\n }\n}\n\n/**\n * An ability, linked to the phases it acts in and the units that have it.\n *\n * Phases are not stored on the ability — they live in `phase-mappings` records.\n *\n * @example\n * units.find(\"Kharn\")!.abilities\n * .filter(a => a.phases.includes(\"shooting\"));\n */\nexport class AbilityView {\n constructor(\n /** The full generated ability record. */\n readonly raw: AbilityDSLEntry,\n private readonly ds: Dataset,\n ) {}\n\n /** The ability's id (`ability_id` in the raw record). */\n get id(): string {\n return this.raw.ability_id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Game phases this ability acts in, unioned across its phase-mappings. */\n get phases(): Phase[] {\n return this.ds.phasesFor(\"ability\", this.raw.ability_id);\n }\n\n /** Units that list this ability in their `ability_ids`. */\n get units(): UnitView[] {\n return this.ds.unitsWithAbility(this.raw.ability_id);\n }\n\n /**\n * Buff stack this ability contributes against `context`, with provenance\n * tagged via `source` (the caller knows whether this ability is being read\n * as army, detachment, unit, leader, etc.). DSL branches the buff layer\n * can't auto-apply are dropped here; call {@link describeBuffs} if you\n * also want the diagnostics. `perspective` defaults to `\"attacker\"`; pass\n * `\"target\"` to translate the ability as a defensive buff (FNP, T/Sv\n * stat-mods, save rerolls, incoming hit penalties).\n */\n getBuffs(\n source: BuffSource,\n context?: EngineContext,\n perspective: TranslationPerspective = \"attacker\",\n ): Buff[] {\n return this.describeBuffs(source, context, perspective).applied;\n }\n\n /**\n * Full DSL→Buff translation, including the `unsupported` list of effect\n * fragments the buff layer can't model. The SPA renders these as warnings\n * so users see which abilities have effects that need a manual toggle.\n */\n describeBuffs(\n source: BuffSource,\n context?: EngineContext,\n perspective: TranslationPerspective = \"attacker\",\n ): EffectTranslation {\n const ctx: EngineContext = context ?? { phase: \"shooting\" };\n return effectToBuffs(this.raw.effect, source, ctx, perspective);\n }\n}\n\n/** A weapon, linked to the units that carry it. */\nexport class WeaponView {\n constructor(\n /** The full generated `Weapon` record. */\n readonly raw: Weapon,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Units that list this weapon in their `weapon_ids`. */\n get units(): UnitView[] {\n return this.ds.unitsWithWeapon(this.raw.id);\n }\n\n /** The stat profile at index `i` (default 0). */\n profileAt(i = 0): Weapon[\"profiles\"][number] {\n const profile = this.raw.profiles[i];\n if (profile === undefined) {\n throw new RangeError(\n `WeaponView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`,\n );\n }\n return profile;\n }\n\n /**\n * Catalog views for each keyword referenced by profile `i`, paired with the\n * reference-site parameters. Unresolved keyword ids are skipped.\n */\n keywordsAt(\n i = 0,\n ): { keyword: WeaponKeywordView; parameters: Record<string, unknown> | undefined }[] {\n const profile = this.profileAt(i);\n const refs = profile.keywords ?? [];\n const out: { keyword: WeaponKeywordView; parameters: Record<string, unknown> | undefined }[] = [];\n for (const ref of refs) {\n const view = this.ds.weaponKeywords.get(ref.keyword_id);\n if (!view) continue;\n out.push({\n keyword: view,\n parameters: ref.parameters as Record<string, unknown> | undefined,\n });\n }\n return out;\n }\n\n /**\n * Buffs contributed by profile `i`'s intrinsic keywords against `context` —\n * the natural \"what does this profile bring on its own?\" call the engine\n * makes automatically before adding ability/manual buffs.\n */\n profileBuffs(i: number | undefined, context: EngineContext): Buff[] {\n const index = i ?? 0;\n const out: Buff[] = [];\n for (const { keyword, parameters } of this.keywordsAt(index)) {\n out.push(\n ...buffsFromKeyword({\n keywordId: keyword.id,\n weaponId: this.raw.id,\n effect: keyword.raw.effect,\n ...(parameters !== undefined ? { parameters } : {}),\n context,\n }),\n );\n }\n return out;\n }\n}\n\n/**\n * A weapon-keyword catalog entry, linked to the weapons whose profiles\n * reference it. Exposes the keyword's mechanical effect as a buff stack\n * via {@link getBuffs}.\n */\nexport class WeaponKeywordView {\n constructor(\n /** The full generated `WeaponKeyword` record. */\n readonly raw: WeaponKeyword,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Weapons whose profiles reference this keyword id. */\n get weapons(): WeaponView[] {\n return this.ds.weaponsWithKeyword(this.raw.id);\n }\n\n /**\n * Buff contributions from this catalog entry, for one reference site:\n * pass the keyword's `parameters` (e.g. `{ value: 1 }` for Sustained Hits 1)\n * along with the `weaponId` that's carrying it (used as the buff source)\n * and the engine `context` (e.g. attacker stationary?).\n */\n getBuffs(\n parameters: Record<string, unknown> | undefined,\n weaponId: string,\n context: EngineContext,\n ): Buff[] {\n return buffsFromKeyword({\n keywordId: this.raw.id,\n weaponId,\n effect: this.raw.effect,\n ...(parameters !== undefined ? { parameters } : {}),\n context,\n });\n }\n}\n\n/** A faction, linked to its units and the records scoped to it. */\nexport class FactionView {\n constructor(\n /** The full generated `Faction` record. */\n readonly raw: Faction,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Units whose `faction_id` is this faction (may be empty for successors). */\n get units(): UnitView[] {\n return this.ds.units.byFaction(this.raw.id);\n }\n\n /** Faction-scoped abilities (abilities whose `faction_id` is this faction). */\n get abilities(): AbilityView[] {\n return this.ds.abilities.byFaction(this.raw.id);\n }\n\n /** Distinct weapons carried by this faction's units. */\n get weapons(): WeaponView[] {\n const seen = new Set<string>();\n const out: WeaponView[] = [];\n for (const unit of this.units) {\n for (const weapon of unit.weapons) {\n if (seen.has(weapon.id)) continue;\n seen.add(weapon.id);\n out.push(weapon);\n }\n }\n return out;\n }\n}\n\n/** Resolve a list of ids, dropping any that don't resolve. */\nfunction resolveAll<V>(ids: string[] | undefined, get: (id: string) => V | undefined): V[] {\n const out: V[] = [];\n for (const id of ids ?? []) {\n const v = get(id);\n if (v !== undefined) out.push(v);\n }\n return out;\n}\n"]}
|
package/dist/data/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* factions.find("World Eaters")!.units.length;
|
|
21
21
|
*/
|
|
22
22
|
export { Dataset } from "./dataset.js";
|
|
23
|
+
export type { StackableBuff, StackableBuffGroup } from "./dataset.js";
|
|
23
24
|
export { Collection } from "./collection.js";
|
|
24
25
|
export type { CollectionConfig } from "./collection.js";
|
|
25
26
|
export { UnitView, AbilityView, WeaponView, WeaponKeywordView, FactionView, } from "./entities.js";
|
|
@@ -28,9 +29,9 @@ export { emptyRawData } from "./types.js";
|
|
|
28
29
|
export type { RawData } from "./types.js";
|
|
29
30
|
export * from "../cruncher/index.js";
|
|
30
31
|
export { effectToBuffs, parseKeywordGrant } from "../cruncher/from-dsl.js";
|
|
31
|
-
export type { EffectTranslation, TranslationPerspective, UnsupportedFragment, } from "../cruncher/from-dsl.js";
|
|
32
|
+
export type { ActivatableBuff, ActivatableGroupRef, EffectTranslation, TranslationPerspective, UnsupportedFragment, } from "../cruncher/from-dsl.js";
|
|
32
33
|
export * from "../abilities-resolver/index.js";
|
|
33
|
-
export { resolveRosterUnit, resolveRosterWargear } from "./roster-resolve.js";
|
|
34
|
+
export { resolveRosterUnit, resolveRosterWargear, resolveAttachedLeader, resolveAttachmentPartners, } from "./roster-resolve.js";
|
|
34
35
|
import { Dataset } from "./dataset.js";
|
|
35
36
|
/** The dataset built from this package's embedded data. */
|
|
36
37
|
export declare const dataset: Dataset;
|
package/dist/data/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,QAAQ,EACR,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAK1C,cAAc,sBAAsB,CAAC;AAGrC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EACV,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AAGjC,cAAc,gCAAgC,CAAC;AAG/C,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,QAAQ,EACR,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAK1C,cAAc,sBAAsB,CAAC;AAGrC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,yBAAyB,CAAC;AAGjC,cAAc,gCAAgC,CAAC;AAG/C,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,2DAA2D;AAC3D,eAAO,MAAM,OAAO,SAAqB,CAAC;AAE1C,kEAAkE;AAClE,eAAO,MAAM,KAAK,wGAAgB,CAAC;AACnC,wDAAwD;AACxD,eAAO,MAAM,OAAO,4GAAkB,CAAC;AACvC,kFAAkF;AAClF,eAAO,MAAM,cAAc,0HAAyB,CAAC;AACrD,mEAAmE;AACnE,eAAO,MAAM,QAAQ,8GAAmB,CAAC;AACzC,0EAA0E;AAC1E,eAAO,MAAM,SAAS,sHAAoB,CAAC;AAC3C,uBAAuB;AACvB,eAAO,MAAM,WAAW,kHAAsB,CAAC;AAC/C,wBAAwB;AACxB,eAAO,MAAM,YAAY,oHAAuB,CAAC;AACjD,sBAAsB;AACtB,eAAO,MAAM,UAAU,gHAAqB,CAAC;AAC7C,2BAA2B;AAC3B,eAAO,MAAM,cAAc,wHAAyB,CAAC;AACrD,oBAAoB;AACpB,eAAO,MAAM,QAAQ,4GAAmB,CAAC;AACzC,4BAA4B;AAC5B,eAAO,MAAM,eAAe,0HAA0B,CAAC;AACvD,mCAAmC;AACnC,eAAO,MAAM,cAAc,wHAAyB,CAAC;AACrD,+BAA+B;AAC/B,eAAO,MAAM,kBAAkB,gIAA6B,CAAC;AAC7D,8BAA8B;AAC9B,eAAO,MAAM,iBAAiB,8HAA4B,CAAC;AAC3D,0BAA0B;AAC1B,eAAO,MAAM,aAAa,sHAAwB,CAAC"}
|
package/dist/data/index.js
CHANGED
|
@@ -33,7 +33,7 @@ export { effectToBuffs, parseKeywordGrant } from "../cruncher/from-dsl.js";
|
|
|
33
33
|
// The eligible-abilities resolver (also reachable as Dataset.eligibleAbilities).
|
|
34
34
|
export * from "../abilities-resolver/index.js";
|
|
35
35
|
// Bridge helpers from the importer's RosterUnit → linked views.
|
|
36
|
-
export { resolveRosterUnit, resolveRosterWargear } from "./roster-resolve.js";
|
|
36
|
+
export { resolveRosterUnit, resolveRosterWargear, resolveAttachedLeader, resolveAttachmentPartners, } from "./roster-resolve.js";
|
|
37
37
|
import { Dataset } from "./dataset.js";
|
|
38
38
|
/** The dataset built from this package's embedded data. */
|
|
39
39
|
export const dataset = Dataset.embedded();
|
package/dist/data/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EACL,QAAQ,EACR,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,6EAA6E;AAC7E,sEAAsE;AACtE,gEAAgE;AAChE,cAAc,sBAAsB,CAAC;AAErC,4EAA4E;AAC5E,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAS3E,iFAAiF;AACjF,cAAc,gCAAgC,CAAC;AAE/C,gEAAgE;AAChE,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,2DAA2D;AAC3D,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;AAE1C,kEAAkE;AAClE,MAAM,CAAC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;AACnC,wDAAwD;AACxD,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;AACvC,kFAAkF;AAClF,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACrD,mEAAmE;AACnE,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;AACzC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;AAC3C,uBAAuB;AACvB,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;AAC/C,wBAAwB;AACxB,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;AACjD,sBAAsB;AACtB,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;AAC7C,2BAA2B;AAC3B,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACrD,oBAAoB;AACpB,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;AACzC,4BAA4B;AAC5B,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;AACvD,mCAAmC;AACnC,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;AACrD,+BAA+B;AAC/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;AAC7D,8BAA8B;AAC9B,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;AAC3D,0BAA0B;AAC1B,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC","sourcesContent":["/**\n * The linked, typed 40kdc dataset.\n *\n * The default {@link dataset} is built once from the data embedded in this\n * package; the top-level collections below are its accessors, re-exported for\n * the ergonomic one-liner form.\n *\n * @packageDocumentation\n *\n * @example\n * import { units } from \"@alpaca-software/40kdc-data\";\n *\n * units.find(\"Kharn\")!.abilities\n * .filter(a => a.phases.includes(\"shooting\"))\n * .map(a => a.id); // [\"berzerker-frenzy\"]\n *\n * @example\n * import { factions } from \"@alpaca-software/40kdc-data\";\n *\n * factions.find(\"World Eaters\")!.units.length;\n */\nexport { Dataset } from \"./dataset.js\";\nexport type { StackableBuff, StackableBuffGroup } from \"./dataset.js\";\nexport { Collection } from \"./collection.js\";\nexport type { CollectionConfig } from \"./collection.js\";\nexport {\n UnitView,\n AbilityView,\n WeaponView,\n WeaponKeywordView,\n FactionView,\n} from \"./entities.js\";\nexport { normalizeName } from \"./normalize.js\";\nexport { emptyRawData } from \"./types.js\";\nexport type { RawData } from \"./types.js\";\n\n// The cruncher surface — buff types + the engine — re-exported from the data\n// package so downstream callers can import their whole 40kdc API from\n// `@alpaca-software/40kdc-data` without reaching into subpaths.\nexport * from \"../cruncher/index.js\";\n\n// The DSL→Buff translator that powers AbilityView.getBuffs / describeBuffs.\nexport { effectToBuffs, parseKeywordGrant } from \"../cruncher/from-dsl.js\";\nexport type {\n ActivatableBuff,\n ActivatableGroupRef,\n EffectTranslation,\n TranslationPerspective,\n UnsupportedFragment,\n} from \"../cruncher/from-dsl.js\";\n\n// The eligible-abilities resolver (also reachable as Dataset.eligibleAbilities).\nexport * from \"../abilities-resolver/index.js\";\n\n// Bridge helpers from the importer's RosterUnit → linked views.\nexport {\n resolveRosterUnit,\n resolveRosterWargear,\n resolveAttachedLeader,\n resolveAttachmentPartners,\n} from \"./roster-resolve.js\";\n\nimport { Dataset } from \"./dataset.js\";\n\n/** The dataset built from this package's embedded data. */\nexport const dataset = Dataset.embedded();\n\n/** All units, linked to their faction, weapons, and abilities. */\nexport const units = dataset.units;\n/** All weapons, linked to the units that carry them. */\nexport const weapons = dataset.weapons;\n/** Catalog of weapon keywords (Lethal Hits, Sustained Hits N, Anti-X N+, ...). */\nexport const weaponKeywords = dataset.weaponKeywords;\n/** All factions, linked to their units, abilities, and weapons. */\nexport const factions = dataset.factions;\n/** All abilities, linked to their phases and the units that have them. */\nexport const abilities = dataset.abilities;\n/** All detachments. */\nexport const detachments = dataset.detachments;\n/** All enhancements. */\nexport const enhancements = dataset.enhancements;\n/** All stratagems. */\nexport const stratagems = dataset.stratagems;\n/** All wargear options. */\nexport const wargearOptions = dataset.wargearOptions;\n/** All missions. */\nexport const missions = dataset.missions;\n/** All mission matchups. */\nexport const missionMatchups = dataset.missionMatchups;\n/** All secondary mission cards. */\nexport const secondaryCards = dataset.secondaryCards;\n/** All deployment patterns. */\nexport const deploymentPatterns = dataset.deploymentPatterns;\n/** All force dispositions. */\nexport const forceDispositions = dataset.forceDispositions;\n/** All resource pools. */\nexport const resourcePools = dataset.resourcePools;\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/data/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/data/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAenD"}
|
package/dist/data/normalize.js
CHANGED
|
@@ -29,7 +29,14 @@
|
|
|
29
29
|
export function normalizeName(input) {
|
|
30
30
|
return input
|
|
31
31
|
.normalize("NFD")
|
|
32
|
-
|
|
32
|
+
// Strip the Unicode Mark category (Mn/Mc/Me) — every combining mark.
|
|
33
|
+
// \p{Diacritic} is a smaller property that misses some Mn characters,
|
|
34
|
+
// which let TS and Rust drift apart on non-Latin combining marks (the
|
|
35
|
+
// Rust mirror uses unicode-normalization's is_combining_mark, which is
|
|
36
|
+
// \p{M}). The cross-impl property fuzz in tooling/parity/ surfaces the
|
|
37
|
+
// divergence; the documented contract in CONFORMANCE.md is "strip
|
|
38
|
+
// combining marks", so \p{M} is canonical.
|
|
39
|
+
.replace(/\p{M}/gu, "")
|
|
33
40
|
.toLowerCase()
|
|
34
41
|
.replace(/['’‘`"“”]/g, "")
|
|
35
42
|
.replace(/[\s-]+/g, " ")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/data/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK;SACT,SAAS,CAAC,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/data/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK;SACT,SAAS,CAAC,KAAK,CAAC;QACjB,qEAAqE;QACrE,sEAAsE;QACtE,sEAAsE;QACtE,uEAAuE;QACvE,uEAAuE;QACvE,kEAAkE;QAClE,2CAA2C;SAC1C,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;SACtB,WAAW,EAAE;SACb,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;SACzB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,IAAI,EAAE,CAAC;AACZ,CAAC","sourcesContent":["/**\n * Name normalization for diacritic- and punctuation-insensitive lookup.\n *\n * Warhammer 40,000 is played globally and many entity names carry diacritics or\n * punctuation — \"Khârn the Betrayer\", \"T'au\", \"Be'lakor\". A user typing the\n * plain-ASCII form of a name must still find the entity. Every name comparison\n * in this package routes through {@link normalizeName} so the matching rule is\n * defined in exactly one place; consumers can import it to reproduce the same\n * behaviour in their own search UIs.\n *\n * @packageDocumentation\n */\n\n/**\n * Reduce a display name to a canonical lookup key.\n *\n * The transform, in order:\n * 1. Unicode NFD-decompose, then strip combining marks — `Khârn` → `Kharn`.\n * 2. Casefold to lower case.\n * 3. Remove apostrophe and quote variants (`' ’ ‘ \\` \" “ ”`) — `T'au` → `Tau`.\n * 4. Collapse any run of whitespace or hyphens to a single space, then trim —\n * `Be'lakor` → `belakor`, `the betrayer` → `the betrayer`.\n *\n * The result is intended only for comparison; it is not a display value.\n *\n * @example\n * normalizeName(\"Khârn the Betrayer\"); // \"kharn the betrayer\"\n * normalizeName(\"T'au\"); // \"tau\"\n */\nexport function normalizeName(input: string): string {\n return input\n .normalize(\"NFD\")\n // Strip the Unicode Mark category (Mn/Mc/Me) — every combining mark.\n // \\p{Diacritic} is a smaller property that misses some Mn characters,\n // which let TS and Rust drift apart on non-Latin combining marks (the\n // Rust mirror uses unicode-normalization's is_combining_mark, which is\n // \\p{M}). The cross-impl property fuzz in tooling/parity/ surfaces the\n // divergence; the documented contract in CONFORMANCE.md is \"strip\n // combining marks\", so \\p{M} is canonical.\n .replace(/\\p{M}/gu, \"\")\n .toLowerCase()\n .replace(/['’‘`\"“”]/g, \"\")\n .replace(/[\\s-]+/g, \" \")\n .trim();\n}\n"]}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
|
-
import type { RosterUnit, RosterWargear } from "../import/types.js";
|
|
9
|
+
import type { Roster, RosterUnit, RosterWargear } from "../import/types.js";
|
|
10
10
|
import type { Dataset } from "./dataset.js";
|
|
11
11
|
import type { UnitView, WeaponView } from "./entities.js";
|
|
12
12
|
/**
|
|
@@ -30,4 +30,29 @@ export declare function resolveRosterWargear(wargear: RosterWargear[], dataset:
|
|
|
30
30
|
weapon: WeaponView;
|
|
31
31
|
count: number;
|
|
32
32
|
}[];
|
|
33
|
+
/**
|
|
34
|
+
* The roster's leader entry attached to `bodyguardUnitId`, if any. Import
|
|
35
|
+
* stores the inferred (always-provisional) attachment on the *leader's*
|
|
36
|
+
* {@link RosterUnit}, pointing down to its bodyguard via
|
|
37
|
+
* `leader_attachment.bodyguard_ref`. Selection UIs start from the body unit,
|
|
38
|
+
* so this scans for the leader whose `bodyguard_ref.id` matches. Returns
|
|
39
|
+
* `undefined` when no leader in the roster is attached to that unit (the
|
|
40
|
+
* common case — attachments are optional at game start).
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveAttachedLeader(roster: Roster, bodyguardUnitId: string): RosterUnit | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Every roster unit attached to `unitId`, resolved from *either* end of the
|
|
45
|
+
* attachment. A leader+bodyguard are one combined unit, so a selection UI may
|
|
46
|
+
* start from either half:
|
|
47
|
+
* - `unitId` is the **bodyguard** → the leader(s) whose
|
|
48
|
+
* `leader_attachment.bodyguard_ref.id` points at it (body-first, the
|
|
49
|
+
* {@link resolveAttachedLeader} direction), and
|
|
50
|
+
* - `unitId` is the **leader** → the bodyguard its own `leader_attachment`
|
|
51
|
+
* points to.
|
|
52
|
+
* Returns the partner {@link RosterUnit}s (deduped, source order). Empty when
|
|
53
|
+
* the unit has no attachment in this roster — the common case, since
|
|
54
|
+
* attachments are optional at game start. Shaped as a list to carry 11th
|
|
55
|
+
* edition's multi-member attachments without an API change.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveAttachmentPartners(roster: Roster, unitId: string): RosterUnit[];
|
|
33
58
|
//# sourceMappingURL=roster-resolve.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roster-resolve.d.ts","sourceRoot":"","sources":["../../src/data/roster-resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"roster-resolve.d.ts","sourceRoot":"","sources":["../../src/data/roster-resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,GACf,QAAQ,GAAG,SAAS,CAItB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,EAAE,EACxB,OAAO,EAAE,OAAO,GACf;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAUzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,GACtB,UAAU,GAAG,SAAS,CAIxB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,UAAU,EAAE,CAkBd"}
|
|
@@ -33,4 +33,50 @@ export function resolveRosterWargear(wargear, dataset) {
|
|
|
33
33
|
}
|
|
34
34
|
return out;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* The roster's leader entry attached to `bodyguardUnitId`, if any. Import
|
|
38
|
+
* stores the inferred (always-provisional) attachment on the *leader's*
|
|
39
|
+
* {@link RosterUnit}, pointing down to its bodyguard via
|
|
40
|
+
* `leader_attachment.bodyguard_ref`. Selection UIs start from the body unit,
|
|
41
|
+
* so this scans for the leader whose `bodyguard_ref.id` matches. Returns
|
|
42
|
+
* `undefined` when no leader in the roster is attached to that unit (the
|
|
43
|
+
* common case — attachments are optional at game start).
|
|
44
|
+
*/
|
|
45
|
+
export function resolveAttachedLeader(roster, bodyguardUnitId) {
|
|
46
|
+
return roster.units.find((u) => u.leader_attachment?.bodyguard_ref.id === bodyguardUnitId);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Every roster unit attached to `unitId`, resolved from *either* end of the
|
|
50
|
+
* attachment. A leader+bodyguard are one combined unit, so a selection UI may
|
|
51
|
+
* start from either half:
|
|
52
|
+
* - `unitId` is the **bodyguard** → the leader(s) whose
|
|
53
|
+
* `leader_attachment.bodyguard_ref.id` points at it (body-first, the
|
|
54
|
+
* {@link resolveAttachedLeader} direction), and
|
|
55
|
+
* - `unitId` is the **leader** → the bodyguard its own `leader_attachment`
|
|
56
|
+
* points to.
|
|
57
|
+
* Returns the partner {@link RosterUnit}s (deduped, source order). Empty when
|
|
58
|
+
* the unit has no attachment in this roster — the common case, since
|
|
59
|
+
* attachments are optional at game start. Shaped as a list to carry 11th
|
|
60
|
+
* edition's multi-member attachments without an API change.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveAttachmentPartners(roster, unitId) {
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
const out = [];
|
|
65
|
+
const add = (u) => {
|
|
66
|
+
if (!u || seen.has(u))
|
|
67
|
+
return;
|
|
68
|
+
seen.add(u);
|
|
69
|
+
out.push(u);
|
|
70
|
+
};
|
|
71
|
+
for (const u of roster.units) {
|
|
72
|
+
// Body-first: leaders pointing down at `unitId`.
|
|
73
|
+
if (u.leader_attachment?.bodyguard_ref.id === unitId)
|
|
74
|
+
add(u);
|
|
75
|
+
// Leader-first: `unitId`'s own entry points down at a bodyguard.
|
|
76
|
+
if (u.ref.id === unitId && u.leader_attachment) {
|
|
77
|
+
add(roster.units.find((b) => b.ref.id === u.leader_attachment.bodyguard_ref.id));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
36
82
|
//# sourceMappingURL=roster-resolve.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roster-resolve.js","sourceRoot":"","sources":["../../src/data/roster-resolve.ts"],"names":[],"mappings":"AAYA;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAAsB,EACtB,OAAgB;IAEhB,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;IAC7B,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAClC,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAwB,EACxB,OAAgB;IAEhB,MAAM,GAAG,GAA4C,EAAE,CAAC;IACxD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QACpB,IAAI,EAAE,KAAK,IAAI;YAAE,SAAS;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * Bridge helpers between the importer's flat-data {@link Roster} types and\n * the linked {@link UnitView} the cruncher consumes. The importer ships\n * unit entries as plain interfaces (`RosterUnit` is data, not behaviour),\n * so the lookup is a free function rather than a method.\n *\n * @packageDocumentation\n */\nimport type { RosterUnit, RosterWargear } from \"../import/types.js\";\nimport type { Dataset } from \"./dataset.js\";\nimport type { UnitView, WeaponView } from \"./entities.js\";\n\n/**\n * Resolve a roster's unit entry against the dataset, returning the linked\n * {@link UnitView}. Returns `undefined` when:\n * - the roster's `ref.id` is `null` (the importer couldn't match the unit), or\n * - the id doesn't appear in the dataset (e.g. the roster was authored\n * against an older dataslate than the bundled one).\n *\n * Doesn't surface diagnostics — the caller already has them on the roster's\n * own `diagnostics` field.\n */\nexport function resolveRosterUnit(\n rosterUnit: RosterUnit,\n dataset: Dataset,\n): UnitView | undefined {\n const id = rosterUnit.ref.id;\n if (id === null) return undefined;\n return dataset.units.get(id);\n}\n\n/**\n * Resolve every wargear entry on a roster unit to a {@link WeaponView},\n * keeping each entry's count alongside. Unresolved entries are dropped\n * silently (matching {@link resolveRosterUnit}). Useful when the SPA\n * needs to enumerate firing options after the user picks a roster unit.\n */\nexport function resolveRosterWargear(\n wargear: RosterWargear[],\n dataset: Dataset,\n): { weapon: WeaponView; count: number }[] {\n const out: { weapon: WeaponView; count: number }[] = [];\n for (const w of wargear) {\n const id = w.ref.id;\n if (id === null) continue;\n const weapon = dataset.weapons.get(id);\n if (!weapon) continue;\n out.push({ weapon, count: w.count });\n }\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"roster-resolve.js","sourceRoot":"","sources":["../../src/data/roster-resolve.ts"],"names":[],"mappings":"AAYA;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAAsB,EACtB,OAAgB;IAEhB,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;IAC7B,IAAI,EAAE,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAClC,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAwB,EACxB,OAAgB;IAEhB,MAAM,GAAG,GAA4C,EAAE,CAAC;IACxD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QACpB,IAAI,EAAE,KAAK,IAAI;YAAE,SAAS;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAc,EACd,eAAuB;IAEvB,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CACtB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,EAAE,aAAa,CAAC,EAAE,KAAK,eAAe,CACjE,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,yBAAyB,CACvC,MAAc,EACd,MAAc;IAEd,MAAM,IAAI,GAAG,IAAI,GAAG,EAAc,CAAC;IACnC,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,CAAC,CAAyB,EAAE,EAAE;QACxC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,OAAO;QAC9B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACZ,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC,CAAC;IAEF,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAC7B,iDAAiD;QACjD,IAAI,CAAC,CAAC,iBAAiB,EAAE,aAAa,CAAC,EAAE,KAAK,MAAM;YAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7D,iEAAiE;QACjE,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,iBAAiB,EAAE,CAAC;YAC/C,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,iBAAkB,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * Bridge helpers between the importer's flat-data {@link Roster} types and\n * the linked {@link UnitView} the cruncher consumes. The importer ships\n * unit entries as plain interfaces (`RosterUnit` is data, not behaviour),\n * so the lookup is a free function rather than a method.\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit, RosterWargear } from \"../import/types.js\";\nimport type { Dataset } from \"./dataset.js\";\nimport type { UnitView, WeaponView } from \"./entities.js\";\n\n/**\n * Resolve a roster's unit entry against the dataset, returning the linked\n * {@link UnitView}. Returns `undefined` when:\n * - the roster's `ref.id` is `null` (the importer couldn't match the unit), or\n * - the id doesn't appear in the dataset (e.g. the roster was authored\n * against an older dataslate than the bundled one).\n *\n * Doesn't surface diagnostics — the caller already has them on the roster's\n * own `diagnostics` field.\n */\nexport function resolveRosterUnit(\n rosterUnit: RosterUnit,\n dataset: Dataset,\n): UnitView | undefined {\n const id = rosterUnit.ref.id;\n if (id === null) return undefined;\n return dataset.units.get(id);\n}\n\n/**\n * Resolve every wargear entry on a roster unit to a {@link WeaponView},\n * keeping each entry's count alongside. Unresolved entries are dropped\n * silently (matching {@link resolveRosterUnit}). Useful when the SPA\n * needs to enumerate firing options after the user picks a roster unit.\n */\nexport function resolveRosterWargear(\n wargear: RosterWargear[],\n dataset: Dataset,\n): { weapon: WeaponView; count: number }[] {\n const out: { weapon: WeaponView; count: number }[] = [];\n for (const w of wargear) {\n const id = w.ref.id;\n if (id === null) continue;\n const weapon = dataset.weapons.get(id);\n if (!weapon) continue;\n out.push({ weapon, count: w.count });\n }\n return out;\n}\n\n/**\n * The roster's leader entry attached to `bodyguardUnitId`, if any. Import\n * stores the inferred (always-provisional) attachment on the *leader's*\n * {@link RosterUnit}, pointing down to its bodyguard via\n * `leader_attachment.bodyguard_ref`. Selection UIs start from the body unit,\n * so this scans for the leader whose `bodyguard_ref.id` matches. Returns\n * `undefined` when no leader in the roster is attached to that unit (the\n * common case — attachments are optional at game start).\n */\nexport function resolveAttachedLeader(\n roster: Roster,\n bodyguardUnitId: string,\n): RosterUnit | undefined {\n return roster.units.find(\n (u) => u.leader_attachment?.bodyguard_ref.id === bodyguardUnitId,\n );\n}\n\n/**\n * Every roster unit attached to `unitId`, resolved from *either* end of the\n * attachment. A leader+bodyguard are one combined unit, so a selection UI may\n * start from either half:\n * - `unitId` is the **bodyguard** → the leader(s) whose\n * `leader_attachment.bodyguard_ref.id` points at it (body-first, the\n * {@link resolveAttachedLeader} direction), and\n * - `unitId` is the **leader** → the bodyguard its own `leader_attachment`\n * points to.\n * Returns the partner {@link RosterUnit}s (deduped, source order). Empty when\n * the unit has no attachment in this roster — the common case, since\n * attachments are optional at game start. Shaped as a list to carry 11th\n * edition's multi-member attachments without an API change.\n */\nexport function resolveAttachmentPartners(\n roster: Roster,\n unitId: string,\n): RosterUnit[] {\n const seen = new Set<RosterUnit>();\n const out: RosterUnit[] = [];\n const add = (u: RosterUnit | undefined) => {\n if (!u || seen.has(u)) return;\n seen.add(u);\n out.push(u);\n };\n\n for (const u of roster.units) {\n // Body-first: leaders pointing down at `unitId`.\n if (u.leader_attachment?.bodyguard_ref.id === unitId) add(u);\n // Leader-first: `unitId`'s own entry points down at a bodyguard.\n if (u.ref.id === unitId && u.leader_attachment) {\n add(roster.units.find((b) => b.ref.id === u.leader_attachment!.bodyguard_ref.id));\n }\n }\n return out;\n}\n"]}
|
package/dist/export/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { newRecruitJsonSerializer } from "./newrecruit-json.js";
|
|
|
16
16
|
export { newRecruitSimpleSerializer } from "./newrecruit-simple.js";
|
|
17
17
|
export { newRecruitWtcCompactSerializer, newRecruitWtcFullSerializer, } from "./newrecruit-wtc.js";
|
|
18
18
|
export { rosterJsonSerializer } from "./roster-json.js";
|
|
19
|
+
export { rosterizerSerializer } from "./rosterizer.js";
|
|
19
20
|
/** Serialize a {@link Roster} into the named target format. */
|
|
20
21
|
export declare function exportRoster(roster: Roster, format: ExportFormat): string;
|
|
21
22
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/export/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/export/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AASjD,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iBAAiB,CAAC;AAEtE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAYvD,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAQzE"}
|
package/dist/export/index.js
CHANGED
|
@@ -2,10 +2,12 @@ import { newRecruitJsonSerializer } from "./newrecruit-json.js";
|
|
|
2
2
|
import { newRecruitSimpleSerializer } from "./newrecruit-simple.js";
|
|
3
3
|
import { newRecruitWtcCompactSerializer, newRecruitWtcFullSerializer, } from "./newrecruit-wtc.js";
|
|
4
4
|
import { rosterJsonSerializer } from "./roster-json.js";
|
|
5
|
+
import { rosterizerSerializer } from "./rosterizer.js";
|
|
5
6
|
export { newRecruitJsonSerializer } from "./newrecruit-json.js";
|
|
6
7
|
export { newRecruitSimpleSerializer } from "./newrecruit-simple.js";
|
|
7
8
|
export { newRecruitWtcCompactSerializer, newRecruitWtcFullSerializer, } from "./newrecruit-wtc.js";
|
|
8
9
|
export { rosterJsonSerializer } from "./roster-json.js";
|
|
10
|
+
export { rosterizerSerializer } from "./rosterizer.js";
|
|
9
11
|
/** All registered serializers, keyed by their {@link ExportFormat} id. */
|
|
10
12
|
const SERIALIZERS = [
|
|
11
13
|
newRecruitJsonSerializer,
|
|
@@ -13,6 +15,7 @@ const SERIALIZERS = [
|
|
|
13
15
|
newRecruitWtcFullSerializer,
|
|
14
16
|
newRecruitSimpleSerializer,
|
|
15
17
|
rosterJsonSerializer,
|
|
18
|
+
rosterizerSerializer,
|
|
16
19
|
];
|
|
17
20
|
/** Serialize a {@link Roster} into the named target format. */
|
|
18
21
|
export function exportRoster(roster, format) {
|
package/dist/export/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/export/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/export/index.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAIvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,0EAA0E;AAC1E,MAAM,WAAW,GAAgC;IAC/C,wBAAwB;IACxB,8BAA8B;IAC9B,2BAA2B;IAC3B,0BAA0B;IAC1B,oBAAoB;IACpB,oBAAoB;CACrB,CAAC;AAEF,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,MAAoB;IAC/D,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;IACnD,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CACb,0BAA0B,MAAM,iBAAiB,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC5F,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC","sourcesContent":["/**\n * Roster exporters — the symmetric counterpart to the importer.\n *\n * `exportRoster(roster, format)` dispatches to one of five registered\n * serializers (NewRecruit JSON, the three NewRecruit text formats, and the\n * canonical Roster JSON). Each serializer is deterministic and Dataset-free,\n * so the TS and Rust mirrors can produce byte-identical output for\n * cross-implementation conformance.\n *\n * @packageDocumentation\n */\nimport type { Roster } from \"../import/types.js\";\nimport { newRecruitJsonSerializer } from \"./newrecruit-json.js\";\nimport { newRecruitSimpleSerializer } from \"./newrecruit-simple.js\";\nimport {\n newRecruitWtcCompactSerializer,\n newRecruitWtcFullSerializer,\n} from \"./newrecruit-wtc.js\";\nimport { rosterJsonSerializer } from \"./roster-json.js\";\nimport { rosterizerSerializer } from \"./rosterizer.js\";\nimport type { ExportFormat, RosterSerializer } from \"./serializer.js\";\n\nexport type { ExportFormat, RosterSerializer } from \"./serializer.js\";\nexport { newRecruitJsonSerializer } from \"./newrecruit-json.js\";\nexport { newRecruitSimpleSerializer } from \"./newrecruit-simple.js\";\nexport {\n newRecruitWtcCompactSerializer,\n newRecruitWtcFullSerializer,\n} from \"./newrecruit-wtc.js\";\nexport { rosterJsonSerializer } from \"./roster-json.js\";\nexport { rosterizerSerializer } from \"./rosterizer.js\";\n\n/** All registered serializers, keyed by their {@link ExportFormat} id. */\nconst SERIALIZERS: readonly RosterSerializer[] = [\n newRecruitJsonSerializer,\n newRecruitWtcCompactSerializer,\n newRecruitWtcFullSerializer,\n newRecruitSimpleSerializer,\n rosterJsonSerializer,\n rosterizerSerializer,\n];\n\n/** Serialize a {@link Roster} into the named target format. */\nexport function exportRoster(roster: Roster, format: ExportFormat): string {\n const s = SERIALIZERS.find((s) => s.id === format);\n if (!s) {\n throw new Error(\n `unknown export format: ${format} (registered: ${SERIALIZERS.map((s) => s.id).join(\", \")})`,\n );\n }\n return s.serialize(roster);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rosterizer.d.ts","sourceRoot":"","sources":["../../src/export/rosterizer.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAqIxD,eAAO,MAAM,oBAAoB,EAAE,gBAuClC,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { prettyJson, titleCaseId, totalArmyPoints } from "./helpers.js";
|
|
2
|
+
// Mirror the importer's constants (kept inline rather than imported so the
|
|
3
|
+
// exporter stays decoupled — the seams are the `item` keys themselves).
|
|
4
|
+
const CLS_ROSTER = "Roster";
|
|
5
|
+
const CLS_FACTION = "Faction";
|
|
6
|
+
const CLS_DETACHMENT = "Detachment";
|
|
7
|
+
const CLS_UNIT = "Unit";
|
|
8
|
+
const CLS_WEAPON = "Weapon";
|
|
9
|
+
const CLS_ENHANCEMENT = "Enhancement";
|
|
10
|
+
const CLS_BATTLE_SIZE = "Battle Size";
|
|
11
|
+
const CLS_TRAIT = "Trait";
|
|
12
|
+
const DSG_WARLORD = "Warlord";
|
|
13
|
+
const RULEBOOK_NAME = "40kdc";
|
|
14
|
+
const RULEBOOK_GAME = "Warhammer 40,000";
|
|
15
|
+
const RULEBOOK_PUBLISHER = "Tabletop Developer Consortium";
|
|
16
|
+
const RULEBOOK_URL = "https://40kdc.dev";
|
|
17
|
+
const RULEBOOK_GENRE = "wargame";
|
|
18
|
+
function key(classification, designation) {
|
|
19
|
+
return `${classification}§${designation}`; // §
|
|
20
|
+
}
|
|
21
|
+
function pointsStat(value) {
|
|
22
|
+
if (value === null || value === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
return { Points: { value } };
|
|
25
|
+
}
|
|
26
|
+
function wargearAsset(w) {
|
|
27
|
+
return {
|
|
28
|
+
item: key(CLS_WEAPON, w.ref.raw_name),
|
|
29
|
+
name: w.ref.raw_name,
|
|
30
|
+
quantity: w.count,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function enhancementAsset(u) {
|
|
34
|
+
if (!u.enhancement)
|
|
35
|
+
return null;
|
|
36
|
+
return {
|
|
37
|
+
item: key(CLS_ENHANCEMENT, u.enhancement.raw_name),
|
|
38
|
+
name: u.enhancement.raw_name,
|
|
39
|
+
quantity: 1,
|
|
40
|
+
...(u.enhancement_points !== null
|
|
41
|
+
? { stats: pointsStat(u.enhancement_points) }
|
|
42
|
+
: {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function warlordTraitAsset() {
|
|
46
|
+
return {
|
|
47
|
+
item: key(CLS_TRAIT, DSG_WARLORD),
|
|
48
|
+
name: DSG_WARLORD,
|
|
49
|
+
quantity: 1,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function unitAsset(u) {
|
|
53
|
+
const included = [];
|
|
54
|
+
const enh = enhancementAsset(u);
|
|
55
|
+
if (enh !== null)
|
|
56
|
+
included.push(enh);
|
|
57
|
+
for (const w of u.wargear)
|
|
58
|
+
included.push(wargearAsset(w));
|
|
59
|
+
const traits = [];
|
|
60
|
+
if (u.is_warlord)
|
|
61
|
+
traits.push(warlordTraitAsset());
|
|
62
|
+
const asset = {
|
|
63
|
+
item: key(CLS_UNIT, u.ref.raw_name),
|
|
64
|
+
name: u.ref.raw_name,
|
|
65
|
+
quantity: u.model_count,
|
|
66
|
+
};
|
|
67
|
+
const stats = pointsStat(u.points);
|
|
68
|
+
if (stats !== undefined)
|
|
69
|
+
asset.stats = stats;
|
|
70
|
+
if (included.length > 0 || traits.length > 0) {
|
|
71
|
+
asset.assets = {};
|
|
72
|
+
if (included.length > 0)
|
|
73
|
+
asset.assets.included = included;
|
|
74
|
+
if (traits.length > 0)
|
|
75
|
+
asset.assets.traits = traits;
|
|
76
|
+
}
|
|
77
|
+
return asset;
|
|
78
|
+
}
|
|
79
|
+
function factionAsset(roster) {
|
|
80
|
+
const display = titleCaseId(roster.faction_id);
|
|
81
|
+
if (display === null)
|
|
82
|
+
return null;
|
|
83
|
+
return { item: key(CLS_FACTION, display), name: display, quantity: 1 };
|
|
84
|
+
}
|
|
85
|
+
function detachmentAsset(roster) {
|
|
86
|
+
const display = titleCaseId(roster.detachment_id);
|
|
87
|
+
if (display === null)
|
|
88
|
+
return null;
|
|
89
|
+
return { item: key(CLS_DETACHMENT, display), name: display, quantity: 1 };
|
|
90
|
+
}
|
|
91
|
+
function battleSizeAsset(roster) {
|
|
92
|
+
if (roster.battle_size === "strike-force") {
|
|
93
|
+
const limit = roster.points.declared_limit ?? 2000;
|
|
94
|
+
const label = `Strike Force (${limit} Point limit)`;
|
|
95
|
+
return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };
|
|
96
|
+
}
|
|
97
|
+
if (roster.battle_size === "incursion") {
|
|
98
|
+
const limit = roster.points.declared_limit ?? 1000;
|
|
99
|
+
const label = `Incursion (${limit} Point limit)`;
|
|
100
|
+
return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
export const rosterizerSerializer = {
|
|
105
|
+
id: "rosterizer",
|
|
106
|
+
serialize(roster) {
|
|
107
|
+
const included = [];
|
|
108
|
+
const faction = factionAsset(roster);
|
|
109
|
+
if (faction)
|
|
110
|
+
included.push(faction);
|
|
111
|
+
const detachment = detachmentAsset(roster);
|
|
112
|
+
if (detachment)
|
|
113
|
+
included.push(detachment);
|
|
114
|
+
const battleSize = battleSizeAsset(roster);
|
|
115
|
+
if (battleSize)
|
|
116
|
+
included.push(battleSize);
|
|
117
|
+
for (const u of roster.units)
|
|
118
|
+
included.push(unitAsset(u));
|
|
119
|
+
const total = totalArmyPoints(roster);
|
|
120
|
+
const snapshot = {
|
|
121
|
+
item: key(CLS_ROSTER, CLS_ROSTER),
|
|
122
|
+
name: roster.name,
|
|
123
|
+
quantity: 1,
|
|
124
|
+
...(total > 0 ? { stats: pointsStat(total) } : {}),
|
|
125
|
+
assets: { included },
|
|
126
|
+
};
|
|
127
|
+
const envelope = {
|
|
128
|
+
slug: "",
|
|
129
|
+
key: "",
|
|
130
|
+
visible: "hidden",
|
|
131
|
+
locked: false,
|
|
132
|
+
rulebook: {
|
|
133
|
+
name: RULEBOOK_NAME,
|
|
134
|
+
game: RULEBOOK_GAME,
|
|
135
|
+
publisher: RULEBOOK_PUBLISHER,
|
|
136
|
+
url: RULEBOOK_URL,
|
|
137
|
+
genre: RULEBOOK_GENRE,
|
|
138
|
+
},
|
|
139
|
+
snapshot,
|
|
140
|
+
};
|
|
141
|
+
return prettyJson(envelope);
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=rosterizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rosterizer.js","sourceRoot":"","sources":["../../src/export/rosterizer.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGxE,2EAA2E;AAC3E,wEAAwE;AACxE,MAAM,UAAU,GAAG,QAAQ,CAAC;AAC5B,MAAM,WAAW,GAAG,SAAS,CAAC;AAC9B,MAAM,cAAc,GAAG,YAAY,CAAC;AACpC,MAAM,QAAQ,GAAG,MAAM,CAAC;AACxB,MAAM,UAAU,GAAG,QAAQ,CAAC;AAC5B,MAAM,eAAe,GAAG,aAAa,CAAC;AACtC,MAAM,eAAe,GAAG,aAAa,CAAC;AACtC,MAAM,SAAS,GAAG,OAAO,CAAC;AAC1B,MAAM,WAAW,GAAG,SAAS,CAAC;AAE9B,MAAM,aAAa,GAAG,OAAO,CAAC;AAC9B,MAAM,aAAa,GAAG,kBAAkB,CAAC;AACzC,MAAM,kBAAkB,GAAG,+BAA+B,CAAC;AAC3D,MAAM,YAAY,GAAG,mBAAmB,CAAC;AACzC,MAAM,cAAc,GAAG,SAAS,CAAC;AA4BjC,SAAS,GAAG,CAAC,cAAsB,EAAE,WAAmB;IACtD,OAAO,GAAG,cAAc,IAAI,WAAW,EAAE,CAAC,CAAC,IAAI;AACjD,CAAC;AAED,SAAS,UAAU,CAAC,KAAgC;IAClD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5D,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC;AAC/B,CAAC;AAED,SAAS,YAAY,CAAC,CAAgB;IACpC,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;QACrC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ;QACpB,QAAQ,EAAE,CAAC,CAAC,KAAK;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAa;IACrC,IAAI,CAAC,CAAC,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAChC,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC;QAClD,IAAI,EAAE,CAAC,CAAC,WAAW,CAAC,QAAQ;QAC5B,QAAQ,EAAE,CAAC;QACX,GAAG,CAAC,CAAC,CAAC,kBAAkB,KAAK,IAAI;YAC/B,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,kBAAkB,CAAC,EAAE;YAC7C,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB;IACxB,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC;QACjC,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,CAAC;KACZ,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,CAAa;IAC9B,MAAM,QAAQ,GAAY,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAChC,IAAI,GAAG,KAAK,IAAI;QAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO;QAAE,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAE1D,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,IAAI,CAAC,CAAC,UAAU;QAAE,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAEnD,MAAM,KAAK,GAAU;QACnB,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ;QACpB,QAAQ,EAAE,CAAC,CAAC,WAAW;KACxB,CAAC;IACF,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IAC7C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7C,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;QAClB,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC1D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACtD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAAc;IAClC,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC/C,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AACzE,CAAC;AAED,SAAS,eAAe,CAAC,MAAc;IACrC,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAClD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED,SAAS,eAAe,CAAC,MAAc;IACrC,IAAI,MAAM,CAAC,WAAW,KAAK,cAAc,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC;QACnD,MAAM,KAAK,GAAG,iBAAiB,KAAK,eAAe,CAAC;QACpD,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,eAAe,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzE,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,KAAK,WAAW,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC;QACnD,MAAM,KAAK,GAAG,cAAc,KAAK,eAAe,CAAC;QACjD,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,eAAe,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAqB;IACpD,EAAE,EAAE,YAAY;IAEhB,SAAS,CAAC,MAAc;QACtB,MAAM,QAAQ,GAAY,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,OAAO;YAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,UAAU;YAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,UAAU;YAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK;YAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAE1D,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAU;YACtB,IAAI,EAAE,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC;YACjC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EAAE,CAAC;YACX,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClD,MAAM,EAAE,EAAE,QAAQ,EAAE;SACrB,CAAC;QAEF,MAAM,QAAQ,GAAa;YACzB,IAAI,EAAE,EAAE;YACR,GAAG,EAAE,EAAE;YACP,OAAO,EAAE,QAAQ;YACjB,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE;gBACR,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,aAAa;gBACnB,SAAS,EAAE,kBAAkB;gBAC7B,GAAG,EAAE,YAAY;gBACjB,KAAK,EAAE,cAAc;aACtB;YACD,QAAQ;SACT,CAAC;QAEF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;CACF,CAAC","sourcesContent":["/**\n * Rosterizer serializer — emits a Rosterizer-shaped roster JSON skeleton that\n * round-trips through {@link rosterizerAdapter}.\n *\n * The shape carries only fields the importer reads: `rulebook` (envelope),\n * `snapshot` (an `Asset` tree rooted at `Roster§Roster`), and per-unit\n * `item`/`name`/`quantity`/`stats.Points.value`/`assets.included`/`assets.traits`.\n * No `text`, `description`, `rules`, `lineage`, `_layers`, `classIdentity`,\n * `processed`, or `bareResourceKey` ever appear — they aren't stored in the\n * Roster and emitting them could leak prose.\n *\n * Faction and detachment display names come from {@link titleCaseId} — the\n * Roster doesn't carry the source's raw faction name, so we reconstruct it\n * from the kebab-case id. Same lossy hop as the NewRecruit JSON serializer.\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit, RosterWargear } from \"../import/types.js\";\nimport { prettyJson, titleCaseId, totalArmyPoints } from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\n// Mirror the importer's constants (kept inline rather than imported so the\n// exporter stays decoupled — the seams are the `item` keys themselves).\nconst CLS_ROSTER = \"Roster\";\nconst CLS_FACTION = \"Faction\";\nconst CLS_DETACHMENT = \"Detachment\";\nconst CLS_UNIT = \"Unit\";\nconst CLS_WEAPON = \"Weapon\";\nconst CLS_ENHANCEMENT = \"Enhancement\";\nconst CLS_BATTLE_SIZE = \"Battle Size\";\nconst CLS_TRAIT = \"Trait\";\nconst DSG_WARLORD = \"Warlord\";\n\nconst RULEBOOK_NAME = \"40kdc\";\nconst RULEBOOK_GAME = \"Warhammer 40,000\";\nconst RULEBOOK_PUBLISHER = \"Tabletop Developer Consortium\";\nconst RULEBOOK_URL = \"https://40kdc.dev\";\nconst RULEBOOK_GENRE = \"wargame\";\n\ninterface Asset {\n item: string;\n name?: string;\n quantity?: number;\n stats?: Record<string, { value: number }>;\n assets?: {\n included?: Asset[];\n traits?: Asset[];\n };\n}\n\ninterface Envelope {\n slug: string;\n key: string;\n visible: \"hidden\" | \"public\" | \"friends\";\n locked: boolean;\n rulebook: {\n name: string;\n game: string;\n publisher: string;\n url: string;\n genre: string;\n };\n snapshot: Asset;\n}\n\nfunction key(classification: string, designation: string): string {\n return `${classification}§${designation}`; // §\n}\n\nfunction pointsStat(value: number | null | undefined): Record<string, { value: number }> | undefined {\n if (value === null || value === undefined) return undefined;\n return { Points: { value } };\n}\n\nfunction wargearAsset(w: RosterWargear): Asset {\n return {\n item: key(CLS_WEAPON, w.ref.raw_name),\n name: w.ref.raw_name,\n quantity: w.count,\n };\n}\n\nfunction enhancementAsset(u: RosterUnit): Asset | null {\n if (!u.enhancement) return null;\n return {\n item: key(CLS_ENHANCEMENT, u.enhancement.raw_name),\n name: u.enhancement.raw_name,\n quantity: 1,\n ...(u.enhancement_points !== null\n ? { stats: pointsStat(u.enhancement_points) }\n : {}),\n };\n}\n\nfunction warlordTraitAsset(): Asset {\n return {\n item: key(CLS_TRAIT, DSG_WARLORD),\n name: DSG_WARLORD,\n quantity: 1,\n };\n}\n\nfunction unitAsset(u: RosterUnit): Asset {\n const included: Asset[] = [];\n const enh = enhancementAsset(u);\n if (enh !== null) included.push(enh);\n for (const w of u.wargear) included.push(wargearAsset(w));\n\n const traits: Asset[] = [];\n if (u.is_warlord) traits.push(warlordTraitAsset());\n\n const asset: Asset = {\n item: key(CLS_UNIT, u.ref.raw_name),\n name: u.ref.raw_name,\n quantity: u.model_count,\n };\n const stats = pointsStat(u.points);\n if (stats !== undefined) asset.stats = stats;\n if (included.length > 0 || traits.length > 0) {\n asset.assets = {};\n if (included.length > 0) asset.assets.included = included;\n if (traits.length > 0) asset.assets.traits = traits;\n }\n return asset;\n}\n\nfunction factionAsset(roster: Roster): Asset | null {\n const display = titleCaseId(roster.faction_id);\n if (display === null) return null;\n return { item: key(CLS_FACTION, display), name: display, quantity: 1 };\n}\n\nfunction detachmentAsset(roster: Roster): Asset | null {\n const display = titleCaseId(roster.detachment_id);\n if (display === null) return null;\n return { item: key(CLS_DETACHMENT, display), name: display, quantity: 1 };\n}\n\nfunction battleSizeAsset(roster: Roster): Asset | null {\n if (roster.battle_size === \"strike-force\") {\n const limit = roster.points.declared_limit ?? 2000;\n const label = `Strike Force (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n if (roster.battle_size === \"incursion\") {\n const limit = roster.points.declared_limit ?? 1000;\n const label = `Incursion (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n return null;\n}\n\nexport const rosterizerSerializer: RosterSerializer = {\n id: \"rosterizer\",\n\n serialize(roster: Roster): string {\n const included: Asset[] = [];\n const faction = factionAsset(roster);\n if (faction) included.push(faction);\n const detachment = detachmentAsset(roster);\n if (detachment) included.push(detachment);\n const battleSize = battleSizeAsset(roster);\n if (battleSize) included.push(battleSize);\n for (const u of roster.units) included.push(unitAsset(u));\n\n const total = totalArmyPoints(roster);\n const snapshot: Asset = {\n item: key(CLS_ROSTER, CLS_ROSTER),\n name: roster.name,\n quantity: 1,\n ...(total > 0 ? { stats: pointsStat(total) } : {}),\n assets: { included },\n };\n\n const envelope: Envelope = {\n slug: \"\",\n key: \"\",\n visible: \"hidden\",\n locked: false,\n rulebook: {\n name: RULEBOOK_NAME,\n game: RULEBOOK_GAME,\n publisher: RULEBOOK_PUBLISHER,\n url: RULEBOOK_URL,\n genre: RULEBOOK_GENRE,\n },\n snapshot,\n };\n\n return prettyJson(envelope);\n },\n};\n"]}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { Roster } from "../import/types.js";
|
|
20
20
|
/** Stable id for an export target. */
|
|
21
|
-
export type ExportFormat = "newrecruit-json" | "newrecruit-wtc-compact" | "newrecruit-wtc-full" | "newrecruit-simple" | "roster-json";
|
|
21
|
+
export type ExportFormat = "newrecruit-json" | "newrecruit-wtc-compact" | "newrecruit-wtc-full" | "newrecruit-simple" | "roster-json" | "rosterizer";
|
|
22
22
|
/** Serializes a {@link Roster} into one specific format. */
|
|
23
23
|
export interface RosterSerializer {
|
|
24
24
|
id: ExportFormat;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../src/export/serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,sCAAsC;AACtC,MAAM,MAAM,YAAY,GACpB,iBAAiB,GACjB,wBAAwB,GACxB,qBAAqB,GACrB,mBAAmB,GACnB,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../src/export/serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,sCAAsC;AACtC,MAAM,MAAM,YAAY,GACpB,iBAAiB,GACjB,wBAAwB,GACxB,qBAAqB,GACrB,mBAAmB,GACnB,aAAa,GACb,YAAY,CAAC;AAEjB,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,YAAY,CAAC;IACjB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../src/export/serializer.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The roster-serializer seam — symmetric counterpart to the\n * {@link FormatAdapter} import seam.\n *\n * Each supported export target implements {@link RosterSerializer}: it takes a\n * fully-resolved {@link Roster} and produces a deterministic string in that\n * format. The seam stays Dataset-free so the TS and Rust mirrors can produce\n * byte-identical output for conformance.\n *\n * Five targets are registered:\n * - `newrecruit-json` — NewRecruit-shaped JSON skeleton (rules-free).\n * - `newrecruit-wtc-compact` — tournament-friendly one-line-per-unit text.\n * - `newrecruit-wtc-full` — tournament-friendly section-and-wargear text.\n * - `newrecruit-simple` — markdown-ish text.\n * - `roster-json` — canonical Roster JSON (the lossless pivot).\n *\n * @packageDocumentation\n */\nimport type { Roster } from \"../import/types.js\";\n\n/** Stable id for an export target. */\nexport type ExportFormat =\n | \"newrecruit-json\"\n | \"newrecruit-wtc-compact\"\n | \"newrecruit-wtc-full\"\n | \"newrecruit-simple\"\n | \"roster-json\";\n\n/** Serializes a {@link Roster} into one specific format. */\nexport interface RosterSerializer {\n id: ExportFormat;\n serialize(roster: Roster): string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../src/export/serializer.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The roster-serializer seam — symmetric counterpart to the\n * {@link FormatAdapter} import seam.\n *\n * Each supported export target implements {@link RosterSerializer}: it takes a\n * fully-resolved {@link Roster} and produces a deterministic string in that\n * format. The seam stays Dataset-free so the TS and Rust mirrors can produce\n * byte-identical output for conformance.\n *\n * Five targets are registered:\n * - `newrecruit-json` — NewRecruit-shaped JSON skeleton (rules-free).\n * - `newrecruit-wtc-compact` — tournament-friendly one-line-per-unit text.\n * - `newrecruit-wtc-full` — tournament-friendly section-and-wargear text.\n * - `newrecruit-simple` — markdown-ish text.\n * - `roster-json` — canonical Roster JSON (the lossless pivot).\n *\n * @packageDocumentation\n */\nimport type { Roster } from \"../import/types.js\";\n\n/** Stable id for an export target. */\nexport type ExportFormat =\n | \"newrecruit-json\"\n | \"newrecruit-wtc-compact\"\n | \"newrecruit-wtc-full\"\n | \"newrecruit-simple\"\n | \"roster-json\"\n | \"rosterizer\";\n\n/** Serializes a {@link Roster} into one specific format. */\nexport interface RosterSerializer {\n id: ExportFormat;\n serialize(roster: Roster): string;\n}\n"]}
|