@yipe/dice 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ import type { RollType } from "../common/types.js";
2
+ import { PMF } from "../pmf/pmf.js";
3
+ /**
4
+ * Parse without throwing — for UI code that reparses on every keystroke, where
5
+ * a transiently invalid expression is normal rather than exceptional.
6
+ *
7
+ * Also accepts a *signed* integer, which the grammar rejects: `"-3"` becomes a
8
+ * delta at -3, `"+7"` one at 7. Unsigned integers need no help — `parse("7")`
9
+ * already returns a delta at 7 — but a half-typed damage field is a bare signed
10
+ * number often enough to be worth covering.
11
+ *
12
+ * The failure value is {@link PMF.empty}, which has **mass 0**, not a
13
+ * distribution. Convolving it collapses the whole result to mass 0, so a caller
14
+ * combining several expressions should check `mass()` (or skip empties) rather
15
+ * than assume a usable PMF. Anywhere a bad expression should be surfaced instead
16
+ * of absorbed, call {@link parse} and handle `DiceParseError`.
17
+ *
18
+ * Takes no second argument on purpose. {@link parse}'s is `n`, the substitution
19
+ * value for an `n`-dice expression — not an epsilon — so forwarding one here
20
+ * would silently reinterpret it: `tryParse("nd6", 1e-9)` rolled `1d6` and
21
+ * reported 3.5 where the default `n` of 0 means no dice at all.
22
+ *
23
+ * @returns the parsed PMF, or an empty (mass 0) PMF for input that is neither a
24
+ * valid expression nor an integer.
25
+ */
26
+ export declare function tryParse(expression: string): PMF;
27
+ /**
28
+ * Rewrite an expression's attack roll to a different d20 {@link RollType},
29
+ * leaving everything else — damage, crit clause, miss clause, bonuses —
30
+ * untouched.
31
+ *
32
+ * ```ts
33
+ * withRollType("(d20 + 8 AC 16) * (1d4 + 4)", "advantage");
34
+ * // "(d20 > d20 + 8 AC 16) * (1d4 + 4)"
35
+ * ```
36
+ *
37
+ * **Every** `AC` group is rewritten, because one expression can hold several
38
+ * attacks (`(d20 + 8 AC 16) * (1d8) + (d20 + 5 AC 16) * (1d6)`) and leaving the
39
+ * later ones flat would quietly chart the wrong curve.
40
+ *
41
+ * A `DC` group is the *target's* saving throw, which the attacker's advantage
42
+ * does not touch, so save expressions come back unchanged — as does anything
43
+ * with no attack roll at all. This makes the function safe to map over a mixed
44
+ * list of expressions.
45
+ *
46
+ * Assumes **one check per group**, which is what every well-formed attack or
47
+ * save expression looks like and what `modelToExpression` emits. The grammar
48
+ * will swallow a group naming two — `(d20 + 5 DC 16 + d20 + 8 AC 16)` parses,
49
+ * as a single `AC` check whose roll happens to contain the save's 0/1 result —
50
+ * but that is not an expression anyone means, and the roll type it should get is
51
+ * undefined. Such input is rewritten on a best-effort basis rather than
52
+ * diagnosed.
53
+ *
54
+ * A halfling-luck `h` prefix is preserved on the first die of each run, since it
55
+ * describes the same roll.
56
+ */
57
+ export declare function withRollType(expression: string, rollType: RollType): string;
58
+ //# sourceMappingURL=rollType.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollType.d.ts","sourceRoot":"","sources":["../../src/parser/rollType.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAMjC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAchD;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAkB3E"}
@@ -457,6 +457,32 @@ export declare class DiceQuery {
457
457
  avg: number;
458
458
  max: number;
459
459
  }>;
460
+ /**
461
+ * Per-outcome probabilities and damage ranges, aggregated over the individual
462
+ * singles rather than read off the combined distribution.
463
+ *
464
+ * `damageRange` is the sum, over every single that can produce the outcome, of
465
+ * that single's own conditional damage range: "what this outcome contributes
466
+ * across the whole turn when every attack that can produce it does". Linear in
467
+ * the number of attacks by construction.
468
+ *
469
+ * Prefer this over {@link DiceQuery.snapshot} for a multi-attack query.
470
+ * `snapshot` reads `damageRange` off the combined PMF's `count`, which the
471
+ * convolution accumulates as an expected count, so its `avg` is size-biased
472
+ * for N≥2 (its own doc comment says so). The two agree for a single attack.
473
+ *
474
+ * Only outcomes that actually occur appear in the result.
475
+ *
476
+ * Like every `singles`-based helper on this class, it describes the singles
477
+ * and not an explicitly provided `combined`. `Turn.toQuery()` supplies one whose
478
+ * distribution also contains rider attacks that are absent from `singles`
479
+ * (an `otherwise([unarmed, unarmed])` flurry, say), so those attacks do not
480
+ * appear here. For rider-inclusive figures read the combined distribution
481
+ * directly: {@link DiceQuery.outcomeTotals}, {@link DiceQuery.outcomeDamageRanges}.
482
+ *
483
+ * @param outcomes Which outcomes to consider; defaults to every canonical one.
484
+ */
485
+ outcomeStats(outcomes?: readonly OutcomeType[]): Map<OutcomeType, OutcomeSnapshot>;
460
486
  /**
461
487
  * Snapshot of the distribution in the exact shape the UI consumes.
462
488
  * - outcome probabilities are "at least one" (and equal to "all" for a single PMF)
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/pmf/query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/pmf/query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from "./turn.js";
2
+ export * from "./types.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/turn/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { PMF } from "../pmf/pmf.js";
2
+ import type { Trigger, TurnSpec } from "./types.js";
3
+ /**
4
+ * A resolved, validated turn: the ordered steps to walk, plus the trigger
5
+ * groups it tracks. Building this is where every {@link TurnSpecError} is raised,
6
+ * so the walk itself can assume a well-formed plan.
7
+ */
8
+ export interface TurnPlan {
9
+ steps: readonly Step[];
10
+ /** One entry per distinct `of` set; each holds the step indices that update it. */
11
+ groupCount: number;
12
+ /** Declared attacks only, in order — what `DiceQuery.singles` gets. */
13
+ attackPMFs: readonly PMF[];
14
+ /** Every attack id, in declaration order. */
15
+ attackIds: readonly string[];
16
+ /** Every rider id, in declaration order, including `every-hit` riders. */
17
+ riderIds: readonly string[];
18
+ /** Rider id → step index, for `fireProbability` and `not-fired`. */
19
+ riderSteps: ReadonlyMap<string, number>;
20
+ /**
21
+ * `every-hit` rider id → the group index whose "something landed" bit answers
22
+ * P(it fired at least once). Such riders are folded into their sources' slices
23
+ * rather than becoming steps, so they have no step index.
24
+ */
25
+ perHitGroups: ReadonlyMap<string, number>;
26
+ }
27
+ /**
28
+ * One step of the turn. `slices` non-null ⇒ the step rolls its own attack and
29
+ * advances the groups listed in `updates`; null ⇒ it is pure damage whose amount
30
+ * depends only on the mode it fires in.
31
+ */
32
+ export interface Step {
33
+ id: string;
34
+ /** Declared attacks always fire; riders consult their trigger. */
35
+ trigger: Trigger | null;
36
+ /** Outcome-labelled sub-mass PMFs, masses summing to 1. */
37
+ slices: {
38
+ hit: PMF;
39
+ crit: PMF;
40
+ miss: PMF;
41
+ } | null;
42
+ /** Pure-damage payloads, mass 1 each. */
43
+ damage: {
44
+ hit: PMF;
45
+ crit: PMF;
46
+ } | null;
47
+ /** Group indices this step's outcome advances. */
48
+ updates: readonly number[];
49
+ /** Group index this step's trigger reads, or -1 for `not-fired` / always-fires. */
50
+ reads: number;
51
+ /** For `not-fired`: the step index of the rider being negated. */
52
+ negates: number;
53
+ }
54
+ export declare function buildPlan(spec: TurnSpec, eps?: number): TurnPlan;
55
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/turn/plan.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAKV,OAAO,EACP,QAAQ,EAET,MAAM,SAAS,CAAC;AAGjB;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;IACvB,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,UAAU,EAAE,SAAS,GAAG,EAAE,CAAC;IAC3B,6CAA6C;IAC7C,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,oEAAoE;IACpE,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;OAIG;IACH,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED;;;;GAIG;AACH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,kEAAkE;IAClE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,2DAA2D;IAC3D,MAAM,EAAE;QAAE,GAAG,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,GAAG,IAAI,CAAC;IAClD,yCAAyC;IACzC,MAAM,EAAE;QAAE,GAAG,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,GAAG,IAAI,CAAC;IACvC,kDAAkD;IAClD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;CACjB;AA6FD,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,GAAE,MAAY,GAAG,QAAQ,CAoRrE"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Per-group trigger state, packed into one byte.
3
+ *
4
+ * A "group" is one distinct set of source ids referenced by a trigger. Everything
5
+ * any trigger needs to know about a group is which outcome landed *first*, whether
6
+ * anything crit, and whether anything missed — so the whole turn state is one byte
7
+ * per group and the state space stays small (12 codes per group, ~10 reachable).
8
+ *
9
+ * Layout: `first << 2 | anyCrit << 1 | anyMiss`, where `first` is one of
10
+ * {@link FIRST_NONE} / {@link FIRST_HIT} / {@link FIRST_CRIT}. Readers decode with
11
+ * `code >> 2`, `code & CRIT_BIT`, `code & MISS_BIT`.
12
+ */
13
+ export declare const FIRST_NONE = 0;
14
+ export declare const FIRST_HIT = 1;
15
+ export declare const FIRST_CRIT = 2;
16
+ export declare const CRIT_BIT = 2;
17
+ export declare const MISS_BIT = 1;
18
+ /** A group that has seen nothing yet: no first landing, no crit, no miss. */
19
+ export declare const START_CODE: number;
20
+ export type StepOutcome = "hit" | "crit" | "miss";
21
+ /** Fold one source outcome into a group's state. */
22
+ export declare function advance(code: number, outcome: StepOutcome): number;
23
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/turn/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,SAAS,IAAI,CAAC;AAC3B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,eAAO,MAAM,QAAQ,IAAI,CAAC;AAE1B,6EAA6E;AAC7E,eAAO,MAAM,UAAU,QAAkB,CAAC;AAE1C,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD,oDAAoD;AACpD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CASlE"}
@@ -0,0 +1,149 @@
1
+ import { PMF } from "../pmf/pmf.js";
2
+ import { DiceQuery } from "../pmf/query.js";
3
+ import type { Attack, Rider, RiderDamage, RiderOptions, Source, TurnSpec } from "./types.js";
4
+ /**
5
+ * A turn of attacks plus conditional damage riders, resolved to one **exact**
6
+ * joint distribution.
7
+ *
8
+ * Riders are correlated with the attacks that trigger them, so a rider cannot be
9
+ * a separate `DiceQuery` single convolved in afterwards: that preserves the mean
10
+ * but corrupts the distribution (two daggers + Sneak Attack report P(0 damage) of
11
+ * 0.015 instead of the true 0.1225). `Turn` owns the sources and enumerates the
12
+ * joint outcome space instead, carrying one byte of state per trigger group.
13
+ *
14
+ * @example
15
+ * const dagger = d20.plus(8).ac(16).onHit(d4.plus(4));
16
+ * const rogue = turn([dagger, dagger]).rider({ damage: roll(3, d6), on: "first-hit" });
17
+ * rogue.mean(); // 18.6225
18
+ */
19
+ export declare class Turn {
20
+ private readonly eps;
21
+ private readonly declaredAttacks;
22
+ private readonly riders;
23
+ private readonly plan;
24
+ private resolved?;
25
+ private constructor();
26
+ /**
27
+ * Builds a turn from plain data, throwing {@link TurnSpecError} if it is
28
+ * malformed. Use this from a UI, where `error.code` maps to the field state to
29
+ * show.
30
+ */
31
+ static from(spec: TurnSpec, eps?: number): Turn;
32
+ /**
33
+ * Appends an attack, throwing {@link TurnSpecError} if that makes the turn
34
+ * invalid.
35
+ *
36
+ * A rider with no explicit `of` watches every declared attack *including ones
37
+ * appended after it*, because `of` is resolved when the plan is built rather
38
+ * than when the rider is added. Pass an explicit `of` to pin a rider to the
39
+ * attacks it already saw. One attack must exist before a rider with a default
40
+ * `of` is added, or the build fails `unknown-id`.
41
+ */
42
+ attack(source: Source, id?: string): Turn;
43
+ /**
44
+ * Appends `count` copies of the same attack — the Extra Attack case, which is
45
+ * most of 5e. Argument order mirrors `roll(count, die)`.
46
+ *
47
+ * ```ts
48
+ * turn().attacks(4, greatsword).onEveryHit(d6); // fighter 20 + hunter's mark
49
+ * ```
50
+ *
51
+ * @throws {RangeError} if `count` is not a positive integer.
52
+ */
53
+ attacks(count: number, source: Source): Turn;
54
+ /**
55
+ * Appends a rider, throwing {@link TurnSpecError} if that makes the turn
56
+ * invalid. The `onX` methods below are the readable way to call this.
57
+ */
58
+ rider(rider: Rider): Turn;
59
+ /**
60
+ * Fires once, on the first source that lands, in that source's mode — so a
61
+ * crit on the first landing attack doubles the rider's dice. Sneak Attack.
62
+ */
63
+ onFirstHit(damage: RiderDamage, options?: RiderOptions): Turn;
64
+ /**
65
+ * Fires once if any source crit, always in crit mode. Divine Smite: nothing is
66
+ * lost by holding it for a crit, so this is "any", not "first".
67
+ */
68
+ onAnyCrit(damage: RiderDamage, options?: RiderOptions): Turn;
69
+ /**
70
+ * Fires once if any source missed. The reroll gate: a reroll is a fresh attack,
71
+ * so pass one as the damage. Kensei's Unerring Accuracy, Lucky.
72
+ */
73
+ onAnyMiss(damage: RiderDamage, options?: RiderOptions): Turn;
74
+ /**
75
+ * Fires once per source that lands, in that hit's mode — so it can fire several
76
+ * times in a turn. Hunter's Mark, Hex, Rage.
77
+ */
78
+ onEveryHit(damage: RiderDamage, options?: RiderOptions): Turn;
79
+ /**
80
+ * Damage for the turns where the rider added just before this one did *not*
81
+ * fire: "flurry of blows if I didn't smite".
82
+ *
83
+ * ```ts
84
+ * turn([dagger, dagger])
85
+ * .onAnyCrit(roll(2, d8)) // smite
86
+ * .otherwise([flurry, flurry]) // ... or two more attacks
87
+ * ```
88
+ *
89
+ * Always binds to the *immediately* preceding rider, so the two are branches of
90
+ * one decision and can never both land. Note that chaining it therefore
91
+ * alternates rather than laddering: `a.otherwise(b).otherwise(c)` makes `c`
92
+ * fire whenever `b` did not, which is exactly when `a` did. For a genuine
93
+ * three-way priority chain, name the riders and use explicit `not-fired`
94
+ * triggers against the right one.
95
+ */
96
+ otherwise(damage: RiderDamage, options?: Omit<RiderOptions, "of">): Turn;
97
+ /**
98
+ * The exact joint distribution: mass 1, outcome-labelled. Resolved once and
99
+ * cached.
100
+ *
101
+ * There is no `toPMF(eps)` to match the builders: a turn's epsilon is fixed
102
+ * when it is constructed, because the plan is validated and its sources are
103
+ * resolved at that point.
104
+ */
105
+ get pmf(): PMF;
106
+ /** Mean damage for the turn. */
107
+ mean(): number;
108
+ /**
109
+ * A query whose `singles` are the **declared attacks** and whose combined
110
+ * distribution is the exact turn PMF.
111
+ *
112
+ * Riders are inside the combined PMF, not in `singles`, so singles-based
113
+ * helpers (`probAtLeastOne`, `countSinglesWith`, `outcomeStats`) describe the
114
+ * attacks only. Read rider-inclusive statistics off the combined PMF —
115
+ * `outcomeTotals`, `outcomeDamageRanges`, `damageAttributionChartModel`.
116
+ */
117
+ toQuery(): DiceQuery;
118
+ /**
119
+ * Attack ids in declaration order, including the `attack 1`, `attack 2`, …
120
+ * defaults given to bare sources. These are the names `of` accepts.
121
+ */
122
+ get attackIds(): readonly string[];
123
+ /**
124
+ * Rider ids in declaration order, including the `rider 1`, `rider 2`, …
125
+ * defaults. These are the names {@link Turn.fireProbability} accepts.
126
+ */
127
+ get riderIds(): readonly string[];
128
+ /**
129
+ * P(this rider fired). For an `every-hit` rider it is P(at least one source
130
+ * hit), since that rider can fire more than once in a turn.
131
+ *
132
+ * @throws {TurnSpecError} `unknown-id` if `id` is not a rider — attack ids
133
+ * included, since attacks always happen and have no firing probability.
134
+ */
135
+ fireProbability(id: string): number;
136
+ private resolve;
137
+ }
138
+ /**
139
+ * Starts a {@link Turn}. Takes one attack or a list of them, so the two common
140
+ * shapes both read straight:
141
+ *
142
+ * ```ts
143
+ * turn(greatsword).onAnyCrit(roll(4, d8)); // one attack
144
+ * turn([dagger, dagger]).onFirstHit(roll(3, d6)); // two
145
+ * turn().attacks(4, greatsword); // four
146
+ * ```
147
+ */
148
+ export declare function turn(attacks?: Attack | readonly Attack[], eps?: number): Turn;
149
+ //# sourceMappingURL=turn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../../src/turn/turn.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAYzC,OAAO,KAAK,EACV,MAAM,EACN,KAAK,EACL,WAAW,EACX,YAAY,EACZ,MAAM,EACN,QAAQ,EACT,MAAM,SAAS,CAAC;AA6CjB;;;;;;;;;;;;;;GAcG;AACH,qBAAa,IAAI;IACf,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoB;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAW;IAChC,OAAO,CAAC,QAAQ,CAAC,CAAsD;IAEvE,OAAO,eAeN;IAED;;;;OAIG;IACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,GAAE,MAAY,GAAG,IAAI,CAEnD;IAED;;;;;;;;;OASG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAGxC;IAED;;;;;;;;;OASG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAQ3C;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAExB;IAED;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,YAAiB,GAAG,IAAI,CAEhE;IAED;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,YAAiB,GAAG,IAAI,CAE/D;IAED;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,YAAiB,GAAG,IAAI,CAE/D;IAED;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,YAAiB,GAAG,IAAI,CAEhE;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,CACP,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAM,GACrC,IAAI,CAwBN;IAED;;;;;;;OAOG;IACH,IAAI,GAAG,IAAI,GAAG,CAEb;IAED,gCAAgC;IAChC,IAAI,IAAI,MAAM,CAEb;IAED;;;;;;;;OAQG;IACH,OAAO,IAAI,SAAS,CAEnB;IAED;;;OAGG;IACH,IAAI,SAAS,IAAI,SAAS,MAAM,EAAE,CAEjC;IAED;;;OAGG;IACH,IAAI,QAAQ,IAAI,SAAS,MAAM,EAAE,CAEhC;IAED;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAYlC;IAED,OAAO,CAAC,OAAO;CA6HhB;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAClB,OAAO,GAAE,MAAM,GAAG,SAAS,MAAM,EAAO,EACxC,GAAG,GAAE,MAAY,GAChB,IAAI,CAKN"}
@@ -0,0 +1,99 @@
1
+ import type { PMF } from "../pmf/pmf.js";
2
+ /**
3
+ * Triggers that read the outcomes of source attacks, as opposed to `not-fired`,
4
+ * which reads another rider. Includes `any-miss`, so this is not "hit triggers".
5
+ */
6
+ export type AttackTriggerOn = "first-hit" | "any-crit" | "any-miss" | "every-hit";
7
+ /**
8
+ * When a rider fires. JSON-safe, so a consumer can persist this verbatim and hand
9
+ * it straight back in a {@link TurnSpec}.
10
+ *
11
+ * - `first-hit` — the first source that lands. Fires in that source's mode, so a
12
+ * crit on the *first* landing attack doubles the rider's dice (Sneak Attack).
13
+ * - `any-crit` — at least one source crit. Always fires in crit mode (Divine Smite).
14
+ * - `any-miss` — at least one source missed. The reroll gate (Unerring Accuracy, Lucky).
15
+ * - `every-hit` — once per landing source, in that hit's mode (Hunter's Mark, Hex, Rage).
16
+ * - `not-fired` — the named rider did **not** fire ("flurry of blows if I didn't smite").
17
+ *
18
+ * For the attack triggers `of` is a list of attack ids and defaults to every
19
+ * declared attack, which is what most riders mean. For `not-fired` it is the
20
+ * single required id of the rider being negated — negating a set of riders has
21
+ * no unambiguous meaning, so the type does not offer it.
22
+ */
23
+ export type Trigger = {
24
+ on: AttackTriggerOn;
25
+ of?: readonly string[];
26
+ } | {
27
+ on: "not-fired";
28
+ of: string;
29
+ };
30
+ /** Anything that can produce a PMF: `RollBuilder`, `AttackBuilder`, `SaveBuilder`, or a `PMF`. */
31
+ export interface ToPMF {
32
+ toPMF(eps?: number): PMF;
33
+ }
34
+ /**
35
+ * Rider or attack damage. Strings are not accepted: a parsed expression cannot
36
+ * have its dice doubled, so a string rider would deal single dice on a crit. Call
37
+ * `parse()` yourself if that is what you want.
38
+ */
39
+ export type Damage = PMF | ToPMF;
40
+ /**
41
+ * Same shape as {@link Damage}, named separately because the requirement is
42
+ * stronger: a source must resolve to an *outcome-labelled* PMF carrying
43
+ * hit/crit/miss, which no type can express. Supplying one that does not is a
44
+ * `not-an-attack` {@link TurnSpecError} at build time, not a compile error.
45
+ */
46
+ export type Source = Damage;
47
+ /** One payload, or several to convolve: Flurry of Blows is `[flurry, flurry]`. */
48
+ export type RiderDamage = Damage | readonly Damage[];
49
+ /** Everything about a rider except what it does and when — see `Turn.onFirstHit`. */
50
+ export interface RiderOptions {
51
+ /** Required only if another rider names this one in `of`. */
52
+ id?: string;
53
+ /** Which attacks to watch. Defaults to every declared attack. */
54
+ of?: readonly string[];
55
+ /** Defaults to `damage` with dice doubled when that is possible, else `damage`. */
56
+ critDamage?: RiderDamage;
57
+ }
58
+ export type Rider = Trigger & {
59
+ id?: string;
60
+ damage: RiderDamage;
61
+ critDamage?: RiderDamage;
62
+ };
63
+ /**
64
+ * A bare source gets the id `attack 1`, `attack 2`, … in declaration order.
65
+ *
66
+ * Unlike {@link Rider}, the id lives in a wrapper rather than on the value
67
+ * itself: an attack's value is a builder or PMF that this module does not own,
68
+ * so there is nowhere to hang a field.
69
+ */
70
+ export type Attack = Source | {
71
+ id: string;
72
+ source: Source;
73
+ };
74
+ export interface TurnSpec {
75
+ attacks: readonly Attack[];
76
+ riders?: readonly Rider[];
77
+ }
78
+ export type TurnSpecErrorCode = "unknown-id" | "duplicate-id" | "self-reference" | "cycle" | "not-an-attack" | "unused-crit-damage" | "too-many-groups";
79
+ /**
80
+ * A malformed turn. `code` is a stable contract: consumer UIs map it to their own
81
+ * states (greyed dropdown option, "trigger missing" row error, …) instead of
82
+ * re-implementing validation.
83
+ */
84
+ export declare class TurnSpecError extends Error {
85
+ readonly code: TurnSpecErrorCode;
86
+ readonly id: string;
87
+ constructor(code: TurnSpecErrorCode, id: string, message: string);
88
+ }
89
+ /**
90
+ * How many distinct `of` sets a single turn may track.
91
+ *
92
+ * Each group multiplies the state space, so the cap is a cost ceiling rather
93
+ * than a modelling limit. Measured on four attacks with two riders per group:
94
+ * 2.1ms for one group, 3.1 for two, 5.9 for three, 23.5 for four — roughly 4x
95
+ * per group. Real builds use one or two (the goliath rogue/monk/paladin uses
96
+ * one), so four leaves plenty of room while keeping an AC sweep viable.
97
+ */
98
+ export declare const MAX_TRIGGER_GROUPS = 4;
99
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/turn/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC;;;GAGG;AACH,MAAM,MAAM,eAAe,GACvB,WAAW,GACX,UAAU,GACV,UAAU,GACV,WAAW,CAAC;AAEhB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,OAAO,GACf;IAAE,EAAE,EAAE,eAAe,CAAC;IAAC,EAAE,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC/C;IAAE,EAAE,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpC,kGAAkG;AAClG,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;AAErD,qFAAqF;AACrF,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,iEAAiE;IACjE,EAAE,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvB,mFAAmF;IACnF,UAAU,CAAC,EAAE,WAAW,CAAC;CAC1B;AAED,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,EAAE,WAAW,CAAC;CAC1B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,iBAAiB,GACzB,YAAY,GACZ,cAAc,GACd,gBAAgB,GAChB,OAAO,GACP,eAAe,GACf,oBAAoB,GACpB,iBAAiB,CAAC;AAEtB;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAEpC,QAAQ,CAAC,IAAI,EAAE,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM;IAFrB,YACW,IAAI,EAAE,iBAAiB,EACvB,EAAE,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAIhB;CACF;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yipe/dice",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "A high-performance dice probability engine for D&D 5e DPR calculations. Powers dprcalc.com.",
5
5
  "keywords": [
6
6
  "dnd",
@@ -91,4 +91,4 @@
91
91
  "typescript": "^7.0.2",
92
92
  "vitest": "^4.1.10"
93
93
  }
94
- }
94
+ }