@alpaca-software/40kdc-data 0.1.2 → 0.1.3
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 +2 -0
- package/dist/abilities-resolver/resolver.d.ts +13 -4
- package/dist/abilities-resolver/resolver.d.ts.map +1 -1
- package/dist/abilities-resolver/resolver.js +22 -15
- package/dist/abilities-resolver/resolver.js.map +1 -1
- package/dist/audit-coverage.d.ts +78 -0
- package/dist/audit-coverage.d.ts.map +1 -0
- package/dist/audit-coverage.js +341 -0
- package/dist/audit-coverage.js.map +1 -0
- package/dist/author-batch.d.ts +147 -0
- package/dist/author-batch.d.ts.map +1 -0
- package/dist/author-batch.js +675 -0
- package/dist/author-batch.js.map +1 -0
- package/dist/author-input.d.ts +37 -0
- package/dist/author-input.d.ts.map +1 -0
- package/dist/author-input.js +162 -0
- package/dist/author-input.js.map +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/translate.d.ts.map +1 -1
- package/dist/commands/translate.js +9 -4
- package/dist/commands/translate.js.map +1 -1
- package/dist/cruncher/attribution.d.ts +66 -0
- package/dist/cruncher/attribution.d.ts.map +1 -0
- package/dist/cruncher/attribution.js +88 -0
- package/dist/cruncher/attribution.js.map +1 -0
- package/dist/cruncher/buffs.d.ts +23 -1
- package/dist/cruncher/buffs.d.ts.map +1 -1
- package/dist/cruncher/buffs.js +1 -1
- package/dist/cruncher/buffs.js.map +1 -1
- package/dist/cruncher/from-dsl.d.ts +32 -0
- package/dist/cruncher/from-dsl.d.ts.map +1 -1
- package/dist/cruncher/from-dsl.js +485 -40
- package/dist/cruncher/from-dsl.js.map +1 -1
- package/dist/cruncher/index.d.ts +1 -0
- package/dist/cruncher/index.d.ts.map +1 -1
- package/dist/cruncher/index.js +1 -0
- package/dist/cruncher/index.js.map +1 -1
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/collection.d.ts +9 -0
- package/dist/data/collection.d.ts.map +1 -1
- package/dist/data/collection.js +14 -0
- package/dist/data/collection.js.map +1 -1
- package/dist/data/dataset.d.ts +80 -2
- package/dist/data/dataset.d.ts.map +1 -1
- package/dist/data/dataset.js +143 -6
- package/dist/data/dataset.js.map +1 -1
- package/dist/data/entities.d.ts +2 -5
- package/dist/data/entities.d.ts.map +1 -1
- package/dist/data/entities.js.map +1 -1
- package/dist/data/index.d.ts +3 -2
- 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/roster-resolve.d.ts +26 -1
- package/dist/data/roster-resolve.d.ts.map +1 -1
- package/dist/data/roster-resolve.js +46 -0
- package/dist/data/roster-resolve.js.map +1 -1
- package/dist/export/index.d.ts +1 -0
- package/dist/export/index.d.ts.map +1 -1
- package/dist/export/index.js +3 -0
- package/dist/export/index.js.map +1 -1
- package/dist/export/rosterizer.d.ts +3 -0
- package/dist/export/rosterizer.d.ts.map +1 -0
- package/dist/export/rosterizer.js +144 -0
- package/dist/export/rosterizer.js.map +1 -0
- package/dist/export/serializer.d.ts +1 -1
- package/dist/export/serializer.d.ts.map +1 -1
- package/dist/export/serializer.js.map +1 -1
- package/dist/gen-conformance.js +212 -11
- package/dist/gen-conformance.js.map +1 -1
- package/dist/import/gw.d.ts +69 -0
- package/dist/import/gw.d.ts.map +1 -0
- package/dist/import/gw.js +245 -0
- package/dist/import/gw.js.map +1 -0
- package/dist/import/import-roster.d.ts +52 -3
- package/dist/import/import-roster.d.ts.map +1 -1
- package/dist/import/import-roster.js +114 -4
- package/dist/import/import-roster.js.map +1 -1
- package/dist/import/index.d.ts +2 -2
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -1
- package/dist/import/index.js.map +1 -1
- package/dist/import/listforge.d.ts.map +1 -1
- package/dist/import/listforge.js +15 -1
- package/dist/import/listforge.js.map +1 -1
- package/dist/import/newrecruit-text.d.ts +3 -0
- package/dist/import/newrecruit-text.d.ts.map +1 -1
- package/dist/import/newrecruit-text.js +6 -0
- package/dist/import/newrecruit-text.js.map +1 -1
- package/dist/import/newrecruit-wtc.d.ts.map +1 -1
- package/dist/import/newrecruit-wtc.js +10 -7
- package/dist/import/newrecruit-wtc.js.map +1 -1
- package/dist/import/rosterizer.d.ts +70 -0
- package/dist/import/rosterizer.d.ts.map +1 -0
- package/dist/import/rosterizer.js +348 -0
- package/dist/import/rosterizer.js.map +1 -0
- package/dist/import/types.d.ts +1 -1
- package/dist/import/types.d.ts.map +1 -1
- package/dist/import/types.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/migrations/2026-weapon-keywords.js +4 -0
- package/dist/migrations/2026-weapon-keywords.js.map +1 -1
- package/dist/runner.d.ts +38 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +492 -0
- package/dist/runner.js.map +1 -0
- package/dist/scrub-ip.d.ts +14 -0
- package/dist/scrub-ip.d.ts.map +1 -0
- package/dist/scrub-ip.js +88 -0
- package/dist/scrub-ip.js.map +1 -0
- package/package.json +9 -2
- package/schemas/core/roster.schema.json +3 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/commands/translate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Translates ability DSL entries into plain English descriptions.\n *\n * Recursively walks the effect/condition tree and produces human-readable\n * text purely from the structured data — no external text sources needed.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n// ─── Types (minimal, matching schema shapes) ─────────────────────────\n\ninterface Condition {\n type?: string;\n operator?: string;\n operands?: Condition[];\n parameters?: Record<string, unknown>;\n negated?: boolean;\n}\n\ninterface Effect {\n type: string;\n target?: string;\n modifier?: Record<string, unknown>;\n condition?: Condition;\n effect?: Effect;\n steps?: Effect[];\n options?: (Effect & { name?: string; requirement?: Record<string, unknown>; choice_label?: string })[];\n choice_label?: string;\n dice?: string;\n threshold?: number | string;\n comparison?: string;\n on_success?: Effect | null;\n on_fail?: Effect | null;\n pool?: { count: number; die: string };\n max_activations?: number;\n}\n\ninterface Ability {\n ability_id: string;\n name: string;\n ability_type?: string;\n behavior?: string;\n detachment_id?: string | null;\n faction_id?: string | null;\n unit_ids?: string[];\n effect: Effect;\n scope?: { range: string; duration: string; range_inches?: number };\n}\n\n// ─── Condition translator ────────────────────────────────────────────\n\nfunction translateCondition(c: Condition): string {\n const negate = c.negated ? \"not \" : \"\";\n\n // Compound conditions\n if (c.operator === \"and\" && c.operands) {\n const parts = c.operands.map(translateCondition);\n return parts.join(\" AND \");\n }\n if (c.operator === \"or\" && c.operands) {\n const parts = c.operands.map(translateCondition);\n return parts.join(\" OR \");\n }\n if (c.operator === \"not\" && c.operands) {\n return `NOT (${c.operands.map(translateCondition).join(\", \")})`;\n }\n\n const p = c.parameters ?? {};\n\n switch (c.type) {\n case \"phase-is\":\n return `${negate}during the ${p.phase} phase`;\n case \"timing-is\":\n return `${negate}at ${formatTiming(p.timing as string)}`;\n case \"player-turn-is\":\n return `${negate}in ${p.turn === \"your-turn\" ? \"your\" : p.turn === \"opponent-turn\" ? \"opponent's\" : \"either player's\"} turn`;\n case \"charged-this-turn\":\n return `${negate}unit charged this turn`;\n case \"advanced-this-turn\":\n return `${negate}unit advanced this turn`;\n case \"remained-stationary\":\n return `${negate}unit remained stationary`;\n case \"unit-below-starting-strength\":\n return `${negate}target is below starting strength`;\n case \"unit-below-half-strength\":\n return `${negate}target is below half strength`;\n case \"unit-has-keyword\":\n return `${negate}unit has \"${p.keyword}\"`;\n case \"target-has-keyword\":\n return `${negate}target has \"${p.keyword}\"`;\n case \"model-is-leader\":\n return `${negate}model is leading a unit`;\n case \"is-attached\":\n return `${negate}attached to a ${p.keyword ?? \"\"} unit`;\n case \"attack-is-type\":\n return `${negate}for ${p.attack_type} attacks`;\n case \"is-battle-shocked\":\n return `${negate}unit is battle-shocked`;\n case \"has-lost-wounds\":\n return `${negate}model has lost wounds`;\n case \"opponent-unit-within-range\":\n return `${negate}enemy unit within ${p.range === \"engagement\" ? \"engagement range\" : p.range + '\"'}`;\n case \"unit-within-range-of\":\n return `${negate}within ${p.range}\" of ${p.target_type ?? \"target\"}${p.keyword ? ` (${p.keyword})` : \"\"}`;\n case \"within-range-of-objective\":\n return `${negate}within range of an objective`;\n case \"controls-objective\":\n return `${negate}controlling 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 ${p.attack_type} attack`;\n default:\n return `${negate}[${c.type ?? \"unknown\"}]`;\n }\n}\n\nfunction formatTiming(t: string): string {\n return t.replace(/-/g, \" \");\n}\n\n// ─── Effect translator ───────────────────────────────────────────────\n\nfunction translateEffect(e: Effect, depth: number = 0): string {\n const indent = \" \".repeat(depth);\n const arrow = depth > 0 ? \"→ \" : \"\";\n\n switch (e.type) {\n case \"conditional\":\n return (\n `${indent}If ${translateCondition(e.condition!)}:\\n` +\n translateEffect(e.effect!, depth + 1)\n );\n\n case \"sequence\":\n return e\n .steps!.map((s) => translateEffect(s, depth))\n .join(\"\\n\");\n\n case \"choice\":\n return (\n `${indent}${arrow}Choose one${e.choice_label ? ` (${e.choice_label})` : \"\"}:\\n` +\n e\n .options!.map((o, i) => `${indent} ${i + 1}. ${translateEffectInline(o)}`)\n .join(\"\\n\")\n );\n\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n const success = e.on_success\n ? translateEffectInline(e.on_success)\n : \"nothing\";\n const fail = e.on_fail\n ? `, otherwise ${translateEffectInline(e.on_fail)}`\n : \"\";\n return `${indent}${arrow}Roll ${e.dice}: on ${comp}, ${success}${fail}`;\n }\n\n case \"dice-pool-allocation\": {\n const poolStr = `${e.pool!.count}${e.pool!.die}`;\n const lines = [`${indent}${arrow}Roll ${poolStr} (max ${e.max_activations} activations):`];\n for (const opt of e.options!) {\n const req = opt.requirement!;\n lines.push(\n `${indent} - ${opt.name}: need ${req.type} of ${req.min_value}+ → ${translateEffectInline(opt.effect!)}`\n );\n }\n return lines.join(\"\\n\");\n }\n\n default:\n return `${indent}${arrow}${translateEffectInline(e)}`;\n }\n}\n\n/** Single-line translation for leaf effects. */\nfunction translateEffectInline(e: Effect): string {\n const m = e.modifier ?? {};\n const target = formatTarget(e.target);\n\n switch (e.type) {\n case \"stat-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : m.operation === \"subtract\" ? \"-\" : `${m.operation} `;\n const scope = m.attack_type ? ` (${m.attack_type})` : \"\";\n return `${op}${m.value} ${m.stat}${scope} for ${target}`;\n }\n case \"roll-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : \"-\";\n return `${op}${m.value} to ${m.roll} rolls for ${target}`;\n }\n case \"re-roll\":\n return `re-roll ${m.roll}${m.value ? ` (${m.value}s)` : \"\"} for ${target}`;\n case \"mortal-wounds\":\n return `deal ${m.amount ?? m.amount_table ? \"variable\" : \"?\"} mortal wounds to ${target}`;\n case \"feel-no-pain\":\n return `${target} gains Feel No Pain ${m.threshold}+`;\n case \"keyword-grant\":\n return `${target}'s ${m.weapon_type ?? \"all\"} weapons gain ${m.keyword}`;\n case \"ability-grant\":\n return `${target} gains ${formatGrantType(m.grant_type as string)}`;\n case \"movement-modifier\":\n return `${target} gains ${m.move_type}${m.value ? ` ${m.value}\"` : \"\"}`;\n case \"damage-reduction\":\n return `reduce incoming damage to ${target} by ${m.amount}`;\n case \"resurrection\":\n return `return ${m.count ?? 1} model(s) to ${target} with ${m.wounds_remaining ?? \"full\"} wounds`;\n case \"model-destruction\":\n return `destroy ${m.count} non-leader model(s) from ${target}`;\n case \"cp-gain\":\n return `gain ${m.amount} CP`;\n case \"cp-refund\":\n return `refund ${m.amount} CP`;\n case \"resource-gain\":\n return `gain ${m.amount} to ${m.pool_id}`;\n case \"resource-spend\":\n return `spend ${m.amount} from ${m.pool_id}`;\n case \"invulnerable-save\":\n return `${target} gains ${m.value}+ invulnerable save`;\n case \"leadership-modifier\":\n return `force battle-shock test on ${target}`;\n case \"fight-on-death\":\n return `${target} fights on death`;\n case \"shoot-on-death\":\n return `${target} shoots on death`;\n case \"fight-first\":\n return `${target} fights first`;\n case \"fight-last\":\n return `${target} fights last`;\n case \"deep-strike\":\n return `${target} can deep strike`;\n case \"fallback-and-act\":\n return `${target} can fall back and act`;\n case \"attack-restriction\":\n return `${target}: ${m.restriction_type ?? \"restriction\"} (max ${m.max_models ?? \"?\"} models)`;\n case \"objective-control-modifier\":\n return `modify OC of ${target} by ${m.value}`;\n\n // Container types — recurse\n case \"conditional\":\n return `if ${translateCondition(e.condition!)}: ${translateEffectInline(e.effect!)}`;\n case \"sequence\":\n return e.steps!.map(translateEffectInline).join(\"; \");\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n return `roll ${e.dice} (${comp}): ${e.on_success ? translateEffectInline(e.on_success) : \"nothing\"}`;\n }\n\n default:\n return `[${e.type}]`;\n }\n}\n\nfunction formatTarget(t?: string): string {\n if (!t) return \"target\";\n return t.replace(/-/g, \" \");\n}\n\nfunction formatGrantType(g: string): string {\n return g.replace(/-/g, \" \");\n}\n\nfunction formatComparison(comp: string, threshold: number | string): string {\n const thStr = typeof threshold === \"string\" ? threshold : `${threshold}`;\n switch (comp) {\n case \"gte\": return `${thStr}+`;\n case \"lte\": return `${thStr} or less`;\n case \"gt\": return `greater than ${thStr}`;\n case \"lt\": return `less than ${thStr}`;\n case \"eq\": return `exactly ${thStr}`;\n default: return `${thStr}+`;\n }\n}\n\n// ─── Scope translator ────────────────────────────────────────────────\n\nfunction translateScope(s?: { range: string; duration: string; range_inches?: number }): string {\n if (!s) return \"\";\n const range = s.range.replace(/-/g, \" \");\n const duration = s.duration.replace(/-/g, \" \");\n return `Scope: ${range}${s.range_inches ? ` (${s.range_inches}\")` : \"\"}. Duration: ${duration}.`;\n}\n\n// ─── Main command ────────────────────────────────────────────────────\n\nexport async function translateCommand(\n path?: string\n): Promise<void> {\n const filePath = resolve(\n process.cwd(),\n path ?? \"../data/enrichment/world-eaters/abilities.json\"\n );\n const abilities: Ability[] = JSON.parse(readFileSync(filePath, \"utf-8\"));\n\n for (const a of abilities) {\n const meta: string[] = [];\n if (a.ability_type) meta.push(a.ability_type);\n if (a.behavior) meta.push(a.behavior);\n if (a.detachment_id) meta.push(`detachment: ${a.detachment_id}`);\n if (a.unit_ids?.length) meta.push(`units: ${a.unit_ids.join(\", \")}`);\n\n console.log(`\\n═══ ${a.name} [${a.ability_id}] ═══`);\n if (meta.length) console.log(` ${meta.join(\" | \")}`);\n console.log(translateEffect(a.effect));\n const scope = translateScope(a.scope);\n if (scope) console.log(scope);\n }\n\n console.log(`\\n── ${abilities.length} abilities translated ──`);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/commands/translate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Translates ability DSL entries into plain English descriptions.\n *\n * Recursively walks the effect/condition tree and produces human-readable\n * text purely from the structured data — no external text sources needed.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\n// ─── Types (minimal, matching schema shapes) ─────────────────────────\n\ninterface Condition {\n type?: string;\n operator?: string;\n operands?: Condition[];\n parameters?: Record<string, unknown>;\n negated?: boolean;\n}\n\ninterface Effect {\n type: string;\n target?: string;\n modifier?: Record<string, unknown>;\n condition?: Condition;\n effect?: Effect;\n steps?: Effect[];\n options?: (Effect & { name?: string; requirement?: Record<string, unknown>; choice_label?: string })[];\n choice_label?: string;\n dice?: string;\n threshold?: number | string;\n comparison?: string;\n on_success?: Effect | null;\n on_fail?: Effect | null;\n pool?: { count: number; die: string };\n max_activations?: number;\n}\n\ninterface Ability {\n ability_id: string;\n name: string;\n ability_type?: string;\n behavior?: string;\n detachment_id?: string | null;\n faction_id?: string | null;\n unit_ids?: string[];\n effect: Effect;\n scope?: { range: string; duration: string; range_inches?: number };\n}\n\n// ─── Condition translator ────────────────────────────────────────────\n\nfunction translateCondition(c: Condition): string {\n const negate = c.negated ? \"not \" : \"\";\n\n // Compound conditions\n if (c.operator === \"and\" && c.operands) {\n const parts = c.operands.map(translateCondition);\n return parts.join(\" AND \");\n }\n if (c.operator === \"or\" && c.operands) {\n const parts = c.operands.map(translateCondition);\n return parts.join(\" OR \");\n }\n if (c.operator === \"not\" && c.operands) {\n return `NOT (${c.operands.map(translateCondition).join(\", \")})`;\n }\n\n const p = c.parameters ?? {};\n\n switch (c.type) {\n case \"phase-is\":\n return `${negate}during the ${p.phase} phase`;\n case \"timing-is\":\n return `${negate}at ${formatTiming(p.timing as string)}`;\n case \"player-turn-is\":\n return `${negate}in ${p.turn === \"your-turn\" ? \"your\" : p.turn === \"opponent-turn\" ? \"opponent's\" : \"either player's\"} turn`;\n case \"charged-this-turn\":\n return `${negate}unit charged this turn`;\n case \"advanced-this-turn\":\n return `${negate}unit advanced this turn`;\n case \"remained-stationary\":\n return `${negate}unit remained stationary`;\n case \"unit-below-starting-strength\":\n return `${negate}target is below starting strength`;\n case \"unit-below-half-strength\":\n return `${negate}target is below half strength`;\n case \"unit-has-keyword\":\n return `${negate}unit has \"${p.keyword}\"`;\n case \"target-has-keyword\":\n return `${negate}target has \"${p.keyword}\"`;\n case \"model-is-leader\":\n return `${negate}model is leading a unit`;\n case \"is-attached\":\n return `${negate}attached to a ${p.keyword ?? \"\"} unit`;\n case \"attack-is-type\":\n return `${negate}for ${p.attack_type} attacks`;\n case \"is-battle-shocked\":\n return `${negate}unit is battle-shocked`;\n case \"has-lost-wounds\":\n return `${negate}model has lost wounds`;\n case \"opponent-unit-within-range\":\n return `${negate}enemy unit within ${p.range === \"engagement\" ? \"engagement range\" : p.range + '\"'}`;\n case \"unit-within-range-of\":\n return `${negate}within ${p.range}\" of ${p.target_type ?? \"target\"}${p.keyword ? ` (${p.keyword})` : \"\"}`;\n case \"within-range-of-objective\":\n return `${negate}within range of an objective`;\n case \"controls-objective\":\n return `${negate}controlling 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 ${p.attack_type} attack`;\n default:\n return `${negate}[${c.type ?? \"unknown\"}]`;\n }\n}\n\nfunction formatTiming(t: string): string {\n return t.replace(/-/g, \" \");\n}\n\n// ─── Effect translator ───────────────────────────────────────────────\n\nfunction translateEffect(e: Effect, depth: number = 0): string {\n const indent = \" \".repeat(depth);\n const arrow = depth > 0 ? \"→ \" : \"\";\n\n switch (e.type) {\n case \"conditional\":\n return (\n `${indent}If ${translateCondition(e.condition!)}:\\n` +\n translateEffect(e.effect!, depth + 1)\n );\n\n case \"sequence\":\n return e\n .steps!.map((s) => translateEffect(s, depth))\n .join(\"\\n\");\n\n case \"choice\":\n return (\n `${indent}${arrow}Choose one${e.choice_label ? ` (${e.choice_label})` : \"\"}:\\n` +\n e\n .options!.map((o, i) => `${indent} ${i + 1}. ${translateEffectInline(o)}`)\n .join(\"\\n\")\n );\n\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n const success = e.on_success\n ? translateEffectInline(e.on_success)\n : \"nothing\";\n const fail = e.on_fail\n ? `, otherwise ${translateEffectInline(e.on_fail)}`\n : \"\";\n return `${indent}${arrow}Roll ${e.dice}: on ${comp}, ${success}${fail}`;\n }\n\n case \"dice-pool-allocation\": {\n const poolStr = `${e.pool!.count}${e.pool!.die}`;\n const lines = [`${indent}${arrow}Roll ${poolStr} (max ${e.max_activations} activations):`];\n for (const opt of e.options!) {\n const req = opt.requirement!;\n lines.push(\n `${indent} - ${opt.name}: need ${req.type} of ${req.min_value}+ → ${translateEffectInline(opt.effect!)}`\n );\n }\n return lines.join(\"\\n\");\n }\n\n default:\n return `${indent}${arrow}${translateEffectInline(e)}`;\n }\n}\n\n/** Single-line translation for leaf effects. */\nfunction translateEffectInline(e: Effect): string {\n const m = e.modifier ?? {};\n const target = formatTarget(e.target);\n\n switch (e.type) {\n case \"stat-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : m.operation === \"subtract\" ? \"-\" : `${m.operation} `;\n const scope = m.attack_type ? ` (${m.attack_type})` : \"\";\n return `${op}${m.value} ${m.stat}${scope} for ${target}`;\n }\n case \"roll-modifier\": {\n const op = m.operation === \"add\" ? \"+\" : \"-\";\n return `${op}${m.value} to ${m.roll} rolls for ${target}`;\n }\n case \"re-roll\":\n return `re-roll ${m.roll}${m.value ? ` (${m.value}s)` : \"\"} for ${target}`;\n case \"mortal-wounds\":\n return `deal ${m.amount ?? m.amount_table ? \"variable\" : \"?\"} mortal wounds to ${target}`;\n case \"feel-no-pain\":\n return `${target} gains Feel No Pain ${m.threshold}+`;\n case \"keyword-grant\": {\n // Grants come as singular `keyword` or the (dominant) `keywords` array.\n const kw = Array.isArray(m.keywords) ? m.keywords.join(\", \") : (m.keyword ?? \"keywords\");\n return `${target}'s ${m.weapon_type ?? \"all\"} weapons gain ${kw}`;\n }\n case \"ability-grant\":\n return `${target} gains ${formatGrantType((m.grant_type ?? m.ability_id) as string | undefined)}`;\n case \"movement-modifier\":\n return `${target} gains ${m.move_type}${m.value ? ` ${m.value}\"` : \"\"}`;\n case \"damage-reduction\":\n return `reduce incoming damage to ${target} by ${m.amount}`;\n case \"resurrection\":\n return `return ${m.count ?? 1} model(s) to ${target} with ${m.wounds_remaining ?? \"full\"} wounds`;\n case \"model-destruction\":\n return `destroy ${m.count} non-leader model(s) from ${target}`;\n case \"cp-gain\":\n return `gain ${m.amount} CP`;\n case \"cp-refund\":\n return `refund ${m.amount} CP`;\n case \"resource-gain\":\n return `gain ${m.amount} to ${m.pool_id}`;\n case \"resource-spend\":\n return `spend ${m.amount} from ${m.pool_id}`;\n case \"invulnerable-save\":\n return `${target} gains ${m.value}+ invulnerable save`;\n case \"leadership-modifier\":\n return `force battle-shock test on ${target}`;\n case \"fight-on-death\":\n return `${target} fights on death`;\n case \"shoot-on-death\":\n return `${target} shoots on death`;\n case \"fight-first\":\n return `${target} fights first`;\n case \"fight-last\":\n return `${target} fights last`;\n case \"deep-strike\":\n return `${target} can deep strike`;\n case \"fallback-and-act\":\n return `${target} can fall back and act`;\n case \"attack-restriction\":\n return `${target}: ${m.restriction_type ?? \"restriction\"} (max ${m.max_models ?? \"?\"} models)`;\n case \"objective-control-modifier\":\n return `modify OC of ${target} by ${m.value}`;\n\n // Container types — recurse\n case \"conditional\":\n return `if ${translateCondition(e.condition!)}: ${translateEffectInline(e.effect!)}`;\n case \"sequence\":\n return e.steps!.map(translateEffectInline).join(\"; \");\n case \"dice-gated\": {\n const comp = formatComparison(e.comparison ?? \"gte\", e.threshold!);\n return `roll ${e.dice} (${comp}): ${e.on_success ? translateEffectInline(e.on_success) : \"nothing\"}`;\n }\n\n default:\n return `[${e.type}]`;\n }\n}\n\nfunction formatTarget(t?: string): string {\n if (!t) return \"target\";\n return t.replace(/-/g, \" \");\n}\n\nfunction formatGrantType(g: string | undefined): string {\n // ability-grant carries either `grant_type` or `ability_id`; an unauthored\n // stub may carry neither. Don't crash the whole translation on a missing key.\n return g ? g.replace(/-/g, \" \") : \"an ability\";\n}\n\nfunction formatComparison(comp: string, threshold: number | string): string {\n const thStr = typeof threshold === \"string\" ? threshold : `${threshold}`;\n switch (comp) {\n case \"gte\": return `${thStr}+`;\n case \"lte\": return `${thStr} or less`;\n case \"gt\": return `greater than ${thStr}`;\n case \"lt\": return `less than ${thStr}`;\n case \"eq\": return `exactly ${thStr}`;\n default: return `${thStr}+`;\n }\n}\n\n// ─── Scope translator ────────────────────────────────────────────────\n\nfunction translateScope(s?: { range: string; duration: string; range_inches?: number }): string {\n if (!s) return \"\";\n const range = s.range.replace(/-/g, \" \");\n const duration = s.duration.replace(/-/g, \" \");\n return `Scope: ${range}${s.range_inches ? ` (${s.range_inches}\")` : \"\"}. Duration: ${duration}.`;\n}\n\n// ─── Main command ────────────────────────────────────────────────────\n\nexport async function translateCommand(\n path?: string\n): Promise<void> {\n const filePath = resolve(\n process.cwd(),\n path ?? \"../data/enrichment/world-eaters/abilities.json\"\n );\n const abilities: Ability[] = JSON.parse(readFileSync(filePath, \"utf-8\"));\n\n for (const a of abilities) {\n const meta: string[] = [];\n if (a.ability_type) meta.push(a.ability_type);\n if (a.behavior) meta.push(a.behavior);\n if (a.detachment_id) meta.push(`detachment: ${a.detachment_id}`);\n if (a.unit_ids?.length) meta.push(`units: ${a.unit_ids.join(\", \")}`);\n\n console.log(`\\n═══ ${a.name} [${a.ability_id}] ═══`);\n if (meta.length) console.log(` ${meta.join(\" | \")}`);\n console.log(translateEffect(a.effect));\n const scope = translateScope(a.scope);\n if (scope) console.log(scope);\n }\n\n console.log(`\\n── ${abilities.length} abilities translated ──`);\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-stage buff attribution by leave-one-out (LOO) recompute.
|
|
3
|
+
*
|
|
4
|
+
* The engine is closed-form, so the honest way to answer "how much did this
|
|
5
|
+
* buff lift this stage?" is to re-run {@link crunch} with that buff removed and
|
|
6
|
+
* diff the stage value. LOO is exactly correct through every non-linearity the
|
|
7
|
+
* pipeline has — the ±1 hit/wound caps, the wound-threshold table, save clamps,
|
|
8
|
+
* FNP, the models-killed cap — and it respects the resolver's non-additive
|
|
9
|
+
* rules for free: a buff that grants a keyword the weapon already carries (e.g.
|
|
10
|
+
* a second Sustained Hits 1) is deduped inside `resolveBuffs`, so its LOO delta
|
|
11
|
+
* comes out ≈ 0 rather than double-counting.
|
|
12
|
+
*
|
|
13
|
+
* Only *toggleable* buffs are attributed — abilities (army / detachment /
|
|
14
|
+
* stratagem / unit / attached / support) and manual UI toggles. The weapon's
|
|
15
|
+
* intrinsic keywords are auto-injected inside `crunch`, are not levers, and are
|
|
16
|
+
* never removed; they're reported by id in {@link AttributedStage.intrinsics}
|
|
17
|
+
* so a UI can explain an elevated baseline without splitting them out.
|
|
18
|
+
*
|
|
19
|
+
* @packageDocumentation
|
|
20
|
+
*/
|
|
21
|
+
import type { Dataset } from "../data/dataset.js";
|
|
22
|
+
import type { BuffSource } from "./buffs.js";
|
|
23
|
+
import { type EngineInput, type Stage } from "./engine.js";
|
|
24
|
+
/** One toggleable buff group's marginal effect on a single stage. */
|
|
25
|
+
export type StageLift = {
|
|
26
|
+
/** Representative source of the group (all its `Buff`s share a group key). */
|
|
27
|
+
source: BuffSource;
|
|
28
|
+
/** `stageValue(all buffs) − stageValue(all buffs minus this group)`. */
|
|
29
|
+
delta: number;
|
|
30
|
+
};
|
|
31
|
+
/** A pipeline stage with its value decomposed across the toggleable buffs. */
|
|
32
|
+
export type AttributedStage = {
|
|
33
|
+
name: Stage["name"];
|
|
34
|
+
/** Stage value with every buff on — identical to {@link crunch}'s stage. */
|
|
35
|
+
expected: number;
|
|
36
|
+
/** The engine's stage detail string, unchanged. */
|
|
37
|
+
detail: string;
|
|
38
|
+
/** Stage value with all groupable buffs removed (intrinsics kept). */
|
|
39
|
+
baseline: number;
|
|
40
|
+
/** Per-group marginal effect; groups whose |delta| ≤ epsilon are dropped. */
|
|
41
|
+
lifts: StageLift[];
|
|
42
|
+
/**
|
|
43
|
+
* `expected − baseline − Σ lifts`. Non-zero when buffs collide under a cap
|
|
44
|
+
* (two +1s sharing one ±1 cap each show ≈0 lift; the real +1 lands here),
|
|
45
|
+
* so a UI can surface it honestly as "overlap (capped)".
|
|
46
|
+
*/
|
|
47
|
+
residual: number;
|
|
48
|
+
/** Active weapon-keyword ids (intrinsic, auto-injected); display-only. */
|
|
49
|
+
intrinsics: string[];
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Decompose each pipeline stage of `crunch(input)` into the marginal lift of
|
|
53
|
+
* every toggleable buff group, via leave-one-out recompute.
|
|
54
|
+
*
|
|
55
|
+
* Cost is `groups + 2` `crunch` calls (full + baseline + one per group); the
|
|
56
|
+
* engine is closed-form, so this is cheap to call per weapon line.
|
|
57
|
+
*
|
|
58
|
+
* @param input The same {@link EngineInput} you'd pass to {@link crunch}.
|
|
59
|
+
* @param dataset Optional dataset override (defaults to the embedded one).
|
|
60
|
+
* @param opts `epsilon` — lifts/residuals at or below this magnitude are
|
|
61
|
+
* treated as zero (default 1e-6).
|
|
62
|
+
*/
|
|
63
|
+
export declare function attributeStages(input: EngineInput, dataset?: Dataset, opts?: {
|
|
64
|
+
epsilon?: number;
|
|
65
|
+
}): AttributedStage[];
|
|
66
|
+
//# sourceMappingURL=attribution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../src/cruncher/attribution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAU,KAAK,WAAW,EAAqB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEtF,qEAAqE;AACrE,MAAM,MAAM,SAAS,GAAG;IACtB,8EAA8E;IAC9E,MAAM,EAAE,UAAU,CAAC;IACnB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AA4BF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,OAAO,EACjB,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1B,eAAe,EAAE,CA2DnB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { crunch } from "./engine.js";
|
|
2
|
+
const DEFAULT_EPSILON = 1e-6;
|
|
3
|
+
/**
|
|
4
|
+
* Buffs the UI toggles on/off — the only kinds we attribute. Weapon-keyword
|
|
5
|
+
* buffs (intrinsics) are left in place for every recompute, so they never show
|
|
6
|
+
* up as a lift and never perturb the baseline.
|
|
7
|
+
*/
|
|
8
|
+
function isGroupable(source) {
|
|
9
|
+
return source.kind === "ability" || source.kind === "manual";
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Stable grouping key. Every `Buff` a single UI toggle flatMaps to shares one
|
|
13
|
+
* key, so a LOO pass removes the whole toggle, never a fragment of it.
|
|
14
|
+
*/
|
|
15
|
+
function groupKey(source) {
|
|
16
|
+
switch (source.kind) {
|
|
17
|
+
case "ability":
|
|
18
|
+
return `a:${source.abilityId}:${source.sourceUnitId ?? ""}`;
|
|
19
|
+
case "manual":
|
|
20
|
+
return `m:${source.label}`;
|
|
21
|
+
case "weapon-keyword":
|
|
22
|
+
return `w:${source.weaponId}:${source.keywordId}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Decompose each pipeline stage of `crunch(input)` into the marginal lift of
|
|
27
|
+
* every toggleable buff group, via leave-one-out recompute.
|
|
28
|
+
*
|
|
29
|
+
* Cost is `groups + 2` `crunch` calls (full + baseline + one per group); the
|
|
30
|
+
* engine is closed-form, so this is cheap to call per weapon line.
|
|
31
|
+
*
|
|
32
|
+
* @param input The same {@link EngineInput} you'd pass to {@link crunch}.
|
|
33
|
+
* @param dataset Optional dataset override (defaults to the embedded one).
|
|
34
|
+
* @param opts `epsilon` — lifts/residuals at or below this magnitude are
|
|
35
|
+
* treated as zero (default 1e-6).
|
|
36
|
+
*/
|
|
37
|
+
export function attributeStages(input, dataset, opts) {
|
|
38
|
+
const epsilon = opts?.epsilon ?? DEFAULT_EPSILON;
|
|
39
|
+
const full = crunch(input, dataset);
|
|
40
|
+
// First-seen order of groupable buff groups, with a representative source.
|
|
41
|
+
const order = [];
|
|
42
|
+
const repSource = new Map();
|
|
43
|
+
for (const b of input.buffs) {
|
|
44
|
+
if (!isGroupable(b.source))
|
|
45
|
+
continue;
|
|
46
|
+
const key = groupKey(b.source);
|
|
47
|
+
if (!repSource.has(key)) {
|
|
48
|
+
repSource.set(key, b.source);
|
|
49
|
+
order.push(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Baseline keeps only non-groupable buffs (weapon-keyword passthroughs) plus
|
|
53
|
+
// the engine's auto-injected intrinsics.
|
|
54
|
+
const baseline = crunch({ ...input, buffs: input.buffs.filter((b) => !isGroupable(b.source)) }, dataset);
|
|
55
|
+
// Leave-one-out: drop one whole group, keep the rest.
|
|
56
|
+
const loo = new Map();
|
|
57
|
+
for (const key of order) {
|
|
58
|
+
const without = input.buffs.filter((b) => !isGroupable(b.source) || groupKey(b.source) !== key);
|
|
59
|
+
loo.set(key, crunch({ ...input, buffs: without }, dataset));
|
|
60
|
+
}
|
|
61
|
+
const intrinsics = full.resolved.extraKeywords.map((e) => e.keywordRef.keyword_id);
|
|
62
|
+
// crunch always emits the same seven stages in the same order, so index
|
|
63
|
+
// alignment across full / baseline / loo is sound.
|
|
64
|
+
return full.stages.map((s, i) => {
|
|
65
|
+
const expected = s.expected;
|
|
66
|
+
const baseExpected = baseline.stages[i].expected;
|
|
67
|
+
let totalLift = 0;
|
|
68
|
+
const lifts = [];
|
|
69
|
+
for (const key of order) {
|
|
70
|
+
const delta = expected - loo.get(key).stages[i].expected;
|
|
71
|
+
totalLift += delta;
|
|
72
|
+
if (Math.abs(delta) > epsilon) {
|
|
73
|
+
lifts.push({ source: repSource.get(key), delta });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const residual = expected - baseExpected - totalLift;
|
|
77
|
+
return {
|
|
78
|
+
name: s.name,
|
|
79
|
+
expected,
|
|
80
|
+
detail: s.detail,
|
|
81
|
+
baseline: baseExpected,
|
|
82
|
+
lifts,
|
|
83
|
+
residual: Math.abs(residual) > epsilon ? residual : 0,
|
|
84
|
+
intrinsics,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=attribution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attribution.js","sourceRoot":"","sources":["../../src/cruncher/attribution.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Per-stage buff attribution by leave-one-out (LOO) recompute.\n *\n * The engine is closed-form, so the honest way to answer \"how much did this\n * buff lift this stage?\" is to re-run {@link crunch} with that buff removed and\n * diff the stage value. LOO is exactly correct through every non-linearity the\n * pipeline has — the ±1 hit/wound caps, the wound-threshold table, save clamps,\n * FNP, the models-killed cap — and it respects the resolver's non-additive\n * rules for free: a buff that grants a keyword the weapon already carries (e.g.\n * a second Sustained Hits 1) is deduped inside `resolveBuffs`, so its LOO delta\n * comes out ≈ 0 rather than double-counting.\n *\n * Only *toggleable* buffs are attributed — abilities (army / detachment /\n * stratagem / unit / attached / support) and manual UI toggles. The weapon's\n * intrinsic keywords are auto-injected inside `crunch`, are not levers, and are\n * never removed; they're reported by id in {@link AttributedStage.intrinsics}\n * so a UI can explain an elevated baseline without splitting them out.\n *\n * @packageDocumentation\n */\nimport type { Dataset } from \"../data/dataset.js\";\nimport type { BuffSource } from \"./buffs.js\";\nimport { crunch, type EngineInput, type EngineOutput, type Stage } from \"./engine.js\";\n\n/** One toggleable buff group's marginal effect on a single stage. */\nexport type StageLift = {\n /** Representative source of the group (all its `Buff`s share a group key). */\n source: BuffSource;\n /** `stageValue(all buffs) − stageValue(all buffs minus this group)`. */\n delta: number;\n};\n\n/** A pipeline stage with its value decomposed across the toggleable buffs. */\nexport type AttributedStage = {\n name: Stage[\"name\"];\n /** Stage value with every buff on — identical to {@link crunch}'s stage. */\n expected: number;\n /** The engine's stage detail string, unchanged. */\n detail: string;\n /** Stage value with all groupable buffs removed (intrinsics kept). */\n baseline: number;\n /** Per-group marginal effect; groups whose |delta| ≤ epsilon are dropped. */\n lifts: StageLift[];\n /**\n * `expected − baseline − Σ lifts`. Non-zero when buffs collide under a cap\n * (two +1s sharing one ±1 cap each show ≈0 lift; the real +1 lands here),\n * so a UI can surface it honestly as \"overlap (capped)\".\n */\n residual: number;\n /** Active weapon-keyword ids (intrinsic, auto-injected); display-only. */\n intrinsics: string[];\n};\n\nconst DEFAULT_EPSILON = 1e-6;\n\n/**\n * Buffs the UI toggles on/off — the only kinds we attribute. Weapon-keyword\n * buffs (intrinsics) are left in place for every recompute, so they never show\n * up as a lift and never perturb the baseline.\n */\nfunction isGroupable(source: BuffSource): boolean {\n return source.kind === \"ability\" || source.kind === \"manual\";\n}\n\n/**\n * Stable grouping key. Every `Buff` a single UI toggle flatMaps to shares one\n * key, so a LOO pass removes the whole toggle, never a fragment of it.\n */\nfunction groupKey(source: BuffSource): string {\n switch (source.kind) {\n case \"ability\":\n return `a:${source.abilityId}:${source.sourceUnitId ?? \"\"}`;\n case \"manual\":\n return `m:${source.label}`;\n case \"weapon-keyword\":\n return `w:${source.weaponId}:${source.keywordId}`;\n }\n}\n\n/**\n * Decompose each pipeline stage of `crunch(input)` into the marginal lift of\n * every toggleable buff group, via leave-one-out recompute.\n *\n * Cost is `groups + 2` `crunch` calls (full + baseline + one per group); the\n * engine is closed-form, so this is cheap to call per weapon line.\n *\n * @param input The same {@link EngineInput} you'd pass to {@link crunch}.\n * @param dataset Optional dataset override (defaults to the embedded one).\n * @param opts `epsilon` — lifts/residuals at or below this magnitude are\n * treated as zero (default 1e-6).\n */\nexport function attributeStages(\n input: EngineInput,\n dataset?: Dataset,\n opts?: { epsilon?: number },\n): AttributedStage[] {\n const epsilon = opts?.epsilon ?? DEFAULT_EPSILON;\n const full = crunch(input, dataset);\n\n // First-seen order of groupable buff groups, with a representative source.\n const order: string[] = [];\n const repSource = new Map<string, BuffSource>();\n for (const b of input.buffs) {\n if (!isGroupable(b.source)) continue;\n const key = groupKey(b.source);\n if (!repSource.has(key)) {\n repSource.set(key, b.source);\n order.push(key);\n }\n }\n\n // Baseline keeps only non-groupable buffs (weapon-keyword passthroughs) plus\n // the engine's auto-injected intrinsics.\n const baseline = crunch(\n { ...input, buffs: input.buffs.filter((b) => !isGroupable(b.source)) },\n dataset,\n );\n\n // Leave-one-out: drop one whole group, keep the rest.\n const loo = new Map<string, EngineOutput>();\n for (const key of order) {\n const without = input.buffs.filter(\n (b) => !isGroupable(b.source) || groupKey(b.source) !== key,\n );\n loo.set(key, crunch({ ...input, buffs: without }, dataset));\n }\n\n const intrinsics = full.resolved.extraKeywords.map((e) => e.keywordRef.keyword_id);\n\n // crunch always emits the same seven stages in the same order, so index\n // alignment across full / baseline / loo is sound.\n return full.stages.map((s, i) => {\n const expected = s.expected;\n const baseExpected = baseline.stages[i].expected;\n let totalLift = 0;\n const lifts: StageLift[] = [];\n for (const key of order) {\n const delta = expected - loo.get(key)!.stages[i].expected;\n totalLift += delta;\n if (Math.abs(delta) > epsilon) {\n lifts.push({ source: repSource.get(key)!, delta });\n }\n }\n const residual = expected - baseExpected - totalLift;\n return {\n name: s.name,\n expected,\n detail: s.detail,\n baseline: baseExpected,\n lifts,\n residual: Math.abs(residual) > epsilon ? residual : 0,\n intrinsics,\n };\n });\n}\n"]}
|
package/dist/cruncher/buffs.d.ts
CHANGED
|
@@ -19,7 +19,13 @@ export type BuffSource = {
|
|
|
19
19
|
} | {
|
|
20
20
|
kind: "ability";
|
|
21
21
|
abilityId: string;
|
|
22
|
-
abilityKind: "army" | "detachment" | "detachment-stratagem" | "unit" | "
|
|
22
|
+
abilityKind: "army" | "detachment" | "detachment-stratagem" | "unit" | "attached" | "support";
|
|
23
|
+
/**
|
|
24
|
+
* For `abilityKind: "attached"`, the combined-unit member the ability
|
|
25
|
+
* came from (so the UI can name it and show its leader/bodyguard role).
|
|
26
|
+
* Absent for other kinds.
|
|
27
|
+
*/
|
|
28
|
+
sourceUnitId?: string;
|
|
23
29
|
} | {
|
|
24
30
|
kind: "manual";
|
|
25
31
|
label: string;
|
|
@@ -104,6 +110,13 @@ export type EngineContext = {
|
|
|
104
110
|
phase: Phase;
|
|
105
111
|
/** Attacker has not moved this turn — Heavy fires its +1 to hit. */
|
|
106
112
|
attackerStationary?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Attacker made a charge move this turn — drives the `charged-this-turn`
|
|
115
|
+
* condition (e.g. World Eaters' Relentless Rage). Left undefined when the
|
|
116
|
+
* caller can't determine it — the condition then evaluates as `"unknown"` and
|
|
117
|
+
* the SPA surfaces a diagnostic (mirrors `attackerStationary` / `timing`).
|
|
118
|
+
*/
|
|
119
|
+
attackerCharged?: boolean;
|
|
107
120
|
/** Within half the weapon's range — Melta / Rapid Fire fire. */
|
|
108
121
|
withinHalfRange?: boolean;
|
|
109
122
|
/** Attacker benefits from cover (mostly informational; cover applies to defenders). */
|
|
@@ -121,6 +134,15 @@ export type EngineContext = {
|
|
|
121
134
|
* as `"unknown"` and the SPA surfaces a diagnostic.
|
|
122
135
|
*/
|
|
123
136
|
timing?: string;
|
|
137
|
+
/**
|
|
138
|
+
* The buffed unit is part of a combined ("attached") unit — a leader is
|
|
139
|
+
* attached to a bodyguard, or vice-versa. Drives the `is-attached` and
|
|
140
|
+
* `model-is-leader` conditions. Derived from a non-empty
|
|
141
|
+
* `EligibilityInput.attachedUnitIds`. Left undefined when the caller can't
|
|
142
|
+
* determine attachment — the conditions then evaluate as `"unknown"` and the
|
|
143
|
+
* SPA surfaces a diagnostic (mirrors how `timing` undefined behaves).
|
|
144
|
+
*/
|
|
145
|
+
attackerAttached?: boolean;
|
|
124
146
|
};
|
|
125
147
|
/** Back-compat alias — `resolveBuffs` accepts the shared engine context. */
|
|
126
148
|
export type ResolveContext = EngineContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buffs.d.ts","sourceRoot":"","sources":["../../src/cruncher/buffs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/D;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EACP,MAAM,GACN,YAAY,GACZ,sBAAsB,GACtB,MAAM,GACN,
|
|
1
|
+
{"version":3,"file":"buffs.d.ts","sourceRoot":"","sources":["../../src/cruncher/buffs.ts"],"names":[],"mappings":"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"}
|
package/dist/cruncher/buffs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buffs.js","sourceRoot":"","sources":["../../src/cruncher/buffs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The flat `Buff` type every contribution flows through, and the\n * {@link resolveBuffs} resolver that collapses a stack into a\n * {@link ResolvedModifiers} read-out the engine can consume.\n *\n * The same shape carries weapon-keyword effects, ability buffs, stratagem\n * effects, and manual UI toggles — reroll-stacking, hit/wound caps, and\n * feel-no-pain-best-threshold all fall out of one resolver rather than each\n * source kind reinventing precedence.\n *\n * @packageDocumentation\n */\nimport type { Phase } from \"../generated.js\";\n\n/** Where a buff originated. Drives stable tie-breaking inside `resolveBuffs`. */\nexport type BuffSource =\n | { kind: \"weapon-keyword\"; weaponId: string; keywordId: string }\n | {\n kind: \"ability\";\n abilityId: string;\n abilityKind:\n | \"army\"\n | \"detachment\"\n | \"detachment-stratagem\"\n | \"unit\"\n | \"leader\"\n | \"support\";\n }\n | { kind: \"manual\"; label: string };\n\n/** A weapon-keyword reference (id + parameter map), as found on weapon profiles. */\nexport type WeaponKeywordRef = {\n keyword_id: string;\n parameters?: Record<string, unknown>;\n};\n\n/** One typed contribution; the engine reads `ResolvedModifiers` for the rest. */\nexport type BuffContribution =\n | { type: \"hit-mod\"; value: number }\n | { type: \"wound-mod\"; value: number }\n | { type: \"save-mod\"; value: number }\n | { type: \"cover\" }\n | {\n type: \"reroll\";\n roll: \"hit\" | \"wound\" | \"save\" | \"damage\";\n subset: \"ones\" | \"all-failures\";\n }\n | { type: \"extra-keyword\"; keywordRef: WeaponKeywordRef }\n | { type: \"feel-no-pain\"; threshold: number }\n | { type: \"damage-mod\"; value: number }\n /** Additive modifier to the attacker's per-model attack count (A stat). */\n | { type: \"attacks-mod\"; value: number }\n /** Additive modifier to the attacker's Strength stat. */\n | { type: \"strength-mod\"; value: number }\n /** Additive modifier to the defender's Toughness stat. */\n | { type: \"toughness-mod\"; value: number }\n /**\n * Additive modifier to the attacker's weapon AP. AP is signed against the\n * defender's save (negative = more piercing), so a value of `-1` here makes\n * the weapon one AP more piercing.\n */\n | { type: \"ap-mod\"; value: number };\n\n/** Optional gating; the resolver drops buffs whose gate fails. */\nexport type BuffApplicability = {\n phases?: Phase[];\n rollType?: \"hit\" | \"wound\" | \"save\" | \"damage\";\n /** Target must carry this keyword (case-insensitive). */\n requiresTargetKeyword?: string;\n /** Attacker must carry this keyword (case-insensitive). */\n requiresAttackerKeyword?: string;\n};\n\n/** A single buff: where it came from, when it applies, what it contributes. */\nexport type Buff = {\n source: BuffSource;\n applicableWhen?: BuffApplicability;\n contribution: BuffContribution;\n};\n\n/**\n * Shared engine context. Carries the phase plus a few attacker/target flags\n * the keyword translator and the resolver both need. The engine fills it from\n * its `EngineInput.context` plus the unit-keyword unions; the resolver reads\n * only the subset relevant to its `applicableWhen` checks.\n */\nexport type EngineContext = {\n phase: Phase;\n /** Attacker has not moved this turn — Heavy fires its +1 to hit. */\n attackerStationary?: boolean;\n /** Within half the weapon's range — Melta / Rapid Fire fire. */\n withinHalfRange?: boolean;\n /** Attacker benefits from cover (mostly informational; cover applies to defenders). */\n attackerInCover?: boolean;\n /** Target is in cover — the resolver flips on `cover`, the engine applies +1 to save. */\n targetInCover?: boolean;\n /** Attacker keywords (union of unit.keywords + faction_keywords), lower-cased. */\n attackerKeywords?: ReadonlyArray<string>;\n /** Target keywords (union of unit.keywords + faction_keywords), lower-cased. */\n targetKeywords?: ReadonlyArray<string>;\n /**\n * Sub-phase timing flag (e.g. `\"start-of-phase\"`, `\"end-of-phase\"`,\n * `\"on-destroyed\"`). Consumed by the `timing-is` condition. Left undefined\n * when the caller can't pin a sub-phase down — the condition then evaluates\n * as `\"unknown\"` and the SPA surfaces a diagnostic.\n */\n timing?: string;\n};\n\n/** Back-compat alias — `resolveBuffs` accepts the shared engine context. */\nexport type ResolveContext = EngineContext;\n\n/** Read-out of a resolved buff stack, with provenance per field. */\nexport type ResolvedModifiers = {\n hitMod: { value: number; dominantSource: BuffSource | null };\n woundMod: { value: number; dominantSource: BuffSource | null };\n saveMod: { value: number; sources: BuffSource[] };\n cover: { active: boolean; source: BuffSource | null };\n rerolls: Partial<\n Record<\n \"hit\" | \"wound\" | \"save\" | \"damage\",\n { subset: \"ones\" | \"all-failures\"; dominantSource: BuffSource }\n >\n >;\n extraKeywords: { keywordRef: WeaponKeywordRef; source: BuffSource }[];\n feelNoPain: { threshold: number; dominantSource: BuffSource } | null;\n damageMod: { value: number; sources: BuffSource[] };\n attacksMod: { value: number; sources: BuffSource[] };\n strengthMod: { value: number; sources: BuffSource[] };\n toughnessMod: { value: number; sources: BuffSource[] };\n apMod: { value: number; sources: BuffSource[] };\n};\n\n/** Stable ordering used to break ties when multiple buffs claim the same field. */\nconst SOURCE_KIND_RANK: Record<string, number> = {\n \"ability:army\": 0,\n \"ability:detachment\": 1,\n \"ability:detachment-stratagem\": 2,\n \"ability:unit\": 3,\n \"ability:leader\": 4,\n \"ability:support\": 5,\n manual: 6,\n \"weapon-keyword\": 7,\n};\n\nfunction rank(s: BuffSource): number {\n if (s.kind === \"ability\") return SOURCE_KIND_RANK[`ability:${s.abilityKind}`] ?? 99;\n return SOURCE_KIND_RANK[s.kind] ?? 99;\n}\n\nfunction applies(buff: Buff, ctx: ResolveContext): boolean {\n const w = buff.applicableWhen;\n if (!w) return true;\n if (w.phases && w.phases.length > 0 && !w.phases.includes(ctx.phase)) return false;\n if (w.rollType && buff.contribution.type === \"reroll\" && buff.contribution.roll !== w.rollType) {\n return false;\n }\n if (w.requiresTargetKeyword) {\n const target = ctx.targetKeywords ?? [];\n if (!target.includes(w.requiresTargetKeyword.toLowerCase())) return false;\n }\n if (w.requiresAttackerKeyword) {\n const attacker = ctx.attackerKeywords ?? [];\n if (!attacker.includes(w.requiresAttackerKeyword.toLowerCase())) return false;\n }\n return true;\n}\n\n/**\n * Collapse a flat buff stack into a {@link ResolvedModifiers} read-out. Pure\n * function; the engine — and any UI that wants to render the resolved table\n * before crunching — both go through this.\n */\nexport function resolveBuffs(buffs: Buff[], ctx: ResolveContext): ResolvedModifiers {\n const live = buffs.filter((b) => applies(b, ctx));\n\n const out: ResolvedModifiers = {\n hitMod: { value: 0, dominantSource: null },\n woundMod: { value: 0, dominantSource: null },\n saveMod: { value: 0, sources: [] },\n cover: { active: false, source: null },\n rerolls: {},\n extraKeywords: [],\n feelNoPain: null,\n damageMod: { value: 0, sources: [] },\n attacksMod: { value: 0, sources: [] },\n strengthMod: { value: 0, sources: [] },\n toughnessMod: { value: 0, sources: [] },\n apMod: { value: 0, sources: [] },\n };\n\n // Hit / wound mods: sum, then cap at ±1, with dominant source picked from\n // the contributors whose sign matches the surviving value.\n const hitContribs: { value: number; source: BuffSource }[] = [];\n const woundContribs: { value: number; source: BuffSource }[] = [];\n\n for (const b of live) {\n const c = b.contribution;\n switch (c.type) {\n case \"hit-mod\":\n hitContribs.push({ value: c.value, source: b.source });\n break;\n case \"wound-mod\":\n woundContribs.push({ value: c.value, source: b.source });\n break;\n case \"save-mod\":\n out.saveMod.value += c.value;\n out.saveMod.sources.push(b.source);\n break;\n case \"cover\":\n if (!out.cover.active || rank(b.source) < rank(out.cover.source!)) {\n out.cover = { active: true, source: b.source };\n }\n break;\n case \"reroll\": {\n const cur = out.rerolls[c.roll];\n const incoming = c.subset;\n if (!cur) {\n out.rerolls[c.roll] = { subset: incoming, dominantSource: b.source };\n } else {\n const incomingStronger =\n (incoming === \"all-failures\" && cur.subset === \"ones\") ||\n (incoming === cur.subset && rank(b.source) < rank(cur.dominantSource));\n if (incomingStronger) {\n out.rerolls[c.roll] = { subset: incoming, dominantSource: b.source };\n }\n }\n break;\n }\n case \"extra-keyword\": {\n const key = `${c.keywordRef.keyword_id}::${JSON.stringify(c.keywordRef.parameters ?? {})}`;\n if (!out.extraKeywords.some((e) => keyOf(e.keywordRef) === key)) {\n out.extraKeywords.push({ keywordRef: c.keywordRef, source: b.source });\n }\n break;\n }\n case \"feel-no-pain\":\n if (out.feelNoPain === null || c.threshold < out.feelNoPain.threshold) {\n out.feelNoPain = { threshold: c.threshold, dominantSource: b.source };\n }\n break;\n case \"damage-mod\":\n out.damageMod.value += c.value;\n out.damageMod.sources.push(b.source);\n break;\n case \"attacks-mod\":\n out.attacksMod.value += c.value;\n out.attacksMod.sources.push(b.source);\n break;\n case \"strength-mod\":\n out.strengthMod.value += c.value;\n out.strengthMod.sources.push(b.source);\n break;\n case \"toughness-mod\":\n out.toughnessMod.value += c.value;\n out.toughnessMod.sources.push(b.source);\n break;\n case \"ap-mod\":\n out.apMod.value += c.value;\n out.apMod.sources.push(b.source);\n break;\n }\n }\n\n out.hitMod = capModifier(hitContribs);\n out.woundMod = capModifier(woundContribs);\n\n return out;\n}\n\nfunction keyOf(ref: WeaponKeywordRef): string {\n return `${ref.keyword_id}::${JSON.stringify(ref.parameters ?? {})}`;\n}\n\n/** Sum, clamp to ±1, then pick the dominant contributing source by rank. */\nfunction capModifier(\n contribs: { value: number; source: BuffSource }[],\n): { value: number; dominantSource: BuffSource | null } {\n if (contribs.length === 0) return { value: 0, dominantSource: null };\n const sum = contribs.reduce((a, c) => a + c.value, 0);\n const capped = Math.max(-1, Math.min(1, sum));\n if (capped === 0) return { value: 0, dominantSource: null };\n const sign = Math.sign(capped);\n const matching = contribs.filter((c) => Math.sign(c.value) === sign);\n matching.sort((a, b) => rank(a.source) - rank(b.source));\n return { value: capped, dominantSource: matching[0]?.source ?? null };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"buffs.js","sourceRoot":"","sources":["../../src/cruncher/buffs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The flat `Buff` type every contribution flows through, and the\n * {@link resolveBuffs} resolver that collapses a stack into a\n * {@link ResolvedModifiers} read-out the engine can consume.\n *\n * The same shape carries weapon-keyword effects, ability buffs, stratagem\n * effects, and manual UI toggles — reroll-stacking, hit/wound caps, and\n * feel-no-pain-best-threshold all fall out of one resolver rather than each\n * source kind reinventing precedence.\n *\n * @packageDocumentation\n */\nimport type { Phase } from \"../generated.js\";\n\n/** Where a buff originated. Drives stable tie-breaking inside `resolveBuffs`. */\nexport type BuffSource =\n | { kind: \"weapon-keyword\"; weaponId: string; keywordId: string }\n | {\n kind: \"ability\";\n abilityId: string;\n abilityKind:\n | \"army\"\n | \"detachment\"\n | \"detachment-stratagem\"\n | \"unit\"\n | \"attached\"\n | \"support\";\n /**\n * For `abilityKind: \"attached\"`, the combined-unit member the ability\n * came from (so the UI can name it and show its leader/bodyguard role).\n * Absent for other kinds.\n */\n sourceUnitId?: string;\n }\n | { kind: \"manual\"; label: string };\n\n/** A weapon-keyword reference (id + parameter map), as found on weapon profiles. */\nexport type WeaponKeywordRef = {\n keyword_id: string;\n parameters?: Record<string, unknown>;\n};\n\n/** One typed contribution; the engine reads `ResolvedModifiers` for the rest. */\nexport type BuffContribution =\n | { type: \"hit-mod\"; value: number }\n | { type: \"wound-mod\"; value: number }\n | { type: \"save-mod\"; value: number }\n | { type: \"cover\" }\n | {\n type: \"reroll\";\n roll: \"hit\" | \"wound\" | \"save\" | \"damage\";\n subset: \"ones\" | \"all-failures\";\n }\n | { type: \"extra-keyword\"; keywordRef: WeaponKeywordRef }\n | { type: \"feel-no-pain\"; threshold: number }\n | { type: \"damage-mod\"; value: number }\n /** Additive modifier to the attacker's per-model attack count (A stat). */\n | { type: \"attacks-mod\"; value: number }\n /** Additive modifier to the attacker's Strength stat. */\n | { type: \"strength-mod\"; value: number }\n /** Additive modifier to the defender's Toughness stat. */\n | { type: \"toughness-mod\"; value: number }\n /**\n * Additive modifier to the attacker's weapon AP. AP is signed against the\n * defender's save (negative = more piercing), so a value of `-1` here makes\n * the weapon one AP more piercing.\n */\n | { type: \"ap-mod\"; value: number };\n\n/** Optional gating; the resolver drops buffs whose gate fails. */\nexport type BuffApplicability = {\n phases?: Phase[];\n rollType?: \"hit\" | \"wound\" | \"save\" | \"damage\";\n /** Target must carry this keyword (case-insensitive). */\n requiresTargetKeyword?: string;\n /** Attacker must carry this keyword (case-insensitive). */\n requiresAttackerKeyword?: string;\n};\n\n/** A single buff: where it came from, when it applies, what it contributes. */\nexport type Buff = {\n source: BuffSource;\n applicableWhen?: BuffApplicability;\n contribution: BuffContribution;\n};\n\n/**\n * Shared engine context. Carries the phase plus a few attacker/target flags\n * the keyword translator and the resolver both need. The engine fills it from\n * its `EngineInput.context` plus the unit-keyword unions; the resolver reads\n * only the subset relevant to its `applicableWhen` checks.\n */\nexport type EngineContext = {\n phase: Phase;\n /** Attacker has not moved this turn — Heavy fires its +1 to hit. */\n attackerStationary?: boolean;\n /**\n * Attacker made a charge move this turn — drives the `charged-this-turn`\n * condition (e.g. World Eaters' Relentless Rage). Left undefined when the\n * caller can't determine it — the condition then evaluates as `\"unknown\"` and\n * the SPA surfaces a diagnostic (mirrors `attackerStationary` / `timing`).\n */\n attackerCharged?: boolean;\n /** Within half the weapon's range — Melta / Rapid Fire fire. */\n withinHalfRange?: boolean;\n /** Attacker benefits from cover (mostly informational; cover applies to defenders). */\n attackerInCover?: boolean;\n /** Target is in cover — the resolver flips on `cover`, the engine applies +1 to save. */\n targetInCover?: boolean;\n /** Attacker keywords (union of unit.keywords + faction_keywords), lower-cased. */\n attackerKeywords?: ReadonlyArray<string>;\n /** Target keywords (union of unit.keywords + faction_keywords), lower-cased. */\n targetKeywords?: ReadonlyArray<string>;\n /**\n * Sub-phase timing flag (e.g. `\"start-of-phase\"`, `\"end-of-phase\"`,\n * `\"on-destroyed\"`). Consumed by the `timing-is` condition. Left undefined\n * when the caller can't pin a sub-phase down — the condition then evaluates\n * as `\"unknown\"` and the SPA surfaces a diagnostic.\n */\n timing?: string;\n /**\n * The buffed unit is part of a combined (\"attached\") unit — a leader is\n * attached to a bodyguard, or vice-versa. Drives the `is-attached` and\n * `model-is-leader` conditions. Derived from a non-empty\n * `EligibilityInput.attachedUnitIds`. Left undefined when the caller can't\n * determine attachment — the conditions then evaluate as `\"unknown\"` and the\n * SPA surfaces a diagnostic (mirrors how `timing` undefined behaves).\n */\n attackerAttached?: boolean;\n};\n\n/** Back-compat alias — `resolveBuffs` accepts the shared engine context. */\nexport type ResolveContext = EngineContext;\n\n/** Read-out of a resolved buff stack, with provenance per field. */\nexport type ResolvedModifiers = {\n hitMod: { value: number; dominantSource: BuffSource | null };\n woundMod: { value: number; dominantSource: BuffSource | null };\n saveMod: { value: number; sources: BuffSource[] };\n cover: { active: boolean; source: BuffSource | null };\n rerolls: Partial<\n Record<\n \"hit\" | \"wound\" | \"save\" | \"damage\",\n { subset: \"ones\" | \"all-failures\"; dominantSource: BuffSource }\n >\n >;\n extraKeywords: { keywordRef: WeaponKeywordRef; source: BuffSource }[];\n feelNoPain: { threshold: number; dominantSource: BuffSource } | null;\n damageMod: { value: number; sources: BuffSource[] };\n attacksMod: { value: number; sources: BuffSource[] };\n strengthMod: { value: number; sources: BuffSource[] };\n toughnessMod: { value: number; sources: BuffSource[] };\n apMod: { value: number; sources: BuffSource[] };\n};\n\n/** Stable ordering used to break ties when multiple buffs claim the same field. */\nconst SOURCE_KIND_RANK: Record<string, number> = {\n \"ability:army\": 0,\n \"ability:detachment\": 1,\n \"ability:detachment-stratagem\": 2,\n \"ability:unit\": 3,\n \"ability:attached\": 4,\n \"ability:support\": 5,\n manual: 6,\n \"weapon-keyword\": 7,\n};\n\nfunction rank(s: BuffSource): number {\n if (s.kind === \"ability\") return SOURCE_KIND_RANK[`ability:${s.abilityKind}`] ?? 99;\n return SOURCE_KIND_RANK[s.kind] ?? 99;\n}\n\nfunction applies(buff: Buff, ctx: ResolveContext): boolean {\n const w = buff.applicableWhen;\n if (!w) return true;\n if (w.phases && w.phases.length > 0 && !w.phases.includes(ctx.phase)) return false;\n if (w.rollType && buff.contribution.type === \"reroll\" && buff.contribution.roll !== w.rollType) {\n return false;\n }\n if (w.requiresTargetKeyword) {\n const target = ctx.targetKeywords ?? [];\n if (!target.includes(w.requiresTargetKeyword.toLowerCase())) return false;\n }\n if (w.requiresAttackerKeyword) {\n const attacker = ctx.attackerKeywords ?? [];\n if (!attacker.includes(w.requiresAttackerKeyword.toLowerCase())) return false;\n }\n return true;\n}\n\n/**\n * Collapse a flat buff stack into a {@link ResolvedModifiers} read-out. Pure\n * function; the engine — and any UI that wants to render the resolved table\n * before crunching — both go through this.\n */\nexport function resolveBuffs(buffs: Buff[], ctx: ResolveContext): ResolvedModifiers {\n const live = buffs.filter((b) => applies(b, ctx));\n\n const out: ResolvedModifiers = {\n hitMod: { value: 0, dominantSource: null },\n woundMod: { value: 0, dominantSource: null },\n saveMod: { value: 0, sources: [] },\n cover: { active: false, source: null },\n rerolls: {},\n extraKeywords: [],\n feelNoPain: null,\n damageMod: { value: 0, sources: [] },\n attacksMod: { value: 0, sources: [] },\n strengthMod: { value: 0, sources: [] },\n toughnessMod: { value: 0, sources: [] },\n apMod: { value: 0, sources: [] },\n };\n\n // Hit / wound mods: sum, then cap at ±1, with dominant source picked from\n // the contributors whose sign matches the surviving value.\n const hitContribs: { value: number; source: BuffSource }[] = [];\n const woundContribs: { value: number; source: BuffSource }[] = [];\n\n for (const b of live) {\n const c = b.contribution;\n switch (c.type) {\n case \"hit-mod\":\n hitContribs.push({ value: c.value, source: b.source });\n break;\n case \"wound-mod\":\n woundContribs.push({ value: c.value, source: b.source });\n break;\n case \"save-mod\":\n out.saveMod.value += c.value;\n out.saveMod.sources.push(b.source);\n break;\n case \"cover\":\n if (!out.cover.active || rank(b.source) < rank(out.cover.source!)) {\n out.cover = { active: true, source: b.source };\n }\n break;\n case \"reroll\": {\n const cur = out.rerolls[c.roll];\n const incoming = c.subset;\n if (!cur) {\n out.rerolls[c.roll] = { subset: incoming, dominantSource: b.source };\n } else {\n const incomingStronger =\n (incoming === \"all-failures\" && cur.subset === \"ones\") ||\n (incoming === cur.subset && rank(b.source) < rank(cur.dominantSource));\n if (incomingStronger) {\n out.rerolls[c.roll] = { subset: incoming, dominantSource: b.source };\n }\n }\n break;\n }\n case \"extra-keyword\": {\n const key = `${c.keywordRef.keyword_id}::${JSON.stringify(c.keywordRef.parameters ?? {})}`;\n if (!out.extraKeywords.some((e) => keyOf(e.keywordRef) === key)) {\n out.extraKeywords.push({ keywordRef: c.keywordRef, source: b.source });\n }\n break;\n }\n case \"feel-no-pain\":\n if (out.feelNoPain === null || c.threshold < out.feelNoPain.threshold) {\n out.feelNoPain = { threshold: c.threshold, dominantSource: b.source };\n }\n break;\n case \"damage-mod\":\n out.damageMod.value += c.value;\n out.damageMod.sources.push(b.source);\n break;\n case \"attacks-mod\":\n out.attacksMod.value += c.value;\n out.attacksMod.sources.push(b.source);\n break;\n case \"strength-mod\":\n out.strengthMod.value += c.value;\n out.strengthMod.sources.push(b.source);\n break;\n case \"toughness-mod\":\n out.toughnessMod.value += c.value;\n out.toughnessMod.sources.push(b.source);\n break;\n case \"ap-mod\":\n out.apMod.value += c.value;\n out.apMod.sources.push(b.source);\n break;\n }\n }\n\n out.hitMod = capModifier(hitContribs);\n out.woundMod = capModifier(woundContribs);\n\n return out;\n}\n\nfunction keyOf(ref: WeaponKeywordRef): string {\n return `${ref.keyword_id}::${JSON.stringify(ref.parameters ?? {})}`;\n}\n\n/** Sum, clamp to ±1, then pick the dominant contributing source by rank. */\nfunction capModifier(\n contribs: { value: number; source: BuffSource }[],\n): { value: number; dominantSource: BuffSource | null } {\n if (contribs.length === 0) return { value: 0, dominantSource: null };\n const sum = contribs.reduce((a, c) => a + c.value, 0);\n const capped = Math.max(-1, Math.min(1, sum));\n if (capped === 0) return { value: 0, dominantSource: null };\n const sign = Math.sign(capped);\n const matching = contribs.filter((c) => Math.sign(c.value) === sign);\n matching.sort((a, b) => rank(a.source) - rank(b.source));\n return { value: capped, dominantSource: matching[0]?.source ?? null };\n}\n"]}
|
|
@@ -26,9 +26,41 @@ export type UnsupportedFragment = {
|
|
|
26
26
|
reason: string;
|
|
27
27
|
effectFragment: unknown;
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* A mutually-limited pool of {@link ActivatableBuff} levers. Dice-pool
|
|
31
|
+
* allocations cap how many options fire at once (`max_activations`); a `choice`
|
|
32
|
+
* lets the player pick exactly one. Levers sharing a `group.id` are subject to
|
|
33
|
+
* that cap — the SPA greys out further checkboxes once it's reached, and an
|
|
34
|
+
* optimizer enumerates subsets within it.
|
|
35
|
+
*/
|
|
36
|
+
export type ActivatableGroupRef = {
|
|
37
|
+
id: string;
|
|
38
|
+
maxActivations: number;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A buff-bearing *player decision* the cruncher can't make on its own: a
|
|
42
|
+
* dice-pool option, a `choice` branch, or an activation gated on a timing the
|
|
43
|
+
* player controls (e.g. "start of phase"). It is not auto-applied — the
|
|
44
|
+
* consumer opts in (a checkbox, or an optimizer's search) and then folds
|
|
45
|
+
* {@link buffs} into the crunch. Conditions the activation still carries (a
|
|
46
|
+
* target keyword, a phase) ride on each buff's `applicableWhen`, so the
|
|
47
|
+
* resolver gates them per-target rather than the lever vanishing.
|
|
48
|
+
*/
|
|
49
|
+
export type ActivatableBuff = {
|
|
50
|
+
/** Stable toggle id, e.g. `"blessings-of-khorne#Warp Blades"`. */
|
|
51
|
+
id: string;
|
|
52
|
+
/** Human label for the lever (option name, or a summary of its buffs). */
|
|
53
|
+
label: string;
|
|
54
|
+
/** Contributions this activation adds when the player opts in (≥1). */
|
|
55
|
+
buffs: Buff[];
|
|
56
|
+
/** Set when the lever belongs to a mutually-limited pool. */
|
|
57
|
+
group?: ActivatableGroupRef;
|
|
58
|
+
};
|
|
29
59
|
export type EffectTranslation = {
|
|
30
60
|
applied: Buff[];
|
|
31
61
|
unsupported: UnsupportedFragment[];
|
|
62
|
+
/** Buffs sitting behind a player decision — see {@link ActivatableBuff}. */
|
|
63
|
+
activatable: ActivatableBuff[];
|
|
32
64
|
};
|
|
33
65
|
/**
|
|
34
66
|
* Whose perspective the translation runs from.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"from-dsl.d.ts","sourceRoot":"","sources":["../../src/cruncher/from-dsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"from-dsl.d.ts","sourceRoot":"","sources":["../../src/cruncher/from-dsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EACV,IAAI,EAGJ,UAAU,EACV,aAAa,EACb,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,8EAA8E;AAC9E,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,6DAA6D;IAC7D,KAAK,CAAC,EAAE,mBAAmB,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,IAAI,EAAE,CAAC;IAChB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,4EAA4E;IAC5E,WAAW,EAAE,eAAe,EAAE,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,QAAQ,CAAC;AAiB3D;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,aAAa,EACtB,WAAW,GAAE,sBAAmC,GAC/C,iBAAiB,CAKnB;AAi8BD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAuBtE"}
|