@alpaca-software/40kdc-data 0.1.3 → 0.3.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/dist/author-input.d.ts +20 -1
- package/dist/author-input.d.ts.map +1 -1
- package/dist/author-input.js +64 -8
- package/dist/author-input.js.map +1 -1
- package/dist/author-seed.d.ts +62 -0
- package/dist/author-seed.d.ts.map +1 -0
- package/dist/author-seed.js +194 -0
- package/dist/author-seed.js.map +1 -0
- package/dist/commands/translate.d.ts.map +1 -1
- package/dist/commands/translate.js +6 -68
- package/dist/commands/translate.js.map +1 -1
- package/dist/cruncher/buffs.d.ts +57 -1
- package/dist/cruncher/buffs.d.ts.map +1 -1
- package/dist/cruncher/buffs.js +32 -3
- package/dist/cruncher/buffs.js.map +1 -1
- package/dist/cruncher/engine.d.ts.map +1 -1
- package/dist/cruncher/engine.js +50 -15
- package/dist/cruncher/engine.js.map +1 -1
- package/dist/cruncher/from-dsl.d.ts.map +1 -1
- package/dist/cruncher/from-dsl.js +121 -6
- package/dist/cruncher/from-dsl.js.map +1 -1
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/normalize.d.ts.map +1 -1
- package/dist/data/normalize.js +8 -1
- package/dist/data/normalize.js.map +1 -1
- package/dist/gen-conformance.js +22 -1
- package/dist/gen-conformance.js.map +1 -1
- package/dist/generated.d.ts +181 -146
- package/dist/generated.d.ts.map +1 -1
- package/dist/generated.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +85 -24
- package/dist/runner.js.map +1 -1
- package/dist/scrub-defensive-flag.d.ts +23 -0
- package/dist/scrub-defensive-flag.d.ts.map +1 -0
- package/dist/scrub-defensive-flag.js +149 -0
- package/dist/scrub-defensive-flag.js.map +1 -0
- package/dist/translate/condition.d.ts +26 -0
- package/dist/translate/condition.d.ts.map +1 -0
- package/dist/translate/condition.js +171 -0
- package/dist/translate/condition.js.map +1 -0
- package/dist/translate/index.d.ts +9 -0
- package/dist/translate/index.d.ts.map +1 -0
- package/dist/translate/index.js +9 -0
- package/dist/translate/index.js.map +1 -0
- package/dist/translate/scoring.d.ts +38 -0
- package/dist/translate/scoring.d.ts.map +1 -0
- package/dist/translate/scoring.js +80 -0
- package/dist/translate/scoring.js.map +1 -0
- package/package.json +3 -1
- package/schemas/core/secondary-card.schema.json +50 -28
- package/schemas/enrichment/ability-dsl/condition.schema.json +5 -2
- package/schemas/enrichment/ability-dsl/effect.schema.json +2 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"condition.js","sourceRoot":"","sources":["../../src/translate/condition.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Humanize an Ability-DSL / scoring `condition` into plain English.\n *\n * Shared by the ability-text CLI (`commands/translate.ts`) and the scoring-card\n * translator (`scoring.ts`). Output is **ASCII-only** with a fixed clause and\n * parameter order: it is pinned byte-for-byte across the TS and Rust ports by\n * the `conformance/scoring-translation` corpus, so any phrasing change here is a\n * semantic corpus change (bump `conformance/SPEC_VERSION`).\n */\n\n/**\n * Minimal structural view of a condition node. Matches both the ability-dsl\n * condition schema and the `secondary-card` award `when` field (a simple node\n * carries `type` + `parameters` + `negated`; a compound node carries\n * `operator` + `operands`).\n */\nexport interface Condition {\n type?: string;\n operator?: \"and\" | \"or\" | \"not\";\n operands?: Condition[];\n parameters?: Record<string, unknown>;\n negated?: boolean;\n}\n\n/** kebab-case → space-separated words (`enemy-territory` → `enemy territory`). */\nexport function dekebab(s: string): string {\n return s.replace(/-/g, \" \");\n}\n\nfunction str(v: unknown): string {\n return typeof v === \"string\" ? v : String(v);\n}\n\n/** `2` + `objective` → `2+ objectives`. Nouns here are all regular plurals. */\nfunction count(n: unknown, noun: string): string {\n return `${str(n)}+ ${noun}s`;\n}\n\nexport function describeCondition(c: Condition): string {\n // Compound nodes first — join the operands with lowercase connectives so the\n // result reads naturally inside a \"... when X and Y\" clause.\n if (c.operator === \"and\" && c.operands) {\n return c.operands.map(describeCondition).join(\" and \");\n }\n if (c.operator === \"or\" && c.operands) {\n return c.operands.map(describeCondition).join(\" or \");\n }\n if (c.operator === \"not\" && c.operands) {\n return `not (${c.operands.map(describeCondition).join(\", \")})`;\n }\n\n const negate = c.negated ? \"not \" : \"\";\n const p = c.parameters ?? {};\n\n switch (c.type) {\n // ── Ability-DSL conditions (ported from commands/translate.ts) ──────────\n case \"phase-is\":\n return `${negate}during the ${str(p.phase)} phase`;\n case \"timing-is\":\n return `${negate}at ${dekebab(str(p.timing))}`;\n case \"player-turn-is\":\n return `${negate}in ${p.turn === \"your-turn\" ? \"your\" : p.turn === \"opponent-turn\" ? \"the opponent's\" : \"either player's\"} turn`;\n case \"charged-this-turn\":\n return `${negate}the unit charged this turn`;\n case \"advanced-this-turn\":\n return `${negate}the unit advanced this turn`;\n case \"remained-stationary\":\n return `${negate}the unit remained stationary`;\n case \"unit-below-starting-strength\":\n return `${negate}the unit is below starting strength`;\n case \"unit-below-half-strength\":\n return `${negate}the unit is below half strength`;\n case \"unit-has-keyword\":\n return `${negate}the unit has \"${str(p.keyword)}\"`;\n case \"target-has-keyword\":\n return `${negate}the target has \"${str(p.keyword)}\"`;\n case \"model-is-leader\":\n return `${negate}the model is leading a unit`;\n case \"is-attached\":\n return `${negate}attached to a ${p.keyword ? `${str(p.keyword)} ` : \"\"}unit`;\n case \"attack-is-type\":\n return `${negate}for ${str(p.attack_type)} attacks`;\n case \"is-battle-shocked\":\n return `${negate}the unit is battle-shocked`;\n case \"has-lost-wounds\":\n return `${negate}the model has lost wounds`;\n case \"opponent-unit-within-range\":\n return `${negate}an enemy unit is within ${p.range === \"engagement\" ? \"engagement range\" : `${str(p.range)}\"`}`;\n case \"unit-within-range-of\":\n return `${negate}within ${str(p.range)}\" of ${str(p.target_type ?? \"target\")}${p.keyword ? ` (${str(p.keyword)})` : \"\"}`;\n case \"within-range-of-objective\":\n return `${negate}within range of an objective`;\n case \"has-fought-this-phase\":\n return `${negate}has fought this phase`;\n case \"destroyed-by-attack-type\":\n return `${negate}destroyed by a ${str(p.attack_type)} attack`;\n\n // ── Scoring conditions (secondary-card award `when`) ────────────────────\n case \"objective-majority\":\n return `${negate}you hold more objectives than the ${dekebab(str(p.relative_to ?? \"opponent\"))}`;\n case \"controls-objective\": {\n const noun = p.objective_role ? `${dekebab(str(p.objective_role))} objective` : \"objective\";\n let s = `${negate}you control ${count(p.count_min ?? 1, noun)}`;\n if (p.objective != null) s += ` (${dekebab(str(p.objective))})`;\n if (p.scope != null) s += ` in ${dekebab(str(p.scope))}`;\n if (p.exclude != null) s += ` (excluding ${dekebab(str(p.exclude))})`;\n return s;\n }\n case \"units-destroyed\":\n return `${negate}${count(p.count_min ?? 1, `${str(p.side)} unit`)} destroyed ${dekebab(str(p.window))}`;\n case \"units-destroyed-comparison\": {\n const subj = (p.subject ?? {}) as Record<string, unknown>;\n const ref = (p.reference ?? {}) as Record<string, unknown>;\n const cmp = p.comparator === \"greater-or-equal\" ? \"at least as many\" : \"more\";\n const link = p.comparator === \"greater-or-equal\" ? \"as\" : \"than\";\n return `${negate}you destroyed ${cmp} ${str(subj.side)} units ${dekebab(str(subj.window))} ${link} ${str(ref.side)} units ${dekebab(str(ref.window))}`;\n }\n case \"new-objective-controlled\":\n return `${negate}you newly control ${count(p.count_min ?? 1, \"objective\")} this turn`;\n case \"destroyed-while-on-objective\": {\n let s = `${negate}${count(p.count_min ?? 1, \"enemy unit\")} destroyed`;\n if (p.destroyer_on_objective) s += \" by a unit on an objective\";\n if (p.victim_on_objective) s += \" while on an objective\";\n return s;\n }\n case \"action-completed\": {\n let s = `${negate}${count(p.count_min ?? 1, \"action\")} completed`;\n if (p.action_id != null) s += ` (${dekebab(str(p.action_id))})`;\n if (p.target_kind != null) s += ` on ${dekebab(str(p.target_kind))}`;\n const tf = (p.target_filter ?? {}) as Record<string, unknown>;\n if (tf.objective_role != null) s += ` (${dekebab(str(tf.objective_role))})`;\n if (tf.in_enemy_territory) s += \" in enemy territory\";\n if (tf.exclude != null) s += ` (excluding ${dekebab(str(tf.exclude))})`;\n if (p.window != null) s += ` ${dekebab(str(p.window))}`;\n return s;\n }\n case \"objective-has-tag\": {\n let s = `${negate}${count(p.count_min ?? 1, \"objective\")} tagged ${dekebab(str(p.tag))}`;\n if (p.count_max != null) s += ` (at most ${str(p.count_max)})`;\n if (p.objective != null) s += ` (${dekebab(str(p.objective))})`;\n if (p.scope != null) s += ` in ${dekebab(str(p.scope))}`;\n if (p.last_marked) s += \" (most recently marked)\";\n return s;\n }\n case \"unit-has-tag\": {\n let s = `${negate}${count(p.count_min ?? 1, `${str(p.side)} unit`)} tagged ${dekebab(str(p.tag))}`;\n if (p.window != null) s += ` (${dekebab(str(p.window))})`;\n return s;\n }\n case \"terrain-has-tag\": {\n let s = `${negate}terrain tagged ${dekebab(str(p.tag))}`;\n if (p.friendly_units_min != null) s += ` with ${str(p.friendly_units_min)}+ friendly units`;\n if (p.enemy_units_max != null) s += ` and at most ${str(p.enemy_units_max)} enemy units`;\n if (p.last_marked) s += \" (most recently marked)\";\n if (p.in_enemy_dz) s += \" in the enemy deployment zone\";\n return s;\n }\n case \"terrain-area-control\":\n return `${negate}you control a terrain area with ${str(p.min_models ?? 1)}+ models`;\n case \"territory-control\": {\n let s = `${negate}you control ${dekebab(str(p.territory_ref ?? \"your-territory\"))}`;\n if (p.enemy_units_max != null) s += ` with at most ${str(p.enemy_units_max)} enemy units`;\n return s;\n }\n case \"engagement-fronts\":\n return `${negate}you are engaged on ${str(p.count_min ?? 1)}+ fronts`;\n\n default:\n return `${negate}${dekebab(c.type ?? \"unknown\")}`;\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain-English translation of structured game data — currently the
|
|
3
|
+
* `secondary-card` scoring `awards` (mission "how to play" readouts) and the
|
|
4
|
+
* shared Ability-DSL condition humanizer. Output is ASCII-only and pinned
|
|
5
|
+
* across language ports by the `conformance/scoring-translation` corpus.
|
|
6
|
+
*/
|
|
7
|
+
export { describeCondition, dekebab, type Condition } from "./condition.js";
|
|
8
|
+
export { describeTrigger, describeAward, describeScoringCard, type ScoringTrigger, type ScoringAward, } from "./scoring.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/translate/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain-English translation of structured game data — currently the
|
|
3
|
+
* `secondary-card` scoring `awards` (mission "how to play" readouts) and the
|
|
4
|
+
* shared Ability-DSL condition humanizer. Output is ASCII-only and pinned
|
|
5
|
+
* across language ports by the `conformance/scoring-translation` corpus.
|
|
6
|
+
*/
|
|
7
|
+
export { describeCondition, dekebab } from "./condition.js";
|
|
8
|
+
export { describeTrigger, describeAward, describeScoringCard, } from "./scoring.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/translate/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAkB,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,aAAa,EACb,mBAAmB,GAGpB,MAAM,cAAc,CAAC","sourcesContent":["/**\n * Plain-English translation of structured game data — currently the\n * `secondary-card` scoring `awards` (mission \"how to play\" readouts) and the\n * shared Ability-DSL condition humanizer. Output is ASCII-only and pinned\n * across language ports by the `conformance/scoring-translation` corpus.\n */\nexport { describeCondition, dekebab, type Condition } from \"./condition.js\";\nexport {\n describeTrigger,\n describeAward,\n describeScoringCard,\n type ScoringTrigger,\n type ScoringAward,\n} from \"./scoring.js\";\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Humanize a `secondary-card` scoring `award` into plain English.
|
|
3
|
+
*
|
|
4
|
+
* Output is **ASCII-only** with a fixed clause order, pinned byte-for-byte
|
|
5
|
+
* across the TS and Rust ports by the `conformance/scoring-translation` corpus.
|
|
6
|
+
* The community `text` summary and the `actions` list are verbatim data, not
|
|
7
|
+
* translation, so they are not produced here — only the structured `awards`.
|
|
8
|
+
*/
|
|
9
|
+
import type { SecondaryCard } from "../generated.js";
|
|
10
|
+
import { type Condition } from "./condition.js";
|
|
11
|
+
/** When a VP award is evaluated (the `trigger` block on an award). */
|
|
12
|
+
export interface ScoringTrigger {
|
|
13
|
+
timing?: "start-of-turn" | "end-of-turn" | "start-of-phase" | "end-of-phase" | "end-of-battle";
|
|
14
|
+
phase?: "command" | "movement" | "shooting" | "charge" | "fight";
|
|
15
|
+
player_turn?: "your-turn" | "opponent-turn" | "either";
|
|
16
|
+
battle_round?: {
|
|
17
|
+
min?: number;
|
|
18
|
+
max?: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** One VP-award block on a scoring card. */
|
|
22
|
+
export interface ScoringAward {
|
|
23
|
+
trigger?: ScoringTrigger;
|
|
24
|
+
when?: Condition;
|
|
25
|
+
vp?: number;
|
|
26
|
+
vp_per?: number;
|
|
27
|
+
per?: string;
|
|
28
|
+
per_max?: number;
|
|
29
|
+
cumulative?: boolean;
|
|
30
|
+
exclusive_group?: string;
|
|
31
|
+
}
|
|
32
|
+
/** "End of your Command phase (round 2+)" and friends. */
|
|
33
|
+
export declare function describeTrigger(t: ScoringTrigger): string;
|
|
34
|
+
/** "End of your Command phase (round 2+): 3 VP per controlled objective when ..." */
|
|
35
|
+
export declare function describeAward(a: ScoringAward): string;
|
|
36
|
+
/** Humanize every award on a card, in array order (the order is load-bearing). */
|
|
37
|
+
export declare function describeScoringCard(card: SecondaryCard): string[];
|
|
38
|
+
//# sourceMappingURL=scoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../../src/translate/scoring.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE5E,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,eAAe,GAAG,aAAa,GAAG,gBAAgB,GAAG,cAAc,GAAG,eAAe,CAAC;IAC/F,KAAK,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC;IACjE,WAAW,CAAC,EAAE,WAAW,GAAG,eAAe,GAAG,QAAQ,CAAC;IACvD,YAAY,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAyCzD;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAiBrD;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,EAAE,CAGjE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Humanize a `secondary-card` scoring `award` into plain English.
|
|
3
|
+
*
|
|
4
|
+
* Output is **ASCII-only** with a fixed clause order, pinned byte-for-byte
|
|
5
|
+
* across the TS and Rust ports by the `conformance/scoring-translation` corpus.
|
|
6
|
+
* The community `text` summary and the `actions` list are verbatim data, not
|
|
7
|
+
* translation, so they are not produced here — only the structured `awards`.
|
|
8
|
+
*/
|
|
9
|
+
import { describeCondition, dekebab } from "./condition.js";
|
|
10
|
+
function capitalize(s) {
|
|
11
|
+
return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
|
|
12
|
+
}
|
|
13
|
+
/** "End of your Command phase (round 2+)" and friends. */
|
|
14
|
+
export function describeTrigger(t) {
|
|
15
|
+
const turn = t.player_turn === "opponent-turn"
|
|
16
|
+
? "the opponent's"
|
|
17
|
+
: t.player_turn === "either"
|
|
18
|
+
? "any"
|
|
19
|
+
: "your";
|
|
20
|
+
let base;
|
|
21
|
+
switch (t.timing) {
|
|
22
|
+
case "start-of-turn":
|
|
23
|
+
base = `Start of ${turn} turn`;
|
|
24
|
+
break;
|
|
25
|
+
case "end-of-turn":
|
|
26
|
+
base = `End of ${turn} turn`;
|
|
27
|
+
break;
|
|
28
|
+
case "start-of-phase":
|
|
29
|
+
base = `Start of ${turn} ${capitalize(t.phase ?? "")} phase`;
|
|
30
|
+
break;
|
|
31
|
+
case "end-of-phase":
|
|
32
|
+
base = `End of ${turn} ${capitalize(t.phase ?? "")} phase`;
|
|
33
|
+
break;
|
|
34
|
+
case "end-of-battle":
|
|
35
|
+
base = "End of the battle";
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
base = t.phase ? `During ${turn} ${capitalize(t.phase)} phase` : "Any time";
|
|
39
|
+
}
|
|
40
|
+
const br = t.battle_round;
|
|
41
|
+
if (br) {
|
|
42
|
+
const { min, max } = br;
|
|
43
|
+
if (min != null && max != null) {
|
|
44
|
+
base += min === max ? ` (round ${min})` : ` (rounds ${min}-${max})`;
|
|
45
|
+
}
|
|
46
|
+
else if (min != null) {
|
|
47
|
+
base += ` (round ${min}+)`;
|
|
48
|
+
}
|
|
49
|
+
else if (max != null) {
|
|
50
|
+
base += ` (rounds 1-${max})`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return base;
|
|
54
|
+
}
|
|
55
|
+
/** "End of your Command phase (round 2+): 3 VP per controlled objective when ..." */
|
|
56
|
+
export function describeAward(a) {
|
|
57
|
+
const trigger = a.trigger ? describeTrigger(a.trigger) : "Any time";
|
|
58
|
+
let amount;
|
|
59
|
+
if (a.vp != null) {
|
|
60
|
+
amount = `${a.vp} VP`;
|
|
61
|
+
}
|
|
62
|
+
else if (a.vp_per != null) {
|
|
63
|
+
amount = `${a.vp_per} VP per ${a.per ? dekebab(a.per) : "instance"}`;
|
|
64
|
+
if (a.per_max != null)
|
|
65
|
+
amount += ` (max ${a.per_max})`;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
amount = "no VP";
|
|
69
|
+
}
|
|
70
|
+
const prefix = a.cumulative ? "+ " : "";
|
|
71
|
+
const when = a.when ? ` when ${describeCondition(a.when)}` : "";
|
|
72
|
+
const tier = a.exclusive_group ? " [highest tier]" : "";
|
|
73
|
+
return `${prefix}${trigger}: ${amount}${when}${tier}`;
|
|
74
|
+
}
|
|
75
|
+
/** Humanize every award on a card, in array order (the order is load-bearing). */
|
|
76
|
+
export function describeScoringCard(card) {
|
|
77
|
+
const awards = (card.awards ?? []);
|
|
78
|
+
return awards.map(describeAward);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=scoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.js","sourceRoot":"","sources":["../../src/translate/scoring.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Humanize a `secondary-card` scoring `award` into plain English.\n *\n * Output is **ASCII-only** with a fixed clause order, pinned byte-for-byte\n * across the TS and Rust ports by the `conformance/scoring-translation` corpus.\n * The community `text` summary and the `actions` list are verbatim data, not\n * translation, so they are not produced here — only the structured `awards`.\n */\n\nimport type { SecondaryCard } from \"../generated.js\";\nimport { describeCondition, dekebab, type Condition } from \"./condition.js\";\n\n/** When a VP award is evaluated (the `trigger` block on an award). */\nexport interface ScoringTrigger {\n timing?: \"start-of-turn\" | \"end-of-turn\" | \"start-of-phase\" | \"end-of-phase\" | \"end-of-battle\";\n phase?: \"command\" | \"movement\" | \"shooting\" | \"charge\" | \"fight\";\n player_turn?: \"your-turn\" | \"opponent-turn\" | \"either\";\n battle_round?: { min?: number; max?: number };\n}\n\n/** One VP-award block on a scoring card. */\nexport interface ScoringAward {\n trigger?: ScoringTrigger;\n when?: Condition;\n vp?: number;\n vp_per?: number;\n per?: string;\n per_max?: number;\n cumulative?: boolean;\n exclusive_group?: string;\n}\n\nfunction capitalize(s: string): string {\n return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);\n}\n\n/** \"End of your Command phase (round 2+)\" and friends. */\nexport function describeTrigger(t: ScoringTrigger): string {\n const turn =\n t.player_turn === \"opponent-turn\"\n ? \"the opponent's\"\n : t.player_turn === \"either\"\n ? \"any\"\n : \"your\";\n\n let base: string;\n switch (t.timing) {\n case \"start-of-turn\":\n base = `Start of ${turn} turn`;\n break;\n case \"end-of-turn\":\n base = `End of ${turn} turn`;\n break;\n case \"start-of-phase\":\n base = `Start of ${turn} ${capitalize(t.phase ?? \"\")} phase`;\n break;\n case \"end-of-phase\":\n base = `End of ${turn} ${capitalize(t.phase ?? \"\")} phase`;\n break;\n case \"end-of-battle\":\n base = \"End of the battle\";\n break;\n default:\n base = t.phase ? `During ${turn} ${capitalize(t.phase)} phase` : \"Any time\";\n }\n\n const br = t.battle_round;\n if (br) {\n const { min, max } = br;\n if (min != null && max != null) {\n base += min === max ? ` (round ${min})` : ` (rounds ${min}-${max})`;\n } else if (min != null) {\n base += ` (round ${min}+)`;\n } else if (max != null) {\n base += ` (rounds 1-${max})`;\n }\n }\n return base;\n}\n\n/** \"End of your Command phase (round 2+): 3 VP per controlled objective when ...\" */\nexport function describeAward(a: ScoringAward): string {\n const trigger = a.trigger ? describeTrigger(a.trigger) : \"Any time\";\n\n let amount: string;\n if (a.vp != null) {\n amount = `${a.vp} VP`;\n } else if (a.vp_per != null) {\n amount = `${a.vp_per} VP per ${a.per ? dekebab(a.per) : \"instance\"}`;\n if (a.per_max != null) amount += ` (max ${a.per_max})`;\n } else {\n amount = \"no VP\";\n }\n\n const prefix = a.cumulative ? \"+ \" : \"\";\n const when = a.when ? ` when ${describeCondition(a.when)}` : \"\";\n const tier = a.exclusive_group ? \" [highest tier]\" : \"\";\n return `${prefix}${trigger}: ${amount}${when}${tier}`;\n}\n\n/** Humanize every award on a card, in array order (the order is load-bearing). */\nexport function describeScoringCard(card: SecondaryCard): string[] {\n const awards = (card.awards ?? []) as unknown as ScoringAward[];\n return awards.map(describeAward);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpaca-software/40kdc-data",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The 40kdc Warhammer 40K dataset behind a linked, typed API — find units, follow them to their weapons, abilities, phases, and factions. Also validates data against the canonical JSON Schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -58,7 +58,9 @@
|
|
|
58
58
|
"validate:enrichment": "tsx src/cli.ts validate-enrichment",
|
|
59
59
|
"translate": "tsx src/cli.ts translate",
|
|
60
60
|
"audit:coverage": "tsx src/cli.ts audit-coverage --write",
|
|
61
|
+
"scrub:defensive-flag": "tsx src/scrub-defensive-flag.ts",
|
|
61
62
|
"author:input": "tsx src/author-input.ts",
|
|
63
|
+
"author:seed": "tsx src/author-seed.ts",
|
|
62
64
|
"author:propose": "tsx src/author-batch.ts propose",
|
|
63
65
|
"author:repair": "tsx src/author-batch.ts repair",
|
|
64
66
|
"author:apply": "tsx src/author-batch.ts apply",
|
|
@@ -111,39 +111,55 @@
|
|
|
111
111
|
"required": ["operation"],
|
|
112
112
|
"additionalProperties": false
|
|
113
113
|
},
|
|
114
|
-
"
|
|
115
|
-
"type": "
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
114
|
+
"actions": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"minItems": 1,
|
|
117
|
+
"description": "Optional player actions the card enables. Most cards have a single action; a few (e.g. Observe Enemy, with separate Baited-removal and Spotted actions) have two distinct actions on the same card.",
|
|
118
|
+
"items": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"properties": {
|
|
121
|
+
"action_id": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"minLength": 1,
|
|
124
|
+
"maxLength": 64,
|
|
125
|
+
"description": "Optional kebab-case identifier used to reference this action from `action-completed` conditions in `awards[].when`."
|
|
126
|
+
},
|
|
127
|
+
"starts": {
|
|
128
|
+
"$ref": "../defs/common.schema.json#/$defs/phase",
|
|
129
|
+
"description": "Phase in which the action can be started."
|
|
130
|
+
},
|
|
131
|
+
"player_turn": { "$ref": "../defs/common.schema.json#/$defs/player-turn" },
|
|
132
|
+
"units": {
|
|
133
|
+
"$ref": "../enrichment/ability-dsl/condition.schema.json",
|
|
134
|
+
"description": "Eligibility predicate for which units may perform the action."
|
|
135
|
+
},
|
|
136
|
+
"use_limit": {
|
|
137
|
+
"type": "integer",
|
|
138
|
+
"minimum": 1,
|
|
139
|
+
"description": "Maximum number of times the action may be performed (per turn unless `use_limit_scope` says otherwise)."
|
|
140
|
+
},
|
|
141
|
+
"use_limit_scope": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"enum": ["per-turn", "per-game"],
|
|
144
|
+
"default": "per-turn",
|
|
145
|
+
"description": "Whether `use_limit` is enforced per turn or once per game (e.g. Recover the Relics / Find and Deny 'Overwhelming Force' is once per game)."
|
|
146
|
+
},
|
|
147
|
+
"completes": {
|
|
148
|
+
"$ref": "../enrichment/ability-dsl/condition.schema.json",
|
|
149
|
+
"description": "Predicate for when the action is considered complete."
|
|
150
|
+
},
|
|
151
|
+
"effect": {
|
|
152
|
+
"$ref": "../enrichment/ability-dsl/effect.schema.json",
|
|
153
|
+
"description": "Effect applied when the action completes (e.g. terrain-area-tag, objective-tag, or unit-tag to mark transient state)."
|
|
154
|
+
}
|
|
135
155
|
},
|
|
136
|
-
"
|
|
137
|
-
|
|
138
|
-
"description": "Effect applied when the action completes (e.g. terrain-area-tag to mark transient state on a terrain piece)."
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
"additionalProperties": false
|
|
156
|
+
"additionalProperties": false
|
|
157
|
+
}
|
|
142
158
|
},
|
|
143
159
|
"awards": {
|
|
144
160
|
"type": "array",
|
|
145
161
|
"minItems": 1,
|
|
146
|
-
"description": "VP-award blocks: each scores when `trigger` fires and the optional `when` condition holds. An award scores either a flat `vp` or a count-scaled `vp_per` (VP per instance of the thing named by `per`). Awards accrue independently and sum; a card's '+ ... CUMULATIVE' rows are modelled as separate awards flagged `cumulative` for faithful round-trip.",
|
|
162
|
+
"description": "VP-award blocks: each scores when `trigger` fires and the optional `when` condition holds. An award scores either a flat `vp` or a count-scaled `vp_per` (VP per instance of the thing named by `per`). Awards accrue independently and sum; a card's '+ ... CUMULATIVE' rows are modelled as separate awards flagged `cumulative` for faithful round-trip. Awards sharing the same `exclusive_group` value within a card resolve as the highest-scoring single award fires (the card's literal 'OR' rows between tier breakpoints, e.g. Record-Breaking Mission's 3-Fronts vs 4-Fronts).",
|
|
147
163
|
"items": {
|
|
148
164
|
"type": "object",
|
|
149
165
|
"properties": {
|
|
@@ -174,6 +190,12 @@
|
|
|
174
190
|
"type": "boolean",
|
|
175
191
|
"default": false,
|
|
176
192
|
"description": "Marks an award the card shows as an additive '+' bonus to the preceding award in the same trigger block (the card's CUMULATIVE rows). Purely descriptive — all awards accrue independently and are summed."
|
|
193
|
+
},
|
|
194
|
+
"exclusive_group": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"minLength": 1,
|
|
197
|
+
"maxLength": 64,
|
|
198
|
+
"description": "Awards sharing this kebab-case group key resolve as 'score only the highest, not the sum' (the card's literal OR between tier rows). Awards with different `exclusive_group` values, or no value, accrue independently."
|
|
177
199
|
}
|
|
178
200
|
},
|
|
179
201
|
"oneOf": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"simple-condition": {
|
|
13
13
|
"type": "object",
|
|
14
|
-
"$comment": "Board/meta-state and scoring predicates. `parameters` is intentionally open (additionalProperties: true); each type documents its own param convention. Scoring predicates added for mission cards: `units-destroyed` { side: 'enemy'|'friendly', window: 'this-turn'|'previous-turn', count_min: int } — at least count_min units of `side` were destroyed in `window`. `units-destroyed-comparison` { subject: {side, window}, comparator: 'greater-than'|'greater-or-equal', reference: {side, window} } — compares two destruction tallies (e.g. more enemy units destroyed this turn than friendly last turn). `objective-majority` { relative_to: 'opponent' } — you control more objectives than the named party. `controls-objective` params: { count_min: int, objective_role?: 'central', exclude?: 'home', objective?: 'opponent-home' }.",
|
|
14
|
+
"$comment": "Board/meta-state and scoring predicates. `parameters` is intentionally open (additionalProperties: true); each type documents its own param convention. Scoring predicates added for mission cards: `units-destroyed` { side: 'enemy'|'friendly', window: 'this-turn'|'previous-turn', count_min: int } — at least count_min units of `side` were destroyed in `window`. `units-destroyed-comparison` { subject: {side, window}, comparator: 'greater-than'|'greater-or-equal', reference: {side, window} } — compares two destruction tallies (e.g. more enemy units destroyed this turn than friendly last turn). `objective-majority` { relative_to: 'opponent' } — you control more objectives than the named party. `controls-objective` params: { count_min: int, objective_role?: 'central'|'expansion'|'non-home'|'home', exclude?: 'home', objective?: 'opponent-home'|'your-home', scope?: 'enemy-territory'|'your-territory' }. Mission-card extensions (11e primary deck): `action-completed` { action_id?: string, target_kind?: 'objective'|'terrain'|'enemy-unit'|'self', target_filter?: { in_enemy_territory?: bool, objective_role?: 'central'|'non-home', exclude?: 'home' }, count_min: int, window?: 'this-turn'|'previous-turn'|'cumulative' } — at least count_min instances of a named action were completed in the window. `objective-has-tag` { tag: 'baited'|'triangulated'|'consecrated'|'sabotaged'|'marked'|'vanguard'|'spotted', count_min: int, count_max?: int, objective?: 'opponent-home'|'your-home', scope?: 'enemy-territory'|'your-territory' } — at least count_min objectives carry the named transient tag. `unit-has-tag` { tag: 'doomed'|'spotted', side: 'enemy'|'friendly', count_min: int, window?: 'destroyed-this-turn'|'still-on-board' } — at least count_min units of `side` carry the tag (optionally with a destruction filter — Punishment scores when a Doomed unit was destroyed or left the battlefield). `terrain-has-tag` { tag: 'mined'|'marked'|'vanguard', friendly_units_min?: int, enemy_units_max?: int, last_marked?: bool, in_enemy_dz?: bool } — terrain piece state predicate; `last_marked` selects the most-recently-marked piece (Find and Deny / Recover the Relics' Overwhelming Force trigger). `new-objective-controlled` { count_min: int } — at least count_min objectives are controlled this turn that were not controlled in the previous command phase. `engagement-fronts` { count_min: int } — friendly units engage enemies in at least count_min distinct fronts; a 'front' is one of the territory zones from the deployment-pattern's `territories[]`, so this composes with the existing `territory-control` predicate. `destroyed-while-on-objective` { destroyer_on_objective?: bool, victim_on_objective?: bool, count_min: int } — count_min enemy units were destroyed this turn under the named spatial condition (the destroying friendly unit, the destroyed enemy unit, or both were standing on an objective at the moment of the kill).",
|
|
15
15
|
"properties": {
|
|
16
16
|
"type": {
|
|
17
17
|
"type": "string",
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
"destroyed-by-attack-type", "controls-objective", "is-attached",
|
|
28
28
|
"terrain-area-control", "engagement-state", "territory-control",
|
|
29
29
|
"fights-first", "disposition-matches",
|
|
30
|
-
"units-destroyed", "units-destroyed-comparison", "objective-majority"
|
|
30
|
+
"units-destroyed", "units-destroyed-comparison", "objective-majority",
|
|
31
|
+
"action-completed", "objective-has-tag", "unit-has-tag",
|
|
32
|
+
"terrain-has-tag", "new-objective-controlled",
|
|
33
|
+
"engagement-fronts", "destroyed-while-on-objective"
|
|
31
34
|
]
|
|
32
35
|
},
|
|
33
36
|
"parameters": { "type": "object", "additionalProperties": true },
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"model-destruction", "resurrection",
|
|
31
31
|
"resource-gain", "resource-spend",
|
|
32
32
|
"charge-roll-modifier", "terrain-area-tag",
|
|
33
|
+
"objective-tag", "unit-tag",
|
|
33
34
|
"bs-modifier", "engagement-passthrough"
|
|
34
35
|
]
|
|
35
36
|
},
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"modifier": { "type": "object", "additionalProperties": true }
|
|
46
47
|
},
|
|
47
48
|
"required": ["type", "target"],
|
|
48
|
-
"$comment": "When `type` is `re-roll`, `modifier` must carry `roll` (string) and `subset` (`ones` | `all-failures`). Rerolls always target failures; the subset decides whether only 1s are rerolled or every failed die. The constraint is enforced by AJV at validation time and stripped from the codegen bundle (typify can't model if/then/else) — the generated TS/Rust types therefore see `modifier` as an open object, matching its other-`type` callers.",
|
|
49
|
+
"$comment": "When `type` is `re-roll`, `modifier` must carry `roll` (string) and `subset` (`ones` | `all-failures`). Rerolls always target failures; the subset decides whether only 1s are rerolled or every failed die. The constraint is enforced by AJV at validation time and stripped from the codegen bundle (typify can't model if/then/else) — the generated TS/Rust types therefore see `modifier` as an open object, matching its other-`type` callers. When `type` is `feel-no-pain`, `modifier` carries `threshold` (the FNP save target) and optionally `scope` ∈ {`all`, `mortal`}; an absent scope defaults to `all` (fires on every unsaved wound). The two scopes compose independently against the mortal-wound stream. Tag effects (`terrain-area-tag`, `objective-tag`, `unit-tag`) set a transient marker on the named subject; `modifier` carries `tag` (string) and optionally `source` ('this-action'|'destroying-unit') and `clears_on` ('turn-rollover'|'never'). `target` for tag effects names the kind of entity the tag is applied to ('unit', 'self') — a placeholder, since the marker target is the objective/terrain/unit specified by the action context, not a combat target.",
|
|
49
50
|
"allOf": [
|
|
50
51
|
{
|
|
51
52
|
"if": {
|