@alpaca-software/40kdc-data 1.0.9 → 1.0.10

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.
@@ -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: …"), reusing {@link allocateModels}'s
98
- * partition and the per-model-type option scoping. Returns `null` when the
99
- * decomposition is not *exact* a single model, no recorded per-model defaults, or
100
- * leftover counts no swap explains so callers omit `loadout_groups` and renderers
101
- * fall back to their unit-wide rendering unchanged. Mirror of
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":"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"}
1
+ {"version":3,"file":"loadout.d.ts","sourceRoot":"","sources":["../../src/data/loadout.ts"],"names":[],"mappings":"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"}
@@ -351,63 +351,147 @@ function optionBundles(option) {
351
351
  return (option.replacement_choice ?? []).map((b) => [...b]);
352
352
  }
353
353
  /**
354
- * Explain the leftover weapon counts as option swaps, peeling a variant sub-group
355
- * off the base group of the model-type the option is scoped to. Each peel moves
356
- * `take` models to `(base replaces + bundle)` and updates `remaining` (added ids
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 applySwaps(groups, models, options, modelCount, remaining) {
361
- for (const option of options) {
362
- const cap = optionCap(option, modelCount, models);
363
- if (cap <= 0)
364
- continue;
365
- const replaces = option.replaces ?? [];
366
- const scopedName = option.model_constraint?.model_name ?? null;
367
- for (const bundle of optionBundles(option)) {
368
- if (bundle.length === 0)
369
- continue;
370
- const addM = toMultiset(bundle);
371
- let k = cap;
372
- for (const [id, mult] of addM)
373
- k = Math.min(k, Math.floor(Math.max(0, remaining.get(id) ?? 0) / mult));
374
- if (k <= 0)
375
- continue;
376
- // Source: a group of the scoped model-type still holding every replaced weapon
377
- // (fall back to any such holder when the scoped name matches nothing).
378
- const holds = (g) => g.count > 0 && replaces.every((id) => (g.weapons.get(id) ?? 0) >= 1);
379
- let src = groups.find((g) => holds(g) && (scopedName == null || g.model_name === scopedName));
380
- if (!src && scopedName != null)
381
- src = groups.find(holds);
382
- if (!src)
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 take = Math.min(k, src.count);
385
- if (take <= 0)
397
+ const replaces = options[oi].replaces ?? [];
398
+ if (!replaces.every((id) => (cur.weapons.get(id) ?? 0) >= 1))
386
399
  continue;
387
- const w = new Map(src.weapons);
388
- for (const id of replaces)
389
- w.set(id, (w.get(id) ?? 0) - 1);
390
- for (const [id, mult] of addM)
391
- w.set(id, (w.get(id) ?? 0) + mult);
392
- for (const [id, c] of [...w])
393
- if (c <= 0)
394
- w.delete(id);
395
- src.count -= take;
396
- groups.push({ model_name: src.model_name, count: take, weapons: w });
397
- for (const [id, mult] of addM)
398
- remaining.set(id, (remaining.get(id) ?? 0) - mult * take);
399
- for (const id of replaces)
400
- remaining.set(id, (remaining.get(id) ?? 0) + take);
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: …"), reusing {@link allocateModels}'s
407
- * partition and the per-model-type option scoping. Returns `null` when the
408
- * decomposition is not *exact* a single model, no recorded per-model defaults, or
409
- * leftover counts no swap explains so callers omit `loadout_groups` and renderers
410
- * fall back to their unit-wide rendering unchanged. Mirror of
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 remaining = new Map();
503
+ const bag = new Map();
420
504
  for (const [id, c] of counts)
421
505
  if (c > 0)
422
- remaining.set(id, c);
423
- const rowN = assignRowCounts(models, n, remaining);
424
- const groups = [];
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 def = toMultiset(models[i].default_weapon_ids ?? []);
430
- for (const [id, c] of def)
431
- remaining.set(id, (remaining.get(id) ?? 0) - c * k);
432
- groups.push({ model_name: models[i].name ?? null, count: k, weapons: def });
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
- applySwaps(groups, models, options, n, remaining);
435
- // Exact only: any unexplained surplus or deficit means we can't faithfully
436
- // partition the bag bail so the renderer keeps its existing fallback.
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.model_name,
543
+ model_name: g.name,
445
544
  count: g.count,
446
545
  weapons: sortedGroupWeapons(g.weapons),
447
546
  }));
@@ -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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpaca-software/40kdc-data",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
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": [