@alpaca-software/40kdc-data 1.0.9 → 1.0.11
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/data/index.d.ts +1 -1
- 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/loadout.d.ts +8 -5
- package/dist/data/loadout.d.ts.map +1 -1
- package/dist/data/loadout.js +164 -65
- package/dist/data/loadout.js.map +1 -1
- package/dist/share/from-roster.d.ts +27 -0
- package/dist/share/from-roster.d.ts.map +1 -0
- package/dist/share/from-roster.js +58 -0
- package/dist/share/from-roster.js.map +1 -0
- package/dist/share/index.d.ts +8 -0
- package/dist/share/index.d.ts.map +1 -1
- package/dist/share/index.js +10 -0
- package/dist/share/index.js.map +1 -1
- package/package.json +1 -1
package/dist/data/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export * from "../cruncher/index.js";
|
|
|
34
34
|
export { effectToBuffs, parseKeywordGrant } from "../cruncher/from-dsl.js";
|
|
35
35
|
export type { ActivatableBuff, ActivatableGroupRef, EffectTranslation, TranslationPerspective, UnsupportedFragment, } from "../cruncher/from-dsl.js";
|
|
36
36
|
export * from "../abilities-resolver/index.js";
|
|
37
|
-
export { encodeShareToken, decodeShareToken, shareRegistryVersion, ShareRegistryIndex, SHARE_FORMAT_VERSION, SHARE_KINDS, } from "../share/index.js";
|
|
37
|
+
export { encodeShareToken, decodeShareToken, rosterToShareList, shareRegistryVersion, ShareRegistryIndex, SHARE_FORMAT_VERSION, SHARE_KINDS, } from "../share/index.js";
|
|
38
38
|
export type { DecodeResult, ShareBattleSize, ShareList, ShareLoadoutEntry, ShareUnit, ShareKind, ShareRegistry, } from "../share/index.js";
|
|
39
39
|
export { resolveRosterUnit, resolveRosterWargear, resolveAttachedLeader, resolveAttachmentPartners, primaryDetachment, primaryDetachmentId, checkRosterLegality, checkRoster, validateRosterCore, } from "./roster-resolve.js";
|
|
40
40
|
export type { UnitLegality, RosterLegality, RosterViolation, RosterViolationCode, NormRoster, } from "./roster-resolve.js";
|
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":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"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"}
|
package/dist/data/index.js
CHANGED
|
@@ -37,7 +37,7 @@ export { effectToBuffs, parseKeywordGrant } from "../cruncher/from-dsl.js";
|
|
|
37
37
|
// The eligible-abilities resolver (also reachable as Dataset.eligibleAbilities).
|
|
38
38
|
export * from "../abilities-resolver/index.js";
|
|
39
39
|
// Compact, URL-safe list sharing (registry-indexed `share-v1` tokens).
|
|
40
|
-
export { encodeShareToken, decodeShareToken, shareRegistryVersion, ShareRegistryIndex, SHARE_FORMAT_VERSION, SHARE_KINDS, } from "../share/index.js";
|
|
40
|
+
export { encodeShareToken, decodeShareToken, rosterToShareList, shareRegistryVersion, ShareRegistryIndex, SHARE_FORMAT_VERSION, SHARE_KINDS, } from "../share/index.js";
|
|
41
41
|
// Bridge helpers from the importer's RosterUnit → linked views.
|
|
42
42
|
export { resolveRosterUnit, resolveRosterWargear, resolveAttachedLeader, resolveAttachmentPartners, primaryDetachment, primaryDetachmentId, checkRosterLegality, checkRoster, validateRosterCore, } from "./roster-resolve.js";
|
|
43
43
|
export { pointsLimitForBattleSize, detachmentCapForBattleSize } from "./battle-sizes.js";
|
package/dist/data/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"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","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, ReactiveTrigger } 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// Wargear-loadout maths: base/maximal loadout, per-weapon bounds, validation.\nexport {\n optionCap,\n baseLoadout,\n maximalLoadout,\n weaponBounds,\n clampWeaponCount,\n validateLoadout,\n groupLoadout,\n} from \"./loadout.js\";\nexport type {\n Loadout,\n WeaponBound,\n Violation,\n LoadoutModel,\n LoadoutGroup,\n LoadoutGroupWeapon,\n} from \"./loadout.js\";\n\n// Unit point-cost maths: ordinal-aware tier selection over `points` bands.\nexport { baseUnitPoints, pointsTierMissing } from \"./pricing.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// Compact, URL-safe list sharing (registry-indexed `share-v1` tokens).\nexport {\n encodeShareToken,\n decodeShareToken,\n shareRegistryVersion,\n ShareRegistryIndex,\n SHARE_FORMAT_VERSION,\n SHARE_KINDS,\n} from \"../share/index.js\";\nexport type {\n DecodeResult,\n ShareBattleSize,\n ShareList,\n ShareLoadoutEntry,\n ShareUnit,\n ShareKind,\n ShareRegistry,\n} from \"../share/index.js\";\n\n// Bridge helpers from the importer's RosterUnit → linked views.\nexport {\n resolveRosterUnit,\n resolveRosterWargear,\n resolveAttachedLeader,\n resolveAttachmentPartners,\n primaryDetachment,\n primaryDetachmentId,\n checkRosterLegality,\n checkRoster,\n validateRosterCore,\n} from \"./roster-resolve.js\";\nexport type {\n UnitLegality,\n RosterLegality,\n RosterViolation,\n RosterViolationCode,\n NormRoster,\n} from \"./roster-resolve.js\";\nexport { pointsLimitForBattleSize, detachmentCapForBattleSize } from \"./battle-sizes.js\";\nexport { candidateAffordability } from \"./affordability.js\";\nexport type { AffordabilitySpec, AffordabilityUnit, CandidateCost } from \"./affordability.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/** Named target archetypes referencing real units (faction_id + unit_id). */\nexport const targetProfiles = dataset.targetProfiles;\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/** Catalog of universal unit abilities (Deep Strike, Scouts X\", Feel No Pain X+, ...). */\nexport const unitKeywords = dataset.unitKeywords;\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 allied-detachment / 'soup' rules. */\nexport const alliedRules = dataset.alliedRules;\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 non-weapon wargear items (icons, attachments). */\nexport const wargear = dataset.wargear;\n/** All missions. */\nexport const missions = dataset.missions;\n/** All mission matchups. */\nexport const missionMatchups = dataset.missionMatchups;\n/** All secondary mission cards. */\nexport const missionCards = dataset.missionCards;\n/** All deployment patterns. */\nexport const deploymentPatterns = dataset.deploymentPatterns;\n/** All force dispositions. */\nexport const forceDispositions = dataset.forceDispositions;\n/** Reusable terrain catalog: standard areas and scenery features. */\nexport const terrainTemplates = dataset.terrainTemplates;\n/** All terrain layouts. */\nexport const terrainLayouts = dataset.terrainLayouts;\n/** Reusable model collision hulls (polygon footprints) referenced by id. */\nexport const hullShapes = dataset.hullShapes;\n/** All resource pools. */\nexport const resourcePools = dataset.resourcePools;\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"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","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, ReactiveTrigger } 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// Wargear-loadout maths: base/maximal loadout, per-weapon bounds, validation.\nexport {\n optionCap,\n baseLoadout,\n maximalLoadout,\n weaponBounds,\n clampWeaponCount,\n validateLoadout,\n groupLoadout,\n} from \"./loadout.js\";\nexport type {\n Loadout,\n WeaponBound,\n Violation,\n LoadoutModel,\n LoadoutGroup,\n LoadoutGroupWeapon,\n} from \"./loadout.js\";\n\n// Unit point-cost maths: ordinal-aware tier selection over `points` bands.\nexport { baseUnitPoints, pointsTierMissing } from \"./pricing.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// Compact, URL-safe list sharing (registry-indexed `share-v1` tokens).\nexport {\n encodeShareToken,\n decodeShareToken,\n rosterToShareList,\n shareRegistryVersion,\n ShareRegistryIndex,\n SHARE_FORMAT_VERSION,\n SHARE_KINDS,\n} from \"../share/index.js\";\nexport type {\n DecodeResult,\n ShareBattleSize,\n ShareList,\n ShareLoadoutEntry,\n ShareUnit,\n ShareKind,\n ShareRegistry,\n} from \"../share/index.js\";\n\n// Bridge helpers from the importer's RosterUnit → linked views.\nexport {\n resolveRosterUnit,\n resolveRosterWargear,\n resolveAttachedLeader,\n resolveAttachmentPartners,\n primaryDetachment,\n primaryDetachmentId,\n checkRosterLegality,\n checkRoster,\n validateRosterCore,\n} from \"./roster-resolve.js\";\nexport type {\n UnitLegality,\n RosterLegality,\n RosterViolation,\n RosterViolationCode,\n NormRoster,\n} from \"./roster-resolve.js\";\nexport { pointsLimitForBattleSize, detachmentCapForBattleSize } from \"./battle-sizes.js\";\nexport { candidateAffordability } from \"./affordability.js\";\nexport type { AffordabilitySpec, AffordabilityUnit, CandidateCost } from \"./affordability.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/** Named target archetypes referencing real units (faction_id + unit_id). */\nexport const targetProfiles = dataset.targetProfiles;\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/** Catalog of universal unit abilities (Deep Strike, Scouts X\", Feel No Pain X+, ...). */\nexport const unitKeywords = dataset.unitKeywords;\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 allied-detachment / 'soup' rules. */\nexport const alliedRules = dataset.alliedRules;\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 non-weapon wargear items (icons, attachments). */\nexport const wargear = dataset.wargear;\n/** All missions. */\nexport const missions = dataset.missions;\n/** All mission matchups. */\nexport const missionMatchups = dataset.missionMatchups;\n/** All secondary mission cards. */\nexport const missionCards = dataset.missionCards;\n/** All deployment patterns. */\nexport const deploymentPatterns = dataset.deploymentPatterns;\n/** All force dispositions. */\nexport const forceDispositions = dataset.forceDispositions;\n/** Reusable terrain catalog: standard areas and scenery features. */\nexport const terrainTemplates = dataset.terrainTemplates;\n/** All terrain layouts. */\nexport const terrainLayouts = dataset.terrainLayouts;\n/** Reusable model collision hulls (polygon footprints) referenced by id. */\nexport const hullShapes = dataset.hullShapes;\n/** All resource pools. */\nexport const resourcePools = dataset.resourcePools;\n"]}
|
package/dist/data/loadout.d.ts
CHANGED
|
@@ -94,11 +94,14 @@ export interface LoadoutGroup {
|
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
96
|
* Decompose a unit's flat loadout into per-model-type groups (e.g. "1x Blood Herald:
|
|
97
|
-
* …" + "6x Goremongers: …" + "1x Goremongers: …")
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
97
|
+
* …" + "6x Goremongers: …" + "1x Goremongers: …"). The composition's
|
|
98
|
+
* {@link assignRowCounts} fixes how many models each row fields; {@link solveAssignment}
|
|
99
|
+
* then searches, completely and deterministically, for an assignment of each row's
|
|
100
|
+
* models to legal per-model loadouts ({@link enumerateRowCandidates}) whose weapons sum
|
|
101
|
+
* to `counts` exactly while respecting every option's {@link optionCap}. Returns `null`
|
|
102
|
+
* only when no such exact partition exists (a single model, no recorded per-model
|
|
103
|
+
* defaults, or a genuinely indivisible bag) — callers then omit `loadout_groups` and
|
|
104
|
+
* renderers fall back to their unit-wide rendering unchanged. Mirror of
|
|
102
105
|
* `crates/wh40kdc/src/data/loadout.rs`.
|
|
103
106
|
*/
|
|
104
107
|
export declare function groupLoadout(unit: Unit, modelCount: number, options: readonly WargearOption[], models: readonly LoadoutModel[] | undefined, counts: Map<string, number>): LoadoutGroup[] | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadout.d.ts","sourceRoot":"","sources":["../../src/data/loadout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE3D,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,iFAAiF;AACjF,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,yEAAyE;AACzE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,aAAa,GAAG,WAAW,GAAG,eAAe,GAAG,mBAAmB,GAAG,qBAAqB,CAAC;IAClG,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,GAC/B,MAAM,CAqBR;AA4DD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA2ED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,GAC/B,OAAO,CAET;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,GAC/B,OAAO,CAgBT;AAsBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,GAC/B,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAgC1B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAChC,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,GAChB,MAAM,CAKR;AAED,uGAAuG;AACvG,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B;
|
|
1
|
+
{"version":3,"file":"loadout.d.ts","sourceRoot":"","sources":["../../src/data/loadout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE3D,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,iFAAiF;AACjF,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,yEAAyE;AACzE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,aAAa,GAAG,WAAW,GAAG,eAAe,GAAG,mBAAmB,GAAG,qBAAqB,CAAC;IAClG,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,GAC/B,MAAM,CAqBR;AA4DD;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA2ED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,GAC/B,OAAO,CAET;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,GAC/B,OAAO,CAgBT;AAsBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,GAC/B,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAgC1B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAChC,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,GAChB,MAAM,CAKR;AAED,uGAAuG;AACvG,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B;AA8OD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,EAC3C,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1B,YAAY,EAAE,GAAG,IAAI,CAqDvB;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,GAC/B,SAAS,EAAE,CAyBb;AAuCD,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAChE;AAgBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,EAChC,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,GAC7B,SAAS,EAAE,CA4Bb"}
|
package/dist/data/loadout.js
CHANGED
|
@@ -351,63 +351,147 @@ function optionBundles(option) {
|
|
|
351
351
|
return (option.replacement_choice ?? []).map((b) => [...b]);
|
|
352
352
|
}
|
|
353
353
|
/**
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* consumed, replaced ids returned). What it can't explain stays in `remaining`,
|
|
358
|
-
* which {@link groupLoadout} then treats as a failed (inexact) decomposition.
|
|
354
|
+
* A stable key for a weapon multiset: `count:id` parts in id order, joined by `|`.
|
|
355
|
+
* Zero/negative entries are dropped. Identical multisets yield identical keys
|
|
356
|
+
* across implementations, so it drives deterministic candidate ordering + grouping.
|
|
359
357
|
*/
|
|
360
|
-
function
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
358
|
+
function multisetKey(m) {
|
|
359
|
+
return [...m.entries()]
|
|
360
|
+
.filter(([, c]) => c > 0)
|
|
361
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
362
|
+
.map(([id, c]) => `${c}:${id}`)
|
|
363
|
+
.join("|");
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Enumerate every legal single-model loadout for one composition row: start from the
|
|
367
|
+
* row's base defaults and apply any *compatible* subset of the options that scope to
|
|
368
|
+
* this row (unscoped options, or those whose `model_constraint.model_name` matches the
|
|
369
|
+
* row). An option applies only when all its `replaces` weapons are currently present
|
|
370
|
+
* (so a slot is swapped at most once) and is used at most once per model; each
|
|
371
|
+
* `replacement_choice` branch is a distinct transformation. Reachable
|
|
372
|
+
* `(weapons, usedOptions)` states are deduped and returned — bounded, since options and
|
|
373
|
+
* their branches are few. Caps are *not* applied here; the assignment search charges
|
|
374
|
+
* them globally, so two derivations of the same weapon-set with different option usage
|
|
375
|
+
* are kept as distinct candidates (one may fit the caps where the other can't).
|
|
376
|
+
*/
|
|
377
|
+
function enumerateRowCandidates(base, rowName, options) {
|
|
378
|
+
const applicable = [];
|
|
379
|
+
for (let i = 0; i < options.length; i++) {
|
|
380
|
+
const name = options[i].model_constraint?.model_name ?? null;
|
|
381
|
+
if (name == null || name === rowName)
|
|
382
|
+
applicable.push(i);
|
|
383
|
+
}
|
|
384
|
+
const stateKey = (w, used) => `${multisetKey(w)}#${[...used].sort((a, b) => a - b).join(",")}`;
|
|
385
|
+
const result = [];
|
|
386
|
+
const seen = new Set();
|
|
387
|
+
const queue = [
|
|
388
|
+
{ weapons: new Map(base), used: [] },
|
|
389
|
+
];
|
|
390
|
+
seen.add(stateKey(base, []));
|
|
391
|
+
for (let head = 0; head < queue.length; head++) {
|
|
392
|
+
const cur = queue[head];
|
|
393
|
+
result.push({ weapons: cur.weapons, usedOptions: cur.used, key: multisetKey(cur.weapons) });
|
|
394
|
+
for (const oi of applicable) {
|
|
395
|
+
if (cur.used.includes(oi))
|
|
383
396
|
continue;
|
|
384
|
-
const
|
|
385
|
-
if (
|
|
397
|
+
const replaces = options[oi].replaces ?? [];
|
|
398
|
+
if (!replaces.every((id) => (cur.weapons.get(id) ?? 0) >= 1))
|
|
386
399
|
continue;
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
w.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
400
|
+
for (const bundle of optionBundles(options[oi])) {
|
|
401
|
+
if (bundle.length === 0)
|
|
402
|
+
continue;
|
|
403
|
+
const w = new Map(cur.weapons);
|
|
404
|
+
for (const id of replaces)
|
|
405
|
+
w.set(id, (w.get(id) ?? 0) - 1);
|
|
406
|
+
for (const id of bundle)
|
|
407
|
+
w.set(id, (w.get(id) ?? 0) + 1);
|
|
408
|
+
for (const [id, c] of [...w])
|
|
409
|
+
if (c <= 0)
|
|
410
|
+
w.delete(id);
|
|
411
|
+
const used = [...cur.used, oi].sort((a, b) => a - b);
|
|
412
|
+
const k = stateKey(w, used);
|
|
413
|
+
if (seen.has(k))
|
|
414
|
+
continue;
|
|
415
|
+
seen.add(k);
|
|
416
|
+
queue.push({ weapons: w, used });
|
|
417
|
+
}
|
|
401
418
|
}
|
|
402
419
|
}
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* A complete, deterministic exact-cover search: distribute each row's models across its
|
|
424
|
+
* candidate loadouts so the chosen weapons sum to `bag` exactly, never exceeding any
|
|
425
|
+
* option's `optionCaps` usage. Rows are taken in order; within a row, candidates in their
|
|
426
|
+
* (pre-sorted) order, trying the largest feasible count first; the residual bag and
|
|
427
|
+
* per-option usage prune branches that can't complete. Returns the first solution found
|
|
428
|
+
* (so identical inputs yield identical groupings everywhere) as flat per-candidate picks,
|
|
429
|
+
* or `null` when no exact partition exists. Unlike a greedy peeler, it finds a valid
|
|
430
|
+
* partition whenever one exists.
|
|
431
|
+
*/
|
|
432
|
+
function solveAssignment(rows, bag, optionCaps) {
|
|
433
|
+
const residual = new Map(bag);
|
|
434
|
+
const usage = optionCaps.map(() => 0);
|
|
435
|
+
const picks = [];
|
|
436
|
+
const assignRow = (ri) => {
|
|
437
|
+
if (ri === rows.length) {
|
|
438
|
+
for (const c of residual.values())
|
|
439
|
+
if (c !== 0)
|
|
440
|
+
return false;
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
return distribute(ri, 0, rows[ri].count);
|
|
444
|
+
};
|
|
445
|
+
const distribute = (ri, ci, left) => {
|
|
446
|
+
const row = rows[ri];
|
|
447
|
+
if (ci === row.candidates.length)
|
|
448
|
+
return left === 0 && assignRow(ri + 1);
|
|
449
|
+
const cand = row.candidates[ci];
|
|
450
|
+
let hi = left;
|
|
451
|
+
for (const [id, per] of cand.weapons) {
|
|
452
|
+
if (per > 0)
|
|
453
|
+
hi = Math.min(hi, Math.floor((residual.get(id) ?? 0) / per));
|
|
454
|
+
}
|
|
455
|
+
for (const oi of cand.usedOptions)
|
|
456
|
+
hi = Math.min(hi, optionCaps[oi] - usage[oi]);
|
|
457
|
+
hi = Math.max(0, hi);
|
|
458
|
+
for (let take = hi; take >= 0; take--) {
|
|
459
|
+
for (const [id, per] of cand.weapons)
|
|
460
|
+
residual.set(id, (residual.get(id) ?? 0) - per * take);
|
|
461
|
+
for (const oi of cand.usedOptions)
|
|
462
|
+
usage[oi] += take;
|
|
463
|
+
if (take > 0)
|
|
464
|
+
picks.push({ ri, ci, count: take });
|
|
465
|
+
if (distribute(ri, ci + 1, left - take))
|
|
466
|
+
return true;
|
|
467
|
+
if (take > 0)
|
|
468
|
+
picks.pop();
|
|
469
|
+
for (const oi of cand.usedOptions)
|
|
470
|
+
usage[oi] -= take;
|
|
471
|
+
for (const [id, per] of cand.weapons)
|
|
472
|
+
residual.set(id, (residual.get(id) ?? 0) + per * take);
|
|
473
|
+
}
|
|
474
|
+
return false;
|
|
475
|
+
};
|
|
476
|
+
if (!assignRow(0))
|
|
477
|
+
return null;
|
|
478
|
+
return picks.map((p) => ({
|
|
479
|
+
ri: p.ri,
|
|
480
|
+
name: rows[p.ri].name,
|
|
481
|
+
weapons: rows[p.ri].candidates[p.ci].weapons,
|
|
482
|
+
count: p.count,
|
|
483
|
+
}));
|
|
403
484
|
}
|
|
404
485
|
/**
|
|
405
486
|
* Decompose a unit's flat loadout into per-model-type groups (e.g. "1x Blood Herald:
|
|
406
|
-
* …" + "6x Goremongers: …" + "1x Goremongers: …")
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
487
|
+
* …" + "6x Goremongers: …" + "1x Goremongers: …"). The composition's
|
|
488
|
+
* {@link assignRowCounts} fixes how many models each row fields; {@link solveAssignment}
|
|
489
|
+
* then searches, completely and deterministically, for an assignment of each row's
|
|
490
|
+
* models to legal per-model loadouts ({@link enumerateRowCandidates}) whose weapons sum
|
|
491
|
+
* to `counts` exactly while respecting every option's {@link optionCap}. Returns `null`
|
|
492
|
+
* only when no such exact partition exists (a single model, no recorded per-model
|
|
493
|
+
* defaults, or a genuinely indivisible bag) — callers then omit `loadout_groups` and
|
|
494
|
+
* renderers fall back to their unit-wide rendering unchanged. Mirror of
|
|
411
495
|
* `crates/wh40kdc/src/data/loadout.rs`.
|
|
412
496
|
*/
|
|
413
497
|
export function groupLoadout(unit, modelCount, options, models, counts) {
|
|
@@ -416,32 +500,47 @@ export function groupLoadout(unit, modelCount, options, models, counts) {
|
|
|
416
500
|
// from the aggregate; don't synthesise groups for them.
|
|
417
501
|
if (n <= 1 || !hasRecordedDefaults(models))
|
|
418
502
|
return null;
|
|
419
|
-
const
|
|
503
|
+
const bag = new Map();
|
|
420
504
|
for (const [id, c] of counts)
|
|
421
505
|
if (c > 0)
|
|
422
|
-
|
|
423
|
-
const rowN = assignRowCounts(models, n,
|
|
424
|
-
const
|
|
506
|
+
bag.set(id, c);
|
|
507
|
+
const rowN = assignRowCounts(models, n, bag);
|
|
508
|
+
const optionCaps = options.map((o) => optionCap(o, n, models));
|
|
509
|
+
const rows = [];
|
|
425
510
|
for (let i = 0; i < models.length; i++) {
|
|
426
511
|
const k = rowN[i];
|
|
427
512
|
if (k <= 0)
|
|
428
513
|
continue;
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
514
|
+
const base = toMultiset(models[i].default_weapon_ids ?? []);
|
|
515
|
+
const candidates = enumerateRowCandidates(base, models[i].name ?? null, options).sort((a, b) => a.key.localeCompare(b.key) ||
|
|
516
|
+
a.usedOptions.length - b.usedOptions.length ||
|
|
517
|
+
a.usedOptions.join(",").localeCompare(b.usedOptions.join(",")));
|
|
518
|
+
rows.push({ name: models[i].name ?? null, count: k, candidates });
|
|
519
|
+
}
|
|
520
|
+
const solution = solveAssignment(rows, bag, optionCaps);
|
|
521
|
+
if (!solution)
|
|
522
|
+
return null;
|
|
523
|
+
// Merge identical (model-type, loadout) picks, then order deterministically: by row
|
|
524
|
+
// (leaders lead), then larger groups before smaller (bulk before variants), then by
|
|
525
|
+
// canonical loadout key. Stable across implementations and independent of the order
|
|
526
|
+
// the search happened to find picks in.
|
|
527
|
+
const byGroup = new Map();
|
|
528
|
+
for (const s of solution) {
|
|
529
|
+
const key = multisetKey(s.weapons);
|
|
530
|
+
const gkey = `${s.name ?? ""}##${key}`;
|
|
531
|
+
const cur = byGroup.get(gkey);
|
|
532
|
+
if (cur)
|
|
533
|
+
cur.count += s.count;
|
|
534
|
+
else
|
|
535
|
+
byGroup.set(gkey, { ri: s.ri, name: s.name, weapons: s.weapons, count: s.count, key });
|
|
433
536
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
for (const c of remaining.values())
|
|
438
|
-
if (c !== 0)
|
|
439
|
-
return null;
|
|
440
|
-
const live = groups.filter((g) => g.count > 0);
|
|
537
|
+
const live = [...byGroup.values()]
|
|
538
|
+
.filter((g) => g.count > 0)
|
|
539
|
+
.sort((a, b) => a.ri - b.ri || b.count - a.count || a.key.localeCompare(b.key));
|
|
441
540
|
if (live.length === 0)
|
|
442
541
|
return null;
|
|
443
542
|
return live.map((g) => ({
|
|
444
|
-
model_name: g.
|
|
543
|
+
model_name: g.name,
|
|
445
544
|
count: g.count,
|
|
446
545
|
weapons: sortedGroupWeapons(g.weapons),
|
|
447
546
|
}));
|
package/dist/data/loadout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadout.js","sourceRoot":"","sources":["../../src/data/loadout.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wargear-loadout maths shared by every consumer of the dataset: how many\n * models may take an option, what the maximal (take-every-swap) loadout looks\n * like, the valid count range for each weapon, and whether an edited loadout is\n * legal.\n *\n * The base loadout is derived, not stored: a weapon in `unit.weapon_ids` that\n * never appears as the *replacement* of any option is a **base** weapon, carried\n * by every model; a weapon that does appear as a replacement is **optional**,\n * carried only by the models that took the swap. This holds for uniform infantry\n * squads (every model shares the base loadout) and is exactly right for the\n * cases the corpus pins. Mirror of `crates/wh40kdc/src/data/loadout.rs`.\n *\n * @packageDocumentation\n */\nimport type { Unit, WargearOption } from \"../generated.js\";\n\n/** Inclusive count range a single weapon/wargear id may take in a loadout. */\nexport interface WeaponBound {\n min: number;\n max: number;\n}\n\n/** A resolved loadout: entity id (weapon or wargear) → count across the unit. */\nexport interface Loadout {\n counts: Map<string, number>;\n}\n\n/** A loadout-rule violation. `id` is the offending weapon/wargear id. */\nexport interface Violation {\n id: string;\n code: \"exceeds-max\" | \"below-min\" | \"swap-conflict\" | \"exceeds-allowance\" | \"invalid-model-count\";\n message: string;\n}\n\n/**\n * The maximum number of models that may take `option` in a unit of `modelCount`\n * models: `any_number` → all models; else `per_n_models` → floor(n / per); else\n * `max_count ?? 1`; then clamped by `max_count` when set. A null constraint is\n * treated as unrestricted (every model). Never negative.\n */\nexport function optionCap(\n option: WargearOption,\n modelCount: number,\n models?: readonly LoadoutModel[],\n): number {\n const c = option.model_constraint;\n if (!c) return Math.max(0, modelCount);\n let cap: number;\n if (c.any_number) cap = modelCount;\n else if (c.per_n_models) cap = Math.floor(modelCount / c.per_n_models);\n else cap = c.max_count ?? 1;\n if (c.max_count != null) cap = Math.min(cap, c.max_count);\n // Eligible-model clamp: an option scoped to a named model profile can be taken by\n // no more models than exist of that profile — a lone champion caps the swap at 1\n // even when `per_n_models` would allow more. The composition row name is the\n // authority (it and the option's `model_name` both come from the dump miniature);\n // a name with no matching row leaves the cap unclamped (never over-restrict).\n if (c.model_name && models?.length) {\n const eligible = eligibleModelCount(models, modelCount, c.model_name);\n if (eligible != null) cap = Math.min(cap, eligible);\n }\n // A swap is per-model: at most one per model, so never more than modelCount —\n // a `max_count` larger than the current squad size (e.g. a flat BSData cap of 6\n // on a 5-model unit) must not drive a weapon count negative.\n return Math.max(0, Math.min(cap, modelCount));\n}\n\n/**\n * How many models of profile `name` a unit of `modelCount` fields, per the\n * composition allocation ({@link allocateModels}). `null` when no row carries that\n * name — the caller then leaves the option uncapped by eligibility.\n */\nfunction eligibleModelCount(\n models: readonly LoadoutModel[],\n modelCount: number,\n name: string,\n): number | null {\n if (!models.some((m) => m.name === name)) return null;\n let n = 0;\n for (const { model, count } of allocateModels(models, modelCount)) {\n if (model.name === name) n += count;\n }\n return n;\n}\n\n/** The ids a single option can add, given the chosen choice branch (default 0). */\nfunction addedIds(option: WargearOption, choiceIndex = 0): string[] {\n if (option.replacement) return option.replacement;\n return option.replacement_choice?.[choiceIndex] ?? [];\n}\n\n/** Every id that any option can add — across all choice branches. */\nfunction allReplacementIds(options: readonly WargearOption[]): Set<string> {\n const out = new Set<string>();\n for (const o of options) {\n for (const id of o.replacement ?? []) out.add(id);\n for (const group of o.replacement_choice ?? []) for (const id of group) out.add(id);\n }\n return out;\n}\n\n/** Every id that any option swaps OUT (the base weapon a swap replaces). */\nfunction allReplacedIds(options: readonly WargearOption[]): Set<string> {\n const out = new Set<string>();\n for (const o of options) for (const id of o.replaces ?? []) out.add(id);\n return out;\n}\n\n/**\n * Derived base (always-carried) weapon ids — the fallback when a unit has no\n * recorded {@link LoadoutModel.default_weapon_ids}. A `weapon_id` is base iff it\n * is swapped out by some option (`replaces`) OR it never appears on any option's\n * *added* side. The `replaces` clause is load-bearing: a base weapon can also be\n * re-added inside another option's choice branch (e.g. a Chaos Terminator\n * Champion keeps a combi-bolter while swapping its melee), and such a weapon is\n * still base — checking only the added side would wrongly drop it. An *orphan*\n * weapon (in `weapon_ids`, touched by no option) stays base, which is correct for\n * a vehicle's fixed main gun.\n */\nfunction baseWeaponIds(unit: Unit, options: readonly WargearOption[]): string[] {\n const added = allReplacementIds(options);\n const replaced = allReplacedIds(options);\n return (unit.weapon_ids ?? []).filter((id) => replaced.has(id) || !added.has(id));\n}\n\n/**\n * A unit-composition model row, as far as loadout maths cares: its count range,\n * whether it is a leader (taken at a fixed small count), and the weapons every\n * such model carries by default. Pass the unit's `unit_composition.models` here.\n */\nexport interface LoadoutModel {\n /** Model-profile name; matched against an option's `model_constraint.model_name`. */\n name?: string;\n min: number;\n max: number;\n default_weapon_ids?: readonly string[];\n is_leader_model?: boolean;\n}\n\n/** True when every model row records a non-empty default loadout. */\nfunction hasRecordedDefaults(models: readonly LoadoutModel[] | undefined): models is LoadoutModel[] {\n return (\n !!models && models.length > 0 && models.every((m) => (m.default_weapon_ids?.length ?? 0) > 0)\n );\n}\n\n/**\n * Allocate `modelCount` models across the composition's model-types: each leader\n * is taken at its `min` (in declared order, never exceeding the remaining count),\n * then the non-leader \"bulk\" types absorb the rest — each its `min` first, then\n * any leftover to the bulk type with the largest `max`. Deterministic; mirrored\n * across implementations and pinned by the conformance corpus.\n */\nfunction allocateModels(\n models: readonly LoadoutModel[],\n modelCount: number,\n): { model: LoadoutModel; count: number }[] {\n const out = models.map((model) => ({ model, count: 0 }));\n let remaining = Math.max(0, modelCount);\n // Leaders first, at their declared minimum.\n for (const row of out) {\n if (!row.model.is_leader_model) continue;\n const c = Math.min(row.model.min ?? 0, remaining);\n row.count += c;\n remaining -= c;\n }\n const bulk = out.filter((r) => !r.model.is_leader_model);\n if (bulk.length === 0) {\n // No non-leader type: pour any remainder onto the leaders (largest max first).\n bulk.push(...out);\n }\n // Each bulk type takes its min, then the remainder lands on the largest-max type.\n for (const row of bulk) {\n const c = Math.min(row.model.min ?? 0, remaining);\n row.count += c;\n remaining -= c;\n }\n if (remaining > 0 && bulk.length > 0) {\n const sink = bulk.reduce((a, b) => ((b.model.max ?? 0) > (a.model.max ?? 0) ? b : a));\n sink.count += remaining;\n }\n return out;\n}\n\n/**\n * The base loadout counts: id → count across the unit with no swaps applied.\n * When the composition records per-model {@link LoadoutModel.default_weapon_ids},\n * those are authoritative — base = Σ over model-types of (allocated count ×\n * default weapons). Otherwise it falls back to {@link baseWeaponIds} × modelCount.\n */\nfunction baseCounts(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models?: readonly LoadoutModel[],\n): Map<string, number> {\n const counts = new Map<string, number>();\n if (hasRecordedDefaults(models)) {\n for (const { model, count } of allocateModels(models, modelCount)) {\n if (count === 0) continue;\n for (const id of model.default_weapon_ids ?? []) {\n counts.set(id, (counts.get(id) ?? 0) + count);\n }\n }\n return counts;\n }\n for (const id of baseWeaponIds(unit, options)) {\n counts.set(id, (counts.get(id) ?? 0) + modelCount);\n }\n return counts;\n}\n\n/**\n * The base loadout: every model in its out-of-the-box configuration, no swaps\n * applied. This is the legal default a freshly-added unit ships with. Reads the\n * composition's recorded `default_weapon_ids` when present (authoritative),\n * otherwise derives the base set. {@link maximalLoadout} starts from this set and\n * then applies every option at full cap.\n */\nexport function baseLoadout(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models?: readonly LoadoutModel[],\n): Loadout {\n return { counts: baseCounts(unit, modelCount, options, models) };\n}\n\n/**\n * The maximal loadout: every base weapon on every model, then each option\n * applied at its full {@link optionCap} (choices take their first branch). Swaps\n * move count from the replaced id to the added id; add-ons only add.\n */\nexport function maximalLoadout(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models?: readonly LoadoutModel[],\n): Loadout {\n const counts = baseCounts(unit, modelCount, options, models);\n for (const option of options) {\n const cap = optionCap(option, modelCount, models);\n if (cap === 0) continue;\n for (const id of option.replaces ?? []) {\n counts.set(id, (counts.get(id) ?? 0) - cap);\n }\n for (const id of addedIds(option)) {\n counts.set(id, (counts.get(id) ?? 0) + cap);\n }\n }\n clampFlatBudgets(unit, counts);\n // Drop any id that nets to zero so the loadout reads cleanly.\n for (const [id, n] of counts) if (n === 0) counts.delete(id);\n return { counts };\n}\n\n/**\n * Cap each weapon's count by any single-weapon flat {@link Unit.wargear_budgets}\n * (a \"this model takes at most N of weapon X\" line, modelled as `items` of length\n * 1 with `per_models === 0`). A weapon reachable through several swap slots — e.g.\n * a Knight Destrier whose chastiser gatling cannon AND frag bombard can each be\n * swapped for a bellatus reaper chainsword — would otherwise sum to an illegal\n * count; clamping here makes {@link maximalLoadout}/{@link weaponBounds} agree with\n * the same invalid-loadout prevention the editor enforces, so a count consumer\n * (the salvo calculator) never seeds an over-cap value. Shared (multi-item) and\n * ratio (`per_models > 0`) budgets stay policed by {@link budgetViolations}.\n */\nfunction clampFlatBudgets(unit: Unit, counts: Map<string, number>): void {\n for (const budget of unit.wargear_budgets ?? []) {\n if (budget.items.length !== 1 || budget.per_models !== 0) continue;\n const id = budget.items[0];\n const cur = counts.get(id);\n if (cur != null && cur > budget.count) counts.set(id, budget.count);\n }\n}\n\n/**\n * Inclusive valid count range for each weapon/wargear id, used to clamp a UI's\n * per-weapon inputs so invalid loadouts are unreachable. A base weapon ranges\n * `[modelCount − maxSwapsAway, modelCount]`; an optional (replacement) id ranges\n * `[0, Σ caps that add it]`.\n */\nexport function weaponBounds(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models?: readonly LoadoutModel[],\n): Map<string, WeaponBound> {\n const bounds = new Map<string, WeaponBound>();\n for (const [id, count] of baseCounts(unit, modelCount, options, models)) {\n bounds.set(id, { min: count, max: count });\n }\n for (const option of options) {\n const cap = optionCap(option, modelCount, models);\n for (const id of option.replaces ?? []) {\n const b = bounds.get(id) ?? { min: 0, max: 0 };\n bounds.set(id, { min: Math.max(0, b.min - cap), max: b.max });\n }\n // A replacement id can appear in multiple options / both choice branches;\n // sum the caps so its ceiling reflects every way to add it.\n const adds = new Set<string>();\n for (const id of option.replacement ?? []) adds.add(id);\n for (const group of option.replacement_choice ?? []) for (const id of group) adds.add(id);\n for (const id of adds) {\n const b = bounds.get(id) ?? { min: 0, max: 0 };\n bounds.set(id, { min: b.min, max: b.max + cap });\n }\n }\n // A single-weapon flat budget caps the weapon's ceiling regardless of how many\n // swap slots can add it (see {@link clampFlatBudgets}), so an editor/salvo input\n // clamped against these bounds can never reach an over-cap, illegal count.\n for (const budget of unit.wargear_budgets ?? []) {\n if (budget.items.length !== 1 || budget.per_models !== 0) continue;\n const b = bounds.get(budget.items[0]);\n if (b && b.max > budget.count) {\n bounds.set(budget.items[0], { min: Math.min(b.min, budget.count), max: budget.count });\n }\n }\n return bounds;\n}\n\n/**\n * Clamp a single weapon's requested count into its valid range. Ids with no\n * bound (not part of this unit's loadout) are returned unchanged but floored at\n * zero.\n */\nexport function clampWeaponCount(\n bounds: Map<string, WeaponBound>,\n id: string,\n requested: number,\n): number {\n const b = bounds.get(id);\n const n = Math.max(0, Math.floor(requested) || 0);\n if (!b) return n;\n return Math.min(b.max, Math.max(b.min, n));\n}\n\n/** One weapon line within a {@link LoadoutGroup}: entity id and its count *per model* in the group. */\nexport interface LoadoutGroupWeapon {\n id: string;\n count: number;\n}\n\n/**\n * A set of identically-equipped models within a unit: `count` models of model-type\n * `model_name`, each carrying `weapons` (counts are *per model*). Produced by\n * {@link groupLoadout} so an exporter can render \"Nx <model>: <loadout>\" lines\n * instead of one unit-wide weapon bag. Mirror of `crates/wh40kdc/src/data/loadout.rs`.\n */\nexport interface LoadoutGroup {\n model_name: string | null;\n count: number;\n weapons: LoadoutGroupWeapon[];\n}\n\nfunction toMultiset(ids: readonly string[]): Map<string, number> {\n const m = new Map<string, number>();\n for (const id of ids) m.set(id, (m.get(id) ?? 0) + 1);\n return m;\n}\n\n/** Group weapons in a stable, language-agnostic order (by id) for cross-impl parity. */\nfunction sortedGroupWeapons(m: Map<string, number>): LoadoutGroupWeapon[] {\n return [...m.entries()]\n .filter(([, c]) => c > 0)\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([id, count]) => ({ id, count }));\n}\n\n/**\n * Assign each composition row a model count, summing to `modelCount`. Rows are\n * seeded at their `min`; a row with a *distinctive* default weapon (one carried by\n * no other row) present in `counts` grows toward that weapon's implied count (this\n * recovers opt-in weapon-variant rows like a Deathwatch kill-team's plasma models,\n * which sit at `min: 0`); the leftover budget pours into the bulk row (the\n * non-leader, non-distinctive row with the most headroom). Deterministic.\n */\nfunction assignRowCounts(\n models: readonly LoadoutModel[],\n modelCount: number,\n counts: Map<string, number>,\n): number[] {\n const rowDefaults = models.map((m) => toMultiset(m.default_weapon_ids ?? []));\n const rowsWith = new Map<string, number>();\n for (const def of rowDefaults) for (const id of def.keys()) rowsWith.set(id, (rowsWith.get(id) ?? 0) + 1);\n const minOf = (i: number) => Math.max(0, models[i].min ?? 0);\n const maxOf = (i: number) => Math.max(minOf(i), models[i].max ?? minOf(i));\n\n const out = models.map((_, i) => minOf(i));\n let budget = modelCount - out.reduce((a, b) => a + b, 0);\n if (budget < 0) {\n // Σmin exceeds the unit's size: trim from the end, deterministically.\n let over = -budget;\n for (let i = models.length - 1; i >= 0 && over > 0; i--) {\n const cut = Math.min(over, out[i]);\n out[i] -= cut;\n over -= cut;\n }\n budget = 0;\n }\n\n const distinctive = models.map(() => false);\n for (let i = 0; i < models.length && budget > 0; i++) {\n let cap = Infinity;\n let saw = false;\n for (const [id, mult] of rowDefaults[i]) {\n if ((rowsWith.get(id) ?? 0) === 1 && mult > 0 && (counts.get(id) ?? 0) > 0) {\n saw = true;\n cap = Math.min(cap, Math.floor((counts.get(id) ?? 0) / mult));\n }\n }\n if (!saw) continue;\n distinctive[i] = true;\n const add = Math.max(0, Math.min(Math.min(cap, maxOf(i)) - out[i], budget));\n out[i] += add;\n budget -= add;\n }\n\n const headroom = (i: number) => maxOf(i) - out[i];\n while (budget > 0) {\n let pick = -1;\n for (let i = 0; i < models.length; i++) {\n if (headroom(i) <= 0 || models[i].is_leader_model || distinctive[i]) continue;\n if (pick < 0 || headroom(i) > headroom(pick)) pick = i;\n }\n if (pick < 0)\n for (let i = 0; i < models.length; i++) {\n if (headroom(i) <= 0) continue;\n if (pick < 0 || headroom(i) > headroom(pick)) pick = i;\n }\n if (pick < 0) break;\n const add = Math.min(budget, headroom(pick));\n out[pick] += add;\n budget -= add;\n }\n return out;\n}\n\ninterface MutGroup {\n model_name: string | null;\n count: number;\n weapons: Map<string, number>;\n}\n\n/** The bundles (added-id sets) an option offers: a fixed `replacement`, else each `replacement_choice` branch. */\nfunction optionBundles(option: WargearOption): string[][] {\n if (option.replacement) return [option.replacement];\n return (option.replacement_choice ?? []).map((b) => [...b]);\n}\n\n/**\n * Explain the leftover weapon counts as option swaps, peeling a variant sub-group\n * off the base group of the model-type the option is scoped to. Each peel moves\n * `take` models to `(base − replaces + bundle)` and updates `remaining` (added ids\n * consumed, replaced ids returned). What it can't explain stays in `remaining`,\n * which {@link groupLoadout} then treats as a failed (inexact) decomposition.\n */\nfunction applySwaps(\n groups: MutGroup[],\n models: readonly LoadoutModel[],\n options: readonly WargearOption[],\n modelCount: number,\n remaining: Map<string, number>,\n): void {\n for (const option of options) {\n const cap = optionCap(option, modelCount, models);\n if (cap <= 0) continue;\n const replaces = option.replaces ?? [];\n const scopedName = option.model_constraint?.model_name ?? null;\n for (const bundle of optionBundles(option)) {\n if (bundle.length === 0) continue;\n const addM = toMultiset(bundle);\n let k = cap;\n for (const [id, mult] of addM) k = Math.min(k, Math.floor(Math.max(0, remaining.get(id) ?? 0) / mult));\n if (k <= 0) continue;\n // Source: a group of the scoped model-type still holding every replaced weapon\n // (fall back to any such holder when the scoped name matches nothing).\n const holds = (g: MutGroup) => g.count > 0 && replaces.every((id) => (g.weapons.get(id) ?? 0) >= 1);\n let src = groups.find((g) => holds(g) && (scopedName == null || g.model_name === scopedName));\n if (!src && scopedName != null) src = groups.find(holds);\n if (!src) continue;\n const take = Math.min(k, src.count);\n if (take <= 0) continue;\n const w = new Map(src.weapons);\n for (const id of replaces) w.set(id, (w.get(id) ?? 0) - 1);\n for (const [id, mult] of addM) w.set(id, (w.get(id) ?? 0) + mult);\n for (const [id, c] of [...w]) if (c <= 0) w.delete(id);\n src.count -= take;\n groups.push({ model_name: src.model_name, count: take, weapons: w });\n for (const [id, mult] of addM) remaining.set(id, (remaining.get(id) ?? 0) - mult * take);\n for (const id of replaces) remaining.set(id, (remaining.get(id) ?? 0) + take);\n }\n }\n}\n\n/**\n * Decompose a unit's flat loadout into per-model-type groups (e.g. \"1x Blood Herald:\n * …\" + \"6x Goremongers: …\" + \"1x Goremongers: …\"), reusing {@link allocateModels}'s\n * partition and the per-model-type option scoping. Returns `null` when the\n * decomposition is not *exact* — a single model, no recorded per-model defaults, or\n * leftover counts no swap explains — so callers omit `loadout_groups` and renderers\n * fall back to their unit-wide rendering unchanged. Mirror of\n * `crates/wh40kdc/src/data/loadout.rs`.\n */\nexport function groupLoadout(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models: readonly LoadoutModel[] | undefined,\n counts: Map<string, number>,\n): LoadoutGroup[] | null {\n const n = Math.max(0, Math.floor(modelCount) || 0);\n // Single-model units and units without recorded per-model defaults render fine\n // from the aggregate; don't synthesise groups for them.\n if (n <= 1 || !hasRecordedDefaults(models)) return null;\n\n const remaining = new Map<string, number>();\n for (const [id, c] of counts) if (c > 0) remaining.set(id, c);\n\n const rowN = assignRowCounts(models, n, remaining);\n const groups: MutGroup[] = [];\n for (let i = 0; i < models.length; i++) {\n const k = rowN[i];\n if (k <= 0) continue;\n const def = toMultiset(models[i].default_weapon_ids ?? []);\n for (const [id, c] of def) remaining.set(id, (remaining.get(id) ?? 0) - c * k);\n groups.push({ model_name: models[i].name ?? null, count: k, weapons: def });\n }\n\n applySwaps(groups, models, options, n, remaining);\n\n // Exact only: any unexplained surplus or deficit means we can't faithfully\n // partition the bag — bail so the renderer keeps its existing fallback.\n for (const c of remaining.values()) if (c !== 0) return null;\n\n const live = groups.filter((g) => g.count > 0);\n if (live.length === 0) return null;\n return live.map((g) => ({\n model_name: g.model_name,\n count: g.count,\n weapons: sortedGroupWeapons(g.weapons),\n }));\n}\n\n/** Report every weapon/wargear count that falls outside its valid range. */\nexport function validateLoadout(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n counts: Map<string, number>,\n models?: readonly LoadoutModel[],\n): Violation[] {\n const bounds = weaponBounds(unit, modelCount, options, models);\n const out: Violation[] = [];\n // Items governed by a shared-allowance budget are policed solely by\n // `budgetViolations` (the GW `limited_wargear_choice_set` cap). Their per-id\n // `weaponBounds` max is derived from the dump's cross-product loadout branches\n // — the unreliable signal the budget exists to replace (a weapon in several\n // option branches sums an inflated bound) — so skip the per-id check for them.\n const budgeted = new Set<string>();\n for (const b of unit.wargear_budgets ?? []) for (const id of b.items ?? []) budgeted.add(id);\n for (const [id, n] of counts) {\n if (budgeted.has(id)) continue;\n const b = bounds.get(id);\n if (!b) continue;\n if (n > b.max) {\n out.push({ id, code: \"exceeds-max\", message: `${id}: ${n} exceeds max ${b.max}` });\n } else if (n < b.min) {\n out.push({ id, code: \"below-min\", message: `${id}: ${n} below min ${b.min}` });\n }\n }\n out.push(...swapConflicts(unit, modelCount, options, counts, models));\n out.push(...budgetViolations(unit, modelCount, counts));\n // Deterministic order so the result is stable for cross-impl comparison.\n out.sort((a, b) => (a.id === b.id ? a.code.localeCompare(b.code) : a.id.localeCompare(b.id)));\n return out;\n}\n\n/**\n * Shared-allowance violations: each {@link Unit.wargear_budgets} entry lets its\n * listed items take at most `floor(modelCount * count / per_models)` copies between\n * them (a GW \"for every N models, X can take one of …\" line). Summing the final\n * counts is robust to *how* the items are equipped — unlike per-option caps, which\n * the dump's cross-product loadout branches defeat. The violation `id` is the\n * budget's sorted items joined by `+`, so it is stable and identifies the group.\n */\nfunction budgetViolations(\n unit: Unit,\n modelCount: number,\n counts: Map<string, number>,\n): Violation[] {\n const out: Violation[] = [];\n for (const budget of unit.wargear_budgets ?? []) {\n const items = budget.items ?? [];\n if (!items.length) continue;\n const used = items.reduce((sum, id) => sum + (counts.get(id) ?? 0), 0);\n // `per_models === 0` is a flat per-unit cap of `count`; otherwise a ratio.\n const cap = budget.per_models\n ? Math.floor((modelCount * budget.count) / budget.per_models)\n : budget.count;\n if (used > cap) {\n const id = [...items].sort().join(\"+\");\n const limit = budget.per_models\n ? `${budget.count} per ${budget.per_models} models`\n : `${budget.count} per unit`;\n out.push({\n id,\n code: \"exceeds-allowance\",\n message: `${id}: ${used} exceeds shared allowance ${cap} (${limit})`,\n });\n }\n }\n return out;\n}\n\n/** One discrete buildable squad size: a per-model count range, keyed by model name. */\nexport interface LoadoutTier {\n models: readonly { name?: string; min: number; max: number }[];\n}\n\n/**\n * Merge a tier's per-model count ranges onto the composition's `models[]` metadata\n * (default weapons, leader flag, base) by name, producing the {@link LoadoutModel}\n * list the loadout maths consume for that tier. A tier row with no matching base\n * model keeps just its name/min/max.\n */\nfunction tierModels(tier: LoadoutTier, base: readonly LoadoutModel[]): LoadoutModel[] {\n const byName = new Map(base.map((m) => [m.name, m]));\n return tier.models.map((tm) => {\n const b = tm.name != null ? byName.get(tm.name) : undefined;\n return { ...(b ?? {}), name: tm.name, min: tm.min, max: tm.max };\n });\n}\n\n/**\n * Whole-unit legality, tier-aware — the building block for a roster check. GW\n * models a datasheet's buildable sizes as discrete tiers (e.g. Neurogaunts: 1\n * Nodebeast + 10, or 1 + 11–20, or 2 + 20). A roster records only the *total*\n * `modelCount`, so we select every tier whose total range `[Σmin, Σmax]` contains\n * it and run {@link validateLoadout} against each tier's per-model allocation. The\n * unit is legal iff **some** containing tier validates clean — that's the faithful\n * \"does a legal build exist\" semantics, and it lets the maths allocate (say) 2\n * Nodebeasts at 22 models, which the single-envelope allocation cannot.\n *\n * Deterministic reporting (stable for cross-impl conformance): return the empty\n * result of the first clean tier (in tier order); else the violations of the first\n * containing tier. When `modelCount` falls in no tier, a single\n * `invalid-model-count` violation. When no `tiers` are supplied, falls back to a\n * plain {@link validateLoadout} (no size check) — preserving behaviour for units\n * the tier ingest didn't populate. Mirror of `crates/wh40kdc/src/data/loadout.rs`.\n */\nexport function checkUnitLegality(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n counts: Map<string, number>,\n models?: readonly LoadoutModel[],\n tiers?: readonly LoadoutTier[],\n): Violation[] {\n if (!tiers?.length) {\n return validateLoadout(unit, modelCount, options, counts, models);\n }\n const base = models ?? [];\n const candidates: LoadoutModel[][] = [];\n for (const tier of tiers) {\n const tm = tierModels(tier, base);\n const min = tm.reduce((s, m) => s + (m.min ?? 0), 0);\n const max = tm.reduce((s, m) => s + (m.max ?? 0), 0);\n if (modelCount >= min && modelCount <= max) candidates.push(tm);\n }\n if (candidates.length === 0) {\n return [\n {\n id: unit.id,\n code: \"invalid-model-count\",\n message: `${unit.id}: ${modelCount} models matches no composition tier`,\n },\n ];\n }\n let firstViolations: Violation[] | null = null;\n for (const tm of candidates) {\n const violations = validateLoadout(unit, modelCount, options, counts, tm);\n if (violations.length === 0) return [];\n if (firstViolations === null) firstViolations = violations;\n }\n return firstViolations ?? [];\n}\n\n/**\n * Swap-conservation violations the independent per-id {@link weaponBounds} can't\n * see: a model's replaceable slot holds the base weapon OR one of its swap\n * replacements, never both, so `count(base) + Σ count(its replacements)` cannot\n * exceed `modelCount`. Without this, keeping a base weapon *and* adding its\n * replacement passes every per-id bound while being illegal (e.g. War Dog\n * Brigand with both diabolus heavy stubber and havoc multi-launcher).\n *\n * Enforced only for the unambiguous shape — a base weapon swapped out by plain\n * (non-choice) options that replace it alone, whose replacement ids are unique\n * within this unit's option set and aren't themselves base weapons. Choice /\n * multi-replace / shared-replacement options can't be attributed to one slot\n * pool, so they stay on the looser per-id bounds rather than risk a false\n * positive. Mirror of `crates/wh40kdc/src/data/loadout.rs`.\n */\nfunction swapConflicts(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n counts: Map<string, number>,\n models?: readonly LoadoutModel[],\n): Violation[] {\n const baseMap = baseCounts(unit, modelCount, options, models);\n const baseIds = new Set(baseMap.keys());\n const addedBy = new Map<string, number>();\n for (const o of options) {\n for (const id of o.replacement ?? []) addedBy.set(id, (addedBy.get(id) ?? 0) + 1);\n for (const g of o.replacement_choice ?? []) {\n for (const id of g) addedBy.set(id, (addedBy.get(id) ?? 0) + 1);\n }\n }\n const out: Violation[] = [];\n for (const base of baseIds) {\n const cleanAdds = new Set<string>();\n let messy = false;\n for (const o of options) {\n const replaces: readonly string[] = o.replaces ?? [];\n if (!replaces.includes(base)) continue;\n // Only a plain, single-target swap of this exact base weapon is unambiguous.\n if (replaces.length !== 1 || (o.replacement_choice?.length ?? 0) > 0) {\n messy = true;\n break;\n }\n for (const b of o.replacement ?? []) {\n if (baseIds.has(b) || (addedBy.get(b) ?? 0) > 1) {\n messy = true;\n break;\n }\n cleanAdds.add(b);\n }\n if (messy) break;\n }\n if (messy || cleanAdds.size === 0) continue;\n // The slot can hold at most as many weapons as there are models carrying this\n // base weapon by default — its base count (modelCount when not per-model).\n const cap = baseMap.get(base) ?? modelCount;\n let total = counts.get(base) ?? 0;\n for (const b of cleanAdds) total += counts.get(b) ?? 0;\n if (total > cap) {\n out.push({\n id: base,\n code: \"swap-conflict\",\n message: `${base} and its swap replacement(s) total ${total}, exceeding ${cap} (a model takes the base weapon or a swap, not both)`,\n });\n }\n }\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"loadout.js","sourceRoot":"","sources":["../../src/data/loadout.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Wargear-loadout maths shared by every consumer of the dataset: how many\n * models may take an option, what the maximal (take-every-swap) loadout looks\n * like, the valid count range for each weapon, and whether an edited loadout is\n * legal.\n *\n * The base loadout is derived, not stored: a weapon in `unit.weapon_ids` that\n * never appears as the *replacement* of any option is a **base** weapon, carried\n * by every model; a weapon that does appear as a replacement is **optional**,\n * carried only by the models that took the swap. This holds for uniform infantry\n * squads (every model shares the base loadout) and is exactly right for the\n * cases the corpus pins. Mirror of `crates/wh40kdc/src/data/loadout.rs`.\n *\n * @packageDocumentation\n */\nimport type { Unit, WargearOption } from \"../generated.js\";\n\n/** Inclusive count range a single weapon/wargear id may take in a loadout. */\nexport interface WeaponBound {\n min: number;\n max: number;\n}\n\n/** A resolved loadout: entity id (weapon or wargear) → count across the unit. */\nexport interface Loadout {\n counts: Map<string, number>;\n}\n\n/** A loadout-rule violation. `id` is the offending weapon/wargear id. */\nexport interface Violation {\n id: string;\n code: \"exceeds-max\" | \"below-min\" | \"swap-conflict\" | \"exceeds-allowance\" | \"invalid-model-count\";\n message: string;\n}\n\n/**\n * The maximum number of models that may take `option` in a unit of `modelCount`\n * models: `any_number` → all models; else `per_n_models` → floor(n / per); else\n * `max_count ?? 1`; then clamped by `max_count` when set. A null constraint is\n * treated as unrestricted (every model). Never negative.\n */\nexport function optionCap(\n option: WargearOption,\n modelCount: number,\n models?: readonly LoadoutModel[],\n): number {\n const c = option.model_constraint;\n if (!c) return Math.max(0, modelCount);\n let cap: number;\n if (c.any_number) cap = modelCount;\n else if (c.per_n_models) cap = Math.floor(modelCount / c.per_n_models);\n else cap = c.max_count ?? 1;\n if (c.max_count != null) cap = Math.min(cap, c.max_count);\n // Eligible-model clamp: an option scoped to a named model profile can be taken by\n // no more models than exist of that profile — a lone champion caps the swap at 1\n // even when `per_n_models` would allow more. The composition row name is the\n // authority (it and the option's `model_name` both come from the dump miniature);\n // a name with no matching row leaves the cap unclamped (never over-restrict).\n if (c.model_name && models?.length) {\n const eligible = eligibleModelCount(models, modelCount, c.model_name);\n if (eligible != null) cap = Math.min(cap, eligible);\n }\n // A swap is per-model: at most one per model, so never more than modelCount —\n // a `max_count` larger than the current squad size (e.g. a flat BSData cap of 6\n // on a 5-model unit) must not drive a weapon count negative.\n return Math.max(0, Math.min(cap, modelCount));\n}\n\n/**\n * How many models of profile `name` a unit of `modelCount` fields, per the\n * composition allocation ({@link allocateModels}). `null` when no row carries that\n * name — the caller then leaves the option uncapped by eligibility.\n */\nfunction eligibleModelCount(\n models: readonly LoadoutModel[],\n modelCount: number,\n name: string,\n): number | null {\n if (!models.some((m) => m.name === name)) return null;\n let n = 0;\n for (const { model, count } of allocateModels(models, modelCount)) {\n if (model.name === name) n += count;\n }\n return n;\n}\n\n/** The ids a single option can add, given the chosen choice branch (default 0). */\nfunction addedIds(option: WargearOption, choiceIndex = 0): string[] {\n if (option.replacement) return option.replacement;\n return option.replacement_choice?.[choiceIndex] ?? [];\n}\n\n/** Every id that any option can add — across all choice branches. */\nfunction allReplacementIds(options: readonly WargearOption[]): Set<string> {\n const out = new Set<string>();\n for (const o of options) {\n for (const id of o.replacement ?? []) out.add(id);\n for (const group of o.replacement_choice ?? []) for (const id of group) out.add(id);\n }\n return out;\n}\n\n/** Every id that any option swaps OUT (the base weapon a swap replaces). */\nfunction allReplacedIds(options: readonly WargearOption[]): Set<string> {\n const out = new Set<string>();\n for (const o of options) for (const id of o.replaces ?? []) out.add(id);\n return out;\n}\n\n/**\n * Derived base (always-carried) weapon ids — the fallback when a unit has no\n * recorded {@link LoadoutModel.default_weapon_ids}. A `weapon_id` is base iff it\n * is swapped out by some option (`replaces`) OR it never appears on any option's\n * *added* side. The `replaces` clause is load-bearing: a base weapon can also be\n * re-added inside another option's choice branch (e.g. a Chaos Terminator\n * Champion keeps a combi-bolter while swapping its melee), and such a weapon is\n * still base — checking only the added side would wrongly drop it. An *orphan*\n * weapon (in `weapon_ids`, touched by no option) stays base, which is correct for\n * a vehicle's fixed main gun.\n */\nfunction baseWeaponIds(unit: Unit, options: readonly WargearOption[]): string[] {\n const added = allReplacementIds(options);\n const replaced = allReplacedIds(options);\n return (unit.weapon_ids ?? []).filter((id) => replaced.has(id) || !added.has(id));\n}\n\n/**\n * A unit-composition model row, as far as loadout maths cares: its count range,\n * whether it is a leader (taken at a fixed small count), and the weapons every\n * such model carries by default. Pass the unit's `unit_composition.models` here.\n */\nexport interface LoadoutModel {\n /** Model-profile name; matched against an option's `model_constraint.model_name`. */\n name?: string;\n min: number;\n max: number;\n default_weapon_ids?: readonly string[];\n is_leader_model?: boolean;\n}\n\n/** True when every model row records a non-empty default loadout. */\nfunction hasRecordedDefaults(models: readonly LoadoutModel[] | undefined): models is LoadoutModel[] {\n return (\n !!models && models.length > 0 && models.every((m) => (m.default_weapon_ids?.length ?? 0) > 0)\n );\n}\n\n/**\n * Allocate `modelCount` models across the composition's model-types: each leader\n * is taken at its `min` (in declared order, never exceeding the remaining count),\n * then the non-leader \"bulk\" types absorb the rest — each its `min` first, then\n * any leftover to the bulk type with the largest `max`. Deterministic; mirrored\n * across implementations and pinned by the conformance corpus.\n */\nfunction allocateModels(\n models: readonly LoadoutModel[],\n modelCount: number,\n): { model: LoadoutModel; count: number }[] {\n const out = models.map((model) => ({ model, count: 0 }));\n let remaining = Math.max(0, modelCount);\n // Leaders first, at their declared minimum.\n for (const row of out) {\n if (!row.model.is_leader_model) continue;\n const c = Math.min(row.model.min ?? 0, remaining);\n row.count += c;\n remaining -= c;\n }\n const bulk = out.filter((r) => !r.model.is_leader_model);\n if (bulk.length === 0) {\n // No non-leader type: pour any remainder onto the leaders (largest max first).\n bulk.push(...out);\n }\n // Each bulk type takes its min, then the remainder lands on the largest-max type.\n for (const row of bulk) {\n const c = Math.min(row.model.min ?? 0, remaining);\n row.count += c;\n remaining -= c;\n }\n if (remaining > 0 && bulk.length > 0) {\n const sink = bulk.reduce((a, b) => ((b.model.max ?? 0) > (a.model.max ?? 0) ? b : a));\n sink.count += remaining;\n }\n return out;\n}\n\n/**\n * The base loadout counts: id → count across the unit with no swaps applied.\n * When the composition records per-model {@link LoadoutModel.default_weapon_ids},\n * those are authoritative — base = Σ over model-types of (allocated count ×\n * default weapons). Otherwise it falls back to {@link baseWeaponIds} × modelCount.\n */\nfunction baseCounts(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models?: readonly LoadoutModel[],\n): Map<string, number> {\n const counts = new Map<string, number>();\n if (hasRecordedDefaults(models)) {\n for (const { model, count } of allocateModels(models, modelCount)) {\n if (count === 0) continue;\n for (const id of model.default_weapon_ids ?? []) {\n counts.set(id, (counts.get(id) ?? 0) + count);\n }\n }\n return counts;\n }\n for (const id of baseWeaponIds(unit, options)) {\n counts.set(id, (counts.get(id) ?? 0) + modelCount);\n }\n return counts;\n}\n\n/**\n * The base loadout: every model in its out-of-the-box configuration, no swaps\n * applied. This is the legal default a freshly-added unit ships with. Reads the\n * composition's recorded `default_weapon_ids` when present (authoritative),\n * otherwise derives the base set. {@link maximalLoadout} starts from this set and\n * then applies every option at full cap.\n */\nexport function baseLoadout(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models?: readonly LoadoutModel[],\n): Loadout {\n return { counts: baseCounts(unit, modelCount, options, models) };\n}\n\n/**\n * The maximal loadout: every base weapon on every model, then each option\n * applied at its full {@link optionCap} (choices take their first branch). Swaps\n * move count from the replaced id to the added id; add-ons only add.\n */\nexport function maximalLoadout(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models?: readonly LoadoutModel[],\n): Loadout {\n const counts = baseCounts(unit, modelCount, options, models);\n for (const option of options) {\n const cap = optionCap(option, modelCount, models);\n if (cap === 0) continue;\n for (const id of option.replaces ?? []) {\n counts.set(id, (counts.get(id) ?? 0) - cap);\n }\n for (const id of addedIds(option)) {\n counts.set(id, (counts.get(id) ?? 0) + cap);\n }\n }\n clampFlatBudgets(unit, counts);\n // Drop any id that nets to zero so the loadout reads cleanly.\n for (const [id, n] of counts) if (n === 0) counts.delete(id);\n return { counts };\n}\n\n/**\n * Cap each weapon's count by any single-weapon flat {@link Unit.wargear_budgets}\n * (a \"this model takes at most N of weapon X\" line, modelled as `items` of length\n * 1 with `per_models === 0`). A weapon reachable through several swap slots — e.g.\n * a Knight Destrier whose chastiser gatling cannon AND frag bombard can each be\n * swapped for a bellatus reaper chainsword — would otherwise sum to an illegal\n * count; clamping here makes {@link maximalLoadout}/{@link weaponBounds} agree with\n * the same invalid-loadout prevention the editor enforces, so a count consumer\n * (the salvo calculator) never seeds an over-cap value. Shared (multi-item) and\n * ratio (`per_models > 0`) budgets stay policed by {@link budgetViolations}.\n */\nfunction clampFlatBudgets(unit: Unit, counts: Map<string, number>): void {\n for (const budget of unit.wargear_budgets ?? []) {\n if (budget.items.length !== 1 || budget.per_models !== 0) continue;\n const id = budget.items[0];\n const cur = counts.get(id);\n if (cur != null && cur > budget.count) counts.set(id, budget.count);\n }\n}\n\n/**\n * Inclusive valid count range for each weapon/wargear id, used to clamp a UI's\n * per-weapon inputs so invalid loadouts are unreachable. A base weapon ranges\n * `[modelCount − maxSwapsAway, modelCount]`; an optional (replacement) id ranges\n * `[0, Σ caps that add it]`.\n */\nexport function weaponBounds(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models?: readonly LoadoutModel[],\n): Map<string, WeaponBound> {\n const bounds = new Map<string, WeaponBound>();\n for (const [id, count] of baseCounts(unit, modelCount, options, models)) {\n bounds.set(id, { min: count, max: count });\n }\n for (const option of options) {\n const cap = optionCap(option, modelCount, models);\n for (const id of option.replaces ?? []) {\n const b = bounds.get(id) ?? { min: 0, max: 0 };\n bounds.set(id, { min: Math.max(0, b.min - cap), max: b.max });\n }\n // A replacement id can appear in multiple options / both choice branches;\n // sum the caps so its ceiling reflects every way to add it.\n const adds = new Set<string>();\n for (const id of option.replacement ?? []) adds.add(id);\n for (const group of option.replacement_choice ?? []) for (const id of group) adds.add(id);\n for (const id of adds) {\n const b = bounds.get(id) ?? { min: 0, max: 0 };\n bounds.set(id, { min: b.min, max: b.max + cap });\n }\n }\n // A single-weapon flat budget caps the weapon's ceiling regardless of how many\n // swap slots can add it (see {@link clampFlatBudgets}), so an editor/salvo input\n // clamped against these bounds can never reach an over-cap, illegal count.\n for (const budget of unit.wargear_budgets ?? []) {\n if (budget.items.length !== 1 || budget.per_models !== 0) continue;\n const b = bounds.get(budget.items[0]);\n if (b && b.max > budget.count) {\n bounds.set(budget.items[0], { min: Math.min(b.min, budget.count), max: budget.count });\n }\n }\n return bounds;\n}\n\n/**\n * Clamp a single weapon's requested count into its valid range. Ids with no\n * bound (not part of this unit's loadout) are returned unchanged but floored at\n * zero.\n */\nexport function clampWeaponCount(\n bounds: Map<string, WeaponBound>,\n id: string,\n requested: number,\n): number {\n const b = bounds.get(id);\n const n = Math.max(0, Math.floor(requested) || 0);\n if (!b) return n;\n return Math.min(b.max, Math.max(b.min, n));\n}\n\n/** One weapon line within a {@link LoadoutGroup}: entity id and its count *per model* in the group. */\nexport interface LoadoutGroupWeapon {\n id: string;\n count: number;\n}\n\n/**\n * A set of identically-equipped models within a unit: `count` models of model-type\n * `model_name`, each carrying `weapons` (counts are *per model*). Produced by\n * {@link groupLoadout} so an exporter can render \"Nx <model>: <loadout>\" lines\n * instead of one unit-wide weapon bag. Mirror of `crates/wh40kdc/src/data/loadout.rs`.\n */\nexport interface LoadoutGroup {\n model_name: string | null;\n count: number;\n weapons: LoadoutGroupWeapon[];\n}\n\nfunction toMultiset(ids: readonly string[]): Map<string, number> {\n const m = new Map<string, number>();\n for (const id of ids) m.set(id, (m.get(id) ?? 0) + 1);\n return m;\n}\n\n/** Group weapons in a stable, language-agnostic order (by id) for cross-impl parity. */\nfunction sortedGroupWeapons(m: Map<string, number>): LoadoutGroupWeapon[] {\n return [...m.entries()]\n .filter(([, c]) => c > 0)\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([id, count]) => ({ id, count }));\n}\n\n/**\n * Assign each composition row a model count, summing to `modelCount`. Rows are\n * seeded at their `min`; a row with a *distinctive* default weapon (one carried by\n * no other row) present in `counts` grows toward that weapon's implied count (this\n * recovers opt-in weapon-variant rows like a Deathwatch kill-team's plasma models,\n * which sit at `min: 0`); the leftover budget pours into the bulk row (the\n * non-leader, non-distinctive row with the most headroom). Deterministic.\n */\nfunction assignRowCounts(\n models: readonly LoadoutModel[],\n modelCount: number,\n counts: Map<string, number>,\n): number[] {\n const rowDefaults = models.map((m) => toMultiset(m.default_weapon_ids ?? []));\n const rowsWith = new Map<string, number>();\n for (const def of rowDefaults) for (const id of def.keys()) rowsWith.set(id, (rowsWith.get(id) ?? 0) + 1);\n const minOf = (i: number) => Math.max(0, models[i].min ?? 0);\n const maxOf = (i: number) => Math.max(minOf(i), models[i].max ?? minOf(i));\n\n const out = models.map((_, i) => minOf(i));\n let budget = modelCount - out.reduce((a, b) => a + b, 0);\n if (budget < 0) {\n // Σmin exceeds the unit's size: trim from the end, deterministically.\n let over = -budget;\n for (let i = models.length - 1; i >= 0 && over > 0; i--) {\n const cut = Math.min(over, out[i]);\n out[i] -= cut;\n over -= cut;\n }\n budget = 0;\n }\n\n const distinctive = models.map(() => false);\n for (let i = 0; i < models.length && budget > 0; i++) {\n let cap = Infinity;\n let saw = false;\n for (const [id, mult] of rowDefaults[i]) {\n if ((rowsWith.get(id) ?? 0) === 1 && mult > 0 && (counts.get(id) ?? 0) > 0) {\n saw = true;\n cap = Math.min(cap, Math.floor((counts.get(id) ?? 0) / mult));\n }\n }\n if (!saw) continue;\n distinctive[i] = true;\n const add = Math.max(0, Math.min(Math.min(cap, maxOf(i)) - out[i], budget));\n out[i] += add;\n budget -= add;\n }\n\n const headroom = (i: number) => maxOf(i) - out[i];\n while (budget > 0) {\n let pick = -1;\n for (let i = 0; i < models.length; i++) {\n if (headroom(i) <= 0 || models[i].is_leader_model || distinctive[i]) continue;\n if (pick < 0 || headroom(i) > headroom(pick)) pick = i;\n }\n if (pick < 0)\n for (let i = 0; i < models.length; i++) {\n if (headroom(i) <= 0) continue;\n if (pick < 0 || headroom(i) > headroom(pick)) pick = i;\n }\n if (pick < 0) break;\n const add = Math.min(budget, headroom(pick));\n out[pick] += add;\n budget -= add;\n }\n return out;\n}\n\n/** The bundles (added-id sets) an option offers: a fixed `replacement`, else each `replacement_choice` branch. */\nfunction optionBundles(option: WargearOption): string[][] {\n if (option.replacement) return [option.replacement];\n return (option.replacement_choice ?? []).map((b) => [...b]);\n}\n\n/**\n * A stable key for a weapon multiset: `count:id` parts in id order, joined by `|`.\n * Zero/negative entries are dropped. Identical multisets yield identical keys\n * across implementations, so it drives deterministic candidate ordering + grouping.\n */\nfunction multisetKey(m: Map<string, number>): string {\n return [...m.entries()]\n .filter(([, c]) => c > 0)\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([id, c]) => `${c}:${id}`)\n .join(\"|\");\n}\n\n/**\n * One legal single-model loadout for a composition row: the `weapons` a model can\n * carry, plus the global option indices whose application produced it (`usedOptions`,\n * each at most once) so the assignment search can charge per-option {@link optionCap}s.\n * `key` is the {@link multisetKey} of `weapons`, for deterministic ordering/grouping.\n */\ninterface RowCandidate {\n weapons: Map<string, number>;\n usedOptions: number[];\n key: string;\n}\n\n/**\n * Enumerate every legal single-model loadout for one composition row: start from the\n * row's base defaults and apply any *compatible* subset of the options that scope to\n * this row (unscoped options, or those whose `model_constraint.model_name` matches the\n * row). An option applies only when all its `replaces` weapons are currently present\n * (so a slot is swapped at most once) and is used at most once per model; each\n * `replacement_choice` branch is a distinct transformation. Reachable\n * `(weapons, usedOptions)` states are deduped and returned — bounded, since options and\n * their branches are few. Caps are *not* applied here; the assignment search charges\n * them globally, so two derivations of the same weapon-set with different option usage\n * are kept as distinct candidates (one may fit the caps where the other can't).\n */\nfunction enumerateRowCandidates(\n base: Map<string, number>,\n rowName: string | null,\n options: readonly WargearOption[],\n): RowCandidate[] {\n const applicable: number[] = [];\n for (let i = 0; i < options.length; i++) {\n const name = options[i].model_constraint?.model_name ?? null;\n if (name == null || name === rowName) applicable.push(i);\n }\n\n const stateKey = (w: Map<string, number>, used: readonly number[]) =>\n `${multisetKey(w)}#${[...used].sort((a, b) => a - b).join(\",\")}`;\n\n const result: RowCandidate[] = [];\n const seen = new Set<string>();\n const queue: { weapons: Map<string, number>; used: number[] }[] = [\n { weapons: new Map(base), used: [] },\n ];\n seen.add(stateKey(base, []));\n\n for (let head = 0; head < queue.length; head++) {\n const cur = queue[head];\n result.push({ weapons: cur.weapons, usedOptions: cur.used, key: multisetKey(cur.weapons) });\n for (const oi of applicable) {\n if (cur.used.includes(oi)) continue;\n const replaces = options[oi].replaces ?? [];\n if (!replaces.every((id) => (cur.weapons.get(id) ?? 0) >= 1)) continue;\n for (const bundle of optionBundles(options[oi])) {\n if (bundle.length === 0) continue;\n const w = new Map(cur.weapons);\n for (const id of replaces) w.set(id, (w.get(id) ?? 0) - 1);\n for (const id of bundle) w.set(id, (w.get(id) ?? 0) + 1);\n for (const [id, c] of [...w]) if (c <= 0) w.delete(id);\n const used = [...cur.used, oi].sort((a, b) => a - b);\n const k = stateKey(w, used);\n if (seen.has(k)) continue;\n seen.add(k);\n queue.push({ weapons: w, used });\n }\n }\n }\n return result;\n}\n\n/** A composition row prepared for the assignment search: its model count + legal loadouts. */\ninterface SolverRow {\n name: string | null;\n count: number;\n candidates: RowCandidate[];\n}\n\n/**\n * A complete, deterministic exact-cover search: distribute each row's models across its\n * candidate loadouts so the chosen weapons sum to `bag` exactly, never exceeding any\n * option's `optionCaps` usage. Rows are taken in order; within a row, candidates in their\n * (pre-sorted) order, trying the largest feasible count first; the residual bag and\n * per-option usage prune branches that can't complete. Returns the first solution found\n * (so identical inputs yield identical groupings everywhere) as flat per-candidate picks,\n * or `null` when no exact partition exists. Unlike a greedy peeler, it finds a valid\n * partition whenever one exists.\n */\nfunction solveAssignment(\n rows: readonly SolverRow[],\n bag: Map<string, number>,\n optionCaps: readonly number[],\n): { ri: number; name: string | null; weapons: Map<string, number>; count: number }[] | null {\n const residual = new Map(bag);\n const usage = optionCaps.map(() => 0);\n const picks: { ri: number; ci: number; count: number }[] = [];\n\n const assignRow = (ri: number): boolean => {\n if (ri === rows.length) {\n for (const c of residual.values()) if (c !== 0) return false;\n return true;\n }\n return distribute(ri, 0, rows[ri].count);\n };\n\n const distribute = (ri: number, ci: number, left: number): boolean => {\n const row = rows[ri];\n if (ci === row.candidates.length) return left === 0 && assignRow(ri + 1);\n const cand = row.candidates[ci];\n let hi = left;\n for (const [id, per] of cand.weapons) {\n if (per > 0) hi = Math.min(hi, Math.floor((residual.get(id) ?? 0) / per));\n }\n for (const oi of cand.usedOptions) hi = Math.min(hi, optionCaps[oi] - usage[oi]);\n hi = Math.max(0, hi);\n for (let take = hi; take >= 0; take--) {\n for (const [id, per] of cand.weapons) residual.set(id, (residual.get(id) ?? 0) - per * take);\n for (const oi of cand.usedOptions) usage[oi] += take;\n if (take > 0) picks.push({ ri, ci, count: take });\n if (distribute(ri, ci + 1, left - take)) return true;\n if (take > 0) picks.pop();\n for (const oi of cand.usedOptions) usage[oi] -= take;\n for (const [id, per] of cand.weapons) residual.set(id, (residual.get(id) ?? 0) + per * take);\n }\n return false;\n };\n\n if (!assignRow(0)) return null;\n return picks.map((p) => ({\n ri: p.ri,\n name: rows[p.ri].name,\n weapons: rows[p.ri].candidates[p.ci].weapons,\n count: p.count,\n }));\n}\n\n/**\n * Decompose a unit's flat loadout into per-model-type groups (e.g. \"1x Blood Herald:\n * …\" + \"6x Goremongers: …\" + \"1x Goremongers: …\"). The composition's\n * {@link assignRowCounts} fixes how many models each row fields; {@link solveAssignment}\n * then searches, completely and deterministically, for an assignment of each row's\n * models to legal per-model loadouts ({@link enumerateRowCandidates}) whose weapons sum\n * to `counts` exactly while respecting every option's {@link optionCap}. Returns `null`\n * only when no such exact partition exists (a single model, no recorded per-model\n * defaults, or a genuinely indivisible bag) — callers then omit `loadout_groups` and\n * renderers fall back to their unit-wide rendering unchanged. Mirror of\n * `crates/wh40kdc/src/data/loadout.rs`.\n */\nexport function groupLoadout(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n models: readonly LoadoutModel[] | undefined,\n counts: Map<string, number>,\n): LoadoutGroup[] | null {\n const n = Math.max(0, Math.floor(modelCount) || 0);\n // Single-model units and units without recorded per-model defaults render fine\n // from the aggregate; don't synthesise groups for them.\n if (n <= 1 || !hasRecordedDefaults(models)) return null;\n\n const bag = new Map<string, number>();\n for (const [id, c] of counts) if (c > 0) bag.set(id, c);\n\n const rowN = assignRowCounts(models, n, bag);\n const optionCaps = options.map((o) => optionCap(o, n, models));\n\n const rows: SolverRow[] = [];\n for (let i = 0; i < models.length; i++) {\n const k = rowN[i];\n if (k <= 0) continue;\n const base = toMultiset(models[i].default_weapon_ids ?? []);\n const candidates = enumerateRowCandidates(base, models[i].name ?? null, options).sort(\n (a, b) =>\n a.key.localeCompare(b.key) ||\n a.usedOptions.length - b.usedOptions.length ||\n a.usedOptions.join(\",\").localeCompare(b.usedOptions.join(\",\")),\n );\n rows.push({ name: models[i].name ?? null, count: k, candidates });\n }\n\n const solution = solveAssignment(rows, bag, optionCaps);\n if (!solution) return null;\n\n // Merge identical (model-type, loadout) picks, then order deterministically: by row\n // (leaders lead), then larger groups before smaller (bulk before variants), then by\n // canonical loadout key. Stable across implementations and independent of the order\n // the search happened to find picks in.\n const byGroup = new Map<\n string,\n { ri: number; name: string | null; weapons: Map<string, number>; count: number; key: string }\n >();\n for (const s of solution) {\n const key = multisetKey(s.weapons);\n const gkey = `${s.name ?? \"\"}##${key}`;\n const cur = byGroup.get(gkey);\n if (cur) cur.count += s.count;\n else byGroup.set(gkey, { ri: s.ri, name: s.name, weapons: s.weapons, count: s.count, key });\n }\n const live = [...byGroup.values()]\n .filter((g) => g.count > 0)\n .sort((a, b) => a.ri - b.ri || b.count - a.count || a.key.localeCompare(b.key));\n if (live.length === 0) return null;\n return live.map((g) => ({\n model_name: g.name,\n count: g.count,\n weapons: sortedGroupWeapons(g.weapons),\n }));\n}\n\n/** Report every weapon/wargear count that falls outside its valid range. */\nexport function validateLoadout(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n counts: Map<string, number>,\n models?: readonly LoadoutModel[],\n): Violation[] {\n const bounds = weaponBounds(unit, modelCount, options, models);\n const out: Violation[] = [];\n // Items governed by a shared-allowance budget are policed solely by\n // `budgetViolations` (the GW `limited_wargear_choice_set` cap). Their per-id\n // `weaponBounds` max is derived from the dump's cross-product loadout branches\n // — the unreliable signal the budget exists to replace (a weapon in several\n // option branches sums an inflated bound) — so skip the per-id check for them.\n const budgeted = new Set<string>();\n for (const b of unit.wargear_budgets ?? []) for (const id of b.items ?? []) budgeted.add(id);\n for (const [id, n] of counts) {\n if (budgeted.has(id)) continue;\n const b = bounds.get(id);\n if (!b) continue;\n if (n > b.max) {\n out.push({ id, code: \"exceeds-max\", message: `${id}: ${n} exceeds max ${b.max}` });\n } else if (n < b.min) {\n out.push({ id, code: \"below-min\", message: `${id}: ${n} below min ${b.min}` });\n }\n }\n out.push(...swapConflicts(unit, modelCount, options, counts, models));\n out.push(...budgetViolations(unit, modelCount, counts));\n // Deterministic order so the result is stable for cross-impl comparison.\n out.sort((a, b) => (a.id === b.id ? a.code.localeCompare(b.code) : a.id.localeCompare(b.id)));\n return out;\n}\n\n/**\n * Shared-allowance violations: each {@link Unit.wargear_budgets} entry lets its\n * listed items take at most `floor(modelCount * count / per_models)` copies between\n * them (a GW \"for every N models, X can take one of …\" line). Summing the final\n * counts is robust to *how* the items are equipped — unlike per-option caps, which\n * the dump's cross-product loadout branches defeat. The violation `id` is the\n * budget's sorted items joined by `+`, so it is stable and identifies the group.\n */\nfunction budgetViolations(\n unit: Unit,\n modelCount: number,\n counts: Map<string, number>,\n): Violation[] {\n const out: Violation[] = [];\n for (const budget of unit.wargear_budgets ?? []) {\n const items = budget.items ?? [];\n if (!items.length) continue;\n const used = items.reduce((sum, id) => sum + (counts.get(id) ?? 0), 0);\n // `per_models === 0` is a flat per-unit cap of `count`; otherwise a ratio.\n const cap = budget.per_models\n ? Math.floor((modelCount * budget.count) / budget.per_models)\n : budget.count;\n if (used > cap) {\n const id = [...items].sort().join(\"+\");\n const limit = budget.per_models\n ? `${budget.count} per ${budget.per_models} models`\n : `${budget.count} per unit`;\n out.push({\n id,\n code: \"exceeds-allowance\",\n message: `${id}: ${used} exceeds shared allowance ${cap} (${limit})`,\n });\n }\n }\n return out;\n}\n\n/** One discrete buildable squad size: a per-model count range, keyed by model name. */\nexport interface LoadoutTier {\n models: readonly { name?: string; min: number; max: number }[];\n}\n\n/**\n * Merge a tier's per-model count ranges onto the composition's `models[]` metadata\n * (default weapons, leader flag, base) by name, producing the {@link LoadoutModel}\n * list the loadout maths consume for that tier. A tier row with no matching base\n * model keeps just its name/min/max.\n */\nfunction tierModels(tier: LoadoutTier, base: readonly LoadoutModel[]): LoadoutModel[] {\n const byName = new Map(base.map((m) => [m.name, m]));\n return tier.models.map((tm) => {\n const b = tm.name != null ? byName.get(tm.name) : undefined;\n return { ...(b ?? {}), name: tm.name, min: tm.min, max: tm.max };\n });\n}\n\n/**\n * Whole-unit legality, tier-aware — the building block for a roster check. GW\n * models a datasheet's buildable sizes as discrete tiers (e.g. Neurogaunts: 1\n * Nodebeast + 10, or 1 + 11–20, or 2 + 20). A roster records only the *total*\n * `modelCount`, so we select every tier whose total range `[Σmin, Σmax]` contains\n * it and run {@link validateLoadout} against each tier's per-model allocation. The\n * unit is legal iff **some** containing tier validates clean — that's the faithful\n * \"does a legal build exist\" semantics, and it lets the maths allocate (say) 2\n * Nodebeasts at 22 models, which the single-envelope allocation cannot.\n *\n * Deterministic reporting (stable for cross-impl conformance): return the empty\n * result of the first clean tier (in tier order); else the violations of the first\n * containing tier. When `modelCount` falls in no tier, a single\n * `invalid-model-count` violation. When no `tiers` are supplied, falls back to a\n * plain {@link validateLoadout} (no size check) — preserving behaviour for units\n * the tier ingest didn't populate. Mirror of `crates/wh40kdc/src/data/loadout.rs`.\n */\nexport function checkUnitLegality(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n counts: Map<string, number>,\n models?: readonly LoadoutModel[],\n tiers?: readonly LoadoutTier[],\n): Violation[] {\n if (!tiers?.length) {\n return validateLoadout(unit, modelCount, options, counts, models);\n }\n const base = models ?? [];\n const candidates: LoadoutModel[][] = [];\n for (const tier of tiers) {\n const tm = tierModels(tier, base);\n const min = tm.reduce((s, m) => s + (m.min ?? 0), 0);\n const max = tm.reduce((s, m) => s + (m.max ?? 0), 0);\n if (modelCount >= min && modelCount <= max) candidates.push(tm);\n }\n if (candidates.length === 0) {\n return [\n {\n id: unit.id,\n code: \"invalid-model-count\",\n message: `${unit.id}: ${modelCount} models matches no composition tier`,\n },\n ];\n }\n let firstViolations: Violation[] | null = null;\n for (const tm of candidates) {\n const violations = validateLoadout(unit, modelCount, options, counts, tm);\n if (violations.length === 0) return [];\n if (firstViolations === null) firstViolations = violations;\n }\n return firstViolations ?? [];\n}\n\n/**\n * Swap-conservation violations the independent per-id {@link weaponBounds} can't\n * see: a model's replaceable slot holds the base weapon OR one of its swap\n * replacements, never both, so `count(base) + Σ count(its replacements)` cannot\n * exceed `modelCount`. Without this, keeping a base weapon *and* adding its\n * replacement passes every per-id bound while being illegal (e.g. War Dog\n * Brigand with both diabolus heavy stubber and havoc multi-launcher).\n *\n * Enforced only for the unambiguous shape — a base weapon swapped out by plain\n * (non-choice) options that replace it alone, whose replacement ids are unique\n * within this unit's option set and aren't themselves base weapons. Choice /\n * multi-replace / shared-replacement options can't be attributed to one slot\n * pool, so they stay on the looser per-id bounds rather than risk a false\n * positive. Mirror of `crates/wh40kdc/src/data/loadout.rs`.\n */\nfunction swapConflicts(\n unit: Unit,\n modelCount: number,\n options: readonly WargearOption[],\n counts: Map<string, number>,\n models?: readonly LoadoutModel[],\n): Violation[] {\n const baseMap = baseCounts(unit, modelCount, options, models);\n const baseIds = new Set(baseMap.keys());\n const addedBy = new Map<string, number>();\n for (const o of options) {\n for (const id of o.replacement ?? []) addedBy.set(id, (addedBy.get(id) ?? 0) + 1);\n for (const g of o.replacement_choice ?? []) {\n for (const id of g) addedBy.set(id, (addedBy.get(id) ?? 0) + 1);\n }\n }\n const out: Violation[] = [];\n for (const base of baseIds) {\n const cleanAdds = new Set<string>();\n let messy = false;\n for (const o of options) {\n const replaces: readonly string[] = o.replaces ?? [];\n if (!replaces.includes(base)) continue;\n // Only a plain, single-target swap of this exact base weapon is unambiguous.\n if (replaces.length !== 1 || (o.replacement_choice?.length ?? 0) > 0) {\n messy = true;\n break;\n }\n for (const b of o.replacement ?? []) {\n if (baseIds.has(b) || (addedBy.get(b) ?? 0) > 1) {\n messy = true;\n break;\n }\n cleanAdds.add(b);\n }\n if (messy) break;\n }\n if (messy || cleanAdds.size === 0) continue;\n // The slot can hold at most as many weapons as there are models carrying this\n // base weapon by default — its base count (modelCount when not per-model).\n const cap = baseMap.get(base) ?? modelCount;\n let total = counts.get(base) ?? 0;\n for (const b of cleanAdds) total += counts.get(b) ?? 0;\n if (total > cap) {\n out.push({\n id: base,\n code: \"swap-conflict\",\n message: `${base} and its swap replacement(s) total ${total}, exceeding ${cap} (a model takes the base weapon or a swap, not both)`,\n });\n }\n }\n return out;\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge an imported {@link Roster} to a {@link ShareList} for share-token
|
|
3
|
+
* encoding.
|
|
4
|
+
*
|
|
5
|
+
* `tryImportRoster` yields a lossless {@link Roster} whose refs may be
|
|
6
|
+
* unresolved (`id === null`) and whose resolved ids may still predate the
|
|
7
|
+
* embedded share registry. {@link encodeShareList}, by contrast, throws on any
|
|
8
|
+
* id it can't index. {@link rosterToShareList} is the best-effort adapter
|
|
9
|
+
* between them: it emits a {@link ShareList} carrying only resolved,
|
|
10
|
+
* registry-known ids, dropping anything the token can't represent.
|
|
11
|
+
*
|
|
12
|
+
* It never throws. Callers decide what a partial mapping means: compare
|
|
13
|
+
* `result.units.length` to `roster.units.length` to detect dropped units, then
|
|
14
|
+
* encode inside a try/catch (a resolved id absent from the registry still
|
|
15
|
+
* throws at encode time) — a list earns a share link only when it maps and
|
|
16
|
+
* encodes in full.
|
|
17
|
+
*/
|
|
18
|
+
import type { Roster } from "../import/types.js";
|
|
19
|
+
import type { ShareList } from "./codec.js";
|
|
20
|
+
import type { ShareRegistryIndex } from "./registry.js";
|
|
21
|
+
/**
|
|
22
|
+
* Convert a {@link Roster} to a {@link ShareList}, keeping only the ids the
|
|
23
|
+
* given registry knows. Units whose datasheet is unresolved or unknown to the
|
|
24
|
+
* registry are dropped; `attachedToOrdinal` indexes into the *emitted* units.
|
|
25
|
+
*/
|
|
26
|
+
export declare function rosterToShareList(roster: Roster, registry: ShareRegistryIndex): ShareList;
|
|
27
|
+
//# sourceMappingURL=from-roster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from-roster.d.ts","sourceRoot":"","sources":["../../src/share/from-roster.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAgC,MAAM,YAAY,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAOxD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,SAAS,CAoDzF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** A bodyguard match key: prefer the resolved id, fall back to the raw name. */
|
|
2
|
+
function unitKey(u) {
|
|
3
|
+
return u.ref.id ?? u.ref.raw_name;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Convert a {@link Roster} to a {@link ShareList}, keeping only the ids the
|
|
7
|
+
* given registry knows. Units whose datasheet is unresolved or unknown to the
|
|
8
|
+
* registry are dropped; `attachedToOrdinal` indexes into the *emitted* units.
|
|
9
|
+
*/
|
|
10
|
+
export function rosterToShareList(roster, registry) {
|
|
11
|
+
const known = (kind, id) => id !== null && registry.index(kind, id) !== undefined;
|
|
12
|
+
// The units that survive into the token, in roster order. Everything below
|
|
13
|
+
// indexes against this list — never `roster.units` — so a dropped unit can't
|
|
14
|
+
// shift a leader's `attachedToOrdinal`.
|
|
15
|
+
const emitted = roster.units.filter((u) => known("unit", u.ref.id));
|
|
16
|
+
const ordinalByKey = new Map();
|
|
17
|
+
emitted.forEach((u, i) => {
|
|
18
|
+
const k = unitKey(u);
|
|
19
|
+
if (!ordinalByKey.has(k))
|
|
20
|
+
ordinalByKey.set(k, i);
|
|
21
|
+
});
|
|
22
|
+
const units = emitted.map((u) => {
|
|
23
|
+
const loadout = u.wargear
|
|
24
|
+
.filter((w) => known("wargear", w.ref.id))
|
|
25
|
+
.map((w) => [w.ref.id, w.count]);
|
|
26
|
+
const la = u.leader_attachment;
|
|
27
|
+
const attachedToOrdinal = la
|
|
28
|
+
? (ordinalByKey.get(la.bodyguard_ref.id ?? la.bodyguard_ref.raw_name) ?? null)
|
|
29
|
+
: null;
|
|
30
|
+
return {
|
|
31
|
+
datasheetId: u.ref.id,
|
|
32
|
+
modelCount: u.model_count,
|
|
33
|
+
isWarlord: u.is_warlord,
|
|
34
|
+
enhancementId: known("enhancement", u.enhancement?.id ?? null)
|
|
35
|
+
? u.enhancement?.id
|
|
36
|
+
: null,
|
|
37
|
+
// The importer doesn't track allied sourcing or detachment-keyword grants.
|
|
38
|
+
allyFactionId: null,
|
|
39
|
+
allyRuleId: null,
|
|
40
|
+
attachedToOrdinal,
|
|
41
|
+
grants: [],
|
|
42
|
+
loadout,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
name: roster.name,
|
|
47
|
+
factionId: known("faction", roster.faction_id) ? roster.faction_id : null,
|
|
48
|
+
detachmentIds: roster.detachments
|
|
49
|
+
.map((d) => d.ref.id)
|
|
50
|
+
.filter((id) => known("detachment", id)),
|
|
51
|
+
// The codec defaults an absent battle size to strike-force; ATC lists are
|
|
52
|
+
// strike-force, so a null import (the source didn't encode one) maps there.
|
|
53
|
+
battleSize: roster.battle_size ?? "strike-force",
|
|
54
|
+
disposition: known("disposition", roster.force_disposition) ? roster.force_disposition : null,
|
|
55
|
+
units,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=from-roster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"from-roster.js","sourceRoot":"","sources":["../../src/share/from-roster.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bridge an imported {@link Roster} to a {@link ShareList} for share-token\n * encoding.\n *\n * `tryImportRoster` yields a lossless {@link Roster} whose refs may be\n * unresolved (`id === null`) and whose resolved ids may still predate the\n * embedded share registry. {@link encodeShareList}, by contrast, throws on any\n * id it can't index. {@link rosterToShareList} is the best-effort adapter\n * between them: it emits a {@link ShareList} carrying only resolved,\n * registry-known ids, dropping anything the token can't represent.\n *\n * It never throws. Callers decide what a partial mapping means: compare\n * `result.units.length` to `roster.units.length` to detect dropped units, then\n * encode inside a try/catch (a resolved id absent from the registry still\n * throws at encode time) — a list earns a share link only when it maps and\n * encodes in full.\n */\nimport type { Roster, RosterUnit } from \"../import/types.js\";\nimport type { ShareList, ShareLoadoutEntry, ShareUnit } from \"./codec.js\";\nimport type { ShareRegistryIndex } from \"./registry.js\";\n\n/** A bodyguard match key: prefer the resolved id, fall back to the raw name. */\nfunction unitKey(u: RosterUnit): string {\n return u.ref.id ?? u.ref.raw_name;\n}\n\n/**\n * Convert a {@link Roster} to a {@link ShareList}, keeping only the ids the\n * given registry knows. Units whose datasheet is unresolved or unknown to the\n * registry are dropped; `attachedToOrdinal` indexes into the *emitted* units.\n */\nexport function rosterToShareList(roster: Roster, registry: ShareRegistryIndex): ShareList {\n const known = (kind: Parameters<ShareRegistryIndex[\"index\"]>[0], id: string | null): boolean =>\n id !== null && registry.index(kind, id) !== undefined;\n\n // The units that survive into the token, in roster order. Everything below\n // indexes against this list — never `roster.units` — so a dropped unit can't\n // shift a leader's `attachedToOrdinal`.\n const emitted = roster.units.filter((u) => known(\"unit\", u.ref.id));\n const ordinalByKey = new Map<string, number>();\n emitted.forEach((u, i) => {\n const k = unitKey(u);\n if (!ordinalByKey.has(k)) ordinalByKey.set(k, i);\n });\n\n const units: ShareUnit[] = emitted.map((u) => {\n const loadout: ShareLoadoutEntry[] = u.wargear\n .filter((w) => known(\"wargear\", w.ref.id))\n .map((w) => [w.ref.id as string, w.count]);\n\n const la = u.leader_attachment;\n const attachedToOrdinal = la\n ? (ordinalByKey.get(la.bodyguard_ref.id ?? la.bodyguard_ref.raw_name) ?? null)\n : null;\n\n return {\n datasheetId: u.ref.id as string,\n modelCount: u.model_count,\n isWarlord: u.is_warlord,\n enhancementId: known(\"enhancement\", u.enhancement?.id ?? null)\n ? (u.enhancement?.id as string)\n : null,\n // The importer doesn't track allied sourcing or detachment-keyword grants.\n allyFactionId: null,\n allyRuleId: null,\n attachedToOrdinal,\n grants: [],\n loadout,\n };\n });\n\n return {\n name: roster.name,\n factionId: known(\"faction\", roster.faction_id) ? roster.faction_id : null,\n detachmentIds: roster.detachments\n .map((d) => d.ref.id)\n .filter((id): id is string => known(\"detachment\", id)),\n // The codec defaults an absent battle size to strike-force; ATC lists are\n // strike-force, so a null import (the source didn't encode one) maps there.\n battleSize: roster.battle_size ?? \"strike-force\",\n disposition: known(\"disposition\", roster.force_disposition) ? roster.force_disposition : null,\n units,\n };\n}\n"]}
|
package/dist/share/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* @packageDocumentation
|
|
17
17
|
*/
|
|
18
18
|
import { type DecodeResult, type ShareList } from "./codec.js";
|
|
19
|
+
import type { Roster } from "../import/types.js";
|
|
19
20
|
export { SHARE_FORMAT_VERSION } from "./codec.js";
|
|
20
21
|
export type { DecodeResult, ShareBattleSize, ShareList, ShareLoadoutEntry, ShareUnit, } from "./codec.js";
|
|
21
22
|
export { SHARE_KINDS } from "./registry.js";
|
|
@@ -27,4 +28,11 @@ export declare const shareRegistryVersion: number;
|
|
|
27
28
|
export declare function encodeShareToken(list: ShareList): string;
|
|
28
29
|
/** Decode a `share-v1` token using the embedded registry. */
|
|
29
30
|
export declare function decodeShareToken(token: string): DecodeResult;
|
|
31
|
+
/**
|
|
32
|
+
* Adapt an imported {@link Roster} to a {@link ShareList} against the embedded
|
|
33
|
+
* registry, keeping only resolved, registry-known ids. Pair with
|
|
34
|
+
* {@link encodeShareToken} (inside a try/catch) to turn a parsed list into a
|
|
35
|
+
* share link; see {@link rosterToShareListImpl} for the partial-mapping contract.
|
|
36
|
+
*/
|
|
37
|
+
export declare function rosterToShareList(roster: Roster): ShareList;
|
|
30
38
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/share/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAoC,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAIjG,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EACV,YAAY,EACZ,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAKnD,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,EAAE,MAA+B,CAAC;AAEnE,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAExD;AAED,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAE5D"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/share/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAoC,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAIjG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EACV,YAAY,EACZ,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAKnD,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,EAAE,MAA+B,CAAC;AAEnE,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAExD;AAED,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAE3D"}
|
package/dist/share/index.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* @packageDocumentation
|
|
17
17
|
*/
|
|
18
18
|
import { decodeShareList, encodeShareList } from "./codec.js";
|
|
19
|
+
import { rosterToShareList as rosterToShareListImpl } from "./from-roster.js";
|
|
19
20
|
import { SHARE_REGISTRY } from "./registry.generated.js";
|
|
20
21
|
import { ShareRegistryIndex } from "./registry.js";
|
|
21
22
|
export { SHARE_FORMAT_VERSION } from "./codec.js";
|
|
@@ -33,4 +34,13 @@ export function encodeShareToken(list) {
|
|
|
33
34
|
export function decodeShareToken(token) {
|
|
34
35
|
return decodeShareList(token, embeddedIndex);
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Adapt an imported {@link Roster} to a {@link ShareList} against the embedded
|
|
39
|
+
* registry, keeping only resolved, registry-known ids. Pair with
|
|
40
|
+
* {@link encodeShareToken} (inside a try/catch) to turn a parsed list into a
|
|
41
|
+
* share link; see {@link rosterToShareListImpl} for the partial-mapping contract.
|
|
42
|
+
*/
|
|
43
|
+
export function rosterToShareList(roster) {
|
|
44
|
+
return rosterToShareListImpl(roster, embeddedIndex);
|
|
45
|
+
}
|
|
36
46
|
//# sourceMappingURL=index.js.map
|
package/dist/share/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/share/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,eAAe,EAAE,eAAe,EAAqC,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/share/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,eAAe,EAAE,eAAe,EAAqC,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAQlD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,oEAAoE;AACpE,MAAM,aAAa,GAAG,IAAI,kBAAkB,CAAC,cAAc,CAAC,CAAC;AAE7D,iFAAiF;AACjF,MAAM,CAAC,MAAM,oBAAoB,GAAW,cAAc,CAAC,OAAO,CAAC;AAEnE,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAC,IAAe;IAC9C,OAAO,eAAe,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;AAC9C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,eAAe,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AACtD,CAAC","sourcesContent":["/**\n * Compact, URL-safe list sharing for the 40kdc list builder.\n *\n * {@link encodeShareToken} packs a {@link ShareList} into a `share-v1` token\n * (registry-indexed varints, base64url); {@link decodeShareToken} reverses it\n * against the embedded {@link SHARE_REGISTRY}. The wire format and registry are\n * documented in `docs/share-token.md` so other tools can interoperate.\n *\n * @example\n * import { encodeShareToken, decodeShareToken } from \"@alpaca-software/40kdc-data\";\n *\n * const token = encodeShareToken(myList); // → \"AQ...\" (URL-safe)\n * const result = decodeShareToken(token);\n * if (result.ok) useList(result.list);\n *\n * @packageDocumentation\n */\nimport { decodeShareList, encodeShareList, type DecodeResult, type ShareList } from \"./codec.js\";\nimport { rosterToShareList as rosterToShareListImpl } from \"./from-roster.js\";\nimport { SHARE_REGISTRY } from \"./registry.generated.js\";\nimport { ShareRegistryIndex } from \"./registry.js\";\nimport type { Roster } from \"../import/types.js\";\n\nexport { SHARE_FORMAT_VERSION } from \"./codec.js\";\nexport type {\n DecodeResult,\n ShareBattleSize,\n ShareList,\n ShareLoadoutEntry,\n ShareUnit,\n} from \"./codec.js\";\nexport { SHARE_KINDS } from \"./registry.js\";\nexport type { ShareKind, ShareRegistry } from \"./registry.js\";\nexport { ShareRegistryIndex } from \"./registry.js\";\n\n/** The id index built once from the package's embedded registry. */\nconst embeddedIndex = new ShareRegistryIndex(SHARE_REGISTRY);\n\n/** Registry version this package embeds (stamped into every token it writes). */\nexport const shareRegistryVersion: number = SHARE_REGISTRY.version;\n\n/** Encode a list into a URL-safe `share-v1` token using the embedded registry. */\nexport function encodeShareToken(list: ShareList): string {\n return encodeShareList(list, embeddedIndex);\n}\n\n/** Decode a `share-v1` token using the embedded registry. */\nexport function decodeShareToken(token: string): DecodeResult {\n return decodeShareList(token, embeddedIndex);\n}\n\n/**\n * Adapt an imported {@link Roster} to a {@link ShareList} against the embedded\n * registry, keeping only resolved, registry-known ids. Pair with\n * {@link encodeShareToken} (inside a try/catch) to turn a parsed list into a\n * share link; see {@link rosterToShareListImpl} for the partial-mapping contract.\n */\nexport function rosterToShareList(roster: Roster): ShareList {\n return rosterToShareListImpl(roster, embeddedIndex);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpaca-software/40kdc-data",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The 40kdc Warhammer 40K dataset behind a linked, typed API — find units, follow them to their weapons, abilities, phases, and factions. Also validates data against the canonical JSON Schemas.",
|
|
6
6
|
"keywords": [
|