@alpaca-software/40kdc-data 0.5.14 → 0.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abilities-resolver/resolver.d.ts.map +1 -1
- package/dist/abilities-resolver/resolver.js +6 -2
- package/dist/abilities-resolver/resolver.js.map +1 -1
- package/dist/audit-coverage.d.ts.map +1 -1
- package/dist/audit-coverage.js +90 -1
- package/dist/audit-coverage.js.map +1 -1
- package/dist/audit-phrasing.d.ts +64 -0
- package/dist/audit-phrasing.d.ts.map +1 -0
- package/dist/audit-phrasing.js +208 -0
- package/dist/audit-phrasing.js.map +1 -0
- package/dist/build-share-registry.d.ts +2 -0
- package/dist/build-share-registry.d.ts.map +1 -0
- package/dist/build-share-registry.js +115 -0
- package/dist/build-share-registry.js.map +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/codegen-data.js +23 -0
- package/dist/codegen-data.js.map +1 -1
- package/dist/commands/translate.d.ts.map +1 -1
- package/dist/commands/translate.js +2 -5
- package/dist/commands/translate.js.map +1 -1
- package/dist/commands/validate-all.d.ts.map +1 -1
- package/dist/commands/validate-all.js +8 -3
- package/dist/commands/validate-all.js.map +1 -1
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/dataset.d.ts +33 -1
- package/dist/data/dataset.d.ts.map +1 -1
- package/dist/data/dataset.js +107 -1
- package/dist/data/dataset.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/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/gen-conformance.js +162 -0
- package/dist/gen-conformance.js.map +1 -1
- package/dist/generated.d.ts +125 -0
- package/dist/generated.d.ts.map +1 -1
- package/dist/generated.js.map +1 -1
- package/dist/integrity.d.ts +32 -0
- package/dist/integrity.d.ts.map +1 -0
- package/dist/integrity.js +120 -0
- package/dist/integrity.js.map +1 -0
- package/dist/report.d.ts +1 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +2 -2
- package/dist/report.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +42 -0
- package/dist/runner.js.map +1 -1
- package/dist/share/codec.d.ts +83 -0
- package/dist/share/codec.d.ts.map +1 -0
- package/dist/share/codec.js +230 -0
- package/dist/share/codec.js.map +1 -0
- package/dist/share/index.d.ts +30 -0
- package/dist/share/index.d.ts.map +1 -0
- package/dist/share/index.js +36 -0
- package/dist/share/index.js.map +1 -0
- package/dist/share/registry.d.ts +42 -0
- package/dist/share/registry.d.ts.map +1 -0
- package/dist/share/registry.generated.d.ts +4 -0
- package/dist/share/registry.generated.d.ts.map +1 -0
- package/dist/share/registry.generated.js +4 -0
- package/dist/share/registry.generated.js.map +1 -0
- package/dist/share/registry.js +68 -0
- package/dist/share/registry.js.map +1 -0
- package/dist/translate/condition.d.ts +1 -0
- package/dist/translate/condition.d.ts.map +1 -1
- package/dist/translate/condition.js +101 -7
- package/dist/translate/condition.js.map +1 -1
- package/dist/translate/effect.d.ts +28 -20
- package/dist/translate/effect.d.ts.map +1 -1
- package/dist/translate/effect.js +483 -114
- package/dist/translate/effect.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +1 -0
- package/dist/validate.js.map +1 -1
- package/package.json +3 -1
- package/schemas/core/allied-rule.schema.json +115 -0
- package/schemas/core/detachment.schema.json +38 -1
package/dist/gen-conformance.js
CHANGED
|
@@ -28,6 +28,7 @@ import { describeScoringCard, describeAbility } from "./translate/index.js";
|
|
|
28
28
|
import { awardsOf } from "./scoring/index.js";
|
|
29
29
|
import { createRunnerState, dispatch } from "./runner.js";
|
|
30
30
|
import { exportRoster } from "./export/index.js";
|
|
31
|
+
import { decodeShareToken, encodeShareToken, shareRegistryVersion, } from "./share/index.js";
|
|
31
32
|
import { importRoster, REGISTERED_ADAPTERS } from "./import/import-roster.js";
|
|
32
33
|
import { selectAdapter } from "./import/adapter.js";
|
|
33
34
|
import { encodeBase } from "./runner.js";
|
|
@@ -235,6 +236,18 @@ const LINKED_API_QUERIES = [
|
|
|
235
236
|
{ name: "base_size_of windriders (draft flying base)", query: "base_size_of", args: { unitId: "windriders" }, comparison: "scalar" },
|
|
236
237
|
// model_bases_of(unit): ordered per-model bases; jakhals mixes 28.5mm bodies with a 40mm Dishonoured.
|
|
237
238
|
{ name: "model_bases_of jakhals (mixed)", query: "model_bases_of", args: { unitId: "jakhals" }, comparison: "ordered" },
|
|
239
|
+
// units_with_keyword: case-insensitive over keywords ∪ faction_keywords; compared as set.
|
|
240
|
+
{ name: "units_with_keyword Khorne", query: "units_with_keyword", args: { keyword: "Khorne" }, comparison: "set" },
|
|
241
|
+
{ name: "units_with_keyword damned (lowercase)", query: "units_with_keyword", args: { keyword: "damned" }, comparison: "set" },
|
|
242
|
+
// allies_for: two-gate offer; ordered (allied-rules data-file order, locale-independent).
|
|
243
|
+
{ name: "allies_for chaos-knights (no detachment)", query: "allies_for", args: { factionId: "chaos-knights" }, comparison: "ordered" },
|
|
244
|
+
{ name: "allies_for chaos-knights with iconoclast-fiefdom", query: "allies_for", args: { factionId: "chaos-knights", detachmentIds: ["iconoclast-fiefdom"] }, comparison: "ordered" },
|
|
245
|
+
{ name: "allies_for world-eaters (two pools)", query: "allies_for", args: { factionId: "world-eaters" }, comparison: "ordered" },
|
|
246
|
+
{ name: "allies_for genestealer-cults with final-day", query: "allies_for", args: { factionId: "genestealer-cults", detachmentIds: ["final-day"] }, comparison: "ordered" },
|
|
247
|
+
// ally_units_for: resolved pool; compared as set (accessor sorts by name, locale-dependent).
|
|
248
|
+
{ name: "ally_units_for iconoclast-fiefdom-damned", query: "ally_units_for", args: { ruleId: "iconoclast-fiefdom-damned" }, comparison: "set" },
|
|
249
|
+
{ name: "ally_units_for world-eaters-khorne-daemons", query: "ally_units_for", args: { ruleId: "world-eaters-khorne-daemons" }, comparison: "set" },
|
|
250
|
+
{ name: "ally_units_for star-childrens-blessings (excludes broodlord/genestealers)", query: "ally_units_for", args: { ruleId: "star-childrens-blessings" }, comparison: "set" },
|
|
238
251
|
];
|
|
239
252
|
function genLinkedApi() {
|
|
240
253
|
const ds = Dataset.embedded();
|
|
@@ -304,6 +317,13 @@ function runLinkedQuery(ds, q) {
|
|
|
304
317
|
const comp = ds.unitCompositions.find((c) => c.unit_id === q.args.unitId);
|
|
305
318
|
return (comp?.models ?? []).map((m) => `${m.name}=${encodeBase(m.base_size_mm) ?? "none"}`);
|
|
306
319
|
}
|
|
320
|
+
case "units_with_keyword":
|
|
321
|
+
return ds.unitsWithKeyword(q.args.keyword).map((u) => u.id).sort();
|
|
322
|
+
case "allies_for":
|
|
323
|
+
// Ordered: allied-rules data-file order (deterministic across impls).
|
|
324
|
+
return ds.alliesFor(q.args.factionId, q.args.detachmentIds ?? []).map((r) => r.id);
|
|
325
|
+
case "ally_units_for":
|
|
326
|
+
return ds.allyUnitsFor(q.args.ruleId).map((u) => u.id).sort();
|
|
307
327
|
}
|
|
308
328
|
}
|
|
309
329
|
/**
|
|
@@ -971,6 +991,147 @@ function genEffectTranslation() {
|
|
|
971
991
|
writeJson(join(CONFORMANCE, "effect-translation", "cases.json"), cases);
|
|
972
992
|
console.log(`effect-translation/cases.json: ${cases.length} cases (${seen.size} node types)`);
|
|
973
993
|
}
|
|
994
|
+
/** Unsigned LEB128 of a small non-negative integer (for hand-built tokens). */
|
|
995
|
+
function leb128(value) {
|
|
996
|
+
const out = [];
|
|
997
|
+
let v = value;
|
|
998
|
+
while (v >= 0x80) {
|
|
999
|
+
out.push((v & 0x7f) | 0x80);
|
|
1000
|
+
v = Math.floor(v / 0x80);
|
|
1001
|
+
}
|
|
1002
|
+
out.push(v);
|
|
1003
|
+
return out;
|
|
1004
|
+
}
|
|
1005
|
+
/** base64url (no padding) of a raw byte array — for negative decode goldens. */
|
|
1006
|
+
function bytesToBase64url(bytes) {
|
|
1007
|
+
return Buffer.from(bytes)
|
|
1008
|
+
.toString("base64")
|
|
1009
|
+
.replace(/\+/g, "-")
|
|
1010
|
+
.replace(/\//g, "_")
|
|
1011
|
+
.replace(/=+$/, "");
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* The share-token corpus. Each positive case pins `encodeShareToken(list)` to an
|
|
1015
|
+
* exact `token` and asserts `decodeShareToken(token)` round-trips to `list`;
|
|
1016
|
+
* negative cases pin the decoder's `malformed` / `stale-registry` verdicts. The
|
|
1017
|
+
* input lists are derived from the embedded dataset deterministically (sorted,
|
|
1018
|
+
* first-N) so the goldens are stable and self-maintaining. Rust and Python must
|
|
1019
|
+
* reproduce every `token` byte-for-byte and every decode verdict.
|
|
1020
|
+
*/
|
|
1021
|
+
function genShare() {
|
|
1022
|
+
mkdirSync(join(CONFORMANCE, "share"), { recursive: true });
|
|
1023
|
+
const ds = Dataset.embedded();
|
|
1024
|
+
const f = "adeptus-astartes";
|
|
1025
|
+
const dets = ds.detachments.all
|
|
1026
|
+
.filter((d) => d.faction_id === f)
|
|
1027
|
+
.map((d) => d.id)
|
|
1028
|
+
.sort();
|
|
1029
|
+
const units = ds.units
|
|
1030
|
+
.byFaction(f)
|
|
1031
|
+
.map((u) => u.id)
|
|
1032
|
+
.sort();
|
|
1033
|
+
const weapons = ds.weapons.all.map((w) => w.id).sort();
|
|
1034
|
+
const enhancement = ds.enhancements.all.map((e) => e.id).sort()[0];
|
|
1035
|
+
const disposition = ds.forceDispositions.all.map((d) => d.id).sort()[0];
|
|
1036
|
+
const allyFactionId = "chaos-daemons";
|
|
1037
|
+
const allyRuleId = ds.alliedRules.all.map((r) => r.id).sort()[0];
|
|
1038
|
+
const plainUnit = (id) => ({
|
|
1039
|
+
datasheetId: id,
|
|
1040
|
+
modelCount: 1,
|
|
1041
|
+
isWarlord: false,
|
|
1042
|
+
enhancementId: null,
|
|
1043
|
+
allyFactionId: null,
|
|
1044
|
+
allyRuleId: null,
|
|
1045
|
+
attachedToOrdinal: null,
|
|
1046
|
+
grants: [],
|
|
1047
|
+
loadout: [],
|
|
1048
|
+
});
|
|
1049
|
+
const lists = [
|
|
1050
|
+
{
|
|
1051
|
+
name: "minimal",
|
|
1052
|
+
list: {
|
|
1053
|
+
name: "",
|
|
1054
|
+
factionId: f,
|
|
1055
|
+
detachmentIds: [],
|
|
1056
|
+
battleSize: "strike-force",
|
|
1057
|
+
disposition: null,
|
|
1058
|
+
units: [],
|
|
1059
|
+
},
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
name: "warlord-enhancement-grants",
|
|
1063
|
+
list: {
|
|
1064
|
+
name: "Strîke Force 🔨",
|
|
1065
|
+
factionId: f,
|
|
1066
|
+
detachmentIds: [dets[0]],
|
|
1067
|
+
battleSize: "strike-force",
|
|
1068
|
+
disposition,
|
|
1069
|
+
units: [
|
|
1070
|
+
{
|
|
1071
|
+
...plainUnit(units[0]),
|
|
1072
|
+
modelCount: 5,
|
|
1073
|
+
isWarlord: true,
|
|
1074
|
+
enhancementId: enhancement,
|
|
1075
|
+
grants: ["Character"],
|
|
1076
|
+
loadout: [
|
|
1077
|
+
[weapons[0], 2],
|
|
1078
|
+
[weapons[1], 1],
|
|
1079
|
+
],
|
|
1080
|
+
},
|
|
1081
|
+
{ ...plainUnit(units[1]), modelCount: 10, loadout: [[weapons[2], 10]] },
|
|
1082
|
+
],
|
|
1083
|
+
},
|
|
1084
|
+
},
|
|
1085
|
+
{
|
|
1086
|
+
name: "allies-attachment",
|
|
1087
|
+
list: {
|
|
1088
|
+
name: "Soup",
|
|
1089
|
+
factionId: f,
|
|
1090
|
+
detachmentIds: [dets[0], dets[1]],
|
|
1091
|
+
battleSize: "incursion",
|
|
1092
|
+
disposition,
|
|
1093
|
+
units: [
|
|
1094
|
+
{ ...plainUnit(units[0]), isWarlord: true, attachedToOrdinal: 1 }, // leader → bodyguard
|
|
1095
|
+
{ ...plainUnit(units[1]), modelCount: 5 },
|
|
1096
|
+
{
|
|
1097
|
+
...plainUnit(units[2]),
|
|
1098
|
+
allyFactionId,
|
|
1099
|
+
allyRuleId,
|
|
1100
|
+
grants: ["Battleline"],
|
|
1101
|
+
loadout: [[weapons[3], 1]],
|
|
1102
|
+
},
|
|
1103
|
+
],
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1106
|
+
];
|
|
1107
|
+
const cases = lists.map(({ name, list }) => {
|
|
1108
|
+
const token = encodeShareToken(list);
|
|
1109
|
+
const decoded = decodeShareToken(token);
|
|
1110
|
+
if (!decoded.ok)
|
|
1111
|
+
throw new Error(`share case ${name} failed to round-trip: ${decoded.reason}`);
|
|
1112
|
+
// Sanity: the generator's own round-trip must reproduce the input exactly.
|
|
1113
|
+
if (JSON.stringify(decoded.list) !== JSON.stringify(list)) {
|
|
1114
|
+
throw new Error(`share case ${name} did not round-trip losslessly`);
|
|
1115
|
+
}
|
|
1116
|
+
return { name, list, token };
|
|
1117
|
+
});
|
|
1118
|
+
// Negative decode cases (deterministic hand-built tokens).
|
|
1119
|
+
cases.push({
|
|
1120
|
+
name: "malformed-bad-format-byte",
|
|
1121
|
+
decode_token: bytesToBase64url([0x00]),
|
|
1122
|
+
expected_decode: { ok: false, reason: "malformed" },
|
|
1123
|
+
});
|
|
1124
|
+
cases.push({
|
|
1125
|
+
name: "stale-future-index",
|
|
1126
|
+
// Valid header (format 1, registry version, empty name) then a faction
|
|
1127
|
+
// index no committed registry will ever hold → stale-registry.
|
|
1128
|
+
decode_token: bytesToBase64url([0x01, ...leb128(shareRegistryVersion), 0x00, ...leb128(2_000_000)]),
|
|
1129
|
+
expected_decode: { ok: false, reason: "stale-registry" },
|
|
1130
|
+
});
|
|
1131
|
+
writeJson(join(CONFORMANCE, "share", "cases.json"), cases);
|
|
1132
|
+
console.log(`share/cases.json: ${cases.length} cases (registry v${shareRegistryVersion}, ` +
|
|
1133
|
+
`${cases.filter((c) => c.token).length} round-trip + ${cases.filter((c) => c.decode_token).length} negative)`);
|
|
1134
|
+
}
|
|
974
1135
|
genNormalize();
|
|
975
1136
|
genRosters();
|
|
976
1137
|
genLinkedApi();
|
|
@@ -980,4 +1141,5 @@ genEffectTranslation();
|
|
|
980
1141
|
genScoring();
|
|
981
1142
|
genTerrainResolver();
|
|
982
1143
|
genTerrainKeystones();
|
|
1144
|
+
genShare();
|
|
983
1145
|
//# sourceMappingURL=gen-conformance.js.map
|