@alpaca-software/40kdc-data 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/bundle-schemas.d.ts.map +1 -1
- package/dist/bundle-schemas.js +17 -0
- package/dist/bundle-schemas.js.map +1 -1
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -1
- package/dist/codegen-data.js +1 -0
- package/dist/codegen-data.js.map +1 -1
- package/dist/commands/populate-base-sizes.d.ts +2 -0
- package/dist/commands/populate-base-sizes.d.ts.map +1 -0
- package/dist/commands/populate-base-sizes.js +158 -0
- package/dist/commands/populate-base-sizes.js.map +1 -0
- package/dist/convert-faction.d.ts +3 -1
- package/dist/convert-faction.d.ts.map +1 -1
- package/dist/convert-faction.js +49 -16
- package/dist/convert-faction.js.map +1 -1
- package/dist/converters/base-size-bridge.d.ts +122 -0
- package/dist/converters/base-size-bridge.d.ts.map +1 -0
- package/dist/converters/base-size-bridge.js +198 -0
- package/dist/converters/base-size-bridge.js.map +1 -0
- package/dist/converters/base-size-guide-extract.d.ts +11 -0
- package/dist/converters/base-size-guide-extract.d.ts.map +1 -0
- package/dist/converters/base-size-guide-extract.js +59 -0
- package/dist/converters/base-size-guide-extract.js.map +1 -0
- package/dist/converters/option-bridge.d.ts +36 -0
- package/dist/converters/option-bridge.d.ts.map +1 -0
- package/dist/converters/option-bridge.js +72 -0
- package/dist/converters/option-bridge.js.map +1 -0
- package/dist/converters/option-parser.d.ts +56 -0
- package/dist/converters/option-parser.d.ts.map +1 -0
- package/dist/converters/option-parser.js +209 -0
- package/dist/converters/option-parser.js.map +1 -0
- package/dist/converters/wargear-options.d.ts +55 -0
- package/dist/converters/wargear-options.d.ts.map +1 -0
- package/dist/converters/wargear-options.js +187 -0
- package/dist/converters/wargear-options.js.map +1 -0
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/dataset.d.ts +9 -1
- package/dist/data/dataset.d.ts.map +1 -1
- package/dist/data/dataset.js +14 -0
- package/dist/data/dataset.js.map +1 -1
- package/dist/data/entities.d.ts +3 -1
- package/dist/data/entities.d.ts.map +1 -1
- package/dist/data/entities.js +4 -0
- package/dist/data/entities.js.map +1 -1
- package/dist/data/index.d.ts +4 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +4 -0
- package/dist/data/index.js.map +1 -1
- package/dist/data/loadout.d.ts +60 -0
- package/dist/data/loadout.d.ts.map +1 -0
- package/dist/data/loadout.js +135 -0
- package/dist/data/loadout.js.map +1 -0
- package/dist/data/types.d.ts +3 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/data/types.js +1 -0
- package/dist/data/types.js.map +1 -1
- package/dist/export/rosterizer.js +1 -1
- package/dist/export/rosterizer.js.map +1 -1
- package/dist/gen-conformance.js +20 -0
- package/dist/gen-conformance.js.map +1 -1
- package/dist/generated.d.ts +112 -55
- package/dist/generated.d.ts.map +1 -1
- package/dist/generated.js.map +1 -1
- package/dist/import/rosterizer.d.ts +1 -1
- package/dist/import/rosterizer.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts +16 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +54 -0
- package/dist/runner.js.map +1 -1
- package/dist/translate/condition.d.ts.map +1 -1
- package/dist/translate/condition.js +4 -0
- package/dist/translate/condition.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +13 -5
- package/dist/validate.js.map +1 -1
- package/package.json +4 -4
- package/schemas/$defs/common.schema.json +14 -0
- package/schemas/core/unit-composition.schema.json +5 -1
- package/schemas/core/unit.schema.json +2 -10
- package/schemas/core/wargear-option.schema.json +32 -6
- package/schemas/core/wargear.schema.json +24 -0
- package/schemas/enrichment/ability-dsl/condition.schema.json +3 -2
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The maximum number of models that may take `option` in a unit of `modelCount`
|
|
3
|
+
* models: `any_number` → all models; else `per_n_models` → floor(n / per); else
|
|
4
|
+
* `max_count ?? 1`; then clamped by `max_count` when set. A null constraint is
|
|
5
|
+
* treated as unrestricted (every model). Never negative.
|
|
6
|
+
*/
|
|
7
|
+
export function optionCap(option, modelCount) {
|
|
8
|
+
const c = option.model_constraint;
|
|
9
|
+
if (!c)
|
|
10
|
+
return Math.max(0, modelCount);
|
|
11
|
+
let cap;
|
|
12
|
+
if (c.any_number)
|
|
13
|
+
cap = modelCount;
|
|
14
|
+
else if (c.per_n_models)
|
|
15
|
+
cap = Math.floor(modelCount / c.per_n_models);
|
|
16
|
+
else
|
|
17
|
+
cap = c.max_count ?? 1;
|
|
18
|
+
if (c.max_count != null)
|
|
19
|
+
cap = Math.min(cap, c.max_count);
|
|
20
|
+
return Math.max(0, cap);
|
|
21
|
+
}
|
|
22
|
+
/** The ids a single option can add, given the chosen choice branch (default 0). */
|
|
23
|
+
function addedIds(option, choiceIndex = 0) {
|
|
24
|
+
if (option.replacement)
|
|
25
|
+
return option.replacement;
|
|
26
|
+
return option.replacement_choice?.[choiceIndex] ?? [];
|
|
27
|
+
}
|
|
28
|
+
/** Every id that any option can add — across all choice branches. */
|
|
29
|
+
function allReplacementIds(options) {
|
|
30
|
+
const out = new Set();
|
|
31
|
+
for (const o of options) {
|
|
32
|
+
for (const id of o.replacement ?? [])
|
|
33
|
+
out.add(id);
|
|
34
|
+
for (const group of o.replacement_choice ?? [])
|
|
35
|
+
for (const id of group)
|
|
36
|
+
out.add(id);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
/** Base (always-carried) weapon ids: in `weapon_ids`, never a replacement. */
|
|
41
|
+
function baseWeaponIds(unit, options) {
|
|
42
|
+
const replacements = allReplacementIds(options);
|
|
43
|
+
return (unit.weapon_ids ?? []).filter((id) => !replacements.has(id));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The maximal loadout: every base weapon on every model, then each option
|
|
47
|
+
* applied at its full {@link optionCap} (choices take their first branch). Swaps
|
|
48
|
+
* move count from the replaced id to the added id; add-ons only add.
|
|
49
|
+
*/
|
|
50
|
+
export function maximalLoadout(unit, modelCount, options) {
|
|
51
|
+
const counts = new Map();
|
|
52
|
+
for (const id of baseWeaponIds(unit, options)) {
|
|
53
|
+
counts.set(id, (counts.get(id) ?? 0) + modelCount);
|
|
54
|
+
}
|
|
55
|
+
for (const option of options) {
|
|
56
|
+
const cap = optionCap(option, modelCount);
|
|
57
|
+
if (cap === 0)
|
|
58
|
+
continue;
|
|
59
|
+
for (const id of option.replaces ?? []) {
|
|
60
|
+
counts.set(id, (counts.get(id) ?? 0) - cap);
|
|
61
|
+
}
|
|
62
|
+
for (const id of addedIds(option)) {
|
|
63
|
+
counts.set(id, (counts.get(id) ?? 0) + cap);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Drop any id that nets to zero so the loadout reads cleanly.
|
|
67
|
+
for (const [id, n] of counts)
|
|
68
|
+
if (n === 0)
|
|
69
|
+
counts.delete(id);
|
|
70
|
+
return { counts };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Inclusive valid count range for each weapon/wargear id, used to clamp a UI's
|
|
74
|
+
* per-weapon inputs so invalid loadouts are unreachable. A base weapon ranges
|
|
75
|
+
* `[modelCount − maxSwapsAway, modelCount]`; an optional (replacement) id ranges
|
|
76
|
+
* `[0, Σ caps that add it]`.
|
|
77
|
+
*/
|
|
78
|
+
export function weaponBounds(unit, modelCount, options) {
|
|
79
|
+
const bounds = new Map();
|
|
80
|
+
for (const id of baseWeaponIds(unit, options)) {
|
|
81
|
+
bounds.set(id, { min: modelCount, max: modelCount });
|
|
82
|
+
}
|
|
83
|
+
for (const option of options) {
|
|
84
|
+
const cap = optionCap(option, modelCount);
|
|
85
|
+
for (const id of option.replaces ?? []) {
|
|
86
|
+
const b = bounds.get(id) ?? { min: 0, max: 0 };
|
|
87
|
+
bounds.set(id, { min: Math.max(0, b.min - cap), max: b.max });
|
|
88
|
+
}
|
|
89
|
+
// A replacement id can appear in multiple options / both choice branches;
|
|
90
|
+
// sum the caps so its ceiling reflects every way to add it.
|
|
91
|
+
const adds = new Set();
|
|
92
|
+
for (const id of option.replacement ?? [])
|
|
93
|
+
adds.add(id);
|
|
94
|
+
for (const group of option.replacement_choice ?? [])
|
|
95
|
+
for (const id of group)
|
|
96
|
+
adds.add(id);
|
|
97
|
+
for (const id of adds) {
|
|
98
|
+
const b = bounds.get(id) ?? { min: 0, max: 0 };
|
|
99
|
+
bounds.set(id, { min: b.min, max: b.max + cap });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return bounds;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Clamp a single weapon's requested count into its valid range. Ids with no
|
|
106
|
+
* bound (not part of this unit's loadout) are returned unchanged but floored at
|
|
107
|
+
* zero.
|
|
108
|
+
*/
|
|
109
|
+
export function clampWeaponCount(bounds, id, requested) {
|
|
110
|
+
const b = bounds.get(id);
|
|
111
|
+
const n = Math.max(0, Math.floor(requested) || 0);
|
|
112
|
+
if (!b)
|
|
113
|
+
return n;
|
|
114
|
+
return Math.min(b.max, Math.max(b.min, n));
|
|
115
|
+
}
|
|
116
|
+
/** Report every weapon/wargear count that falls outside its valid range. */
|
|
117
|
+
export function validateLoadout(unit, modelCount, options, counts) {
|
|
118
|
+
const bounds = weaponBounds(unit, modelCount, options);
|
|
119
|
+
const out = [];
|
|
120
|
+
for (const [id, n] of counts) {
|
|
121
|
+
const b = bounds.get(id);
|
|
122
|
+
if (!b)
|
|
123
|
+
continue;
|
|
124
|
+
if (n > b.max) {
|
|
125
|
+
out.push({ id, code: "exceeds-max", message: `${id}: ${n} exceeds max ${b.max}` });
|
|
126
|
+
}
|
|
127
|
+
else if (n < b.min) {
|
|
128
|
+
out.push({ id, code: "below-min", message: `${id}: ${n} below min ${b.min}` });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Deterministic order so the result is stable for cross-impl comparison.
|
|
132
|
+
out.sort((a, b) => (a.id === b.id ? a.code.localeCompare(b.code) : a.id.localeCompare(b.id)));
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=loadout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadout.js","sourceRoot":"","sources":["../../src/data/loadout.ts"],"names":[],"mappings":"AAmCA;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,MAAqB,EAAE,UAAkB;IACjE,MAAM,CAAC,GAAG,MAAM,CAAC,gBAAgB,CAAC;IAClC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IACvC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC,CAAC,UAAU;QAAE,GAAG,GAAG,UAAU,CAAC;SAC9B,IAAI,CAAC,CAAC,YAAY;QAAE,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;;QAClE,GAAG,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,CAAC,SAAS,IAAI,IAAI;QAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED,mFAAmF;AACnF,SAAS,QAAQ,CAAC,MAAqB,EAAE,WAAW,GAAG,CAAC;IACtD,IAAI,MAAM,CAAC,WAAW;QAAE,OAAO,MAAM,CAAC,WAAW,CAAC;IAClD,OAAO,MAAM,CAAC,kBAAkB,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,qEAAqE;AACrE,SAAS,iBAAiB,CAAC,OAAiC;IAC1D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,WAAW,IAAI,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClD,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,kBAAkB,IAAI,EAAE;YAAE,KAAK,MAAM,EAAE,IAAI,KAAK;gBAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,8EAA8E;AAC9E,SAAS,aAAa,CAAC,IAAU,EAAE,OAAiC;IAClE,MAAM,YAAY,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AACvE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAU,EACV,UAAkB,EAClB,OAAiC;IAEjC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,EAAE,IAAI,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;QAC9C,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;IACrD,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAC1C,IAAI,GAAG,KAAK,CAAC;YAAE,SAAS;QACxB,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAC9C,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,8DAA8D;IAC9D,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,MAAM;QAAE,IAAI,CAAC,KAAK,CAAC;YAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAU,EACV,UAAkB,EAClB,OAAiC;IAEjC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC9C,KAAK,MAAM,EAAE,IAAI,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;QAC9C,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAC1C,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;YAC/C,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,0EAA0E;QAC1E,4DAA4D;QAC5D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,WAAW,IAAI,EAAE;YAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,kBAAkB,IAAI,EAAE;YAAE,KAAK,MAAM,EAAE,IAAI,KAAK;gBAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1F,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;YAC/C,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAgC,EAChC,EAAU,EACV,SAAiB;IAEjB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACzB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAC7B,IAAU,EACV,UAAkB,EAClB,OAAiC,EACjC,MAA2B;IAE3B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACrF,CAAC;aAAM,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACrB,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IACD,yEAAyE;IACzE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9F,OAAO,GAAG,CAAC;AACb,CAAC","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\";\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(option: WargearOption, modelCount: number): 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 return Math.max(0, cap);\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/** Base (always-carried) weapon ids: in `weapon_ids`, never a replacement. */\nfunction baseWeaponIds(unit: Unit, options: readonly WargearOption[]): string[] {\n const replacements = allReplacementIds(options);\n return (unit.weapon_ids ?? []).filter((id) => !replacements.has(id));\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): Loadout {\n const counts = new Map<string, number>();\n for (const id of baseWeaponIds(unit, options)) {\n counts.set(id, (counts.get(id) ?? 0) + modelCount);\n }\n for (const option of options) {\n const cap = optionCap(option, modelCount);\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 // 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 * 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): Map<string, WeaponBound> {\n const bounds = new Map<string, WeaponBound>();\n for (const id of baseWeaponIds(unit, options)) {\n bounds.set(id, { min: modelCount, max: modelCount });\n }\n for (const option of options) {\n const cap = optionCap(option, modelCount);\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 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/** 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): Violation[] {\n const bounds = weaponBounds(unit, modelCount, options);\n const out: Violation[] = [];\n for (const [id, n] of counts) {\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 // 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"]}
|
package/dist/data/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
-
import type { AbilityDSLEntry, DeploymentPattern, Detachment, Enhancement, Faction, ForceDisposition, GameVersion, InteractionFlag, LeaderAttachment, Mission, MissionMatchup, PhaseMapping, ResourcePool, SecondaryCard, Stratagem, TerrainLayout, TerrainTemplate, TimingFlag, Unit, UnitComposition, WargearOption, Weapon, WeaponKeyword } from "../generated.js";
|
|
10
|
+
import type { AbilityDSLEntry, DeploymentPattern, Detachment, Enhancement, Faction, ForceDisposition, GameVersion, InteractionFlag, LeaderAttachment, Mission, MissionMatchup, PhaseMapping, ResourcePool, SecondaryCard, Stratagem, TerrainLayout, TerrainTemplate, TimingFlag, Unit, UnitComposition, Wargear, WargearOption, Weapon, WeaponKeyword } from "../generated.js";
|
|
11
11
|
/**
|
|
12
12
|
* Every entity collection in the dataset, keyed by camelCase collection name.
|
|
13
13
|
*
|
|
@@ -31,6 +31,8 @@ export interface RawData {
|
|
|
31
31
|
leaderAttachments: LeaderAttachment[];
|
|
32
32
|
unitCompositions: UnitComposition[];
|
|
33
33
|
wargearOptions: WargearOption[];
|
|
34
|
+
/** Non-weapon wargear items (icons, attachments) referenced by wargear options. */
|
|
35
|
+
wargear: Wargear[];
|
|
34
36
|
gameVersions: GameVersion[];
|
|
35
37
|
missions: Mission[];
|
|
36
38
|
missionMatchups: MissionMatchup[];
|
package/dist/data/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,OAAO,EACP,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,SAAS,EACT,aAAa,EACb,eAAe,EACf,UAAU,EACV,IAAI,EACJ,eAAe,EACf,aAAa,EACb,MAAM,EACN,aAAa,EACd,MAAM,iBAAiB,CAAC;AAEzB;;;;;;GAMG;AACH,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kFAAkF;IAClF,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,4EAA4E;IAC5E,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,8EAA8E;IAC9E,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;IACtC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;IACtC,qEAAqE;IACrE,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,2EAA2E;IAC3E,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACrC;AAED,uEAAuE;AACvE,wBAAgB,YAAY,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EACV,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,OAAO,EACP,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,SAAS,EACT,aAAa,EACb,eAAe,EACf,UAAU,EACV,IAAI,EACJ,eAAe,EACf,OAAO,EACP,aAAa,EACb,MAAM,EACN,aAAa,EACd,MAAM,iBAAiB,CAAC;AAEzB;;;;;;GAMG;AACH,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kFAAkF;IAClF,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,4EAA4E;IAC5E,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,8EAA8E;IAC9E,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;IACtC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,mFAAmF;IACnF,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IACxC,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;IACtC,qEAAqE;IACrE,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,2EAA2E;IAC3E,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACrC;AAED,uEAAuE;AACvE,wBAAgB,YAAY,IAAI,OAAO,CA2BtC"}
|
package/dist/data/types.js
CHANGED
package/dist/data/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"AA4EA,uEAAuE;AACvE,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,EAAE;QACX,cAAc,EAAE,EAAE;QAClB,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE,EAAE;QACb,aAAa,EAAE,EAAE;QACjB,WAAW,EAAE,EAAE;QACf,UAAU,EAAE,EAAE;QACd,YAAY,EAAE,EAAE;QAChB,iBAAiB,EAAE,EAAE;QACrB,gBAAgB,EAAE,EAAE;QACpB,cAAc,EAAE,EAAE;QAClB,OAAO,EAAE,EAAE;QACX,YAAY,EAAE,EAAE;QAChB,QAAQ,EAAE,EAAE;QACZ,eAAe,EAAE,EAAE;QACnB,YAAY,EAAE,EAAE;QAChB,kBAAkB,EAAE,EAAE;QACtB,iBAAiB,EAAE,EAAE;QACrB,gBAAgB,EAAE,EAAE;QACpB,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,EAAE;QACjB,WAAW,EAAE,EAAE;QACf,gBAAgB,EAAE,EAAE;KACrB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The shape of the embedded data bundle: one named array per entity collection.\n *\n * `RawData` is the boundary between the generated JSON-Schema types and the\n * linked view layer. The codegen ({@link file://../codegen-data.ts}) emits a\n * `RAW_DATA: RawData` constant; {@link Dataset} wraps it with linked accessors.\n *\n * @packageDocumentation\n */\nimport type {\n AbilityDSLEntry,\n DeploymentPattern,\n Detachment,\n Enhancement,\n Faction,\n ForceDisposition,\n GameVersion,\n InteractionFlag,\n LeaderAttachment,\n Mission,\n MissionMatchup,\n PhaseMapping,\n ResourcePool,\n SecondaryCard,\n Stratagem,\n TerrainLayout,\n TerrainTemplate,\n TimingFlag,\n Unit,\n UnitComposition,\n Wargear,\n WargearOption,\n Weapon,\n WeaponKeyword,\n} from \"../generated.js\";\n\n/**\n * Every entity collection in the dataset, keyed by camelCase collection name.\n *\n * Collections with no authored data yet (e.g. `interactionFlags`) are present\n * as empty arrays so the API surface is stable and new data flows through\n * automatically once authored.\n */\nexport interface RawData {\n units: Unit[];\n weapons: Weapon[];\n /** Catalog of weapon keywords (Lethal Hits, Sustained Hits N, Anti-X N+, ...). */\n weaponKeywords: WeaponKeyword[];\n factions: Faction[];\n /** Community-authored ability mechanics (key is `ability_id`, not `id`). */\n abilities: AbilityDSLEntry[];\n /** Phase assignments, joined to abilities/stratagems/etc. via `source_id`. */\n phaseMappings: PhaseMapping[];\n detachments: Detachment[];\n stratagems: Stratagem[];\n enhancements: Enhancement[];\n leaderAttachments: LeaderAttachment[];\n unitCompositions: UnitComposition[];\n wargearOptions: WargearOption[];\n /** Non-weapon wargear items (icons, attachments) referenced by wargear options. */\n wargear: Wargear[];\n gameVersions: GameVersion[];\n missions: Mission[];\n missionMatchups: MissionMatchup[];\n missionCards: SecondaryCard[];\n deploymentPatterns: DeploymentPattern[];\n forceDispositions: ForceDisposition[];\n /** Reusable terrain catalog: standard areas and scenery features. */\n terrainTemplates: TerrainTemplate[];\n /** Terrain layouts: arrangements of catalog/inline pieces on the board. */\n terrainLayouts: TerrainLayout[];\n resourcePools: ResourcePool[];\n timingFlags: TimingFlag[];\n interactionFlags: InteractionFlag[];\n}\n\n/** A `RawData` with every collection initialised to an empty array. */\nexport function emptyRawData(): RawData {\n return {\n units: [],\n weapons: [],\n weaponKeywords: [],\n factions: [],\n abilities: [],\n phaseMappings: [],\n detachments: [],\n stratagems: [],\n enhancements: [],\n leaderAttachments: [],\n unitCompositions: [],\n wargearOptions: [],\n wargear: [],\n gameVersions: [],\n missions: [],\n missionMatchups: [],\n missionCards: [],\n deploymentPatterns: [],\n forceDispositions: [],\n terrainTemplates: [],\n terrainLayouts: [],\n resourcePools: [],\n timingFlags: [],\n interactionFlags: [],\n };\n}\n"]}
|
|
@@ -12,7 +12,7 @@ const CLS_TRAIT = "Trait";
|
|
|
12
12
|
const DSG_WARLORD = "Warlord";
|
|
13
13
|
const RULEBOOK_NAME = "40kdc";
|
|
14
14
|
const RULEBOOK_GAME = "Warhammer 40,000";
|
|
15
|
-
const RULEBOOK_PUBLISHER = "
|
|
15
|
+
const RULEBOOK_PUBLISHER = "Alpaca Software";
|
|
16
16
|
const RULEBOOK_URL = "https://40kdc.dev";
|
|
17
17
|
const RULEBOOK_GENRE = "wargame";
|
|
18
18
|
function key(classification, designation) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rosterizer.js","sourceRoot":"","sources":["../../src/export/rosterizer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Rosterizer serializer — emits a Rosterizer-shaped roster JSON skeleton that\n * round-trips through {@link rosterizerAdapter}.\n *\n * The shape carries only fields the importer reads: `rulebook` (envelope),\n * `snapshot` (an `Asset` tree rooted at `Roster§Roster`), and per-unit\n * `item`/`name`/`quantity`/`stats.Points.value`/`assets.included`/`assets.traits`.\n * No `text`, `description`, `rules`, `lineage`, `_layers`, `classIdentity`,\n * `processed`, or `bareResourceKey` ever appear — they aren't stored in the\n * Roster and emitting them could leak prose.\n *\n * Faction and detachment display names come from {@link titleCaseId} — the\n * Roster doesn't carry the source's raw faction name, so we reconstruct it\n * from the kebab-case id. Same lossy hop as the NewRecruit JSON serializer.\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit, RosterWargear } from \"../import/types.js\";\nimport { prettyJson, titleCaseId, totalArmyPoints } from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\n// Mirror the importer's constants (kept inline rather than imported so the\n// exporter stays decoupled — the seams are the `item` keys themselves).\nconst CLS_ROSTER = \"Roster\";\nconst CLS_FACTION = \"Faction\";\nconst CLS_DETACHMENT = \"Detachment\";\nconst CLS_UNIT = \"Unit\";\nconst CLS_WEAPON = \"Weapon\";\nconst CLS_ENHANCEMENT = \"Enhancement\";\nconst CLS_BATTLE_SIZE = \"Battle Size\";\nconst CLS_TRAIT = \"Trait\";\nconst DSG_WARLORD = \"Warlord\";\n\nconst RULEBOOK_NAME = \"40kdc\";\nconst RULEBOOK_GAME = \"Warhammer 40,000\";\nconst RULEBOOK_PUBLISHER = \"Tabletop Developer Consortium\";\nconst RULEBOOK_URL = \"https://40kdc.dev\";\nconst RULEBOOK_GENRE = \"wargame\";\n\ninterface Asset {\n item: string;\n name?: string;\n quantity?: number;\n stats?: Record<string, { value: number }>;\n assets?: {\n included?: Asset[];\n traits?: Asset[];\n };\n}\n\ninterface Envelope {\n slug: string;\n key: string;\n visible: \"hidden\" | \"public\" | \"friends\";\n locked: boolean;\n rulebook: {\n name: string;\n game: string;\n publisher: string;\n url: string;\n genre: string;\n };\n snapshot: Asset;\n}\n\nfunction key(classification: string, designation: string): string {\n return `${classification}§${designation}`; // §\n}\n\nfunction pointsStat(value: number | null | undefined): Record<string, { value: number }> | undefined {\n if (value === null || value === undefined) return undefined;\n return { Points: { value } };\n}\n\nfunction wargearAsset(w: RosterWargear): Asset {\n return {\n item: key(CLS_WEAPON, w.ref.raw_name),\n name: w.ref.raw_name,\n quantity: w.count,\n };\n}\n\nfunction enhancementAsset(u: RosterUnit): Asset | null {\n if (!u.enhancement) return null;\n return {\n item: key(CLS_ENHANCEMENT, u.enhancement.raw_name),\n name: u.enhancement.raw_name,\n quantity: 1,\n ...(u.enhancement_points !== null\n ? { stats: pointsStat(u.enhancement_points) }\n : {}),\n };\n}\n\nfunction warlordTraitAsset(): Asset {\n return {\n item: key(CLS_TRAIT, DSG_WARLORD),\n name: DSG_WARLORD,\n quantity: 1,\n };\n}\n\nfunction unitAsset(u: RosterUnit): Asset {\n const included: Asset[] = [];\n const enh = enhancementAsset(u);\n if (enh !== null) included.push(enh);\n for (const w of u.wargear) included.push(wargearAsset(w));\n\n const traits: Asset[] = [];\n if (u.is_warlord) traits.push(warlordTraitAsset());\n\n const asset: Asset = {\n item: key(CLS_UNIT, u.ref.raw_name),\n name: u.ref.raw_name,\n quantity: u.model_count,\n };\n const stats = pointsStat(u.points);\n if (stats !== undefined) asset.stats = stats;\n if (included.length > 0 || traits.length > 0) {\n asset.assets = {};\n if (included.length > 0) asset.assets.included = included;\n if (traits.length > 0) asset.assets.traits = traits;\n }\n return asset;\n}\n\nfunction factionAsset(roster: Roster): Asset | null {\n const display = titleCaseId(roster.faction_id);\n if (display === null) return null;\n return { item: key(CLS_FACTION, display), name: display, quantity: 1 };\n}\n\nfunction detachmentAsset(roster: Roster): Asset | null {\n const display = titleCaseId(roster.detachment_id);\n if (display === null) return null;\n return { item: key(CLS_DETACHMENT, display), name: display, quantity: 1 };\n}\n\nfunction battleSizeAsset(roster: Roster): Asset | null {\n if (roster.battle_size === \"strike-force\") {\n const limit = roster.points.declared_limit ?? 2000;\n const label = `Strike Force (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n if (roster.battle_size === \"incursion\") {\n const limit = roster.points.declared_limit ?? 1000;\n const label = `Incursion (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n return null;\n}\n\nexport const rosterizerSerializer: RosterSerializer = {\n id: \"rosterizer\",\n\n serialize(roster: Roster): string {\n const included: Asset[] = [];\n const faction = factionAsset(roster);\n if (faction) included.push(faction);\n const detachment = detachmentAsset(roster);\n if (detachment) included.push(detachment);\n const battleSize = battleSizeAsset(roster);\n if (battleSize) included.push(battleSize);\n for (const u of roster.units) included.push(unitAsset(u));\n\n const total = totalArmyPoints(roster);\n const snapshot: Asset = {\n item: key(CLS_ROSTER, CLS_ROSTER),\n name: roster.name,\n quantity: 1,\n ...(total > 0 ? { stats: pointsStat(total) } : {}),\n assets: { included },\n };\n\n const envelope: Envelope = {\n slug: \"\",\n key: \"\",\n visible: \"hidden\",\n locked: false,\n rulebook: {\n name: RULEBOOK_NAME,\n game: RULEBOOK_GAME,\n publisher: RULEBOOK_PUBLISHER,\n url: RULEBOOK_URL,\n genre: RULEBOOK_GENRE,\n },\n snapshot,\n };\n\n return prettyJson(envelope);\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"rosterizer.js","sourceRoot":"","sources":["../../src/export/rosterizer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Rosterizer serializer — emits a Rosterizer-shaped roster JSON skeleton that\n * round-trips through {@link rosterizerAdapter}.\n *\n * The shape carries only fields the importer reads: `rulebook` (envelope),\n * `snapshot` (an `Asset` tree rooted at `Roster§Roster`), and per-unit\n * `item`/`name`/`quantity`/`stats.Points.value`/`assets.included`/`assets.traits`.\n * No `text`, `description`, `rules`, `lineage`, `_layers`, `classIdentity`,\n * `processed`, or `bareResourceKey` ever appear — they aren't stored in the\n * Roster and emitting them could leak prose.\n *\n * Faction and detachment display names come from {@link titleCaseId} — the\n * Roster doesn't carry the source's raw faction name, so we reconstruct it\n * from the kebab-case id. Same lossy hop as the NewRecruit JSON serializer.\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit, RosterWargear } from \"../import/types.js\";\nimport { prettyJson, titleCaseId, totalArmyPoints } from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\n// Mirror the importer's constants (kept inline rather than imported so the\n// exporter stays decoupled — the seams are the `item` keys themselves).\nconst CLS_ROSTER = \"Roster\";\nconst CLS_FACTION = \"Faction\";\nconst CLS_DETACHMENT = \"Detachment\";\nconst CLS_UNIT = \"Unit\";\nconst CLS_WEAPON = \"Weapon\";\nconst CLS_ENHANCEMENT = \"Enhancement\";\nconst CLS_BATTLE_SIZE = \"Battle Size\";\nconst CLS_TRAIT = \"Trait\";\nconst DSG_WARLORD = \"Warlord\";\n\nconst RULEBOOK_NAME = \"40kdc\";\nconst RULEBOOK_GAME = \"Warhammer 40,000\";\nconst RULEBOOK_PUBLISHER = \"Alpaca Software\";\nconst RULEBOOK_URL = \"https://40kdc.dev\";\nconst RULEBOOK_GENRE = \"wargame\";\n\ninterface Asset {\n item: string;\n name?: string;\n quantity?: number;\n stats?: Record<string, { value: number }>;\n assets?: {\n included?: Asset[];\n traits?: Asset[];\n };\n}\n\ninterface Envelope {\n slug: string;\n key: string;\n visible: \"hidden\" | \"public\" | \"friends\";\n locked: boolean;\n rulebook: {\n name: string;\n game: string;\n publisher: string;\n url: string;\n genre: string;\n };\n snapshot: Asset;\n}\n\nfunction key(classification: string, designation: string): string {\n return `${classification}§${designation}`; // §\n}\n\nfunction pointsStat(value: number | null | undefined): Record<string, { value: number }> | undefined {\n if (value === null || value === undefined) return undefined;\n return { Points: { value } };\n}\n\nfunction wargearAsset(w: RosterWargear): Asset {\n return {\n item: key(CLS_WEAPON, w.ref.raw_name),\n name: w.ref.raw_name,\n quantity: w.count,\n };\n}\n\nfunction enhancementAsset(u: RosterUnit): Asset | null {\n if (!u.enhancement) return null;\n return {\n item: key(CLS_ENHANCEMENT, u.enhancement.raw_name),\n name: u.enhancement.raw_name,\n quantity: 1,\n ...(u.enhancement_points !== null\n ? { stats: pointsStat(u.enhancement_points) }\n : {}),\n };\n}\n\nfunction warlordTraitAsset(): Asset {\n return {\n item: key(CLS_TRAIT, DSG_WARLORD),\n name: DSG_WARLORD,\n quantity: 1,\n };\n}\n\nfunction unitAsset(u: RosterUnit): Asset {\n const included: Asset[] = [];\n const enh = enhancementAsset(u);\n if (enh !== null) included.push(enh);\n for (const w of u.wargear) included.push(wargearAsset(w));\n\n const traits: Asset[] = [];\n if (u.is_warlord) traits.push(warlordTraitAsset());\n\n const asset: Asset = {\n item: key(CLS_UNIT, u.ref.raw_name),\n name: u.ref.raw_name,\n quantity: u.model_count,\n };\n const stats = pointsStat(u.points);\n if (stats !== undefined) asset.stats = stats;\n if (included.length > 0 || traits.length > 0) {\n asset.assets = {};\n if (included.length > 0) asset.assets.included = included;\n if (traits.length > 0) asset.assets.traits = traits;\n }\n return asset;\n}\n\nfunction factionAsset(roster: Roster): Asset | null {\n const display = titleCaseId(roster.faction_id);\n if (display === null) return null;\n return { item: key(CLS_FACTION, display), name: display, quantity: 1 };\n}\n\nfunction detachmentAsset(roster: Roster): Asset | null {\n const display = titleCaseId(roster.detachment_id);\n if (display === null) return null;\n return { item: key(CLS_DETACHMENT, display), name: display, quantity: 1 };\n}\n\nfunction battleSizeAsset(roster: Roster): Asset | null {\n if (roster.battle_size === \"strike-force\") {\n const limit = roster.points.declared_limit ?? 2000;\n const label = `Strike Force (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n if (roster.battle_size === \"incursion\") {\n const limit = roster.points.declared_limit ?? 1000;\n const label = `Incursion (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n return null;\n}\n\nexport const rosterizerSerializer: RosterSerializer = {\n id: \"rosterizer\",\n\n serialize(roster: Roster): string {\n const included: Asset[] = [];\n const faction = factionAsset(roster);\n if (faction) included.push(faction);\n const detachment = detachmentAsset(roster);\n if (detachment) included.push(detachment);\n const battleSize = battleSizeAsset(roster);\n if (battleSize) included.push(battleSize);\n for (const u of roster.units) included.push(unitAsset(u));\n\n const total = totalArmyPoints(roster);\n const snapshot: Asset = {\n item: key(CLS_ROSTER, CLS_ROSTER),\n name: roster.name,\n quantity: 1,\n ...(total > 0 ? { stats: pointsStat(total) } : {}),\n assets: { included },\n };\n\n const envelope: Envelope = {\n slug: \"\",\n key: \"\",\n visible: \"hidden\",\n locked: false,\n rulebook: {\n name: RULEBOOK_NAME,\n game: RULEBOOK_GAME,\n publisher: RULEBOOK_PUBLISHER,\n url: RULEBOOK_URL,\n genre: RULEBOOK_GENRE,\n },\n snapshot,\n };\n\n return prettyJson(envelope);\n },\n};\n"]}
|
package/dist/gen-conformance.js
CHANGED
|
@@ -28,6 +28,7 @@ import { describeScoringCard } from "./translate/index.js";
|
|
|
28
28
|
import { exportRoster } from "./export/index.js";
|
|
29
29
|
import { importRoster, REGISTERED_ADAPTERS } from "./import/import-roster.js";
|
|
30
30
|
import { selectAdapter } from "./import/adapter.js";
|
|
31
|
+
import { encodeBase } from "./runner.js";
|
|
31
32
|
import { attributeStages } from "./cruncher/attribution.js";
|
|
32
33
|
import { resolveLayout, } from "./terrain/resolve.js";
|
|
33
34
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -206,6 +207,12 @@ const LINKED_API_QUERIES = [
|
|
|
206
207
|
{ name: "abilities_of_faction world-eaters", query: "abilities_of_faction", args: { factionId: "world-eaters" }, comparison: "set" },
|
|
207
208
|
// weapons_of_faction: compared as set.
|
|
208
209
|
{ name: "weapons_of_faction world-eaters", query: "weapons_of_faction", args: { factionId: "world-eaters" }, comparison: "set" },
|
|
210
|
+
// base_size_of(unit): scalar encoded base — round, oval, and a draft flying-base.
|
|
211
|
+
{ name: "base_size_of intercessor-squad", query: "base_size_of", args: { unitId: "intercessor-squad" }, comparison: "scalar" },
|
|
212
|
+
{ name: "base_size_of vertus-praetors", query: "base_size_of", args: { unitId: "vertus-praetors" }, comparison: "scalar" },
|
|
213
|
+
{ name: "base_size_of windriders (draft flying base)", query: "base_size_of", args: { unitId: "windriders" }, comparison: "scalar" },
|
|
214
|
+
// model_bases_of(unit): ordered per-model bases; jakhals mixes 28.5mm bodies with a 40mm Dishonoured.
|
|
215
|
+
{ name: "model_bases_of jakhals (mixed)", query: "model_bases_of", args: { unitId: "jakhals" }, comparison: "ordered" },
|
|
209
216
|
];
|
|
210
217
|
function genLinkedApi() {
|
|
211
218
|
const ds = Dataset.embedded();
|
|
@@ -262,6 +269,19 @@ function runLinkedQuery(ds, q) {
|
|
|
262
269
|
throw new Error(`weapons_of_faction: unknown faction ${q.args.factionId}`);
|
|
263
270
|
return f.weapons.map((w) => w.id).sort();
|
|
264
271
|
}
|
|
272
|
+
case "base_size_of": {
|
|
273
|
+
const u = ds.units.get(q.args.unitId);
|
|
274
|
+
if (!u)
|
|
275
|
+
throw new Error(`base_size_of: unknown unit ${q.args.unitId}`);
|
|
276
|
+
return encodeBase(u.raw.base_size_mm);
|
|
277
|
+
}
|
|
278
|
+
case "model_bases_of": {
|
|
279
|
+
const u = ds.units.get(q.args.unitId);
|
|
280
|
+
if (!u)
|
|
281
|
+
throw new Error(`model_bases_of: unknown unit ${q.args.unitId}`);
|
|
282
|
+
const comp = ds.unitCompositions.find((c) => c.unit_id === q.args.unitId);
|
|
283
|
+
return (comp?.models ?? []).map((m) => `${m.name}=${encodeBase(m.base_size_mm) ?? "none"}`);
|
|
284
|
+
}
|
|
265
285
|
}
|
|
266
286
|
}
|
|
267
287
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gen-conformance.js","sourceRoot":"","sources":["../src/gen-conformance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAqB,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EACL,aAAa,GAGd,MAAM,sBAAsB,CAAC;AAE9B,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AAEnD,MAAM,gBAAgB,GAAG;IACvB,sBAAsB;IACtB,oBAAoB;IACpB,SAAS;IACT,OAAO;IACP,QAAQ;IACR,8BAA8B;IAC9B,MAAM;IACN,UAAU;IACV,gBAAgB;IAChB,kBAAkB;IAClB,UAAU;IACV,sCAAsC;IACtC,qBAAqB;IACrB,oBAAoB;IACpB,gBAAgB;IAChB,WAAW;IACX,oBAAoB;IACpB,mCAAmC;IACnC,oBAAoB;IACpB,oDAAoD;IACpD,QAAQ;IACR,OAAO;IACP,2EAA2E;IAC3E,kEAAkE;IAClE,6DAA6D;IAC7D,aAAa;IACb,aAAa;IACb,4EAA4E;IAC5E,6EAA6E;IAC7E,yEAAyE;IACzE,+DAA+D;IAC/D,gBAAgB;IAChB,yEAAyE;IACzE,0EAA0E;IAC1E,sCAAsC;IACtC,aAAa;CACd,CAAC;AAEF,SAAS,SAAS,CAAC,IAAY,EAAE,KAAc;IAC7C,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,KAAa;IAC5C,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,YAAY;IACnB,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3F,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,EAAE,KAAK,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,CAAC,mBAAmB,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;AACvD,CAAC;AAED;;6EAE6E;AAC7E,SAAS,UAAU,CAAC,OAAe,EAAE,EAAW;IAC9C,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,YAAY,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;AAChD,CAAC;AAED;;kCAEkC;AAClC,SAAS,mBAAmB,CAAC,OAAe;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC7C,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;IAC3D,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC7C,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACvB,OAAO,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,+BAA+B,OAAO,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED;;;kDAGkD;AAClD,SAAS,uBAAuB,CAAC,OAAe;IAC9C,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC;IACjE,OAAO,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,YAAY,GAAsE;IACtF;QACE,MAAM,EAAE,wBAAwB;QAChC,SAAS,EAAE,kCAAkC;QAC7C,UAAU,EAAE,qCAAqC;KAClD;IACD;QACE,MAAM,EAAE,qBAAqB;QAC7B,SAAS,EAAE,+BAA+B;QAC1C,UAAU,EAAE,kCAAkC;KAC/C;IACD;QACE,MAAM,EAAE,mBAAmB;QAC3B,SAAS,EAAE,6BAA6B;QACxC,UAAU,EAAE,gCAAgC;KAC7C;CACF,CAAC;AAEF,SAAS,UAAU;IACjB,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC9C,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACpE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAE5C,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACrC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,sBAAsB,CAAC,EAAE,IAAI,CAAC,CAAC;QAEvD,sEAAsE;QACtE,yEAAyE;QACzE,qEAAqE;QACrE,6DAA6D;QAC7D,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,sBAAsB,CAAC,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC,CAAC;QAEnF,mDAAmD;QACnD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QACtD,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,+BAA+B,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAE/E,mEAAmE;QACnE,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,2BAA2B,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;QAErG,0EAA0E;QAC1E,+DAA+D;QAC/D,sEAAsE;QACtE,mEAAmE;QACnE,0EAA0E;QAC1E,+DAA+D;QAC/D,iDAAiD;QACjD,MAAM,gBAAgB,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC,CAAC;QACjF,KAAK,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,YAAY,EAAE,CAAC;YAC7D,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACvC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,GAAG,CAAC,CAAC;YAC1C,IAAI,gBAAgB,EAAE,CAAC;gBACrB,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QAED,uEAAuE;QACvE,uEAAuE;QACvE,uEAAuE;QACvE,uEAAuE;QACvE,oEAAoE;QACpE,oEAAoE;QACpE,yEAAyE;QACzE,MAAM,aAAa,GAAG,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACvD,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,0BAA0B,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;QAChF,IAAI,gBAAgB,EAAE,CAAC;YACrB,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,uBAAuB,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;QAC/E,CAAC;QAED,OAAO,CAAC,GAAG,CACT,UAAU,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,WAAW,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,WAAW,CACjG,CAAC;IACJ,CAAC;AACH,CAAC;AA8BD,MAAM,kBAAkB,GAAqB;IAC3C,8DAA8D;IAC9D,EAAE,IAAI,EAAE,6BAA6B,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;IAC3G,EAAE,IAAI,EAAE,6BAA6B,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;IACzH,yCAAyC;IACzC,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;IAC1G,iDAAiD;IACjD,EAAE,IAAI,EAAE,8BAA8B,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;IACtH,qCAAqC;IACrC,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;IAClH,gEAAgE;IAChE,EAAE,IAAI,EAAE,gCAAgC,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE;IAC/H,EAAE,IAAI,EAAE,iCAAiC,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE;IACjI,6DAA6D;IAC7D,EAAE,IAAI,EAAE,8BAA8B,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE;IAC3H,EAAE,IAAI,EAAE,+BAA+B,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE;IAC7H,mFAAmF;IACnF,EAAE,IAAI,EAAE,4BAA4B,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE;IACtH,uCAAuC;IACvC,EAAE,IAAI,EAAE,8BAA8B,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;IAC1H,gFAAgF;IAChF,EAAE,IAAI,EAAE,mCAAmC,EAAE,KAAK,EAAE,sBAAsB,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE;IACpI,uCAAuC;IACvC,EAAE,IAAI,EAAE,iCAAiC,EAAE,KAAK,EAAE,oBAAoB,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE;CACjI,CAAC;AAEF,SAAS,YAAY;IACnB,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACzC,MAAM,QAAQ,GAAG,cAAc,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACvC,OAAO,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IACH,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,CAAC,EAAE,KAAK,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,0BAA0B,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,cAAc,CAAC,EAAW,EAAE,CAAiB;IACpD,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;QAChB,KAAK,WAAW;YACd,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;QACjD,KAAK,aAAa;YAChB,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;QACnD,KAAK,cAAc;YACjB,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;QACpD,KAAK,cAAc;YACjB,OAAO,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;QACrD,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACtC,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACvE,OAAO,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACtC,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACrE,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7C,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;YAC1E,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;QACD,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACtC,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACrE,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,IAAI,IAAI,CAAC;QAC/B,CAAC;QACD,KAAK,sBAAsB;YACzB,OAAO,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1E,KAAK,oBAAoB,CAAC,CAAC,CAAC;YAC1B,kEAAkE;YAClE,yDAAyD;YACzD,sEAAsE;YACtE,oEAAoE;YACpE,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5C,IAAI,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;YACnF,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,sBAAsB,GAAG;IAC7B,kCAAkC;IAClC,gDAAgD;CACjD,CAAC;AAWF,SAAS,oBAAoB,CAAC,EAAW,EAAE,QAAgB;IAIzD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IACrD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAsB,CAAC;IACtE,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACnF,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3E,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE;YACL,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC,QAAQ,CAAC,YAAY,EAAE;YACvE,MAAM,EAAE;gBACN,IAAI,EAAE,IAAI,CAAC,GAAG;gBACd,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY;gBACnC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAClF;YACD,YAAY,EAAE,CAAC,CAAC,YAAY;YAC5B,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,OAAO,EAAE,CAAC,CAAC,OAAO;SACnB;KACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc;IACrB,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE;QACzD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC1C,OAAO;YACL,mEAAmE;YACnE,yEAAyE;YACzE,IAAI;YACJ,aAAa,EAAE,QAAQ;YACvB,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC3B,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;gBACjE,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,UAAU,EAAE,CAAC,CAAC,UAAU;aACzB,CAAC,CAAC;YACH,+CAA+C;YAC/C,MAAM,EAAE,GAAG;SACZ,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,sDAAsD;IACtD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;IAChE,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,EAAE,YAAY,CAAC,EAAE,UAAU,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,qBAAqB;IAC5B,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC9B,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,qBAAqB,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,6EAA6E;IAC7E,8EAA8E;IAC9E,uBAAuB;IACvB,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,GAAG;SAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,SAAS,KAAK,WAAW,CAAC;SACvE,KAAK,EAAE;SACP,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACxD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,mBAAmB,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACzF,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,qBAAqB,EAAE,YAAY,CAAC,EAAE,KAAK,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,mCAAmC,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;AACvE,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,kBAAkB;IACzB,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtE,MAAM,SAAS,GAAqB;QAClC,EAAE,EAAE,YAAY;QAChB,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE;KACzD,CAAC;IACF,MAAM,UAAU,GAAqB;QACnC,EAAE,EAAE,aAAa;QACjB,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;KACtD,CAAC;IACF,MAAM,aAAa,GAAqB;QACtC,EAAE,EAAE,gBAAgB;QACpB,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE;YACT,IAAI,EAAE,SAAS;YACf,MAAM,EAAE;gBACN,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;gBACd,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;gBACd,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE;gBACjB,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE;aAClB;SACF;KACF,CAAC;IACF,MAAM,KAAK,GAAqB;QAC9B,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;KAC9D,CAAC;IACF,MAAM,QAAQ,GAAqB;QACjC,EAAE,EAAE,WAAW;QACf,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;KACzD,CAAC;IACF,MAAM,YAAY,GAAqB;QACrC,EAAE,EAAE,eAAe;QACnB,IAAI,EAAE,eAAe;QACrB,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE;QACxD,QAAQ,EAAE;YACR,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;YACrE,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE;SAClH;KACF,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAE5E,MAAM,WAAW,GAA8E;QAC7F;YACE,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;SAC1G;QACD;YACE,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;SACrG;QACD;YACE,IAAI,EAAE,oBAAoB;YAC1B,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;SAC9G;QACD;YACE,IAAI,EAAE,kBAAkB;YACxB,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC,EAAE;SACjI;QACD;YACE,IAAI,EAAE,mBAAmB;YACzB,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,EAAE;SAClI;QACD;YACE,IAAI,EAAE,mBAAmB;YACzB,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,EAAE;SAClI;QACD;YACE,IAAI,EAAE,yBAAyB;YAC/B,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC,EAAE;SAChI;QACD;YACE,IAAI,EAAE,8BAA8B;YACpC,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,EAAE;SACxI;QACD;YACE,IAAI,EAAE,6BAA6B;YACnC,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,EAAE;SACpI;QACD;YACE,IAAI,EAAE,iCAAiC;YACvC,SAAS,EAAE,WAAW;YACtB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,EAAE;SACxJ;QACD;YACE,IAAI,EAAE,iCAAiC;YACvC,SAAS,EAAE,CAAC,YAAY,EAAE,QAAQ,CAAC;YACnC,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,EAAE;SAC1J;QACD;YACE,IAAI,EAAE,yBAAyB;YAC/B,SAAS,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC;YAChC,MAAM,EAAE;gBACN,EAAE,EAAE,GAAG;gBACP,IAAI,EAAE,GAAG;gBACT,MAAM,EAAE;oBACN,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE;oBAC5G,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;iBAC5F;aACF;SACF;QACD;YACE,IAAI,EAAE,0BAA0B;YAChC,SAAS,EAAE,EAAE;YACb,MAAM,EAAE;gBACN,EAAE,EAAE,GAAG;gBACP,IAAI,EAAE,GAAG;gBACT,MAAM,EAAE;oBACN;wBACE,EAAE,EAAE,GAAG;wBACP,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;wBACxF,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;wBAC1B,gBAAgB,EAAE,EAAE;qBACrB;iBACF;aACF;SACF;KACF,CAAC;IAEF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpC,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,QAAQ,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE;KAC3D,CAAC,CAAC,CAAC;IACJ,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,EAAE,YAAY,CAAC,EAAE,KAAK,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,gCAAgC,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;AACpE,CAAC;AAED,YAAY,EAAE,CAAC;AACf,UAAU,EAAE,CAAC;AACb,YAAY,EAAE,CAAC;AACf,cAAc,EAAE,CAAC;AACjB,qBAAqB,EAAE,CAAC;AACxB,kBAAkB,EAAE,CAAC","sourcesContent":["/**\n * Generate the cross-implementation conformance corpus under repo-root\n * `conformance/`. The TypeScript package is the reference implementation, so\n * the goldens it emits are what the Rust crate must reproduce byte-for-byte\n * (structurally). Run via `npm run gen:conformance`; CI regenerates and asserts\n * `git diff --exit-code conformance/` is clean.\n *\n * Outputs:\n * - `conformance/normalize.json` — `[{ input, expected }]` for normalizeName.\n * - `conformance/roster/<case>/expected.roster.json` — the resolved Roster.\n * - `conformance/roster/<case>/expected.<fmt>.{txt,json}` — every export\n * target's golden output. The TS exporter is the oracle; the Rust mirror\n * asserts byte-equal output for the same Roster.\n * - `conformance/roster/<case>/input.newrecruit-{wtc-compact,wtc-full,simple}.txt`\n * — text inputs derived from the seed by the exporter, so a re-import\n * regression in either implementation surfaces immediately.\n *\n * Seeding: each `<case>/` carries one canonical input — either the legacy\n * `input.json` (ListForge) or `input.newrecruit-json.json` (NewRecruit). Other\n * inputs are derived.\n */\nimport { readdirSync, readFileSync, writeFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { Dataset } from \"./data/dataset.js\";\nimport { normalizeName } from \"./data/normalize.js\";\nimport { describeScoringCard } from \"./translate/index.js\";\nimport { exportRoster, type ExportFormat } from \"./export/index.js\";\nimport { importRoster, REGISTERED_ADAPTERS } from \"./import/import-roster.js\";\nimport { selectAdapter } from \"./import/adapter.js\";\nimport type { ParsedRoster, Roster } from \"./import/types.js\";\nimport { attributeStages } from \"./cruncher/attribution.js\";\nimport type { EngineInput } from \"./cruncher/index.js\";\nimport {\n resolveLayout,\n type TerrainTemplate as ResolverTemplate,\n type TerrainLayout as ResolverLayout,\n} from \"./terrain/resolve.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst REPO_ROOT = join(__dirname, \"../..\");\nconst CONFORMANCE = join(REPO_ROOT, \"conformance\");\n\nconst NORMALIZE_INPUTS = [\n // NFD diacritic strip\n \"Khârn the Betrayer\",\n \"Brôkhyr\",\n \"Ûthar\",\n \"Magnús\",\n // apostrophe / quote variants\n \"T'au\",\n \"Be’lakor\",\n \"Kor’sarro Khan\",\n \"Aetaos'rau'keres\",\n \"‘quoted’\",\n // whitespace / hyphen collapse + trim\n \"Brôkhyr Iron-master\",\n \" the betrayer \",\n \"space--marines\",\n // casefold\n \"KHÂRN THE BETRAYER\",\n // already-normalized (idempotence)\n \"kharn the betrayer\",\n // distinctness anchors (must NOT collapse together)\n \"Khorne\",\n \"Khârn\",\n // Unicode whitespace beyond ASCII — every Unicode whitespace must collapse\n // identically across implementations or `find(\"Khorne Lord\")` and\n // `find(\"Khorne Lord\")` will silently disagree across ports.\n \"Khorne Lord\",\n \"Khorne Lord\",\n // Turkish dotted-I: NFD decomposes to `I` + combining dot above; the dot is\n // stripped, then locale-independent lowercase yields `i`. The case pins that\n // no implementation introduces locale-aware casefolding (which would map\n // `I` → `ı` under Turkish locale and break ASCII-text search).\n \"İmperial Fists\",\n // Zero-width joiner: passes through every step today. Pinned so behavior\n // does not silently change — if a future commit strips Cf-category chars,\n // this golden updates in the same PR.\n \"KhorneLord\",\n];\n\nfunction writeJson(path: string, value: unknown): void {\n writeFileSync(path, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nfunction writeText(path: string, value: string): void {\n writeFileSync(path, value);\n}\n\nfunction genNormalize(): void {\n const table = NORMALIZE_INPUTS.map((input) => ({ input, expected: normalizeName(input) }));\n writeJson(join(CONFORMANCE, \"normalize.json\"), table);\n console.log(`normalize.json: ${table.length} cases`);\n}\n\n/** Locate the canonical input for a fixture dir: prefer `input.json` (legacy\n * ListForge), then `input.newrecruit-json.json` (NewRecruit), then the\n * text-only `input.gw.txt` (GW app export — import-only, like ListForge). */\nfunction seedRoster(caseDir: string, ds: Dataset): Roster {\n const decoded = decodeCanonicalSeed(caseDir);\n return importRoster(decoded, { dataset: ds });\n}\n\n/** Return the decoded payload for the canonical seed — the same value the\n * import pipeline would dispatch on. JSON seeds come back parsed; text seeds\n * come back as the raw string. */\nfunction decodeCanonicalSeed(caseDir: string): unknown {\n const jsonSeed = join(caseDir, \"input.json\");\n if (existsSync(jsonSeed)) {\n return JSON.parse(readFileSync(jsonSeed, \"utf8\"));\n }\n const nrSeed = join(caseDir, \"input.newrecruit-json.json\");\n if (existsSync(nrSeed)) {\n return JSON.parse(readFileSync(nrSeed, \"utf8\"));\n }\n const gwSeed = join(caseDir, \"input.gw.txt\");\n if (existsSync(gwSeed)) {\n return readFileSync(gwSeed, \"utf8\");\n }\n throw new Error(`no canonical input found in ${caseDir}`);\n}\n\n/** Run a decoded payload through the adapter pipeline up to (but not past)\n * resolution. The result is the format-agnostic ParsedRoster — the same\n * intermediate the resolver consumes. Pinning this layer surfaces parser\n * regressions even when resolution masks them. */\nfunction parsedFromCanonicalSeed(caseDir: string): ParsedRoster {\n const decoded = decodeCanonicalSeed(caseDir);\n const adapter = selectAdapter(decoded, [...REGISTERED_ADAPTERS]);\n return adapter.parse(decoded);\n}\n\nconst TEXT_FORMATS: { format: ExportFormat; inputName: string; goldenName: string }[] = [\n {\n format: \"newrecruit-wtc-compact\",\n inputName: \"input.newrecruit-wtc-compact.txt\",\n goldenName: \"expected.newrecruit-wtc-compact.txt\",\n },\n {\n format: \"newrecruit-wtc-full\",\n inputName: \"input.newrecruit-wtc-full.txt\",\n goldenName: \"expected.newrecruit-wtc-full.txt\",\n },\n {\n format: \"newrecruit-simple\",\n inputName: \"input.newrecruit-simple.txt\",\n goldenName: \"expected.newrecruit-simple.txt\",\n },\n];\n\nfunction genRosters(): void {\n const ds = Dataset.embedded();\n const rosterDir = join(CONFORMANCE, \"roster\");\n for (const entry of readdirSync(rosterDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const caseDir = join(rosterDir, entry.name);\n\n const seed = seedRoster(caseDir, ds);\n writeJson(join(caseDir, \"expected.roster.json\"), seed);\n\n // Parsed-stage golden — the intermediate ParsedRoster produced by the\n // adapter for the canonical seed, before resolution. Catches parser bugs\n // that resolution would otherwise mask (e.g. wrong unit count from a\n // duplicate cost line that resolves to the same unit twice).\n writeJson(join(caseDir, \"expected.parsed.json\"), parsedFromCanonicalSeed(caseDir));\n\n // JSON export golden — NewRecruit-shaped skeleton.\n const jsonOut = exportRoster(seed, \"newrecruit-json\");\n writeJson(join(caseDir, \"expected.newrecruit-json.json\"), JSON.parse(jsonOut));\n\n // Canonical Roster JSON export — should equal the resolved roster.\n writeJson(join(caseDir, \"expected.roster-json.json\"), JSON.parse(exportRoster(seed, \"roster-json\")));\n\n // Text exports: always write the export golden so every fixture exercises\n // the cross-implementation byte-equality check. Only write the\n // `input.*.txt` round-trip seed when the fixture was authored for the\n // NewRecruit pipeline — legacy ListForge fixtures carry decoration\n // (multi-force warnings, leader-attachment inference) that the simple/wtc\n // exporters can't fully preserve, so the round-trip would fail\n // structurally rather than uncover a parser bug.\n const isNewRecruitSeed = existsSync(join(caseDir, \"input.newrecruit-json.json\"));\n for (const { format, inputName, goldenName } of TEXT_FORMATS) {\n const out = exportRoster(seed, format);\n writeText(join(caseDir, goldenName), out);\n if (isNewRecruitSeed) {\n writeText(join(caseDir, inputName), out);\n }\n }\n\n // Rosterizer JSON export + a derived round-trip input. The exporter is\n // deterministic and round-trips through the adapter, so emitting it as\n // both `expected.rosterizer.json` and `input.rosterizer.json` pins the\n // cross-implementation goldens and the importer regression at the same\n // time. Same NewRecruit-seed gate as the text formats — multi-force\n // ListForge fixtures lose their provisional leader-attachment under\n // round-trip, so they only get the export golden, not the derived input.\n const rosterizerOut = exportRoster(seed, \"rosterizer\");\n writeJson(join(caseDir, \"expected.rosterizer.json\"), JSON.parse(rosterizerOut));\n if (isNewRecruitSeed) {\n writeJson(join(caseDir, \"input.rosterizer.json\"), JSON.parse(rosterizerOut));\n }\n\n console.log(\n `roster/${entry.name}: ${seed.units.length} units, ${seed.diagnostics.warnings.length} warnings`,\n );\n }\n}\n\n/**\n * Linked-API query cases. Each descriptor names a query method on Dataset, the\n * args to call it with, and how the result should be compared.\n *\n * `comparison: \"ordered\"` pins the result order — used for queries that iterate\n * a data-driven array (`unit.ability_ids`, `unit.weapon_ids`) where order is\n * encoded in the data and both implementations iterate it the same way.\n *\n * `comparison: \"set\"` pins only the set of ids — used for queries that walk an\n * index (faction → abilities, ability → phases) where iteration order depends\n * on dataset bundler internals and is incidental. Ids are sorted before\n * comparison.\n *\n * `comparison: \"scalar\"` pins a single id-or-null result (find_* and\n * faction_of(unit)).\n */\ntype LinkedApiQuery =\n | { name: string; query: \"find_unit\"; args: { query: string }; comparison: \"scalar\" }\n | { name: string; query: \"find_weapon\"; args: { query: string }; comparison: \"scalar\" }\n | { name: string; query: \"find_faction\"; args: { query: string }; comparison: \"scalar\" }\n | { name: string; query: \"find_ability\"; args: { query: string }; comparison: \"scalar\" }\n | { name: string; query: \"abilities_of\"; args: { unitId: string }; comparison: \"ordered\" }\n | { name: string; query: \"weapons_of\"; args: { unitId: string }; comparison: \"ordered\" }\n | { name: string; query: \"phases_of\"; args: { abilityId: string }; comparison: \"set\" }\n | { name: string; query: \"faction_of\"; args: { unitId: string }; comparison: \"scalar\" }\n | { name: string; query: \"abilities_of_faction\"; args: { factionId: string }; comparison: \"set\" }\n | { name: string; query: \"weapons_of_faction\"; args: { factionId: string }; comparison: \"set\" };\n\nconst LINKED_API_QUERIES: LinkedApiQuery[] = [\n // find_unit: diacritic-insensitive lookup, miss returns null.\n { name: \"find_unit by diacritic name\", query: \"find_unit\", args: { query: \"Kharn\" }, comparison: \"scalar\" },\n { name: \"find_unit miss returns null\", query: \"find_unit\", args: { query: \"not-a-real-unit-xyz\" }, comparison: \"scalar\" },\n // find_weapon: hyphen + space tolerance.\n { name: \"find_weapon by name\", query: \"find_weapon\", args: { query: \"bolt rifle\" }, comparison: \"scalar\" },\n // find_faction: punctuation/diacritic tolerance.\n { name: \"find_faction by display name\", query: \"find_faction\", args: { query: \"World Eaters\" }, comparison: \"scalar\" },\n // find_ability: ability name lookup.\n { name: \"find_ability by name\", query: \"find_ability\", args: { query: \"Berzerker Frenzy\" }, comparison: \"scalar\" },\n // abilities_of(unit): ordered, iterates unit.ability_ids array.\n { name: \"abilities_of intercessor-squad\", query: \"abilities_of\", args: { unitId: \"intercessor-squad\" }, comparison: \"ordered\" },\n { name: \"abilities_of kharn-the-betrayer\", query: \"abilities_of\", args: { unitId: \"kharn-the-betrayer\" }, comparison: \"ordered\" },\n // weapons_of(unit): ordered, iterates unit.weapon_ids array.\n { name: \"weapons_of intercessor-squad\", query: \"weapons_of\", args: { unitId: \"intercessor-squad\" }, comparison: \"ordered\" },\n { name: \"weapons_of kharn-the-betrayer\", query: \"weapons_of\", args: { unitId: \"kharn-the-betrayer\" }, comparison: \"ordered\" },\n // phases_of(ability): compared as set (phase index iteration order is incidental).\n { name: \"phases_of berzerker-frenzy\", query: \"phases_of\", args: { abilityId: \"berzerker-frenzy\" }, comparison: \"set\" },\n // faction_of(unit): scalar id or null.\n { name: \"faction_of intercessor-squad\", query: \"faction_of\", args: { unitId: \"intercessor-squad\" }, comparison: \"scalar\" },\n // abilities_of_faction: compared as set (collection-index order is incidental).\n { name: \"abilities_of_faction world-eaters\", query: \"abilities_of_faction\", args: { factionId: \"world-eaters\" }, comparison: \"set\" },\n // weapons_of_faction: compared as set.\n { name: \"weapons_of_faction world-eaters\", query: \"weapons_of_faction\", args: { factionId: \"world-eaters\" }, comparison: \"set\" },\n];\n\nfunction genLinkedApi(): void {\n const ds = Dataset.embedded();\n const cases = LINKED_API_QUERIES.map((q) => {\n const expected = runLinkedQuery(ds, q);\n return { ...q, expected };\n });\n writeJson(join(CONFORMANCE, \"linked-api\", \"cases.json\"), cases);\n console.log(`linked-api/cases.json: ${cases.length} cases`);\n}\n\nfunction runLinkedQuery(ds: Dataset, q: LinkedApiQuery): string | null | string[] {\n switch (q.query) {\n case \"find_unit\":\n return ds.units.find(q.args.query)?.id ?? null;\n case \"find_weapon\":\n return ds.weapons.find(q.args.query)?.id ?? null;\n case \"find_faction\":\n return ds.factions.find(q.args.query)?.id ?? null;\n case \"find_ability\":\n return ds.abilities.find(q.args.query)?.id ?? null;\n case \"abilities_of\": {\n const u = ds.units.get(q.args.unitId);\n if (!u) throw new Error(`abilities_of: unknown unit ${q.args.unitId}`);\n return u.abilities.map((a) => a.id);\n }\n case \"weapons_of\": {\n const u = ds.units.get(q.args.unitId);\n if (!u) throw new Error(`weapons_of: unknown unit ${q.args.unitId}`);\n return u.weapons.map((w) => w.id);\n }\n case \"phases_of\": {\n const a = ds.abilities.get(q.args.abilityId);\n if (!a) throw new Error(`phases_of: unknown ability ${q.args.abilityId}`);\n return [...a.phases].sort();\n }\n case \"faction_of\": {\n const u = ds.units.get(q.args.unitId);\n if (!u) throw new Error(`faction_of: unknown unit ${q.args.unitId}`);\n return u.faction?.id ?? null;\n }\n case \"abilities_of_faction\":\n return ds.abilities.byFaction(q.args.factionId).map((a) => a.id).sort();\n case \"weapons_of_faction\": {\n // Mirrors Rust `weapons_of_faction`: aggregate weapons across the\n // faction's units and dedupe by id. The collection-level\n // `weapons.byFaction()` is a different operation (it looks up weapons\n // whose own `faction_id` is set, which is empty for most factions).\n const f = ds.factions.get(q.args.factionId);\n if (!f) throw new Error(`weapons_of_faction: unknown faction ${q.args.factionId}`);\n return f.weapons.map((w) => w.id).sort();\n }\n }\n}\n\n/**\n * Attribution corpus: reuses the existing cruncher inputs from the cases that\n * carry at least one groupable buff (ability or manual). The expected shape\n * is the AttributedStage array produced by attributeStages; both\n * implementations of the leave-one-out decomposition must reproduce it\n * within the per-stage float tolerance.\n */\nconst ATTRIBUTION_CASE_FILES = [\n \"05-anti-infantry-vs-cultist.json\",\n \"07-twin-linked-heavy-stationary-vs-knight.json\",\n];\n\ninterface CruncherCaseInput {\n name: string;\n attacker: { weaponId: string; profileIndex: number };\n modelsFiring: number;\n target: { unitId: string; profileIndex: number; modelCount?: number };\n context: EngineInput[\"context\"];\n buffs: EngineInput[\"buffs\"];\n}\n\nfunction loadAttributionInput(ds: Dataset, filename: string): {\n name: string;\n input: EngineInput;\n} {\n const path = join(CONFORMANCE, \"cruncher\", filename);\n const c = JSON.parse(readFileSync(path, \"utf8\")) as CruncherCaseInput;\n const weapon = ds.weapons.get(c.attacker.weaponId);\n const unit = ds.units.get(c.target.unitId);\n if (!weapon) throw new Error(`attribution: unknown weapon ${c.attacker.weaponId}`);\n if (!unit) throw new Error(`attribution: unknown unit ${c.target.unitId}`);\n return {\n name: c.name,\n input: {\n attacker: { weapon: weapon.raw, profileIndex: c.attacker.profileIndex },\n target: {\n unit: unit.raw,\n profileIndex: c.target.profileIndex,\n ...(c.target.modelCount !== undefined ? { modelCount: c.target.modelCount } : {}),\n },\n modelsFiring: c.modelsFiring,\n buffs: c.buffs,\n context: c.context,\n },\n };\n}\n\nfunction genAttribution(): void {\n const ds = Dataset.embedded();\n const cases = ATTRIBUTION_CASE_FILES.map((filename, idx) => {\n const { name, input } = loadAttributionInput(ds, filename);\n const stages = attributeStages(input, ds);\n return {\n // Persist the input by file reference so the corpus stays a single\n // source of truth — the cruncher case file already pins the EngineInput.\n name,\n cruncher_case: filename,\n expected: stages.map((s) => ({\n name: s.name,\n expected: s.expected,\n baseline: s.baseline,\n lifts: s.lifts.map((l) => ({ source: l.source, delta: l.delta })),\n residual: s.residual,\n intrinsics: s.intrinsics,\n })),\n // Stable ordering of cases in the corpus file.\n _order: idx,\n };\n });\n // Sort by _order and strip the helper before writing.\n cases.sort((a, b) => a._order - b._order);\n const serialised = cases.map(({ _order: _o, ...rest }) => rest);\n writeJson(join(CONFORMANCE, \"attribution\", \"cases.json\"), serialised);\n console.log(`attribution/cases.json: ${cases.length} cases`);\n}\n\n/**\n * Scoring-card translation corpus: humanize each primary mission card's\n * `awards` into plain English. The TS translator is the oracle; the Rust port\n * must reproduce every string byte-for-byte (the differ compares structurally,\n * no tolerance). Only `card_type: \"primary\"` cards are pinned — the 14-card\n * secondary deck isn't revealed yet. Cases are sorted by id for stability, and\n * the `awards` array order within each card is load-bearing.\n */\nfunction genScoringTranslation(): void {\n const ds = Dataset.embedded();\n mkdirSync(join(CONFORMANCE, \"scoring-translation\"), { recursive: true });\n // Pin the translation of every mission card's awards — primary and secondary\n // alike (the secondary deck has the same `awards` shape and deserves the same\n // cross-impl pinning).\n const cases = ds.missionCards.all\n .filter((c) => c.card_type === \"primary\" || c.card_type === \"secondary\")\n .slice()\n .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n .map((card) => ({ cardId: card.id, expected: { awards: describeScoringCard(card) } }));\n writeJson(join(CONFORMANCE, \"scoring-translation\", \"cases.json\"), cases);\n console.log(`scoring-translation/cases.json: ${cases.length} cases`);\n}\n\n/**\n * Terrain-resolver corpus: resolve template-anchored layouts to absolute\n * board-space vertices (y-down inches). The TS resolver is the oracle; the Rust\n * port must reproduce every vertex within 5e-4 (per-area invariant in\n * CONFORMANCE.md). Cases are self-contained — each carries its own `templates`\n * and `layout` — so the corpus does not depend on the bundled catalog and the\n * runner op can pass both in `args`. Coverage: per-template centroid anchoring\n * (identity), cardinal + oblique rotations, both mirror axes on an asymmetric\n * shape, embedded-feature composition, explicit parenting, and the inline\n * footprint escape hatch.\n */\nfunction genTerrainResolver(): void {\n mkdirSync(join(CONFORMANCE, \"terrain-resolver\"), { recursive: true });\n\n const areaLarge: ResolverTemplate = {\n id: \"area-large\",\n name: \"Large Area\",\n kind: \"area\",\n footprint: { type: \"rectangle\", width: 11.5, height: 7 },\n };\n const areaMedium: ResolverTemplate = {\n id: \"area-medium\",\n name: \"Medium Area\",\n kind: \"area\",\n footprint: { type: \"rectangle\", width: 6, height: 4 },\n };\n const areaTrapezoid: ResolverTemplate = {\n id: \"area-trapezoid\",\n name: \"Trapezoid Area\",\n kind: \"area\",\n footprint: {\n type: \"polygon\",\n points: [\n { x: 0, y: 0 },\n { x: 8, y: 0 },\n { x: 2, y: 11.5 },\n { x: 0, y: 11.5 },\n ],\n },\n };\n const wedge: ResolverTemplate = {\n id: \"wedge\",\n name: \"Right Wedge\",\n kind: \"area\",\n footprint: { type: \"right-triangle\", width: 8, height: 11.5 },\n };\n const wallLong: ResolverTemplate = {\n id: \"wall-long\",\n name: \"Long Wall\",\n kind: \"feature\",\n footprint: { type: \"rectangle\", width: 7, height: 0.25 },\n };\n const ruinComposed: ResolverTemplate = {\n id: \"ruin-composed\",\n name: \"Composed Ruin\",\n kind: \"area\",\n footprint: { type: \"rectangle\", width: 11.5, height: 7 },\n features: [\n { id: \"back-wall\", template: \"wall-long\", position: { x: 0, y: -3 } },\n { id: \"side-wall\", template: \"wall-long\", position: { x: -5, y: 0 }, rotation_degrees: 90, mirror: \"horizontal\" },\n ],\n };\n\n const baseCatalog = [areaLarge, areaMedium, areaTrapezoid, wedge, wallLong];\n\n const layoutCases: { name: string; templates: ResolverTemplate[]; layout: ResolverLayout }[] = [\n {\n name: \"identity-large\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-large\", position: { x: 30, y: 22 } }] },\n },\n {\n name: \"identity-wedge\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"wedge\", position: { x: 12, y: 30 } }] },\n },\n {\n name: \"identity-trapezoid\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-trapezoid\", position: { x: 40, y: 18 } }] },\n },\n {\n name: \"rotate-medium-90\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-medium\", position: { x: 30, y: 22 }, rotation_degrees: 90 }] },\n },\n {\n name: \"rotate-medium-180\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-medium\", position: { x: 30, y: 22 }, rotation_degrees: 180 }] },\n },\n {\n name: \"rotate-medium-270\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-medium\", position: { x: 30, y: 22 }, rotation_degrees: 270 }] },\n },\n {\n name: \"rotate-large-oblique-55\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-large\", position: { x: 30, y: 22 }, rotation_degrees: 55 }] },\n },\n {\n name: \"rotate-trapezoid-oblique-235\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-trapezoid\", position: { x: 35.75, y: 27 }, rotation_degrees: 235 }] },\n },\n {\n name: \"mirror-trapezoid-horizontal\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-trapezoid\", position: { x: 40, y: 18 }, mirror: \"horizontal\" }] },\n },\n {\n name: \"mirror-trapezoid-vertical-rot90\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-trapezoid\", position: { x: 40, y: 18 }, rotation_degrees: 90, mirror: \"vertical\" }] },\n },\n {\n name: \"composition-ruin-rot90-mirror-h\",\n templates: [ruinComposed, wallLong],\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"a1\", template: \"ruin-composed\", position: { x: 30, y: 22 }, rotation_degrees: 90, mirror: \"horizontal\" }] },\n },\n {\n name: \"explicit-parent-feature\",\n templates: [areaLarge, wallLong],\n layout: {\n id: \"c\",\n name: \"c\",\n pieces: [\n { id: \"a1\", template: \"area-large\", position: { x: 30, y: 22 }, rotation_degrees: 90, mirror: \"horizontal\" },\n { id: \"back-wall\", template: \"wall-long\", parent_area_id: \"a1\", position: { x: 0, y: -3 } },\n ],\n },\n },\n {\n name: \"inline-footprint-polygon\",\n templates: [],\n layout: {\n id: \"c\",\n name: \"c\",\n pieces: [\n {\n id: \"p\",\n footprint: { type: \"polygon\", points: [{ x: 0, y: 0 }, { x: 4, y: 0 }, { x: 2, y: 5 }] },\n position: { x: 50, y: 40 },\n rotation_degrees: 30,\n },\n ],\n },\n },\n ];\n\n const cases = layoutCases.map((c) => ({\n name: c.name,\n templates: c.templates,\n layout: c.layout,\n expected: { pieces: resolveLayout(c.layout, c.templates) },\n }));\n writeJson(join(CONFORMANCE, \"terrain-resolver\", \"cases.json\"), cases);\n console.log(`terrain-resolver/cases.json: ${cases.length} cases`);\n}\n\ngenNormalize();\ngenRosters();\ngenLinkedApi();\ngenAttribution();\ngenScoringTranslation();\ngenTerrainResolver();\n"]}
|
|
1
|
+
{"version":3,"file":"gen-conformance.js","sourceRoot":"","sources":["../src/gen-conformance.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generate the cross-implementation conformance corpus under repo-root\n * `conformance/`. The TypeScript package is the reference implementation, so\n * the goldens it emits are what the Rust crate must reproduce byte-for-byte\n * (structurally). Run via `npm run gen:conformance`; CI regenerates and asserts\n * `git diff --exit-code conformance/` is clean.\n *\n * Outputs:\n * - `conformance/normalize.json` — `[{ input, expected }]` for normalizeName.\n * - `conformance/roster/<case>/expected.roster.json` — the resolved Roster.\n * - `conformance/roster/<case>/expected.<fmt>.{txt,json}` — every export\n * target's golden output. The TS exporter is the oracle; the Rust mirror\n * asserts byte-equal output for the same Roster.\n * - `conformance/roster/<case>/input.newrecruit-{wtc-compact,wtc-full,simple}.txt`\n * — text inputs derived from the seed by the exporter, so a re-import\n * regression in either implementation surfaces immediately.\n *\n * Seeding: each `<case>/` carries one canonical input — either the legacy\n * `input.json` (ListForge) or `input.newrecruit-json.json` (NewRecruit). Other\n * inputs are derived.\n */\nimport { readdirSync, readFileSync, writeFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { Dataset } from \"./data/dataset.js\";\nimport { normalizeName } from \"./data/normalize.js\";\nimport { describeScoringCard } from \"./translate/index.js\";\nimport { exportRoster, type ExportFormat } from \"./export/index.js\";\nimport { importRoster, REGISTERED_ADAPTERS } from \"./import/import-roster.js\";\nimport { selectAdapter } from \"./import/adapter.js\";\nimport type { ParsedRoster, Roster } from \"./import/types.js\";\nimport { encodeBase } from \"./runner.js\";\nimport { attributeStages } from \"./cruncher/attribution.js\";\nimport type { EngineInput } from \"./cruncher/index.js\";\nimport {\n resolveLayout,\n type TerrainTemplate as ResolverTemplate,\n type TerrainLayout as ResolverLayout,\n} from \"./terrain/resolve.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst REPO_ROOT = join(__dirname, \"../..\");\nconst CONFORMANCE = join(REPO_ROOT, \"conformance\");\n\nconst NORMALIZE_INPUTS = [\n // NFD diacritic strip\n \"Khârn the Betrayer\",\n \"Brôkhyr\",\n \"Ûthar\",\n \"Magnús\",\n // apostrophe / quote variants\n \"T'au\",\n \"Be’lakor\",\n \"Kor’sarro Khan\",\n \"Aetaos'rau'keres\",\n \"‘quoted’\",\n // whitespace / hyphen collapse + trim\n \"Brôkhyr Iron-master\",\n \" the betrayer \",\n \"space--marines\",\n // casefold\n \"KHÂRN THE BETRAYER\",\n // already-normalized (idempotence)\n \"kharn the betrayer\",\n // distinctness anchors (must NOT collapse together)\n \"Khorne\",\n \"Khârn\",\n // Unicode whitespace beyond ASCII — every Unicode whitespace must collapse\n // identically across implementations or `find(\"Khorne Lord\")` and\n // `find(\"Khorne Lord\")` will silently disagree across ports.\n \"Khorne Lord\",\n \"Khorne Lord\",\n // Turkish dotted-I: NFD decomposes to `I` + combining dot above; the dot is\n // stripped, then locale-independent lowercase yields `i`. The case pins that\n // no implementation introduces locale-aware casefolding (which would map\n // `I` → `ı` under Turkish locale and break ASCII-text search).\n \"İmperial Fists\",\n // Zero-width joiner: passes through every step today. Pinned so behavior\n // does not silently change — if a future commit strips Cf-category chars,\n // this golden updates in the same PR.\n \"KhorneLord\",\n];\n\nfunction writeJson(path: string, value: unknown): void {\n writeFileSync(path, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nfunction writeText(path: string, value: string): void {\n writeFileSync(path, value);\n}\n\nfunction genNormalize(): void {\n const table = NORMALIZE_INPUTS.map((input) => ({ input, expected: normalizeName(input) }));\n writeJson(join(CONFORMANCE, \"normalize.json\"), table);\n console.log(`normalize.json: ${table.length} cases`);\n}\n\n/** Locate the canonical input for a fixture dir: prefer `input.json` (legacy\n * ListForge), then `input.newrecruit-json.json` (NewRecruit), then the\n * text-only `input.gw.txt` (GW app export — import-only, like ListForge). */\nfunction seedRoster(caseDir: string, ds: Dataset): Roster {\n const decoded = decodeCanonicalSeed(caseDir);\n return importRoster(decoded, { dataset: ds });\n}\n\n/** Return the decoded payload for the canonical seed — the same value the\n * import pipeline would dispatch on. JSON seeds come back parsed; text seeds\n * come back as the raw string. */\nfunction decodeCanonicalSeed(caseDir: string): unknown {\n const jsonSeed = join(caseDir, \"input.json\");\n if (existsSync(jsonSeed)) {\n return JSON.parse(readFileSync(jsonSeed, \"utf8\"));\n }\n const nrSeed = join(caseDir, \"input.newrecruit-json.json\");\n if (existsSync(nrSeed)) {\n return JSON.parse(readFileSync(nrSeed, \"utf8\"));\n }\n const gwSeed = join(caseDir, \"input.gw.txt\");\n if (existsSync(gwSeed)) {\n return readFileSync(gwSeed, \"utf8\");\n }\n throw new Error(`no canonical input found in ${caseDir}`);\n}\n\n/** Run a decoded payload through the adapter pipeline up to (but not past)\n * resolution. The result is the format-agnostic ParsedRoster — the same\n * intermediate the resolver consumes. Pinning this layer surfaces parser\n * regressions even when resolution masks them. */\nfunction parsedFromCanonicalSeed(caseDir: string): ParsedRoster {\n const decoded = decodeCanonicalSeed(caseDir);\n const adapter = selectAdapter(decoded, [...REGISTERED_ADAPTERS]);\n return adapter.parse(decoded);\n}\n\nconst TEXT_FORMATS: { format: ExportFormat; inputName: string; goldenName: string }[] = [\n {\n format: \"newrecruit-wtc-compact\",\n inputName: \"input.newrecruit-wtc-compact.txt\",\n goldenName: \"expected.newrecruit-wtc-compact.txt\",\n },\n {\n format: \"newrecruit-wtc-full\",\n inputName: \"input.newrecruit-wtc-full.txt\",\n goldenName: \"expected.newrecruit-wtc-full.txt\",\n },\n {\n format: \"newrecruit-simple\",\n inputName: \"input.newrecruit-simple.txt\",\n goldenName: \"expected.newrecruit-simple.txt\",\n },\n];\n\nfunction genRosters(): void {\n const ds = Dataset.embedded();\n const rosterDir = join(CONFORMANCE, \"roster\");\n for (const entry of readdirSync(rosterDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const caseDir = join(rosterDir, entry.name);\n\n const seed = seedRoster(caseDir, ds);\n writeJson(join(caseDir, \"expected.roster.json\"), seed);\n\n // Parsed-stage golden — the intermediate ParsedRoster produced by the\n // adapter for the canonical seed, before resolution. Catches parser bugs\n // that resolution would otherwise mask (e.g. wrong unit count from a\n // duplicate cost line that resolves to the same unit twice).\n writeJson(join(caseDir, \"expected.parsed.json\"), parsedFromCanonicalSeed(caseDir));\n\n // JSON export golden — NewRecruit-shaped skeleton.\n const jsonOut = exportRoster(seed, \"newrecruit-json\");\n writeJson(join(caseDir, \"expected.newrecruit-json.json\"), JSON.parse(jsonOut));\n\n // Canonical Roster JSON export — should equal the resolved roster.\n writeJson(join(caseDir, \"expected.roster-json.json\"), JSON.parse(exportRoster(seed, \"roster-json\")));\n\n // Text exports: always write the export golden so every fixture exercises\n // the cross-implementation byte-equality check. Only write the\n // `input.*.txt` round-trip seed when the fixture was authored for the\n // NewRecruit pipeline — legacy ListForge fixtures carry decoration\n // (multi-force warnings, leader-attachment inference) that the simple/wtc\n // exporters can't fully preserve, so the round-trip would fail\n // structurally rather than uncover a parser bug.\n const isNewRecruitSeed = existsSync(join(caseDir, \"input.newrecruit-json.json\"));\n for (const { format, inputName, goldenName } of TEXT_FORMATS) {\n const out = exportRoster(seed, format);\n writeText(join(caseDir, goldenName), out);\n if (isNewRecruitSeed) {\n writeText(join(caseDir, inputName), out);\n }\n }\n\n // Rosterizer JSON export + a derived round-trip input. The exporter is\n // deterministic and round-trips through the adapter, so emitting it as\n // both `expected.rosterizer.json` and `input.rosterizer.json` pins the\n // cross-implementation goldens and the importer regression at the same\n // time. Same NewRecruit-seed gate as the text formats — multi-force\n // ListForge fixtures lose their provisional leader-attachment under\n // round-trip, so they only get the export golden, not the derived input.\n const rosterizerOut = exportRoster(seed, \"rosterizer\");\n writeJson(join(caseDir, \"expected.rosterizer.json\"), JSON.parse(rosterizerOut));\n if (isNewRecruitSeed) {\n writeJson(join(caseDir, \"input.rosterizer.json\"), JSON.parse(rosterizerOut));\n }\n\n console.log(\n `roster/${entry.name}: ${seed.units.length} units, ${seed.diagnostics.warnings.length} warnings`,\n );\n }\n}\n\n/**\n * Linked-API query cases. Each descriptor names a query method on Dataset, the\n * args to call it with, and how the result should be compared.\n *\n * `comparison: \"ordered\"` pins the result order — used for queries that iterate\n * a data-driven array (`unit.ability_ids`, `unit.weapon_ids`) where order is\n * encoded in the data and both implementations iterate it the same way.\n *\n * `comparison: \"set\"` pins only the set of ids — used for queries that walk an\n * index (faction → abilities, ability → phases) where iteration order depends\n * on dataset bundler internals and is incidental. Ids are sorted before\n * comparison.\n *\n * `comparison: \"scalar\"` pins a single id-or-null result (find_* and\n * faction_of(unit)).\n */\ntype LinkedApiQuery =\n | { name: string; query: \"find_unit\"; args: { query: string }; comparison: \"scalar\" }\n | { name: string; query: \"find_weapon\"; args: { query: string }; comparison: \"scalar\" }\n | { name: string; query: \"find_faction\"; args: { query: string }; comparison: \"scalar\" }\n | { name: string; query: \"find_ability\"; args: { query: string }; comparison: \"scalar\" }\n | { name: string; query: \"abilities_of\"; args: { unitId: string }; comparison: \"ordered\" }\n | { name: string; query: \"weapons_of\"; args: { unitId: string }; comparison: \"ordered\" }\n | { name: string; query: \"phases_of\"; args: { abilityId: string }; comparison: \"set\" }\n | { name: string; query: \"faction_of\"; args: { unitId: string }; comparison: \"scalar\" }\n | { name: string; query: \"abilities_of_faction\"; args: { factionId: string }; comparison: \"set\" }\n | { name: string; query: \"weapons_of_faction\"; args: { factionId: string }; comparison: \"set\" }\n | { name: string; query: \"base_size_of\"; args: { unitId: string }; comparison: \"scalar\" }\n | { name: string; query: \"model_bases_of\"; args: { unitId: string }; comparison: \"ordered\" };\n\nconst LINKED_API_QUERIES: LinkedApiQuery[] = [\n // find_unit: diacritic-insensitive lookup, miss returns null.\n { name: \"find_unit by diacritic name\", query: \"find_unit\", args: { query: \"Kharn\" }, comparison: \"scalar\" },\n { name: \"find_unit miss returns null\", query: \"find_unit\", args: { query: \"not-a-real-unit-xyz\" }, comparison: \"scalar\" },\n // find_weapon: hyphen + space tolerance.\n { name: \"find_weapon by name\", query: \"find_weapon\", args: { query: \"bolt rifle\" }, comparison: \"scalar\" },\n // find_faction: punctuation/diacritic tolerance.\n { name: \"find_faction by display name\", query: \"find_faction\", args: { query: \"World Eaters\" }, comparison: \"scalar\" },\n // find_ability: ability name lookup.\n { name: \"find_ability by name\", query: \"find_ability\", args: { query: \"Berzerker Frenzy\" }, comparison: \"scalar\" },\n // abilities_of(unit): ordered, iterates unit.ability_ids array.\n { name: \"abilities_of intercessor-squad\", query: \"abilities_of\", args: { unitId: \"intercessor-squad\" }, comparison: \"ordered\" },\n { name: \"abilities_of kharn-the-betrayer\", query: \"abilities_of\", args: { unitId: \"kharn-the-betrayer\" }, comparison: \"ordered\" },\n // weapons_of(unit): ordered, iterates unit.weapon_ids array.\n { name: \"weapons_of intercessor-squad\", query: \"weapons_of\", args: { unitId: \"intercessor-squad\" }, comparison: \"ordered\" },\n { name: \"weapons_of kharn-the-betrayer\", query: \"weapons_of\", args: { unitId: \"kharn-the-betrayer\" }, comparison: \"ordered\" },\n // phases_of(ability): compared as set (phase index iteration order is incidental).\n { name: \"phases_of berzerker-frenzy\", query: \"phases_of\", args: { abilityId: \"berzerker-frenzy\" }, comparison: \"set\" },\n // faction_of(unit): scalar id or null.\n { name: \"faction_of intercessor-squad\", query: \"faction_of\", args: { unitId: \"intercessor-squad\" }, comparison: \"scalar\" },\n // abilities_of_faction: compared as set (collection-index order is incidental).\n { name: \"abilities_of_faction world-eaters\", query: \"abilities_of_faction\", args: { factionId: \"world-eaters\" }, comparison: \"set\" },\n // weapons_of_faction: compared as set.\n { name: \"weapons_of_faction world-eaters\", query: \"weapons_of_faction\", args: { factionId: \"world-eaters\" }, comparison: \"set\" },\n // base_size_of(unit): scalar encoded base — round, oval, and a draft flying-base.\n { name: \"base_size_of intercessor-squad\", query: \"base_size_of\", args: { unitId: \"intercessor-squad\" }, comparison: \"scalar\" },\n { name: \"base_size_of vertus-praetors\", query: \"base_size_of\", args: { unitId: \"vertus-praetors\" }, comparison: \"scalar\" },\n { name: \"base_size_of windriders (draft flying base)\", query: \"base_size_of\", args: { unitId: \"windriders\" }, comparison: \"scalar\" },\n // model_bases_of(unit): ordered per-model bases; jakhals mixes 28.5mm bodies with a 40mm Dishonoured.\n { name: \"model_bases_of jakhals (mixed)\", query: \"model_bases_of\", args: { unitId: \"jakhals\" }, comparison: \"ordered\" },\n];\n\nfunction genLinkedApi(): void {\n const ds = Dataset.embedded();\n const cases = LINKED_API_QUERIES.map((q) => {\n const expected = runLinkedQuery(ds, q);\n return { ...q, expected };\n });\n writeJson(join(CONFORMANCE, \"linked-api\", \"cases.json\"), cases);\n console.log(`linked-api/cases.json: ${cases.length} cases`);\n}\n\nfunction runLinkedQuery(ds: Dataset, q: LinkedApiQuery): string | null | string[] {\n switch (q.query) {\n case \"find_unit\":\n return ds.units.find(q.args.query)?.id ?? null;\n case \"find_weapon\":\n return ds.weapons.find(q.args.query)?.id ?? null;\n case \"find_faction\":\n return ds.factions.find(q.args.query)?.id ?? null;\n case \"find_ability\":\n return ds.abilities.find(q.args.query)?.id ?? null;\n case \"abilities_of\": {\n const u = ds.units.get(q.args.unitId);\n if (!u) throw new Error(`abilities_of: unknown unit ${q.args.unitId}`);\n return u.abilities.map((a) => a.id);\n }\n case \"weapons_of\": {\n const u = ds.units.get(q.args.unitId);\n if (!u) throw new Error(`weapons_of: unknown unit ${q.args.unitId}`);\n return u.weapons.map((w) => w.id);\n }\n case \"phases_of\": {\n const a = ds.abilities.get(q.args.abilityId);\n if (!a) throw new Error(`phases_of: unknown ability ${q.args.abilityId}`);\n return [...a.phases].sort();\n }\n case \"faction_of\": {\n const u = ds.units.get(q.args.unitId);\n if (!u) throw new Error(`faction_of: unknown unit ${q.args.unitId}`);\n return u.faction?.id ?? null;\n }\n case \"abilities_of_faction\":\n return ds.abilities.byFaction(q.args.factionId).map((a) => a.id).sort();\n case \"weapons_of_faction\": {\n // Mirrors Rust `weapons_of_faction`: aggregate weapons across the\n // faction's units and dedupe by id. The collection-level\n // `weapons.byFaction()` is a different operation (it looks up weapons\n // whose own `faction_id` is set, which is empty for most factions).\n const f = ds.factions.get(q.args.factionId);\n if (!f) throw new Error(`weapons_of_faction: unknown faction ${q.args.factionId}`);\n return f.weapons.map((w) => w.id).sort();\n }\n case \"base_size_of\": {\n const u = ds.units.get(q.args.unitId);\n if (!u) throw new Error(`base_size_of: unknown unit ${q.args.unitId}`);\n return encodeBase(u.raw.base_size_mm);\n }\n case \"model_bases_of\": {\n const u = ds.units.get(q.args.unitId);\n if (!u) throw new Error(`model_bases_of: unknown unit ${q.args.unitId}`);\n const comp = ds.unitCompositions.find((c) => c.unit_id === q.args.unitId);\n return (comp?.models ?? []).map((m) => `${m.name}=${encodeBase(m.base_size_mm) ?? \"none\"}`);\n }\n }\n}\n\n/**\n * Attribution corpus: reuses the existing cruncher inputs from the cases that\n * carry at least one groupable buff (ability or manual). The expected shape\n * is the AttributedStage array produced by attributeStages; both\n * implementations of the leave-one-out decomposition must reproduce it\n * within the per-stage float tolerance.\n */\nconst ATTRIBUTION_CASE_FILES = [\n \"05-anti-infantry-vs-cultist.json\",\n \"07-twin-linked-heavy-stationary-vs-knight.json\",\n];\n\ninterface CruncherCaseInput {\n name: string;\n attacker: { weaponId: string; profileIndex: number };\n modelsFiring: number;\n target: { unitId: string; profileIndex: number; modelCount?: number };\n context: EngineInput[\"context\"];\n buffs: EngineInput[\"buffs\"];\n}\n\nfunction loadAttributionInput(ds: Dataset, filename: string): {\n name: string;\n input: EngineInput;\n} {\n const path = join(CONFORMANCE, \"cruncher\", filename);\n const c = JSON.parse(readFileSync(path, \"utf8\")) as CruncherCaseInput;\n const weapon = ds.weapons.get(c.attacker.weaponId);\n const unit = ds.units.get(c.target.unitId);\n if (!weapon) throw new Error(`attribution: unknown weapon ${c.attacker.weaponId}`);\n if (!unit) throw new Error(`attribution: unknown unit ${c.target.unitId}`);\n return {\n name: c.name,\n input: {\n attacker: { weapon: weapon.raw, profileIndex: c.attacker.profileIndex },\n target: {\n unit: unit.raw,\n profileIndex: c.target.profileIndex,\n ...(c.target.modelCount !== undefined ? { modelCount: c.target.modelCount } : {}),\n },\n modelsFiring: c.modelsFiring,\n buffs: c.buffs,\n context: c.context,\n },\n };\n}\n\nfunction genAttribution(): void {\n const ds = Dataset.embedded();\n const cases = ATTRIBUTION_CASE_FILES.map((filename, idx) => {\n const { name, input } = loadAttributionInput(ds, filename);\n const stages = attributeStages(input, ds);\n return {\n // Persist the input by file reference so the corpus stays a single\n // source of truth — the cruncher case file already pins the EngineInput.\n name,\n cruncher_case: filename,\n expected: stages.map((s) => ({\n name: s.name,\n expected: s.expected,\n baseline: s.baseline,\n lifts: s.lifts.map((l) => ({ source: l.source, delta: l.delta })),\n residual: s.residual,\n intrinsics: s.intrinsics,\n })),\n // Stable ordering of cases in the corpus file.\n _order: idx,\n };\n });\n // Sort by _order and strip the helper before writing.\n cases.sort((a, b) => a._order - b._order);\n const serialised = cases.map(({ _order: _o, ...rest }) => rest);\n writeJson(join(CONFORMANCE, \"attribution\", \"cases.json\"), serialised);\n console.log(`attribution/cases.json: ${cases.length} cases`);\n}\n\n/**\n * Scoring-card translation corpus: humanize each primary mission card's\n * `awards` into plain English. The TS translator is the oracle; the Rust port\n * must reproduce every string byte-for-byte (the differ compares structurally,\n * no tolerance). Only `card_type: \"primary\"` cards are pinned — the 14-card\n * secondary deck isn't revealed yet. Cases are sorted by id for stability, and\n * the `awards` array order within each card is load-bearing.\n */\nfunction genScoringTranslation(): void {\n const ds = Dataset.embedded();\n mkdirSync(join(CONFORMANCE, \"scoring-translation\"), { recursive: true });\n // Pin the translation of every mission card's awards — primary and secondary\n // alike (the secondary deck has the same `awards` shape and deserves the same\n // cross-impl pinning).\n const cases = ds.missionCards.all\n .filter((c) => c.card_type === \"primary\" || c.card_type === \"secondary\")\n .slice()\n .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))\n .map((card) => ({ cardId: card.id, expected: { awards: describeScoringCard(card) } }));\n writeJson(join(CONFORMANCE, \"scoring-translation\", \"cases.json\"), cases);\n console.log(`scoring-translation/cases.json: ${cases.length} cases`);\n}\n\n/**\n * Terrain-resolver corpus: resolve template-anchored layouts to absolute\n * board-space vertices (y-down inches). The TS resolver is the oracle; the Rust\n * port must reproduce every vertex within 5e-4 (per-area invariant in\n * CONFORMANCE.md). Cases are self-contained — each carries its own `templates`\n * and `layout` — so the corpus does not depend on the bundled catalog and the\n * runner op can pass both in `args`. Coverage: per-template centroid anchoring\n * (identity), cardinal + oblique rotations, both mirror axes on an asymmetric\n * shape, embedded-feature composition, explicit parenting, and the inline\n * footprint escape hatch.\n */\nfunction genTerrainResolver(): void {\n mkdirSync(join(CONFORMANCE, \"terrain-resolver\"), { recursive: true });\n\n const areaLarge: ResolverTemplate = {\n id: \"area-large\",\n name: \"Large Area\",\n kind: \"area\",\n footprint: { type: \"rectangle\", width: 11.5, height: 7 },\n };\n const areaMedium: ResolverTemplate = {\n id: \"area-medium\",\n name: \"Medium Area\",\n kind: \"area\",\n footprint: { type: \"rectangle\", width: 6, height: 4 },\n };\n const areaTrapezoid: ResolverTemplate = {\n id: \"area-trapezoid\",\n name: \"Trapezoid Area\",\n kind: \"area\",\n footprint: {\n type: \"polygon\",\n points: [\n { x: 0, y: 0 },\n { x: 8, y: 0 },\n { x: 2, y: 11.5 },\n { x: 0, y: 11.5 },\n ],\n },\n };\n const wedge: ResolverTemplate = {\n id: \"wedge\",\n name: \"Right Wedge\",\n kind: \"area\",\n footprint: { type: \"right-triangle\", width: 8, height: 11.5 },\n };\n const wallLong: ResolverTemplate = {\n id: \"wall-long\",\n name: \"Long Wall\",\n kind: \"feature\",\n footprint: { type: \"rectangle\", width: 7, height: 0.25 },\n };\n const ruinComposed: ResolverTemplate = {\n id: \"ruin-composed\",\n name: \"Composed Ruin\",\n kind: \"area\",\n footprint: { type: \"rectangle\", width: 11.5, height: 7 },\n features: [\n { id: \"back-wall\", template: \"wall-long\", position: { x: 0, y: -3 } },\n { id: \"side-wall\", template: \"wall-long\", position: { x: -5, y: 0 }, rotation_degrees: 90, mirror: \"horizontal\" },\n ],\n };\n\n const baseCatalog = [areaLarge, areaMedium, areaTrapezoid, wedge, wallLong];\n\n const layoutCases: { name: string; templates: ResolverTemplate[]; layout: ResolverLayout }[] = [\n {\n name: \"identity-large\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-large\", position: { x: 30, y: 22 } }] },\n },\n {\n name: \"identity-wedge\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"wedge\", position: { x: 12, y: 30 } }] },\n },\n {\n name: \"identity-trapezoid\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-trapezoid\", position: { x: 40, y: 18 } }] },\n },\n {\n name: \"rotate-medium-90\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-medium\", position: { x: 30, y: 22 }, rotation_degrees: 90 }] },\n },\n {\n name: \"rotate-medium-180\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-medium\", position: { x: 30, y: 22 }, rotation_degrees: 180 }] },\n },\n {\n name: \"rotate-medium-270\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-medium\", position: { x: 30, y: 22 }, rotation_degrees: 270 }] },\n },\n {\n name: \"rotate-large-oblique-55\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-large\", position: { x: 30, y: 22 }, rotation_degrees: 55 }] },\n },\n {\n name: \"rotate-trapezoid-oblique-235\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-trapezoid\", position: { x: 35.75, y: 27 }, rotation_degrees: 235 }] },\n },\n {\n name: \"mirror-trapezoid-horizontal\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-trapezoid\", position: { x: 40, y: 18 }, mirror: \"horizontal\" }] },\n },\n {\n name: \"mirror-trapezoid-vertical-rot90\",\n templates: baseCatalog,\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"p\", template: \"area-trapezoid\", position: { x: 40, y: 18 }, rotation_degrees: 90, mirror: \"vertical\" }] },\n },\n {\n name: \"composition-ruin-rot90-mirror-h\",\n templates: [ruinComposed, wallLong],\n layout: { id: \"c\", name: \"c\", pieces: [{ id: \"a1\", template: \"ruin-composed\", position: { x: 30, y: 22 }, rotation_degrees: 90, mirror: \"horizontal\" }] },\n },\n {\n name: \"explicit-parent-feature\",\n templates: [areaLarge, wallLong],\n layout: {\n id: \"c\",\n name: \"c\",\n pieces: [\n { id: \"a1\", template: \"area-large\", position: { x: 30, y: 22 }, rotation_degrees: 90, mirror: \"horizontal\" },\n { id: \"back-wall\", template: \"wall-long\", parent_area_id: \"a1\", position: { x: 0, y: -3 } },\n ],\n },\n },\n {\n name: \"inline-footprint-polygon\",\n templates: [],\n layout: {\n id: \"c\",\n name: \"c\",\n pieces: [\n {\n id: \"p\",\n footprint: { type: \"polygon\", points: [{ x: 0, y: 0 }, { x: 4, y: 0 }, { x: 2, y: 5 }] },\n position: { x: 50, y: 40 },\n rotation_degrees: 30,\n },\n ],\n },\n },\n ];\n\n const cases = layoutCases.map((c) => ({\n name: c.name,\n templates: c.templates,\n layout: c.layout,\n expected: { pieces: resolveLayout(c.layout, c.templates) },\n }));\n writeJson(join(CONFORMANCE, \"terrain-resolver\", \"cases.json\"), cases);\n console.log(`terrain-resolver/cases.json: ${cases.length} cases`);\n}\n\ngenNormalize();\ngenRosters();\ngenLinkedApi();\ngenAttribution();\ngenScoringTranslation();\ngenTerrainResolver();\n"]}
|