@alpaca-software/40kdc-data 0.5.5 → 0.5.12
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 +10 -0
- package/dist/codegen-data.js +2 -0
- package/dist/codegen-data.js.map +1 -1
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +3 -1
- package/dist/commands/import.js.map +1 -1
- package/dist/compare.d.ts +167 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +302 -0
- package/dist/compare.js.map +1 -0
- package/dist/convert-faction.d.ts.map +1 -1
- package/dist/convert-faction.js +22 -2
- package/dist/convert-faction.js.map +1 -1
- package/dist/cruncher/buffs.d.ts +13 -0
- package/dist/cruncher/buffs.d.ts.map +1 -1
- package/dist/cruncher/buffs.js +7 -0
- package/dist/cruncher/buffs.js.map +1 -1
- package/dist/cruncher/engine.d.ts.map +1 -1
- package/dist/cruncher/engine.js +14 -10
- package/dist/cruncher/engine.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 +3 -1
- package/dist/data/dataset.d.ts.map +1 -1
- package/dist/data/dataset.js +4 -0
- package/dist/data/dataset.js.map +1 -1
- package/dist/data/entities.d.ts +22 -0
- package/dist/data/entities.d.ts.map +1 -1
- package/dist/data/entities.js +45 -1
- package/dist/data/entities.js.map +1 -1
- package/dist/data/index.d.ts +4 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +4 -0
- package/dist/data/index.js.map +1 -1
- package/dist/data/types.d.ts +5 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/data/types.js +2 -0
- package/dist/data/types.js.map +1 -1
- package/dist/export/newrecruit-json.d.ts.map +1 -1
- package/dist/export/newrecruit-json.js +4 -3
- package/dist/export/newrecruit-json.js.map +1 -1
- package/dist/export/newrecruit-simple.js +2 -2
- package/dist/export/newrecruit-simple.js.map +1 -1
- package/dist/export/newrecruit-wtc.d.ts.map +1 -1
- package/dist/export/newrecruit-wtc.js +3 -1
- package/dist/export/newrecruit-wtc.js.map +1 -1
- package/dist/export/rosterizer.d.ts.map +1 -1
- package/dist/export/rosterizer.js +6 -7
- package/dist/export/rosterizer.js.map +1 -1
- package/dist/extract-faction-pack.d.ts +56 -0
- package/dist/extract-faction-pack.d.ts.map +1 -0
- package/dist/extract-faction-pack.js +374 -0
- package/dist/extract-faction-pack.js.map +1 -0
- package/dist/gen-conformance.js +37 -4
- package/dist/gen-conformance.js.map +1 -1
- package/dist/generated.d.ts +66 -2
- package/dist/generated.d.ts.map +1 -1
- package/dist/generated.js.map +1 -1
- package/dist/import/gw.js +1 -1
- package/dist/import/gw.js.map +1 -1
- package/dist/import/import-roster.d.ts.map +1 -1
- package/dist/import/import-roster.js +5 -0
- package/dist/import/import-roster.js.map +1 -1
- package/dist/import/index.d.ts +1 -0
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -0
- package/dist/import/index.js.map +1 -1
- package/dist/import/listforge-text.js +1 -1
- package/dist/import/listforge-text.js.map +1 -1
- package/dist/import/listforge.d.ts.map +1 -1
- package/dist/import/listforge.js +18 -3
- package/dist/import/listforge.js.map +1 -1
- package/dist/import/newrecruit-json.d.ts.map +1 -1
- package/dist/import/newrecruit-json.js +21 -3
- package/dist/import/newrecruit-json.js.map +1 -1
- package/dist/import/newrecruit-simple.js +1 -1
- package/dist/import/newrecruit-simple.js.map +1 -1
- package/dist/import/newrecruit-wtc.js +1 -1
- package/dist/import/newrecruit-wtc.js.map +1 -1
- package/dist/import/resolve.d.ts.map +1 -1
- package/dist/import/resolve.js +46 -18
- package/dist/import/resolve.js.map +1 -1
- package/dist/import/roster-json.d.ts +32 -0
- package/dist/import/roster-json.d.ts.map +1 -0
- package/dist/import/roster-json.js +92 -0
- package/dist/import/roster-json.js.map +1 -0
- package/dist/import/rosterizer.js +3 -3
- package/dist/import/rosterizer.js.map +1 -1
- package/dist/import/types.d.ts +16 -5
- package/dist/import/types.d.ts.map +1 -1
- package/dist/import/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/migrate-keystone-twins.d.ts +23 -0
- package/dist/migrate-keystone-twins.d.ts.map +1 -0
- package/dist/migrate-keystone-twins.js +184 -0
- package/dist/migrate-keystone-twins.js.map +1 -0
- package/dist/rube-goldberg.js +1 -1
- package/dist/rube-goldberg.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +94 -1
- package/dist/runner.js.map +1 -1
- package/dist/scope.d.ts +31 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +34 -0
- package/dist/scope.js.map +1 -0
- package/dist/translate/effect.d.ts +17 -3
- package/dist/translate/effect.d.ts.map +1 -1
- package/dist/translate/effect.js +23 -4
- package/dist/translate/effect.js.map +1 -1
- package/dist/translate/index.d.ts +1 -1
- package/dist/translate/index.d.ts.map +1 -1
- package/dist/translate/index.js +1 -1
- package/dist/translate/index.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +2 -0
- package/dist/validate.js.map +1 -1
- package/package.json +2 -1
- package/schemas/core/hull-shape.schema.json +30 -0
- package/schemas/core/roster.schema.json +26 -9
- package/schemas/core/stratagem.schema.json +2 -2
- package/schemas/core/target-profile.schema.json +30 -0
- package/schemas/core/unit-composition.schema.json +7 -0
- package/schemas/enrichment/ability-dsl/ability.schema.json +14 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../src/compare.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fleet comparison: expected kills of attacker units against target-profile\n * archetypes. The cross product — a set of attacker units against a set of\n * targets — yielding a matrix of expected models killed per (unit, target).\n *\n * Target profiles reference real dataset units (`faction_id` + `unit_id`), so\n * each cell resolves the live unit and feeds it straight to {@link crunch}; the\n * target's defensive abilities (FNP, ability invulns, save/toughness mods) are\n * resolved via {@link Dataset.defensiveBuffsFor} and stacked onto the crunch.\n * Rapid Fire / Melta come for free: each cell sets `withinHalfRange` from\n * `distance <= range / 2`.\n *\n * Python mirror: `python/src/wh40kdc/compare.py`. The per-cell `expectedKills`\n * is pinned against Python by the `compare` conformance area.\n *\n * @packageDocumentation\n */\nimport { crunch, type EngineContext } from \"./cruncher/index.js\";\nimport type { Dataset } from \"./data/dataset.js\";\nimport type { UnitView } from \"./data/entities.js\";\nimport type { TargetProfile, Unit, Weapon } from \"./generated.js\";\n\nexport type ComparePhase = \"shooting\" | \"fight\";\nexport type CompareMetric = \"best-weapon\" | \"unit-total\";\n\n/** Which weapon type fires in a phase: shooting → ranged, fight → melee. */\nexport function weaponTypeForComparePhase(phase: ComparePhase): \"ranged\" | \"melee\" {\n return phase === \"fight\" ? \"melee\" : \"ranged\";\n}\n\n/** One attacker weapon profile evaluated against one target. */\nexport interface WeaponCell {\n weaponId: string;\n weaponName: string;\n profileIndex: number;\n profileName: string;\n range: number | null;\n reaches: boolean;\n withinHalfRange: boolean;\n expectedKills: number;\n}\n\n/** One (attacker unit, target profile) pair across all the unit's weapons. */\nexport interface MatrixCell {\n targetProfileId: string;\n targetProfileName: string;\n best: WeaponCell | null;\n unitTotal: number;\n weapons: WeaponCell[];\n}\n\nexport interface MatrixRow {\n unitId: string;\n unitName: string;\n cells: MatrixCell[];\n /** Peak cell value across targets — the row's sort key. */\n peak: number;\n}\n\n/** A target profile resolved to its live dataset unit. */\nexport interface ResolvedTarget {\n profileId: string;\n profileName: string;\n unitRaw: Unit;\n modelCount: number;\n}\n\nexport function cellValue(cell: MatrixCell, metric: CompareMetric): number {\n if (metric === \"unit-total\") return cell.unitTotal;\n return cell.best?.expectedKills ?? 0;\n}\n\n/**\n * Resolve a target profile to its referenced unit, faction-scoped (shared ids\n * like Rhino/Forgefiend appear under several factions). Returns null if the\n * referenced unit is absent.\n */\nexport function resolveTarget(ds: Dataset, profile: TargetProfile): ResolvedTarget | null {\n const unit = ds.units.getInFaction(profile.unit_id, profile.faction_id);\n if (!unit) return null;\n const override = profile.model_count_override ?? null;\n const modelCount = override ?? unit.raw.model_count?.min ?? 1;\n return { profileId: profile.id, profileName: profile.name, unitRaw: unit.raw, modelCount };\n}\n\n/**\n * Expected models killed for one (weapon profile, target) at a distance. The\n * single unit of computation the matrix and the conformance corpus share.\n */\nexport function expectedKills(\n ds: Dataset,\n weaponRaw: Weapon,\n profileIndex: number,\n target: ResolvedTarget,\n phase: ComparePhase,\n modelsFiring: number,\n withinHalfRange: boolean,\n): number {\n const ctx: EngineContext = { phase, withinHalfRange };\n const buffs = ds.defensiveBuffsFor(\n { unitId: target.unitRaw.id, factionId: target.unitRaw.faction_id },\n ctx,\n );\n const out = crunch(\n {\n attacker: { weapon: weaponRaw, profileIndex },\n target: { unit: target.unitRaw, profileIndex: 0, modelCount: target.modelCount },\n modelsFiring,\n buffs,\n context: ctx,\n },\n ds,\n );\n const stage = out.stages.find((s) => s.name === \"models-killed\");\n return stage ? stage.expected : 0;\n}\n\n/**\n * Expected post-FNP damage (wounds dealt) for one weapon profile against a\n * target. The summable counterpart to {@link expectedKills}: damage adds\n * linearly across weapons, so a unit's total output is built by summing this\n * over its loadout and converting to kills once — never by summing per-weapon\n * kills (each weapon's models-killed caps independently, which over-counts).\n */\nexport function expectedDamage(\n ds: Dataset,\n weaponRaw: Weapon,\n profileIndex: number,\n target: ResolvedTarget,\n phase: ComparePhase,\n modelsFiring: number,\n withinHalfRange: boolean,\n): number {\n const ctx: EngineContext = { phase, withinHalfRange };\n const buffs = ds.defensiveBuffsFor(\n { unitId: target.unitRaw.id, factionId: target.unitRaw.faction_id },\n ctx,\n );\n const out = crunch(\n {\n attacker: { weapon: weaponRaw, profileIndex },\n target: { unit: target.unitRaw, profileIndex: 0, modelCount: target.modelCount },\n modelsFiring,\n buffs,\n context: ctx,\n },\n ds,\n );\n const stage = out.stages.find((s) => s.name === \"after-fnp\");\n return stage ? stage.expected : 0;\n}\n\n/**\n * Evaluate every phase-appropriate weapon profile of one attacker unit against\n * one target, returning per-weapon results plus the best.\n */\nexport function unitVsTarget(\n ds: Dataset,\n attackerUnit: UnitView,\n target: ResolvedTarget,\n distance: number,\n phase: ComparePhase,\n modelsFiring = 1,\n): MatrixCell {\n const wantType = weaponTypeForComparePhase(phase);\n const weapons: WeaponCell[] = [];\n for (const weapon of attackerUnit.weapons) {\n const wraw = weapon.raw;\n if (wraw.type !== wantType) continue;\n wraw.profiles.forEach((wprofile, idx) => {\n const rng = wprofile.range;\n const isRanged = typeof rng === \"number\";\n const reaches = !isRanged || (rng as number) >= distance;\n const withinHalf = isRanged && distance <= (rng as number) / 2;\n const kills = reaches\n ? expectedKills(ds, wraw, idx, target, phase, modelsFiring, withinHalf)\n : 0;\n weapons.push({\n weaponId: wraw.id,\n weaponName: weapon.name,\n profileIndex: idx,\n profileName: wprofile.name ?? weapon.name,\n range: isRanged ? (rng as number) : null,\n reaches,\n withinHalfRange: withinHalf,\n expectedKills: kills,\n });\n });\n }\n const reaching = weapons.filter((w) => w.reaches);\n const best = reaching.reduce<WeaponCell | null>(\n (acc, w) => (acc === null || w.expectedKills > acc.expectedKills ? w : acc),\n null,\n );\n const unitTotal = reaching.reduce((sum, w) => sum + w.expectedKills, 0);\n return { targetProfileId: target.profileId, targetProfileName: target.profileName, best, unitTotal, weapons };\n}\n\n/** Result of evaluating one (attacker weapon profile, target profile) cell. */\nexport interface CompareCell {\n expectedKills: number;\n reaches: boolean;\n withinHalfRange: boolean;\n modelCount: number;\n}\n\n/**\n * Evaluate one (attacker weapon profile, target profile) cell — the single-cell\n * entry point shared by the conformance runner and consumers. Throws on unknown\n * profile/weapon ids so the runner can map them to the closed error enum.\n */\nexport function compareCell(\n ds: Dataset,\n opts: {\n factionId: string;\n unitId: string;\n weaponId: string;\n profileIndex: number;\n targetProfileId: string;\n distance: number;\n phase: ComparePhase;\n modelsFiring?: number;\n },\n): CompareCell {\n const profile = ds.targetProfiles.get(opts.targetProfileId);\n if (!profile) throw new Error(`unknown target profile: ${opts.targetProfileId}`);\n const target = resolveTarget(ds, profile);\n if (!target) {\n throw new Error(\n `target profile ${opts.targetProfileId} references missing unit ${profile.unit_id}`,\n );\n }\n const weapon = ds.weapons.get(opts.weaponId);\n if (!weapon) throw new Error(`unknown weapon: ${opts.weaponId}`);\n const wraw = weapon.raw;\n const rng = wraw.profiles[opts.profileIndex]?.range;\n const isRanged = typeof rng === \"number\";\n const reaches = !isRanged || (rng as number) >= opts.distance;\n const withinHalfRange = isRanged && opts.distance <= (rng as number) / 2;\n const kills = reaches\n ? expectedKills(ds, wraw, opts.profileIndex, target, opts.phase, opts.modelsFiring ?? 1, withinHalfRange)\n : 0;\n return { expectedKills: kills, reaches, withinHalfRange, modelCount: target.modelCount };\n}\n\n/** The cross product: one row per attacker unit, one column per target. */\nexport function buildMatrix(\n ds: Dataset,\n attackerUnits: UnitView[],\n targets: ResolvedTarget[],\n distance: number,\n phase: ComparePhase,\n metric: CompareMetric,\n modelsFiring = 1,\n): MatrixRow[] {\n const rows = attackerUnits.map((unit) => {\n const cells = targets.map((t) => unitVsTarget(ds, unit, t, distance, phase, modelsFiring));\n const peak = cells.reduce((m, c) => Math.max(m, cellValue(c, metric)), 0);\n return { unitId: unit.id, unitName: unit.name, cells, peak };\n });\n return rows.sort((a, b) => b.peak - a.peak);\n}\n\n// ---------------------------------------------------------------------------\n// Loadout ranking — \"what's the best shooting loadout for this unit/slot?\"\n// ---------------------------------------------------------------------------\n\n/** One weapon in a loadout: `count` copies of `weaponId` profile `profileIndex`. */\nexport interface LoadoutLine {\n weaponId: string;\n profileIndex?: number;\n count: number;\n}\n\n/** A named weapon configuration to evaluate. `points` enables per-point ranking\n * and lets you compare unit *choices* (one Defiler vs two Forgefiends) on equal\n * footing — a config can span multiple bodies (just list all their weapons). */\nexport interface LoadoutConfig {\n label: string;\n points?: number | null;\n lines: LoadoutLine[];\n}\n\nexport interface LoadoutTargetResult {\n targetProfileId: string;\n targetProfileName: string;\n /** Total post-FNP wounds the whole config deals to this target. */\n damage: number;\n /** Models killed = min(modelCount, damage / W) — capped once, after summing. */\n kills: number;\n}\n\nexport interface LoadoutRanking {\n config: LoadoutConfig;\n results: LoadoutTargetResult[];\n /** The metric the configs were ranked by (damage against the rank target, or\n * summed across all targets when no rank target is given). */\n score: number;\n /** `score` per 100 points, or null when the config carries no points. */\n scorePer100Points: number | null;\n}\n\n/**\n * Total a single loadout against one target, summing post-FNP damage across all\n * weapon lines (each fired by `count` models) and converting to kills *once*.\n * A line whose weapon can't reach `distance` contributes nothing.\n */\nexport function loadoutOutput(\n ds: Dataset,\n config: LoadoutConfig,\n target: ResolvedTarget,\n distance: number,\n phase: ComparePhase,\n): LoadoutTargetResult {\n let damage = 0;\n for (const line of config.lines) {\n const weapon = ds.weapons.get(line.weaponId);\n if (!weapon) continue;\n const profileIndex = line.profileIndex ?? 0;\n const rng = weapon.raw.profiles[profileIndex]?.range;\n const isRanged = typeof rng === \"number\";\n if (isRanged && (rng as number) < distance) continue; // out of range\n const withinHalf = isRanged && distance <= (rng as number) / 2;\n damage += expectedDamage(ds, weapon.raw, profileIndex, target, phase, line.count, withinHalf);\n }\n const w = target.unitRaw.profiles[0]?.W ?? 1;\n const kills = w > 0 ? Math.min(target.modelCount, damage / w) : 0;\n return {\n targetProfileId: target.profileId,\n targetProfileName: target.profileName,\n damage,\n kills,\n };\n}\n\n/**\n * Rank loadout configs against a set of targets. Each config is scored by its\n * damage against `rankTargetId` (or summed across all targets when omitted);\n * results are sorted high-to-low, with `scorePer100Points` for points-aware\n * comparison across unit choices.\n */\nexport function rankLoadouts(\n ds: Dataset,\n configs: LoadoutConfig[],\n targets: ResolvedTarget[],\n distance: number,\n phase: ComparePhase,\n rankTargetId?: string,\n): LoadoutRanking[] {\n const ranked = configs.map((config) => {\n const results = targets.map((t) => loadoutOutput(ds, config, t, distance, phase));\n const score = rankTargetId\n ? (results.find((r) => r.targetProfileId === rankTargetId)?.damage ?? 0)\n : results.reduce((sum, r) => sum + r.damage, 0);\n const scorePer100Points =\n config.points && config.points > 0 ? (score / config.points) * 100 : null;\n return { config, results, score, scorePer100Points };\n });\n return ranked.sort((a, b) => b.score - a.score);\n}\n\n/**\n * Evaluate one loadout against one target by ids — the single-call entry point\n * for the conformance runner. Throws on unknown profile so the runner maps it\n * to the closed error enum.\n */\nexport function loadoutCell(\n ds: Dataset,\n opts: {\n lines: LoadoutLine[];\n targetProfileId: string;\n distance: number;\n phase: ComparePhase;\n },\n): { damage: number; kills: number } {\n const profile = ds.targetProfiles.get(opts.targetProfileId);\n if (!profile) throw new Error(`unknown target profile: ${opts.targetProfileId}`);\n const target = resolveTarget(ds, profile);\n if (!target) throw new Error(`target profile ${opts.targetProfileId} references a missing unit`);\n const res = loadoutOutput(ds, { label: \"\", lines: opts.lines }, target, opts.distance, opts.phase);\n return { damage: res.damage, kills: res.kills };\n}\n\n/** Result of deriving a unit's shooting-loadout space from its wargear. */\nexport interface EnumeratedLoadouts {\n configs: LoadoutConfig[];\n diagnostics: string[];\n /** Always false: the dataset doesn't record per-model weapon counts, so every\n * line defaults to count=1. Configs are reliable for *relative* slot ranking;\n * absolute totals need counts supplied by hand. */\n countsKnown: boolean;\n}\n\nfunction resolveWeaponId(ref: string, weaponIds: Set<string>): string | null {\n if (weaponIds.has(ref)) return ref;\n if (ref.endsWith(\"s\") && weaponIds.has(ref.slice(0, -1))) return ref.slice(0, -1);\n return null;\n}\n\n/**\n * Derive the shooting-loadout choice space for a unit from its wargear options:\n * each mutually-exclusive ranged-weapon swap (grouped by option id) is an axis,\n * and the cross product yields candidate configs (count=1 per weapon). Mirror\n * of the Python `enumerate_loadouts`. Heuristic, not spec-pinned — it reflects\n * the (imperfect) wargear data and conservatively drops refs it can't resolve.\n */\nexport function enumerateLoadouts(\n ds: Dataset,\n factionId: string,\n unitId: string,\n): EnumeratedLoadouts {\n const unit = ds.units.getInFaction(unitId, factionId);\n if (!unit) throw new Error(`unit ${unitId} not found in faction ${factionId}`);\n const weaponIds = new Set(ds.weapons.all.map((w) => w.raw.id));\n const isRanged = (wid: string): boolean => ds.weapons.get(wid)?.raw.type === \"ranged\";\n const baseRanged = [\n ...new Set(unit.weapons.filter((w) => w.raw.type === \"ranged\").map((w) => w.raw.id)),\n ].sort();\n const diagnostics: string[] = [];\n\n // Group choices by option id so duplicate rows of one physical slot union\n // into a single exclusive axis (rather than letting two of its options stack).\n const choicesByOption = new Map<string, string[][]>();\n for (const opt of ds.wargearOptionsOf(unit.raw)) {\n const oid = opt.id ?? JSON.stringify(opt);\n const rawChoices: string[][] = [];\n const o = opt as { replaces?: string[]; replacement?: string[]; replacement_choice?: string[][] };\n if (o.replaces) rawChoices.push(o.replaces);\n if (o.replacement) rawChoices.push(o.replacement);\n for (const ch of o.replacement_choice ?? []) rawChoices.push(ch);\n const bucket = choicesByOption.get(oid) ?? [];\n for (const ch of rawChoices) {\n const rids: string[] = [];\n for (const ref of ch) {\n const wid = resolveWeaponId(ref, weaponIds);\n if (wid === null) diagnostics.push(`option ${oid}: unresolved ref ${JSON.stringify(ref)}`);\n else if (isRanged(wid)) rids.push(wid);\n }\n if (rids.length) {\n const key = [...rids].sort().join(\"|\");\n if (!bucket.some((b) => [...b].sort().join(\"|\") === key)) bucket.push(rids);\n }\n }\n choicesByOption.set(oid, bucket);\n }\n\n const axes = [...choicesByOption.values()].filter((c) => c.length > 1);\n const swapMembers = new Set(axes.flat(2));\n const fixed = baseRanged.filter((w) => !swapMembers.has(w));\n\n let combos: string[][] = [fixed];\n for (const axis of axes) {\n combos = combos.flatMap((combo) => axis.map((choice) => [...combo, ...choice]));\n }\n\n const byLabel = new Map<string, LoadoutConfig>();\n const points = unit.raw.points?.[0]?.cost ?? null;\n for (const combo of combos) {\n const weapons = [...new Set(combo)].sort();\n if (!weapons.length) continue;\n const label = weapons.map((w) => ds.weapons.get(w)?.name ?? w).join(\" + \");\n if (!byLabel.has(label)) {\n byLabel.set(label, {\n label,\n points,\n lines: weapons.map((w) => ({ weaponId: w, count: 1 })),\n });\n }\n }\n return { configs: [...byLabel.values()], diagnostics: [...new Set(diagnostics)].sort(), countsKnown: false };\n}\n\n/** Render a matrix as a Discord-paste markdown table. */\nexport function matrixToMarkdown(\n rows: MatrixRow[],\n targets: ResolvedTarget[],\n metric: CompareMetric,\n): string {\n const headers = [\"Unit\", ...targets.map((t) => t.profileName)];\n const lines = [`| ${headers.join(\" | \")} |`, `|${headers.map(() => \"---\").join(\"|\")}|`];\n for (const row of rows) {\n const cells = row.cells.map((c) => {\n const v = cellValue(c, metric);\n const text = v ? v.toFixed(2) : \"—\";\n return v >= 1 ? `**${text}**` : text;\n });\n lines.push(`| ${[row.unitName, ...cells].join(\" | \")} |`);\n }\n return lines.join(\"\\n\");\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convert-faction.d.ts","sourceRoot":"","sources":["../src/convert-faction.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,EAAE,KAAK,aAAa,EAAkC,MAAM,gCAAgC,CAAC;AAGpG,OAAO,sCAAsC,CAAC;AAC9C,OAAO,2CAA2C,CAAC;AACnD,OAAO,uCAAuC,CAAC;AAC/C,OAAO,0CAA0C,CAAC;AAClD,OAAO,2CAA2C,CAAC;AACnD,OAAO,kCAAkC,CAAC;AAC1C,OAAO,2CAA2C,CAAC;AACnD,OAAO,sCAAsC,CAAC;AAC9C,OAAO,uCAAuC,CAAC;AAC/C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,0CAA0C,CAAC;AAClD,OAAO,0CAA0C,CAAC;AAClD,OAAO,gDAAgD,CAAC;AACxD,OAAO,4CAA4C,CAAC;AACpD,OAAO,oCAAoC,CAAC;AAC5C,OAAO,kCAAkC,CAAC;AAC1C,OAAO,iCAAiC,CAAC;AACzC,OAAO,uCAAuC,CAAC;AAC/C,OAAO,8BAA8B,CAAC;AACtC,OAAO,iCAAiC,CAAC;AACzC,OAAO,6CAA6C,CAAC;AACrD,OAAO,yCAAyC,CAAC;AACjD,OAAO,0CAA0C,CAAC;AAClD,OAAO,sCAAsC,CAAC;AAC9C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,sCAAsC,CAAC;AAC9C,OAAO,wCAAwC,CAAC;AAChD,OAAO,oCAAoC,CAAC;AAC5C,OAAO,sCAAsC,CAAC;AAC9C,OAAO,wCAAwC,CAAC;AAChD,OAAO,uCAAuC,CAAC;AAC/C,OAAO,oCAAoC,CAAC;AAC5C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAqJ7C,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,IAAI,
|
|
1
|
+
{"version":3,"file":"convert-faction.d.ts","sourceRoot":"","sources":["../src/convert-faction.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,EAAE,KAAK,aAAa,EAAkC,MAAM,gCAAgC,CAAC;AAGpG,OAAO,sCAAsC,CAAC;AAC9C,OAAO,2CAA2C,CAAC;AACnD,OAAO,uCAAuC,CAAC;AAC/C,OAAO,0CAA0C,CAAC;AAClD,OAAO,2CAA2C,CAAC;AACnD,OAAO,kCAAkC,CAAC;AAC1C,OAAO,2CAA2C,CAAC;AACnD,OAAO,sCAAsC,CAAC;AAC9C,OAAO,uCAAuC,CAAC;AAC/C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,0CAA0C,CAAC;AAClD,OAAO,0CAA0C,CAAC;AAClD,OAAO,gDAAgD,CAAC;AACxD,OAAO,4CAA4C,CAAC;AACpD,OAAO,oCAAoC,CAAC;AAC5C,OAAO,kCAAkC,CAAC;AAC1C,OAAO,iCAAiC,CAAC;AACzC,OAAO,uCAAuC,CAAC;AAC/C,OAAO,8BAA8B,CAAC;AACtC,OAAO,iCAAiC,CAAC;AACzC,OAAO,6CAA6C,CAAC;AACrD,OAAO,yCAAyC,CAAC;AACjD,OAAO,0CAA0C,CAAC;AAClD,OAAO,sCAAsC,CAAC;AAC9C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,sCAAsC,CAAC;AAC9C,OAAO,wCAAwC,CAAC;AAChD,OAAO,oCAAoC,CAAC;AAC5C,OAAO,sCAAsC,CAAC;AAC9C,OAAO,wCAAwC,CAAC;AAChD,OAAO,uCAAuC,CAAC;AAC/C,OAAO,oCAAoC,CAAC;AAC5C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qCAAqC,CAAC;AAqJ7C,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,IAAI,CAmfN"}
|
package/dist/convert-faction.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Faction configs are registered via side-effect imports from ./converters/configs/.
|
|
8
8
|
*/
|
|
9
|
-
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
10
10
|
import { resolve, dirname } from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import { nameToId, parseStratagemType, parsePlayerTurn, mapPhases } from "./converters/id-generator.js";
|
|
@@ -265,6 +265,18 @@ export function convertFaction(config, options = {}) {
|
|
|
265
265
|
factionEnhancements = factionEnhancements.filter((e) => allowedDetachments.has(e.detachment));
|
|
266
266
|
factionStratagems = factionStratagems.filter((s) => allowedDetachments.has(s.detachment));
|
|
267
267
|
}
|
|
268
|
+
// `detachment_points` (11e DP cost) and `force_dispositions` are maintained
|
|
269
|
+
// by hand — army-assist doesn't carry them — so a re-run must NOT drop them.
|
|
270
|
+
// Carry both forward by id from the existing on-disk file.
|
|
271
|
+
const existingDetPath = resolve(ROOT, `data/core/${factionId}/detachments.json`);
|
|
272
|
+
const preservedById = new Map();
|
|
273
|
+
if (existsSync(existingDetPath)) {
|
|
274
|
+
const existing = JSON.parse(readFileSync(existingDetPath, "utf-8"));
|
|
275
|
+
for (const d of existing) {
|
|
276
|
+
if (typeof d.id === "string")
|
|
277
|
+
preservedById.set(d.id, d);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
268
280
|
const detachments = detachmentNames.map((detName) => {
|
|
269
281
|
const detId = nameToId(detName);
|
|
270
282
|
const detEnhIds = factionEnhancements
|
|
@@ -273,7 +285,7 @@ export function convertFaction(config, options = {}) {
|
|
|
273
285
|
const detStratIds = factionStratagems
|
|
274
286
|
.filter((s) => s.detachment === detName)
|
|
275
287
|
.map((s) => nameToId(s.name));
|
|
276
|
-
|
|
288
|
+
const record = {
|
|
277
289
|
id: detId,
|
|
278
290
|
name: detName,
|
|
279
291
|
faction_id: factionId,
|
|
@@ -282,6 +294,14 @@ export function convertFaction(config, options = {}) {
|
|
|
282
294
|
stratagem_ids: detStratIds,
|
|
283
295
|
game_version: GAME_VERSION,
|
|
284
296
|
};
|
|
297
|
+
const preserved = preservedById.get(detId);
|
|
298
|
+
if (preserved && "detachment_points" in preserved) {
|
|
299
|
+
record.detachment_points = preserved.detachment_points;
|
|
300
|
+
}
|
|
301
|
+
if (preserved && "force_dispositions" in preserved) {
|
|
302
|
+
record.force_dispositions = preserved.force_dispositions;
|
|
303
|
+
}
|
|
304
|
+
return record;
|
|
285
305
|
});
|
|
286
306
|
// ─── Build enhancements ───
|
|
287
307
|
console.log("Converting enhancements...");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convert-faction.js","sourceRoot":"","sources":["../src/convert-faction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generic faction converter: army-assist → 40kdc-data format.\n *\n * Usage: npx tsx tools/src/convert-faction.ts <faction-id>\n * Example: npx tsx tools/src/convert-faction.ts emperors-children\n *\n * Faction configs are registered via side-effect imports from ./converters/configs/.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { nameToId, parseStratagemType, parsePlayerTurn, mapPhases } from \"./converters/id-generator.js\";\nimport { parseMove, parseTargetNumber, parseIntStat, parseInvuln } from \"./converters/stat-parser.js\";\nimport { findFactionViewIndex, getViewEntries, getPointsForView, splitIntoViews, type SourceAbility } from \"./converters/view-selector.js\";\nimport { buildWeaponRegistry, type SourceWargear } from \"./converters/weapon-dedup.js\";\nimport { buildWargearOptions } from \"./converters/wargear-options.js\";\nimport { getKeywordsForFaction, type SourceKeyword } from \"./converters/keyword-filter.js\";\nimport { type FactionConfig, getFactionConfig, listFactions } from \"./converters/faction-config.js\";\n\n// Register all faction configs\nimport \"./converters/configs/world-eaters.js\";\nimport \"./converters/configs/emperors-children.js\";\nimport \"./converters/configs/chaos-knights.js\";\nimport \"./converters/configs/imperial-knights.js\";\nimport \"./converters/configs/leagues-of-votann.js\";\nimport \"./converters/configs/drukhari.js\";\nimport \"./converters/configs/genestealer-cults.js\";\nimport \"./converters/configs/grey-knights.js\";\nimport \"./converters/configs/thousand-sons.js\";\nimport \"./converters/configs/death-guard.js\";\nimport \"./converters/configs/adeptus-custodes.js\";\nimport \"./converters/configs/adepta-sororitas.js\";\nimport \"./converters/configs/agents-of-the-imperium.js\";\nimport \"./converters/configs/adeptus-mechanicus.js\";\nimport \"./converters/configs/tau-empire.js\";\nimport \"./converters/configs/tyranids.js\";\nimport \"./converters/configs/necrons.js\";\nimport \"./converters/configs/chaos-daemons.js\";\nimport \"./converters/configs/orks.js\";\nimport \"./converters/configs/aeldari.js\";\nimport \"./converters/configs/chaos-space-marines.js\";\nimport \"./converters/configs/astra-militarum.js\";\nimport \"./converters/configs/adeptus-astartes.js\";\nimport \"./converters/configs/blood-angels.js\";\nimport \"./converters/configs/dark-angels.js\";\nimport \"./converters/configs/space-wolves.js\";\nimport \"./converters/configs/black-templars.js\";\nimport \"./converters/configs/deathwatch.js\";\nimport \"./converters/configs/ultramarines.js\";\nimport \"./converters/configs/imperial-fists.js\";\nimport \"./converters/configs/crimson-fists.js\";\nimport \"./converters/configs/iron-hands.js\";\nimport \"./converters/configs/raven-guard.js\";\nimport \"./converters/configs/salamanders.js\";\nimport \"./converters/configs/white-scars.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst ROOT = resolve(__dirname, \"../..\");\nconst SOURCE = resolve(process.env.HOME!, \"army-assist/src/assets/json\");\nconst GAME_VERSION = { edition: \"10th\", dataslate: \"2025-q3\" };\n\n// ─── Source data types ───────────────────────────────────────────────\n\ninterface SourceDatasheet {\n id: string;\n name: string;\n faction_id: string;\n loadout: string;\n transport: string;\n role: string;\n damaged_w: string;\n damaged_description: string;\n}\n\ninterface SourceModel {\n datasheet_id: string;\n line: string;\n name: string;\n M: string;\n T: string;\n Sv: string;\n inv_sv: string;\n inv_sv_descr: string;\n W: string;\n Ld: string;\n OC: string;\n base_size: string;\n base_size_descr: string;\n}\n\ninterface SourcePoints {\n datasheet_id: string;\n models: string;\n cost: string;\n}\n\ninterface SourceLeader {\n leader_id: string;\n attached_id: string;\n}\n\ninterface SourceEnhancement {\n id: string;\n name: string;\n faction_id: string;\n cost: string;\n detachment: string;\n phases: string[];\n}\n\ninterface SourceStratagem {\n id: string;\n name: string;\n faction_id: string;\n type: string;\n cp_cost: string;\n turn: string;\n phase: string;\n detachment: string;\n phases: string[];\n}\n\ninterface SourceDetachmentAbility {\n name: string;\n faction_id: string;\n detachment: string;\n phases: string[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction readJSON<T>(filename: string): T {\n return JSON.parse(readFileSync(resolve(SOURCE, filename), \"utf-8\"));\n}\n\nfunction writeOutput(relPath: string, data: unknown): void {\n const outPath = resolve(ROOT, relPath);\n writeFileSync(outPath, JSON.stringify(data, null, 2) + \"\\n\");\n console.log(` ✓ ${relPath} (${Array.isArray(data) ? data.length : 1} entries)`);\n}\n\n/** Determine unit role from keywords and abilities. */\nfunction deriveRole(\n keywords: string[],\n abilities: SourceAbility[],\n name: string\n): string | undefined {\n const kw = new Set(keywords.map((k) => k.toLowerCase()));\n if (kw.has(\"epic hero\")) return \"epic-hero\";\n\n if (kw.has(\"character\")) return \"character\";\n const hasLeader = abilities.some(\n (a) => a.type === \"Core\" && a.name === \"Leader\"\n );\n if (hasLeader) return \"character\";\n\n if (kw.has(\"battleline\")) return \"battleline\";\n\n if (\n name.toLowerCase().includes(\"rhino\") ||\n kw.has(\"dedicated transport\")\n )\n return \"dedicated-transport\";\n\n return undefined;\n}\n\n/** Parse base size string. \"32mm\" → { shape: \"round\", diameter: 32 } */\nfunction parseBaseSize(\n s: string\n): { shape: string; diameter?: number; width?: number; length?: number } | undefined {\n if (!s || s.trim() === \"\") return undefined;\n const round = s.match(/(\\d+)\\s*mm/i);\n if (round) return { shape: \"round\", diameter: parseInt(round[1], 10) };\n const oval = s.match(/(\\d+)\\s*x\\s*(\\d+)/i);\n if (oval)\n return {\n shape: \"oval\",\n width: parseInt(oval[1], 10),\n length: parseInt(oval[2], 10),\n };\n return undefined;\n}\n\n/** Parse transport capacity from transport text. */\nfunction parseTransport(\n s: string\n): { capacity: number; keyword_restrictions?: string[]; exclusion_keywords?: string[] } | undefined {\n if (!s || s.trim() === \"\") return undefined;\n // Match \"capacity of N\" or \"N <FACTION> INFANTRY\"\n const capMatch = s.match(/capacity\\s*(?:of\\s*)?(\\d+)/i) || s.match(/(\\d+)\\s+\\S[\\S\\s]*?\\s+(?:infantry|model)/i);\n if (!capMatch) return undefined;\n const capacity = parseInt(capMatch[1], 10);\n const result: { capacity: number; keyword_restrictions?: string[]; exclusion_keywords?: string[] } = { capacity };\n\n if (/jump pack/i.test(s) && /cannot/i.test(s)) {\n result.exclusion_keywords = [\"Jump Pack\"];\n }\n return result;\n}\n\n// ─── Main conversion ─────────────────────────────────────────────────\n\nexport function convertFaction(\n config: FactionConfig,\n options: { wargearOnly?: boolean } = {},\n): void {\n const { sourceFactionId, factionId, factionName, factionAbilityName } = config;\n\n console.log(`Converting ${factionName} (${sourceFactionId} → ${factionId})...`);\n console.log(\"Loading source data from army-assist...\");\n\n const datasheets = readJSON<SourceDatasheet[]>(\"Datasheets.json\");\n const allModels = readJSON<SourceModel[]>(\"Datasheets_models.json\");\n const allWargear = readJSON<SourceWargear[]>(\"Datasheets_wargear.json\");\n const allOptions = readJSON<\n { datasheet_id: string; line: string; description: string | null }[]\n >(\"Datasheets_options.json\");\n const allComposition = readJSON<\n { datasheet_id: string; line: string; description: string }[]\n >(\"Datasheets_unit_composition.json\");\n const allAbilities = readJSON<SourceAbility[]>(\"Datasheets_abilities.json\");\n const allKeywords = readJSON<SourceKeyword[]>(\"Datasheets_keywords.json\");\n const allPoints = readJSON<SourcePoints[]>(\"Datasheets_points.json\");\n const allLeaders = readJSON<SourceLeader[]>(\"Datasheets_leader.json\");\n const enhancements = readJSON<SourceEnhancement[]>(\"Enhancements.json\");\n const stratagems = readJSON<SourceStratagem[]>(\"Stratagems.json\");\n const detachmentAbilities = readJSON<SourceDetachmentAbility[]>(\n \"Detachment_abilities.json\"\n );\n\n // Filter to target faction, skip datasheets with no model data (metadata entries)\n const modelDatasheetIds = new Set(allModels.map((m) => m.datasheet_id));\n const factionDatasheets = datasheets.filter(\n (d) => d.faction_id === sourceFactionId && modelDatasheetIds.has(d.id)\n );\n const factionIds = new Set(factionDatasheets.map((d) => d.id));\n const idToName = new Map(factionDatasheets.map((d) => [d.id, d.name]));\n\n console.log(`Found ${factionDatasheets.length} ${factionName} datasheets\\n`);\n\n // ─── Determine view indices for shared units ───\n const viewIndices = new Map<string, number>();\n for (const ds of factionDatasheets) {\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n viewIndices.set(ds.id, findFactionViewIndex(dsAbilities, factionAbilityName));\n }\n\n // ─── Build units ───\n console.log(\"Converting units...\");\n const units: Record<string, unknown>[] = [];\n\n const unitWargearMap = new Map<string, SourceWargear[]>();\n const unitAbilityNames = new Map<string, string[]>();\n\n for (const ds of factionDatasheets) {\n const viewIdx = viewIndices.get(ds.id)!;\n\n // Models\n const dsModels = allModels.filter((m) => m.datasheet_id === ds.id);\n const viewModels = getViewEntries(dsModels, viewIdx);\n\n // Abilities (for role derivation and ability name collection)\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n const viewAbilities = getViewEntries(dsAbilities, viewIdx);\n\n const abilityNames = viewAbilities\n .filter((a) => a.type !== \"Faction\")\n .map((a) => a.name);\n unitAbilityNames.set(ds.id, [...new Set(abilityNames)]);\n\n // Keywords — use faction-aware filtering for shared units\n const dsKeywords = allKeywords.filter(\n (k) => k.datasheet_id === ds.id\n );\n const { factionKeywords, regularKeywords } = getKeywordsForFaction(\n dsKeywords,\n factionName\n );\n\n // Points — select the correct view for shared units\n const allDsPoints = allPoints.filter((p) => p.datasheet_id === ds.id);\n const dsAbilityViews = splitIntoViews(\n allAbilities.filter((a) => a.datasheet_id === ds.id)\n );\n const numViews = dsAbilityViews.length;\n const viewPoints = getPointsForView(allDsPoints, viewIdx, numViews);\n const dsPoints = viewPoints\n .map((p) => ({\n models: parseInt(p.models, 10),\n cost: parseInt(p.cost, 10),\n }))\n .sort((a, b) => a.models - b.models);\n\n // Wargear for this unit's view\n const dsWargear = allWargear.filter(\n (w) => w.datasheet_id === ds.id\n );\n const viewWargear = getViewEntries(dsWargear, viewIdx);\n unitWargearMap.set(ds.id, viewWargear);\n\n // Build stat profiles\n const profiles = viewModels.map((m) => {\n const profile: Record<string, unknown> = {\n name: m.name,\n M: parseMove(m.M),\n T: parseIntStat(m.T),\n W: parseIntStat(m.W),\n Sv: parseTargetNumber(m.Sv)!,\n invuln_sv: parseInvuln(m.inv_sv),\n Ld: parseTargetNumber(m.Ld)!,\n OC: parseIntStat(m.OC),\n };\n return profile;\n });\n\n const role = deriveRole(regularKeywords, viewAbilities, ds.name);\n const baseSize = parseBaseSize(\n viewModels[0]?.base_size ?? \"\"\n );\n const transport = parseTransport(ds.transport);\n\n const modelMin = dsPoints.length > 0 ? dsPoints[0].models : 1;\n const modelMax =\n dsPoints.length > 0 ? dsPoints[dsPoints.length - 1].models : 1;\n\n const unitId = nameToId(ds.name);\n const unit: Record<string, unknown> = {\n id: unitId,\n name: ds.name,\n faction_id: factionId,\n ...(role ? { role } : {}),\n profiles,\n points: dsPoints,\n keywords: regularKeywords,\n faction_keywords: factionKeywords,\n ...(baseSize ? { base_size_mm: baseSize } : {}),\n model_count: { min: modelMin, max: modelMax },\n weapon_ids: [],\n ability_ids: [],\n ...(transport ? { transport_capacity: transport } : {}),\n game_version: GAME_VERSION,\n is_legend: false,\n };\n\n units.push(unit);\n }\n\n // ─── Build weapons ───\n console.log(\"Converting weapons...\");\n const { weapons, unitWeaponIds } = buildWeaponRegistry(\n unitWargearMap,\n GAME_VERSION\n );\n\n // Wire weapon_ids into units\n for (const unit of units) {\n const dsId = factionDatasheets.find((d) => d.name === (unit as { name: string }).name)!.id;\n const weaponIds = unitWeaponIds.get(dsId);\n if (weaponIds) {\n (unit as { weapon_ids: string[] }).weapon_ids = [...weaponIds].sort();\n }\n }\n\n // ─── Build wargear options + non-weapon wargear ───\n console.log(\"Converting wargear options...\");\n const globalWeaponIds = new Set(weapons.map((w) => w.id));\n const {\n wargearOptions,\n wargear: wargearItems,\n unparsed: unparsedOptions,\n } = buildWargearOptions(\n factionDatasheets.map((d) => ({ id: d.id, name: d.name })),\n allModels,\n allOptions,\n allComposition,\n unitWeaponIds,\n globalWeaponIds,\n GAME_VERSION\n );\n\n // ─── Build leader attachments ───\n console.log(\"Converting leader attachments...\");\n const leaderMap = new Map<string, Set<string>>();\n\n for (const l of allLeaders) {\n if (factionIds.has(l.leader_id) && factionIds.has(l.attached_id)) {\n const leaderId = nameToId(idToName.get(l.leader_id)!);\n const attachedId = nameToId(idToName.get(l.attached_id)!);\n if (!leaderMap.has(leaderId)) {\n leaderMap.set(leaderId, new Set());\n }\n leaderMap.get(leaderId)!.add(attachedId);\n }\n }\n\n const leaderAttachments = [...leaderMap.entries()].map(\n ([leaderId, bodyguards]) => ({\n leader_id: leaderId,\n eligible_bodyguard_ids: [...bodyguards].sort(),\n max_leaders_per_unit: 1,\n game_version: GAME_VERSION,\n })\n );\n // ─── Build detachments ───\n console.log(\"Converting detachments...\");\n const factionDetAbilities = detachmentAbilities.filter(\n (d) => d.faction_id === sourceFactionId\n );\n let factionEnhancements = enhancements.filter(\n (e) => e.faction_id === sourceFactionId\n );\n let factionStratagems = stratagems.filter(\n (s) => s.faction_id === sourceFactionId\n );\n\n // Deduplicate detachments (some have multiple ability entries per detachment)\n let detachmentNames = [...new Set(factionDetAbilities.map((da) => da.detachment))];\n\n // Apply detachment filter for subfactions\n if (config.detachmentFilter) {\n detachmentNames = detachmentNames.filter((d) =>\n config.detachmentFilter!.includes(d)\n );\n // Also filter enhancements and stratagems to matching detachments\n const allowedDetachments = new Set(config.detachmentFilter);\n factionEnhancements = factionEnhancements.filter((e) => allowedDetachments.has(e.detachment));\n factionStratagems = factionStratagems.filter((s) => allowedDetachments.has(s.detachment));\n }\n\n const detachments = detachmentNames.map((detName) => {\n const detId = nameToId(detName);\n const detEnhIds = factionEnhancements\n .filter((e) => e.detachment === detName)\n .map((e) => nameToId(e.name));\n const detStratIds = factionStratagems\n .filter((s) => s.detachment === detName)\n .map((s) => nameToId(s.name));\n\n return {\n id: detId,\n name: detName,\n faction_id: factionId,\n detachment_rule_id: null as string | null,\n enhancement_ids: detEnhIds,\n stratagem_ids: detStratIds,\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Build enhancements ───\n console.log(\"Converting enhancements...\");\n const enhancementEntities = factionEnhancements.map((e) => ({\n id: nameToId(e.name),\n name: e.name,\n detachment_id: nameToId(e.detachment),\n cost: parseInt(e.cost, 10),\n keyword_restrictions: [factionName],\n ability_id: null as string | null,\n is_unique: true,\n game_version: GAME_VERSION,\n }));\n\n // ─── Build stratagems ───\n console.log(\"Converting stratagems...\");\n const stratagemEntities = factionStratagems.map((s) => {\n const { type } = parseStratagemType(s.type);\n const phases = mapPhases(s.phases);\n const playerTurn = parsePlayerTurn(s.turn);\n\n return {\n id: nameToId(s.name),\n name: s.name,\n category: \"detachment\" as const,\n type,\n detachment_id: nameToId(s.detachment),\n cp_cost: parseInt(s.cp_cost, 10),\n phases,\n player_turn: playerTurn,\n timing: \"once-per-phase\" as const,\n target_restrictions: null as null,\n ability_id: null as string | null,\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Build phase mappings from source ability phases ───\n console.log(\"Converting phase mappings...\");\n const phaseMappings: Record<string, unknown>[] = [];\n\n // Unit abilities\n for (const ds of factionDatasheets) {\n const viewIdx = viewIndices.get(ds.id)!;\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n const viewAbilities = getViewEntries(dsAbilities, viewIdx);\n const seen = new Set<string>();\n\n for (const a of viewAbilities) {\n if (a.type === \"Faction\") continue;\n const sourceId = nameToId(a.name);\n if (seen.has(sourceId)) continue;\n seen.add(sourceId);\n\n const sourceType =\n a.type === \"Core\" ? \"ability\" :\n a.type === \"Wargear\" ? \"ability\" :\n \"ability\";\n\n const phases = mapPhases(a.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: sourceId,\n source_type: sourceType,\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n }\n\n // Stratagem phase mappings\n for (const s of factionStratagems) {\n const phases = mapPhases(s.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(s.name),\n source_type: \"stratagem\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Enhancement phase mappings\n for (const e of factionEnhancements) {\n const phases = mapPhases(e.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(e.name),\n source_type: \"enhancement\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Detachment rule phase mappings\n for (const da of factionDetAbilities) {\n const phases = mapPhases(da.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(da.name),\n source_type: \"detachment-rule\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Deduplicate phase mappings\n const dedupedPhaseMappings = [\n ...new Map(\n phaseMappings.map((pm) => [\n `${(pm as { source_id: string }).source_id}|${(pm as { source_type: string }).source_type}`,\n pm,\n ])\n ).values(),\n ];\n\n // ─── Build unit compositions ───\n console.log(\"Generating unit compositions...\");\n\n const unitCompositions = units.map((u) => {\n const unitId = (u as { id: string }).id;\n const modelCount = (u as { model_count: { min: number; max: number } }).model_count;\n\n const override = config.compositionOverrides[unitId];\n if (override) {\n return {\n unit_id: unitId,\n models: override,\n game_version: GAME_VERSION,\n };\n }\n\n // Single-model unit (vehicles, characters, monsters)\n const profileName = (u as { profiles: { name: string }[] }).profiles[0]?.name;\n return {\n unit_id: unitId,\n models: [\n {\n name: profileName || (u as { name: string }).name,\n min: modelCount.min,\n max: modelCount.max,\n is_leader_model: false,\n },\n ],\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Generate factions.json ───\n const factionEntity = [\n {\n id: factionId,\n name: factionName,\n parent_faction_id: config.parentFactionId,\n game_version: GAME_VERSION,\n keywords: config.factionKeywords,\n aliases: config.aliases,\n faction_rule_id: config.factionRuleId,\n },\n ];\n\n // ─── Write output ──────────────────────────────────────────────────\n const coreDir = `data/core/${factionId}`;\n const enrichDir = `data/enrichment/${factionId}`;\n mkdirSync(resolve(ROOT, coreDir), { recursive: true });\n mkdirSync(resolve(ROOT, enrichDir), { recursive: true });\n\n console.log(\"\\nWriting output files...\");\n\n // `wargearOnly` adds just the wargear data: the rest of the converter's output\n // has drifted from the committed dataset (e.g. weapon keywords are object refs\n // there, strings here), so a full rewrite would regress those files. Adding\n // wargear must not touch them.\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/factions.json`, factionEntity);\n }\n if (!config.skipUnits) {\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/units.json`, units);\n writeOutput(`${coreDir}/weapons.json`, weapons);\n writeOutput(`${coreDir}/leader-attachments.json`, leaderAttachments);\n writeOutput(`${coreDir}/unit-compositions.json`, unitCompositions);\n }\n writeOutput(`${coreDir}/wargear-options.json`, wargearOptions);\n if (wargearItems.length > 0) {\n writeOutput(`${coreDir}/wargear.json`, wargearItems);\n }\n if (unparsedOptions.length > 0) {\n // Underscore-prefixed: a report for manual review, skipped by validation\n // and not bundled into the dataset.\n writeOutput(`${coreDir}/_wargear-options.unparsed.json`, unparsedOptions);\n }\n }\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/detachments.json`, detachments);\n writeOutput(`${coreDir}/enhancements.json`, enhancementEntities);\n writeOutput(`${coreDir}/stratagems.json`, stratagemEntities);\n if (!config.skipUnits) {\n writeOutput(`${enrichDir}/phase-mappings.json`, dedupedPhaseMappings);\n }\n }\n\n // ─── Summary ───\n console.log(`\\n── ${factionName} Summary ──`);\n if (!config.skipUnits) {\n console.log(` Units: ${units.length}`);\n console.log(` Weapons: ${weapons.length}`);\n console.log(` Leader attachments: ${leaderAttachments.length}`);\n console.log(` Unit compositions: ${unitCompositions.length}`);\n console.log(` Wargear options: ${wargearOptions.length}`);\n console.log(` Wargear items: ${wargearItems.length}`);\n console.log(` Unparsed options: ${unparsedOptions.length}`);\n }\n console.log(` Detachments: ${detachments.length}`);\n console.log(` Enhancements: ${enhancementEntities.length}`);\n console.log(` Stratagems: ${stratagemEntities.length}`);\n if (!config.skipUnits) {\n console.log(` Phase mappings: ${dedupedPhaseMappings.length}`);\n }\n console.log(\"\\nDone. Run 'npm run validate' to check output.\");\n}\n\n// ─── CLI entry point ─────────────────────────────────────────────────\n\n// Only run CLI when this module is the entry point (not when imported)\nconst isMain = process.argv[1] &&\n resolve(process.argv[1]).replace(/\\.\\w+$/, \"\") ===\n fileURLToPath(import.meta.url).replace(/\\.\\w+$/, \"\");\n\nif (isMain) {\n const args = process.argv.slice(2);\n const wargearOnly = args.includes(\"--wargear-only\");\n const positional = args.filter((a) => !a.startsWith(\"--\"));\n\n if (positional.length === 0 || args[0] === \"--help\") {\n console.log(\n \"Usage: npx tsx tools/src/convert-faction.ts <faction-id|all> [--wargear-only]\",\n );\n console.log(`Available factions: ${listFactions().join(\", \")}`);\n process.exit(args[0] === \"--help\" ? 0 : 1);\n }\n\n const target = positional[0];\n const factionIds = target === \"all\" ? listFactions() : [target];\n for (const id of factionIds) {\n convertFaction(getFactionConfig(id), { wargearOnly });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"convert-faction.js","sourceRoot":"","sources":["../src/convert-faction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generic faction converter: army-assist → 40kdc-data format.\n *\n * Usage: npx tsx tools/src/convert-faction.ts <faction-id>\n * Example: npx tsx tools/src/convert-faction.ts emperors-children\n *\n * Faction configs are registered via side-effect imports from ./converters/configs/.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { nameToId, parseStratagemType, parsePlayerTurn, mapPhases } from \"./converters/id-generator.js\";\nimport { parseMove, parseTargetNumber, parseIntStat, parseInvuln } from \"./converters/stat-parser.js\";\nimport { findFactionViewIndex, getViewEntries, getPointsForView, splitIntoViews, type SourceAbility } from \"./converters/view-selector.js\";\nimport { buildWeaponRegistry, type SourceWargear } from \"./converters/weapon-dedup.js\";\nimport { buildWargearOptions } from \"./converters/wargear-options.js\";\nimport { getKeywordsForFaction, type SourceKeyword } from \"./converters/keyword-filter.js\";\nimport { type FactionConfig, getFactionConfig, listFactions } from \"./converters/faction-config.js\";\n\n// Register all faction configs\nimport \"./converters/configs/world-eaters.js\";\nimport \"./converters/configs/emperors-children.js\";\nimport \"./converters/configs/chaos-knights.js\";\nimport \"./converters/configs/imperial-knights.js\";\nimport \"./converters/configs/leagues-of-votann.js\";\nimport \"./converters/configs/drukhari.js\";\nimport \"./converters/configs/genestealer-cults.js\";\nimport \"./converters/configs/grey-knights.js\";\nimport \"./converters/configs/thousand-sons.js\";\nimport \"./converters/configs/death-guard.js\";\nimport \"./converters/configs/adeptus-custodes.js\";\nimport \"./converters/configs/adepta-sororitas.js\";\nimport \"./converters/configs/agents-of-the-imperium.js\";\nimport \"./converters/configs/adeptus-mechanicus.js\";\nimport \"./converters/configs/tau-empire.js\";\nimport \"./converters/configs/tyranids.js\";\nimport \"./converters/configs/necrons.js\";\nimport \"./converters/configs/chaos-daemons.js\";\nimport \"./converters/configs/orks.js\";\nimport \"./converters/configs/aeldari.js\";\nimport \"./converters/configs/chaos-space-marines.js\";\nimport \"./converters/configs/astra-militarum.js\";\nimport \"./converters/configs/adeptus-astartes.js\";\nimport \"./converters/configs/blood-angels.js\";\nimport \"./converters/configs/dark-angels.js\";\nimport \"./converters/configs/space-wolves.js\";\nimport \"./converters/configs/black-templars.js\";\nimport \"./converters/configs/deathwatch.js\";\nimport \"./converters/configs/ultramarines.js\";\nimport \"./converters/configs/imperial-fists.js\";\nimport \"./converters/configs/crimson-fists.js\";\nimport \"./converters/configs/iron-hands.js\";\nimport \"./converters/configs/raven-guard.js\";\nimport \"./converters/configs/salamanders.js\";\nimport \"./converters/configs/white-scars.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst ROOT = resolve(__dirname, \"../..\");\nconst SOURCE = resolve(process.env.HOME!, \"army-assist/src/assets/json\");\nconst GAME_VERSION = { edition: \"10th\", dataslate: \"2025-q3\" };\n\n// ─── Source data types ───────────────────────────────────────────────\n\ninterface SourceDatasheet {\n id: string;\n name: string;\n faction_id: string;\n loadout: string;\n transport: string;\n role: string;\n damaged_w: string;\n damaged_description: string;\n}\n\ninterface SourceModel {\n datasheet_id: string;\n line: string;\n name: string;\n M: string;\n T: string;\n Sv: string;\n inv_sv: string;\n inv_sv_descr: string;\n W: string;\n Ld: string;\n OC: string;\n base_size: string;\n base_size_descr: string;\n}\n\ninterface SourcePoints {\n datasheet_id: string;\n models: string;\n cost: string;\n}\n\ninterface SourceLeader {\n leader_id: string;\n attached_id: string;\n}\n\ninterface SourceEnhancement {\n id: string;\n name: string;\n faction_id: string;\n cost: string;\n detachment: string;\n phases: string[];\n}\n\ninterface SourceStratagem {\n id: string;\n name: string;\n faction_id: string;\n type: string;\n cp_cost: string;\n turn: string;\n phase: string;\n detachment: string;\n phases: string[];\n}\n\ninterface SourceDetachmentAbility {\n name: string;\n faction_id: string;\n detachment: string;\n phases: string[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction readJSON<T>(filename: string): T {\n return JSON.parse(readFileSync(resolve(SOURCE, filename), \"utf-8\"));\n}\n\nfunction writeOutput(relPath: string, data: unknown): void {\n const outPath = resolve(ROOT, relPath);\n writeFileSync(outPath, JSON.stringify(data, null, 2) + \"\\n\");\n console.log(` ✓ ${relPath} (${Array.isArray(data) ? data.length : 1} entries)`);\n}\n\n/** Determine unit role from keywords and abilities. */\nfunction deriveRole(\n keywords: string[],\n abilities: SourceAbility[],\n name: string\n): string | undefined {\n const kw = new Set(keywords.map((k) => k.toLowerCase()));\n if (kw.has(\"epic hero\")) return \"epic-hero\";\n\n if (kw.has(\"character\")) return \"character\";\n const hasLeader = abilities.some(\n (a) => a.type === \"Core\" && a.name === \"Leader\"\n );\n if (hasLeader) return \"character\";\n\n if (kw.has(\"battleline\")) return \"battleline\";\n\n if (\n name.toLowerCase().includes(\"rhino\") ||\n kw.has(\"dedicated transport\")\n )\n return \"dedicated-transport\";\n\n return undefined;\n}\n\n/** Parse base size string. \"32mm\" → { shape: \"round\", diameter: 32 } */\nfunction parseBaseSize(\n s: string\n): { shape: string; diameter?: number; width?: number; length?: number } | undefined {\n if (!s || s.trim() === \"\") return undefined;\n const round = s.match(/(\\d+)\\s*mm/i);\n if (round) return { shape: \"round\", diameter: parseInt(round[1], 10) };\n const oval = s.match(/(\\d+)\\s*x\\s*(\\d+)/i);\n if (oval)\n return {\n shape: \"oval\",\n width: parseInt(oval[1], 10),\n length: parseInt(oval[2], 10),\n };\n return undefined;\n}\n\n/** Parse transport capacity from transport text. */\nfunction parseTransport(\n s: string\n): { capacity: number; keyword_restrictions?: string[]; exclusion_keywords?: string[] } | undefined {\n if (!s || s.trim() === \"\") return undefined;\n // Match \"capacity of N\" or \"N <FACTION> INFANTRY\"\n const capMatch = s.match(/capacity\\s*(?:of\\s*)?(\\d+)/i) || s.match(/(\\d+)\\s+\\S[\\S\\s]*?\\s+(?:infantry|model)/i);\n if (!capMatch) return undefined;\n const capacity = parseInt(capMatch[1], 10);\n const result: { capacity: number; keyword_restrictions?: string[]; exclusion_keywords?: string[] } = { capacity };\n\n if (/jump pack/i.test(s) && /cannot/i.test(s)) {\n result.exclusion_keywords = [\"Jump Pack\"];\n }\n return result;\n}\n\n// ─── Main conversion ─────────────────────────────────────────────────\n\nexport function convertFaction(\n config: FactionConfig,\n options: { wargearOnly?: boolean } = {},\n): void {\n const { sourceFactionId, factionId, factionName, factionAbilityName } = config;\n\n console.log(`Converting ${factionName} (${sourceFactionId} → ${factionId})...`);\n console.log(\"Loading source data from army-assist...\");\n\n const datasheets = readJSON<SourceDatasheet[]>(\"Datasheets.json\");\n const allModels = readJSON<SourceModel[]>(\"Datasheets_models.json\");\n const allWargear = readJSON<SourceWargear[]>(\"Datasheets_wargear.json\");\n const allOptions = readJSON<\n { datasheet_id: string; line: string; description: string | null }[]\n >(\"Datasheets_options.json\");\n const allComposition = readJSON<\n { datasheet_id: string; line: string; description: string }[]\n >(\"Datasheets_unit_composition.json\");\n const allAbilities = readJSON<SourceAbility[]>(\"Datasheets_abilities.json\");\n const allKeywords = readJSON<SourceKeyword[]>(\"Datasheets_keywords.json\");\n const allPoints = readJSON<SourcePoints[]>(\"Datasheets_points.json\");\n const allLeaders = readJSON<SourceLeader[]>(\"Datasheets_leader.json\");\n const enhancements = readJSON<SourceEnhancement[]>(\"Enhancements.json\");\n const stratagems = readJSON<SourceStratagem[]>(\"Stratagems.json\");\n const detachmentAbilities = readJSON<SourceDetachmentAbility[]>(\n \"Detachment_abilities.json\"\n );\n\n // Filter to target faction, skip datasheets with no model data (metadata entries)\n const modelDatasheetIds = new Set(allModels.map((m) => m.datasheet_id));\n const factionDatasheets = datasheets.filter(\n (d) => d.faction_id === sourceFactionId && modelDatasheetIds.has(d.id)\n );\n const factionIds = new Set(factionDatasheets.map((d) => d.id));\n const idToName = new Map(factionDatasheets.map((d) => [d.id, d.name]));\n\n console.log(`Found ${factionDatasheets.length} ${factionName} datasheets\\n`);\n\n // ─── Determine view indices for shared units ───\n const viewIndices = new Map<string, number>();\n for (const ds of factionDatasheets) {\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n viewIndices.set(ds.id, findFactionViewIndex(dsAbilities, factionAbilityName));\n }\n\n // ─── Build units ───\n console.log(\"Converting units...\");\n const units: Record<string, unknown>[] = [];\n\n const unitWargearMap = new Map<string, SourceWargear[]>();\n const unitAbilityNames = new Map<string, string[]>();\n\n for (const ds of factionDatasheets) {\n const viewIdx = viewIndices.get(ds.id)!;\n\n // Models\n const dsModels = allModels.filter((m) => m.datasheet_id === ds.id);\n const viewModels = getViewEntries(dsModels, viewIdx);\n\n // Abilities (for role derivation and ability name collection)\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n const viewAbilities = getViewEntries(dsAbilities, viewIdx);\n\n const abilityNames = viewAbilities\n .filter((a) => a.type !== \"Faction\")\n .map((a) => a.name);\n unitAbilityNames.set(ds.id, [...new Set(abilityNames)]);\n\n // Keywords — use faction-aware filtering for shared units\n const dsKeywords = allKeywords.filter(\n (k) => k.datasheet_id === ds.id\n );\n const { factionKeywords, regularKeywords } = getKeywordsForFaction(\n dsKeywords,\n factionName\n );\n\n // Points — select the correct view for shared units\n const allDsPoints = allPoints.filter((p) => p.datasheet_id === ds.id);\n const dsAbilityViews = splitIntoViews(\n allAbilities.filter((a) => a.datasheet_id === ds.id)\n );\n const numViews = dsAbilityViews.length;\n const viewPoints = getPointsForView(allDsPoints, viewIdx, numViews);\n const dsPoints = viewPoints\n .map((p) => ({\n models: parseInt(p.models, 10),\n cost: parseInt(p.cost, 10),\n }))\n .sort((a, b) => a.models - b.models);\n\n // Wargear for this unit's view\n const dsWargear = allWargear.filter(\n (w) => w.datasheet_id === ds.id\n );\n const viewWargear = getViewEntries(dsWargear, viewIdx);\n unitWargearMap.set(ds.id, viewWargear);\n\n // Build stat profiles\n const profiles = viewModels.map((m) => {\n const profile: Record<string, unknown> = {\n name: m.name,\n M: parseMove(m.M),\n T: parseIntStat(m.T),\n W: parseIntStat(m.W),\n Sv: parseTargetNumber(m.Sv)!,\n invuln_sv: parseInvuln(m.inv_sv),\n Ld: parseTargetNumber(m.Ld)!,\n OC: parseIntStat(m.OC),\n };\n return profile;\n });\n\n const role = deriveRole(regularKeywords, viewAbilities, ds.name);\n const baseSize = parseBaseSize(\n viewModels[0]?.base_size ?? \"\"\n );\n const transport = parseTransport(ds.transport);\n\n const modelMin = dsPoints.length > 0 ? dsPoints[0].models : 1;\n const modelMax =\n dsPoints.length > 0 ? dsPoints[dsPoints.length - 1].models : 1;\n\n const unitId = nameToId(ds.name);\n const unit: Record<string, unknown> = {\n id: unitId,\n name: ds.name,\n faction_id: factionId,\n ...(role ? { role } : {}),\n profiles,\n points: dsPoints,\n keywords: regularKeywords,\n faction_keywords: factionKeywords,\n ...(baseSize ? { base_size_mm: baseSize } : {}),\n model_count: { min: modelMin, max: modelMax },\n weapon_ids: [],\n ability_ids: [],\n ...(transport ? { transport_capacity: transport } : {}),\n game_version: GAME_VERSION,\n is_legend: false,\n };\n\n units.push(unit);\n }\n\n // ─── Build weapons ───\n console.log(\"Converting weapons...\");\n const { weapons, unitWeaponIds } = buildWeaponRegistry(\n unitWargearMap,\n GAME_VERSION\n );\n\n // Wire weapon_ids into units\n for (const unit of units) {\n const dsId = factionDatasheets.find((d) => d.name === (unit as { name: string }).name)!.id;\n const weaponIds = unitWeaponIds.get(dsId);\n if (weaponIds) {\n (unit as { weapon_ids: string[] }).weapon_ids = [...weaponIds].sort();\n }\n }\n\n // ─── Build wargear options + non-weapon wargear ───\n console.log(\"Converting wargear options...\");\n const globalWeaponIds = new Set(weapons.map((w) => w.id));\n const {\n wargearOptions,\n wargear: wargearItems,\n unparsed: unparsedOptions,\n } = buildWargearOptions(\n factionDatasheets.map((d) => ({ id: d.id, name: d.name })),\n allModels,\n allOptions,\n allComposition,\n unitWeaponIds,\n globalWeaponIds,\n GAME_VERSION\n );\n\n // ─── Build leader attachments ───\n console.log(\"Converting leader attachments...\");\n const leaderMap = new Map<string, Set<string>>();\n\n for (const l of allLeaders) {\n if (factionIds.has(l.leader_id) && factionIds.has(l.attached_id)) {\n const leaderId = nameToId(idToName.get(l.leader_id)!);\n const attachedId = nameToId(idToName.get(l.attached_id)!);\n if (!leaderMap.has(leaderId)) {\n leaderMap.set(leaderId, new Set());\n }\n leaderMap.get(leaderId)!.add(attachedId);\n }\n }\n\n const leaderAttachments = [...leaderMap.entries()].map(\n ([leaderId, bodyguards]) => ({\n leader_id: leaderId,\n eligible_bodyguard_ids: [...bodyguards].sort(),\n max_leaders_per_unit: 1,\n game_version: GAME_VERSION,\n })\n );\n // ─── Build detachments ───\n console.log(\"Converting detachments...\");\n const factionDetAbilities = detachmentAbilities.filter(\n (d) => d.faction_id === sourceFactionId\n );\n let factionEnhancements = enhancements.filter(\n (e) => e.faction_id === sourceFactionId\n );\n let factionStratagems = stratagems.filter(\n (s) => s.faction_id === sourceFactionId\n );\n\n // Deduplicate detachments (some have multiple ability entries per detachment)\n let detachmentNames = [...new Set(factionDetAbilities.map((da) => da.detachment))];\n\n // Apply detachment filter for subfactions\n if (config.detachmentFilter) {\n detachmentNames = detachmentNames.filter((d) =>\n config.detachmentFilter!.includes(d)\n );\n // Also filter enhancements and stratagems to matching detachments\n const allowedDetachments = new Set(config.detachmentFilter);\n factionEnhancements = factionEnhancements.filter((e) => allowedDetachments.has(e.detachment));\n factionStratagems = factionStratagems.filter((s) => allowedDetachments.has(s.detachment));\n }\n\n // `detachment_points` (11e DP cost) and `force_dispositions` are maintained\n // by hand — army-assist doesn't carry them — so a re-run must NOT drop them.\n // Carry both forward by id from the existing on-disk file.\n const existingDetPath = resolve(ROOT, `data/core/${factionId}/detachments.json`);\n const preservedById = new Map<string, Record<string, unknown>>();\n if (existsSync(existingDetPath)) {\n const existing = JSON.parse(readFileSync(existingDetPath, \"utf-8\")) as Array<\n Record<string, unknown>\n >;\n for (const d of existing) {\n if (typeof d.id === \"string\") preservedById.set(d.id, d);\n }\n }\n\n const detachments = detachmentNames.map((detName) => {\n const detId = nameToId(detName);\n const detEnhIds = factionEnhancements\n .filter((e) => e.detachment === detName)\n .map((e) => nameToId(e.name));\n const detStratIds = factionStratagems\n .filter((s) => s.detachment === detName)\n .map((s) => nameToId(s.name));\n\n const record: Record<string, unknown> = {\n id: detId,\n name: detName,\n faction_id: factionId,\n detachment_rule_id: null as string | null,\n enhancement_ids: detEnhIds,\n stratagem_ids: detStratIds,\n game_version: GAME_VERSION,\n };\n const preserved = preservedById.get(detId);\n if (preserved && \"detachment_points\" in preserved) {\n record.detachment_points = preserved.detachment_points;\n }\n if (preserved && \"force_dispositions\" in preserved) {\n record.force_dispositions = preserved.force_dispositions;\n }\n return record;\n });\n\n // ─── Build enhancements ───\n console.log(\"Converting enhancements...\");\n const enhancementEntities = factionEnhancements.map((e) => ({\n id: nameToId(e.name),\n name: e.name,\n detachment_id: nameToId(e.detachment),\n cost: parseInt(e.cost, 10),\n keyword_restrictions: [factionName],\n ability_id: null as string | null,\n is_unique: true,\n game_version: GAME_VERSION,\n }));\n\n // ─── Build stratagems ───\n console.log(\"Converting stratagems...\");\n const stratagemEntities = factionStratagems.map((s) => {\n const { type } = parseStratagemType(s.type);\n const phases = mapPhases(s.phases);\n const playerTurn = parsePlayerTurn(s.turn);\n\n return {\n id: nameToId(s.name),\n name: s.name,\n category: \"detachment\" as const,\n type,\n detachment_id: nameToId(s.detachment),\n cp_cost: parseInt(s.cp_cost, 10),\n phases,\n player_turn: playerTurn,\n timing: \"once-per-phase\" as const,\n target_restrictions: null as null,\n ability_id: null as string | null,\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Build phase mappings from source ability phases ───\n console.log(\"Converting phase mappings...\");\n const phaseMappings: Record<string, unknown>[] = [];\n\n // Unit abilities\n for (const ds of factionDatasheets) {\n const viewIdx = viewIndices.get(ds.id)!;\n const dsAbilities = allAbilities.filter(\n (a) => a.datasheet_id === ds.id\n );\n const viewAbilities = getViewEntries(dsAbilities, viewIdx);\n const seen = new Set<string>();\n\n for (const a of viewAbilities) {\n if (a.type === \"Faction\") continue;\n const sourceId = nameToId(a.name);\n if (seen.has(sourceId)) continue;\n seen.add(sourceId);\n\n const sourceType =\n a.type === \"Core\" ? \"ability\" :\n a.type === \"Wargear\" ? \"ability\" :\n \"ability\";\n\n const phases = mapPhases(a.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: sourceId,\n source_type: sourceType,\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n }\n\n // Stratagem phase mappings\n for (const s of factionStratagems) {\n const phases = mapPhases(s.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(s.name),\n source_type: \"stratagem\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Enhancement phase mappings\n for (const e of factionEnhancements) {\n const phases = mapPhases(e.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(e.name),\n source_type: \"enhancement\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Detachment rule phase mappings\n for (const da of factionDetAbilities) {\n const phases = mapPhases(da.phases);\n if (phases.length > 0) {\n phaseMappings.push({\n source_id: nameToId(da.name),\n source_type: \"detachment-rule\",\n phases,\n game_version: GAME_VERSION,\n authored_by: \"40kdc-community\",\n });\n }\n }\n\n // Deduplicate phase mappings\n const dedupedPhaseMappings = [\n ...new Map(\n phaseMappings.map((pm) => [\n `${(pm as { source_id: string }).source_id}|${(pm as { source_type: string }).source_type}`,\n pm,\n ])\n ).values(),\n ];\n\n // ─── Build unit compositions ───\n console.log(\"Generating unit compositions...\");\n\n const unitCompositions = units.map((u) => {\n const unitId = (u as { id: string }).id;\n const modelCount = (u as { model_count: { min: number; max: number } }).model_count;\n\n const override = config.compositionOverrides[unitId];\n if (override) {\n return {\n unit_id: unitId,\n models: override,\n game_version: GAME_VERSION,\n };\n }\n\n // Single-model unit (vehicles, characters, monsters)\n const profileName = (u as { profiles: { name: string }[] }).profiles[0]?.name;\n return {\n unit_id: unitId,\n models: [\n {\n name: profileName || (u as { name: string }).name,\n min: modelCount.min,\n max: modelCount.max,\n is_leader_model: false,\n },\n ],\n game_version: GAME_VERSION,\n };\n });\n\n // ─── Generate factions.json ───\n const factionEntity = [\n {\n id: factionId,\n name: factionName,\n parent_faction_id: config.parentFactionId,\n game_version: GAME_VERSION,\n keywords: config.factionKeywords,\n aliases: config.aliases,\n faction_rule_id: config.factionRuleId,\n },\n ];\n\n // ─── Write output ──────────────────────────────────────────────────\n const coreDir = `data/core/${factionId}`;\n const enrichDir = `data/enrichment/${factionId}`;\n mkdirSync(resolve(ROOT, coreDir), { recursive: true });\n mkdirSync(resolve(ROOT, enrichDir), { recursive: true });\n\n console.log(\"\\nWriting output files...\");\n\n // `wargearOnly` adds just the wargear data: the rest of the converter's output\n // has drifted from the committed dataset (e.g. weapon keywords are object refs\n // there, strings here), so a full rewrite would regress those files. Adding\n // wargear must not touch them.\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/factions.json`, factionEntity);\n }\n if (!config.skipUnits) {\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/units.json`, units);\n writeOutput(`${coreDir}/weapons.json`, weapons);\n writeOutput(`${coreDir}/leader-attachments.json`, leaderAttachments);\n writeOutput(`${coreDir}/unit-compositions.json`, unitCompositions);\n }\n writeOutput(`${coreDir}/wargear-options.json`, wargearOptions);\n if (wargearItems.length > 0) {\n writeOutput(`${coreDir}/wargear.json`, wargearItems);\n }\n if (unparsedOptions.length > 0) {\n // Underscore-prefixed: a report for manual review, skipped by validation\n // and not bundled into the dataset.\n writeOutput(`${coreDir}/_wargear-options.unparsed.json`, unparsedOptions);\n }\n }\n if (!options.wargearOnly) {\n writeOutput(`${coreDir}/detachments.json`, detachments);\n writeOutput(`${coreDir}/enhancements.json`, enhancementEntities);\n writeOutput(`${coreDir}/stratagems.json`, stratagemEntities);\n if (!config.skipUnits) {\n writeOutput(`${enrichDir}/phase-mappings.json`, dedupedPhaseMappings);\n }\n }\n\n // ─── Summary ───\n console.log(`\\n── ${factionName} Summary ──`);\n if (!config.skipUnits) {\n console.log(` Units: ${units.length}`);\n console.log(` Weapons: ${weapons.length}`);\n console.log(` Leader attachments: ${leaderAttachments.length}`);\n console.log(` Unit compositions: ${unitCompositions.length}`);\n console.log(` Wargear options: ${wargearOptions.length}`);\n console.log(` Wargear items: ${wargearItems.length}`);\n console.log(` Unparsed options: ${unparsedOptions.length}`);\n }\n console.log(` Detachments: ${detachments.length}`);\n console.log(` Enhancements: ${enhancementEntities.length}`);\n console.log(` Stratagems: ${stratagemEntities.length}`);\n if (!config.skipUnits) {\n console.log(` Phase mappings: ${dedupedPhaseMappings.length}`);\n }\n console.log(\"\\nDone. Run 'npm run validate' to check output.\");\n}\n\n// ─── CLI entry point ─────────────────────────────────────────────────\n\n// Only run CLI when this module is the entry point (not when imported)\nconst isMain = process.argv[1] &&\n resolve(process.argv[1]).replace(/\\.\\w+$/, \"\") ===\n fileURLToPath(import.meta.url).replace(/\\.\\w+$/, \"\");\n\nif (isMain) {\n const args = process.argv.slice(2);\n const wargearOnly = args.includes(\"--wargear-only\");\n const positional = args.filter((a) => !a.startsWith(\"--\"));\n\n if (positional.length === 0 || args[0] === \"--help\") {\n console.log(\n \"Usage: npx tsx tools/src/convert-faction.ts <faction-id|all> [--wargear-only]\",\n );\n console.log(`Available factions: ${listFactions().join(\", \")}`);\n process.exit(args[0] === \"--help\" ? 0 : 1);\n }\n\n const target = positional[0];\n const factionIds = target === \"all\" ? listFactions() : [target];\n for (const id of factionIds) {\n convertFaction(getFactionConfig(id), { wargearOnly });\n }\n}\n"]}
|
package/dist/cruncher/buffs.d.ts
CHANGED
|
@@ -125,6 +125,13 @@ export type BuffApplicability = {
|
|
|
125
125
|
requiresTargetKeyword?: string;
|
|
126
126
|
/** Attacker must carry this keyword (case-insensitive). */
|
|
127
127
|
requiresAttackerKeyword?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Range-gated abilities (DSL `scope.range_inches`, e.g. a "within 18\"" reroll):
|
|
130
|
+
* the buff applies only when the target is within this many inches. Gate is
|
|
131
|
+
* permissive when the caller leaves `EngineContext.distanceInches` undefined,
|
|
132
|
+
* so callers that don't track distance keep their current behavior.
|
|
133
|
+
*/
|
|
134
|
+
maxRangeInches?: number;
|
|
128
135
|
};
|
|
129
136
|
/** A single buff: where it came from, when it applies, what it contributes. */
|
|
130
137
|
export type Buff = {
|
|
@@ -151,6 +158,12 @@ export type EngineContext = {
|
|
|
151
158
|
attackerCharged?: boolean;
|
|
152
159
|
/** Within half the weapon's range — Melta / Rapid Fire fire. */
|
|
153
160
|
withinHalfRange?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Distance to the target in inches. Drives `applicableWhen.maxRangeInches`
|
|
163
|
+
* for range-gated abilities. Undefined when the caller doesn't track distance
|
|
164
|
+
* — range gates then evaluate permissively (the buff applies).
|
|
165
|
+
*/
|
|
166
|
+
distanceInches?: number;
|
|
154
167
|
/** Attacker benefits from cover (mostly informational; cover applies to defenders). */
|
|
155
168
|
attackerInCover?: boolean;
|
|
156
169
|
/** Target is in cover — the resolver flips on `cover`, the engine applies +1 to save. */
|
|
@@ -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,UAAU,GACV,SAAS,CAAC;IACd;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC,oFAAoF;AACpF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC,CAAC;AAEF,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC1C,MAAM,EAAE,MAAM,GAAG,cAAc,CAAC;CACjC,GACD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,UAAU,EAAE,gBAAgB,CAAA;CAAE;AACzD;;;;;;;GAOG;GACD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACvC,2EAA2E;GACzE;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACxC,yDAAyD;GACvD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACzC,0DAA0D;GACxD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC1C;;;;GAIG;GACD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AACnC;;;;;;;GAOG;GACD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC7C;;;;;GAKG;GACD;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,kEAAkE;AAClE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC/C,yDAAyD;IACzD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,uBAAuB,CAAC,EAAE,MAAM,CAAC;
|
|
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
|
@@ -33,6 +33,13 @@ function applies(buff, ctx) {
|
|
|
33
33
|
if (!attacker.includes(w.requiresAttackerKeyword.toLowerCase()))
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
|
+
// Range gate: drop only when the distance is known and exceeds the ability's
|
|
37
|
+
// range. Unknown distance is permissive (preserves callers that don't track it).
|
|
38
|
+
if (w.maxRangeInches !== undefined &&
|
|
39
|
+
ctx.distanceInches !== undefined &&
|
|
40
|
+
ctx.distanceInches > w.maxRangeInches) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
36
43
|
return true;
|
|
37
44
|
}
|
|
38
45
|
/**
|
|
@@ -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 | \"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 /**\n * Feel-no-pain: roll one D6 per unsaved wound at `threshold`+, ignoring the\n * wound on a pass. `scope` controls which wound stream it applies to:\n * - `\"all\"` (default): every unsaved wound (main + mortal).\n * - `\"mortal\"`: mortal-wound stream only (e.g. Death Guard 5+ FNP vs\n * mortals). A target may carry both an all-FNP and a mortal-FNP; the\n * engine rolls both against mortals.\n */\n | { type: \"feel-no-pain\"; threshold: number; scope?: \"all\" | \"mortal\" }\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 * Defender-side: subtract `value` from each unsaved damage point (floored at\n * 1 by the engine). Multiple sources do NOT stack in 10e — the largest\n * reduction wins. The corpus also encodes `\"half\"` and `\"to-zero\"`\n * reductions; the buff layer only models the additive form because the\n * other two are typically one-use ablation that doesn't fold into the\n * expected-value math cleanly.\n */\n | { type: \"damage-reduction\"; value: number }\n /**\n * Defender-side: ability-granted invulnerable save threshold (e.g. a buff\n * that grants a 4+ invuln). Best (lowest) threshold wins; the engine then\n * picks the better of `printed Sv after AP/cover` and `effective invuln`\n * (invuln bypasses both AP and cover).\n */\n | { type: \"invulnerable-save\"; threshold: 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 /** All-wound FNP — fires on the main and mortal damage streams alike. */\n feelNoPain: { threshold: number; dominantSource: BuffSource } | null;\n /** Mortal-only FNP — fires only on the mortal-wound damage stream. */\n feelNoPainMortal: { 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 * Defender-side damage reduction. Highest-wins (multiple sources do not\n * stack in 10e); the dominant source is the one whose value matches the\n * surviving reduction.\n */\n damageReduction: { value: number; dominantSource: BuffSource | null };\n /**\n * Ability-granted invulnerable save. Best (lowest) threshold wins. `null`\n * when no ability granted one; the engine still uses the unit's printed\n * `invuln_sv` from the profile in that case.\n */\n invulnerable: { threshold: number; dominantSource: BuffSource } | null;\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 feelNoPainMortal: 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 damageReduction: { value: 0, dominantSource: null },\n invulnerable: null,\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 // Best (lowest) threshold wins per scope. An undeclared scope is\n // treated as \"all\" — that's the existing convention (unscoped FNP =\n // applies to every wound) and keeps every shipped FNP buff regression-safe.\n const scope = c.scope ?? \"all\";\n const slot = scope === \"mortal\" ? \"feelNoPainMortal\" : \"feelNoPain\";\n if (out[slot] === null || c.threshold < out[slot]!.threshold) {\n out[slot] = { threshold: c.threshold, dominantSource: b.source };\n }\n break;\n }\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 case \"damage-reduction\":\n // Highest reduction wins (no stacking). Ties break by source rank so\n // an ability source is preferred over a manual one for provenance\n // purposes; either way the resolved value is unchanged.\n if (\n out.damageReduction.dominantSource === null ||\n c.value > out.damageReduction.value ||\n (c.value === out.damageReduction.value &&\n rank(b.source) < rank(out.damageReduction.dominantSource))\n ) {\n out.damageReduction = { value: c.value, dominantSource: b.source };\n }\n break;\n case \"invulnerable-save\":\n // Best (lowest threshold) wins. Same tie-break by source rank.\n if (\n out.invulnerable === null ||\n c.threshold < out.invulnerable.threshold ||\n (c.threshold === out.invulnerable.threshold &&\n rank(b.source) < rank(out.invulnerable.dominantSource))\n ) {\n out.invulnerable = { threshold: c.threshold, dominantSource: b.source };\n }\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 /**\n * Feel-no-pain: roll one D6 per unsaved wound at `threshold`+, ignoring the\n * wound on a pass. `scope` controls which wound stream it applies to:\n * - `\"all\"` (default): every unsaved wound (main + mortal).\n * - `\"mortal\"`: mortal-wound stream only (e.g. Death Guard 5+ FNP vs\n * mortals). A target may carry both an all-FNP and a mortal-FNP; the\n * engine rolls both against mortals.\n */\n | { type: \"feel-no-pain\"; threshold: number; scope?: \"all\" | \"mortal\" }\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 * Defender-side: subtract `value` from each unsaved damage point (floored at\n * 1 by the engine). Multiple sources do NOT stack in 10e — the largest\n * reduction wins. The corpus also encodes `\"half\"` and `\"to-zero\"`\n * reductions; the buff layer only models the additive form because the\n * other two are typically one-use ablation that doesn't fold into the\n * expected-value math cleanly.\n */\n | { type: \"damage-reduction\"; value: number }\n /**\n * Defender-side: ability-granted invulnerable save threshold (e.g. a buff\n * that grants a 4+ invuln). Best (lowest) threshold wins; the engine then\n * picks the better of `printed Sv after AP/cover` and `effective invuln`\n * (invuln bypasses both AP and cover).\n */\n | { type: \"invulnerable-save\"; threshold: 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 * Range-gated abilities (DSL `scope.range_inches`, e.g. a \"within 18\\\"\" reroll):\n * the buff applies only when the target is within this many inches. Gate is\n * permissive when the caller leaves `EngineContext.distanceInches` undefined,\n * so callers that don't track distance keep their current behavior.\n */\n maxRangeInches?: number;\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 /**\n * Distance to the target in inches. Drives `applicableWhen.maxRangeInches`\n * for range-gated abilities. Undefined when the caller doesn't track distance\n * — range gates then evaluate permissively (the buff applies).\n */\n distanceInches?: number;\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 /** All-wound FNP — fires on the main and mortal damage streams alike. */\n feelNoPain: { threshold: number; dominantSource: BuffSource } | null;\n /** Mortal-only FNP — fires only on the mortal-wound damage stream. */\n feelNoPainMortal: { 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 * Defender-side damage reduction. Highest-wins (multiple sources do not\n * stack in 10e); the dominant source is the one whose value matches the\n * surviving reduction.\n */\n damageReduction: { value: number; dominantSource: BuffSource | null };\n /**\n * Ability-granted invulnerable save. Best (lowest) threshold wins. `null`\n * when no ability granted one; the engine still uses the unit's printed\n * `invuln_sv` from the profile in that case.\n */\n invulnerable: { threshold: number; dominantSource: BuffSource } | null;\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 // Range gate: drop only when the distance is known and exceeds the ability's\n // range. Unknown distance is permissive (preserves callers that don't track it).\n if (\n w.maxRangeInches !== undefined &&\n ctx.distanceInches !== undefined &&\n ctx.distanceInches > w.maxRangeInches\n ) {\n 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 feelNoPainMortal: 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 damageReduction: { value: 0, dominantSource: null },\n invulnerable: null,\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 // Best (lowest) threshold wins per scope. An undeclared scope is\n // treated as \"all\" — that's the existing convention (unscoped FNP =\n // applies to every wound) and keeps every shipped FNP buff regression-safe.\n const scope = c.scope ?? \"all\";\n const slot = scope === \"mortal\" ? \"feelNoPainMortal\" : \"feelNoPain\";\n if (out[slot] === null || c.threshold < out[slot]!.threshold) {\n out[slot] = { threshold: c.threshold, dominantSource: b.source };\n }\n break;\n }\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 case \"damage-reduction\":\n // Highest reduction wins (no stacking). Ties break by source rank so\n // an ability source is preferred over a manual one for provenance\n // purposes; either way the resolved value is unchanged.\n if (\n out.damageReduction.dominantSource === null ||\n c.value > out.damageReduction.value ||\n (c.value === out.damageReduction.value &&\n rank(b.source) < rank(out.damageReduction.dominantSource))\n ) {\n out.damageReduction = { value: c.value, dominantSource: b.source };\n }\n break;\n case \"invulnerable-save\":\n // Best (lowest threshold) wins. Same tie-break by source rank.\n if (\n out.invulnerable === null ||\n c.threshold < out.invulnerable.threshold ||\n (c.threshold === out.invulnerable.threshold &&\n rank(b.source) < rank(out.invulnerable.dominantSource))\n ) {\n out.invulnerable = { threshold: c.threshold, dominantSource: b.source };\n }\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/cruncher/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EACL,KAAK,IAAI,EACT,KAAK,aAAa,EAElB,KAAK,iBAAiB,EAEvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AACxE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,CAAC;IAC3F,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE5E;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/cruncher/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EACL,KAAK,IAAI,EACT,KAAK,aAAa,EAElB,KAAK,iBAAiB,EAEvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AACxE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,CAAC;IAC3F,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE5E;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,YAAY,CA+M1E;AA0LD,YAAY,EAAE,KAAK,EAAE,CAAC"}
|