@alpaca-software/40kdc-data 0.3.1 → 0.3.2
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/codegen-data.js +1 -1
- package/dist/codegen-data.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 +1 -1
- package/dist/data/dataset.d.ts.map +1 -1
- package/dist/data/dataset.js +2 -2
- package/dist/data/dataset.js.map +1 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +1 -1
- package/dist/data/index.js.map +1 -1
- package/dist/data/types.d.ts +1 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/data/types.js +1 -1
- package/dist/data/types.js.map +1 -1
- package/dist/gen-conformance.js +5 -2
- package/dist/gen-conformance.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/runner.js +1 -1
- package/dist/runner.js.map +1 -1
- package/dist/scoring/index.d.ts +135 -0
- package/dist/scoring/index.d.ts.map +1 -0
- package/dist/scoring/index.js +195 -0
- package/dist/scoring/index.js.map +1 -0
- package/dist/translate/index.d.ts +1 -1
- package/dist/translate/index.d.ts.map +1 -1
- package/dist/translate/index.js.map +1 -1
- package/dist/translate/scoring.d.ts +6 -0
- package/dist/translate/scoring.d.ts.map +1 -1
- package/dist/translate/scoring.js.map +1 -1
- package/package.json +1 -1
- package/schemas/core/secondary-card.schema.json +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scoring/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Card-driven secondary-mission scoring, 10th-edition tactical model.\n *\n * Drawn secondaries are *held* in hand across rounds and **scored once**: the\n * player asserts which of a card's awards they achieved, the engine computes the\n * VP (clamped to the card's cap), records it against the current battle round,\n * and the card is then discarded. There is no multi-turn per-card accrual — a\n * card pays out exactly once.\n *\n * Why \"asserted\" rather than evaluated: there is no board-state model here, so\n * an award's `when` condition is a human-readable label (see\n * `translate/scoring.ts`'s `describeScoringCard`, which this module never\n * modifies), not something the engine checks. The player ticks the awards they\n * made; the engine does the arithmetic, the OR-tier resolution, the cumulative\n * sums, and the cap.\n *\n * Deck-level rules the card schema deliberately omits live here as constants —\n * chiefly the 5 VP-per-card ceiling of the Tactical approach. The Fixed approach\n * instead uses each award's printed `vp_max`.\n *\n * `PlayerGame` is a plain JSON-serializable object so a UI can persist a whole\n * match (two of them) to localStorage and rehydrate without a revival step.\n *\n * CONFORMANCE FOLLOW-UP: this engine is TypeScript-only for now. A Rust port\n * plus a `conformance/scoring` corpus area (and a `SPEC_VERSION` bump) are a\n * separate change; the public shapes below are the surface that port mirrors,\n * so keep them stable.\n */\n\nimport type { SecondaryCard } from \"../generated.js\";\nimport type { ScoringAward, ScoringMode } from \"../translate/scoring.js\";\n\n/** The Tactical approach caps a single secondary's score at this many VP. */\nexport const TACTICAL_CARD_CAP = 5;\n/** Battle rounds in a game. */\nexport const ROUNDS = 5;\n/** Per-player VP ceiling (WTC sheet: grand total out of 100). */\nexport const GAME_VP_CAP = 100;\n\n/** An award the player ticks when scoring, with a count for per-instance awards. */\nexport interface AssertedAward {\n award: ScoringAward;\n /** Instances achieved (for `vp_per` awards); defaults to 1. */\n count?: number;\n}\n\n/** VP recorded against a single battle round. */\nexport interface RoundCell {\n primary: number;\n secondary: number;\n}\n\n/** A scored secondary, kept so the record can be shown and undone. */\nexport interface ScoreEntry {\n cardId: string;\n /** Battle round (1-based) the card was scored in. */\n round: number;\n vp: number;\n}\n\n/** One player's whole-game scoring state. Plain data — safe to JSON round-trip. */\nexport interface PlayerGame {\n /** Scoring approach: filters `mode` awards and sets the per-score cap. */\n approach: ScoringMode;\n /** Drawn-but-unscored secondaries, by card id. Scoring removes a card from here. */\n handIds: string[];\n /** Per-round VP, index 0 = round 1. Always length {@link ROUNDS}. */\n rounds: RoundCell[];\n /** Log of scored secondaries, in scoring order — the editable record. */\n log: ScoreEntry[];\n}\n\n/** A fresh player game for the given approach (defaults to tactical). */\nexport function emptyPlayerGame(approach: ScoringMode = \"tactical\"): PlayerGame {\n return {\n approach,\n handIds: [],\n rounds: Array.from({ length: ROUNDS }, () => ({ primary: 0, secondary: 0 })),\n log: [],\n };\n}\n\n/** Read a card's `awards`, typed (the generated `SecondaryCard` leaves them opaque). */\nexport function awardsOf(card: SecondaryCard): ScoringAward[] {\n return (card.awards ?? []) as unknown as ScoringAward[];\n}\n\n/**\n * The awards a player scores under `approach`. An award with no `mode` is flat\n * (it scores the same either way); an award tagged `fixed`/`tactical` scores\n * only under the matching approach.\n */\nexport function awardsForApproach(card: SecondaryCard, approach: ScoringMode): ScoringAward[] {\n return awardsOf(card).filter((a) => a.mode == null || a.mode === approach);\n}\n\n/**\n * VP for a single asserted award. A flat `vp` ignores `count`; a `vp_per` award\n * scores `vp_per × count`, with `count` clamped to `per_max` when present.\n */\nexport function scoreAward(award: ScoringAward, count = 1): number {\n if (award.vp != null) return award.vp;\n if (award.vp_per != null) {\n const capped = award.per_max != null ? Math.min(count, award.per_max) : count;\n return award.vp_per * Math.max(0, capped);\n }\n return 0;\n}\n\n/**\n * VP from everything asserted in one scoring, before the card cap. Awards\n * sharing an `exclusive_group` resolve as \"only the highest scores\" (the card's\n * literal OR between tier rows); everything else, including `cumulative` \"+\"\n * rows, sums.\n */\nexport function scoreTurn(asserted: AssertedAward[]): number {\n const groupBest = new Map<string, number>();\n let total = 0;\n for (const { award, count } of asserted) {\n const v = scoreAward(award, count ?? 1);\n if (award.exclusive_group != null) {\n const prev = groupBest.get(award.exclusive_group) ?? 0;\n if (v > prev) groupBest.set(award.exclusive_group, v);\n } else {\n total += v;\n }\n }\n for (const v of groupBest.values()) total += v;\n return total;\n}\n\n/**\n * A card's per-score VP ceiling under `approach`. Tactical is the universal\n * {@link TACTICAL_CARD_CAP}. Fixed uses the largest `vp_max` printed on the\n * card's scorable awards, or `Infinity` when none is printed (uncapped).\n */\nexport function scoreCap(card: SecondaryCard, approach: ScoringMode): number {\n if (approach === \"tactical\") return TACTICAL_CARD_CAP;\n const caps = awardsForApproach(card, \"fixed\")\n .map((a) => a.vp_max)\n .filter((x): x is number => x != null);\n return caps.length > 0 ? Math.max(...caps) : Infinity;\n}\n\n/**\n * The VP a single scoring of `card` grants under `approach`: the asserted awards'\n * total, clamped to the card's cap. This is the amount banked when the card is\n * scored (and then discarded).\n */\nexport function scoreSecondaryEvent(\n asserted: AssertedAward[],\n card: SecondaryCard,\n approach: ScoringMode,\n): number {\n return Math.min(scoreTurn(asserted), scoreCap(card, approach));\n}\n\nfunction roundIndex(round: number): number {\n return Math.max(0, Math.min(ROUNDS - 1, Math.trunc(round) - 1));\n}\n\n/** Add secondary VP to a battle round (1-based). Pure — returns new state. */\nexport function recordSecondary(pg: PlayerGame, round: number, vp: number): PlayerGame {\n const i = roundIndex(round);\n const rounds = pg.rounds.map((c, idx) =>\n idx === i ? { ...c, secondary: c.secondary + Math.max(0, vp) } : c,\n );\n return { ...pg, rounds };\n}\n\n/**\n * Score a held secondary: add its VP to the round, append it to the log, and\n * discard it from hand. Pure. The caller computes `vp` via\n * {@link scoreSecondaryEvent}.\n */\nexport function scoreSecondary(\n pg: PlayerGame,\n round: number,\n cardId: string,\n vp: number,\n): PlayerGame {\n const banked = Math.max(0, vp);\n const recorded = recordSecondary(pg, round, banked);\n return {\n ...removeFromHand(recorded, cardId),\n log: [...pg.log, { cardId, round, vp: banked }],\n };\n}\n\n/**\n * Undo a logged scoring by index: subtract its VP from its round, drop the log\n * entry, and return the card to hand so it can be re-scored. Pure; a no-op for\n * an out-of-range index.\n */\nexport function removeScore(pg: PlayerGame, index: number): PlayerGame {\n const entry = pg.log[index];\n if (!entry) return pg;\n const i = roundIndex(entry.round);\n const rounds = pg.rounds.map((c, idx) =>\n idx === i ? { ...c, secondary: Math.max(0, c.secondary - entry.vp) } : c,\n );\n const log = pg.log.filter((_, idx) => idx !== index);\n const handIds = pg.handIds.includes(entry.cardId)\n ? pg.handIds\n : [...pg.handIds, entry.cardId];\n return { ...pg, rounds, log, handIds };\n}\n\n/** Set primary VP for a battle round (1-based) to a clamped value. Pure. */\nexport function setPrimary(pg: PlayerGame, round: number, vp: number): PlayerGame {\n const i = roundIndex(round);\n const rounds = pg.rounds.map((c, idx) =>\n idx === i ? { ...c, primary: Math.max(0, vp) } : c,\n );\n return { ...pg, rounds };\n}\n\n/** Put a drawn card in hand (no duplicates). Pure. */\nexport function addToHand(pg: PlayerGame, cardId: string): PlayerGame {\n if (pg.handIds.includes(cardId)) return pg;\n return { ...pg, handIds: [...pg.handIds, cardId] };\n}\n\n/** Remove a card from hand (e.g. on score or discard). Pure. */\nexport function removeFromHand(pg: PlayerGame, cardId: string): PlayerGame {\n return { ...pg, handIds: pg.handIds.filter((id) => id !== cardId) };\n}\n\n/** Total primary VP across the game. */\nexport function playerPrimary(pg: PlayerGame): number {\n return pg.rounds.reduce((sum, c) => sum + c.primary, 0);\n}\n\n/** Total secondary VP across the game. */\nexport function playerSecondary(pg: PlayerGame): number {\n return pg.rounds.reduce((sum, c) => sum + c.secondary, 0);\n}\n\n/** Grand total VP, capped at {@link GAME_VP_CAP}. */\nexport function playerTotal(pg: PlayerGame): number {\n return Math.min(GAME_VP_CAP, playerPrimary(pg) + playerSecondary(pg));\n}\n\n/**\n * The WTC 20-point result from two grand totals. The winner's margin maps onto\n * 11 bands (0-5 → 10-10 draw, 6-10 → 11-9, ... 51+ → 20-0); the loser gets the\n * complement. `a`/`b` correspond to the argument order.\n */\nexport function wtcResult(totalA: number, totalB: number): { a: number; b: number } {\n const diff = Math.abs(totalA - totalB);\n const band = diff <= 5 ? 0 : Math.min(10, Math.ceil((diff - 5) / 5));\n const winner = 10 + band;\n const loser = 10 - band;\n if (totalA === totalB) return { a: 10, b: 10 };\n return totalA > totalB ? { a: winner, b: loser } : { a: loser, b: winner };\n}\n"]}
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* across language ports by the `conformance/scoring-translation` corpus.
|
|
6
6
|
*/
|
|
7
7
|
export { describeCondition, dekebab, type Condition } from "./condition.js";
|
|
8
|
-
export { describeTrigger, describeAward, describeScoringCard, type ScoringTrigger, type ScoringAward, } from "./scoring.js";
|
|
8
|
+
export { describeTrigger, describeAward, describeScoringCard, type ScoringTrigger, type ScoringAward, type ScoringMode, } from "./scoring.js";
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +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,
|
|
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,EACjB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC"}
|
|
@@ -1 +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,
|
|
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,GAIpB,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 type ScoringMode,\n} from \"./scoring.js\";\n"]}
|
|
@@ -18,6 +18,8 @@ export interface ScoringTrigger {
|
|
|
18
18
|
max?: number;
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
/** The scoring approach a card is played under (cards that print both). */
|
|
22
|
+
export type ScoringMode = "fixed" | "tactical";
|
|
21
23
|
/** One VP-award block on a scoring card. */
|
|
22
24
|
export interface ScoringAward {
|
|
23
25
|
trigger?: ScoringTrigger;
|
|
@@ -26,8 +28,12 @@ export interface ScoringAward {
|
|
|
26
28
|
vp_per?: number;
|
|
27
29
|
per?: string;
|
|
28
30
|
per_max?: number;
|
|
31
|
+
/** Per-game VP ceiling for this award (the card's "UP TO N VP"). */
|
|
32
|
+
vp_max?: number;
|
|
29
33
|
cumulative?: boolean;
|
|
30
34
|
exclusive_group?: string;
|
|
35
|
+
/** Which scoring track this award belongs to, on cards that print both. */
|
|
36
|
+
mode?: ScoringMode;
|
|
31
37
|
}
|
|
32
38
|
/** "End of your Command phase (round 2+)" and friends. */
|
|
33
39
|
export declare function describeTrigger(t: ScoringTrigger): string;
|
|
@@ -1 +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;
|
|
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,2EAA2E;AAC3E,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,CAAC;AAE/C,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,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scoring.js","sourceRoot":"","sources":["../../src/translate/scoring.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAkB,MAAM,gBAAgB,CAAC;
|
|
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/** The scoring approach a card is played under (cards that print both). */\nexport type ScoringMode = \"fixed\" | \"tactical\";\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 /** Per-game VP ceiling for this award (the card's \"UP TO N VP\"). */\n vp_max?: number;\n cumulative?: boolean;\n exclusive_group?: string;\n /** Which scoring track this award belongs to, on cards that print both. */\n mode?: ScoringMode;\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.
|
|
3
|
+
"version": "0.3.2",
|
|
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": [
|
|
@@ -186,6 +186,16 @@
|
|
|
186
186
|
"minimum": 1,
|
|
187
187
|
"description": "Optional cap on how many instances `vp_per` counts."
|
|
188
188
|
},
|
|
189
|
+
"vp_max": {
|
|
190
|
+
"type": "integer",
|
|
191
|
+
"minimum": 1,
|
|
192
|
+
"description": "Optional cap on the total VP this award can contribute over the game — the card's 'UP TO N VP' ceiling. Distinct from `per_max`, which caps the instance count; use `vp_max` when the printed ceiling is not a multiple of `vp_per` (e.g. Burden of Trust's '2VP per guarded objective, up to 9VP')."
|
|
193
|
+
},
|
|
194
|
+
"mode": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"enum": ["fixed", "tactical"],
|
|
197
|
+
"description": "Which scoring track this award belongs to on cards that print both. Fixed missions are chosen for the whole game and score the (usually lower) `fixed` values; Tactical missions are drawn each turn and score the `tactical` values. Omitted on cards that score the same regardless of approach. A card may carry parallel `fixed` and `tactical` awards for the same condition; a consumer scores only the awards matching the player's chosen approach."
|
|
198
|
+
},
|
|
189
199
|
"cumulative": {
|
|
190
200
|
"type": "boolean",
|
|
191
201
|
"default": false,
|