@yipe/dice 0.11.0 → 0.12.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.
Files changed (65) hide show
  1. package/README.md +278 -8
  2. package/dist/builder/ac.d.ts +44 -1
  3. package/dist/builder/ac.d.ts.map +1 -1
  4. package/dist/builder/arguments.d.ts +6 -0
  5. package/dist/builder/arguments.d.ts.map +1 -0
  6. package/dist/builder/ast.d.ts +36 -7
  7. package/dist/builder/ast.d.ts.map +1 -1
  8. package/dist/builder/attack.d.ts +88 -5
  9. package/dist/builder/attack.d.ts.map +1 -1
  10. package/dist/builder/dc.d.ts +13 -0
  11. package/dist/builder/dc.d.ts.map +1 -1
  12. package/dist/builder/example.d.ts +11 -15
  13. package/dist/builder/example.d.ts.map +1 -1
  14. package/dist/builder/expression.d.ts +72 -0
  15. package/dist/builder/expression.d.ts.map +1 -0
  16. package/dist/builder/factory.d.ts +2 -3
  17. package/dist/builder/factory.d.ts.map +1 -1
  18. package/dist/builder/index.cjs +3574 -1359
  19. package/dist/builder/index.cjs.map +1 -1
  20. package/dist/builder/index.js +3567 -1360
  21. package/dist/builder/index.js.map +1 -1
  22. package/dist/builder/nodes.d.ts +7 -1
  23. package/dist/builder/nodes.d.ts.map +1 -1
  24. package/dist/builder/prob.d.ts +9 -0
  25. package/dist/builder/prob.d.ts.map +1 -1
  26. package/dist/builder/roll.d.ts +151 -21
  27. package/dist/builder/roll.d.ts.map +1 -1
  28. package/dist/builder/save.d.ts +6 -1
  29. package/dist/builder/save.d.ts.map +1 -1
  30. package/dist/builder/types.d.ts +18 -0
  31. package/dist/builder/types.d.ts.map +1 -1
  32. package/dist/common/bounce.d.ts +24 -11
  33. package/dist/common/bounce.d.ts.map +1 -1
  34. package/dist/common/lru-cache.d.ts +29 -1
  35. package/dist/common/lru-cache.d.ts.map +1 -1
  36. package/dist/index.cjs +1131 -402
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.js +1131 -402
  39. package/dist/index.js.map +1 -1
  40. package/dist/parser/dice.d.ts +52 -16
  41. package/dist/parser/dice.d.ts.map +1 -1
  42. package/dist/parser/parser.d.ts +1 -5
  43. package/dist/parser/parser.d.ts.map +1 -1
  44. package/dist/parser/rollType.d.ts +4 -4
  45. package/dist/parser/scaleDice.d.ts +14 -0
  46. package/dist/parser/scaleDice.d.ts.map +1 -0
  47. package/dist/pmf/mixture.d.ts +17 -3
  48. package/dist/pmf/mixture.d.ts.map +1 -1
  49. package/dist/pmf/pmf.d.ts +118 -33
  50. package/dist/pmf/pmf.d.ts.map +1 -1
  51. package/dist/pmf/query.d.ts +25 -12
  52. package/dist/pmf/query.d.ts.map +1 -1
  53. package/dist/turn/effects.d.ts +114 -0
  54. package/dist/turn/effects.d.ts.map +1 -0
  55. package/dist/turn/index.d.ts +3 -1
  56. package/dist/turn/index.d.ts.map +1 -1
  57. package/dist/turn/plan.d.ts +101 -21
  58. package/dist/turn/plan.d.ts.map +1 -1
  59. package/dist/turn/state.d.ts +14 -8
  60. package/dist/turn/state.d.ts.map +1 -1
  61. package/dist/turn/turn.d.ts +127 -26
  62. package/dist/turn/turn.d.ts.map +1 -1
  63. package/dist/turn/types.d.ts +154 -17
  64. package/dist/turn/types.d.ts.map +1 -1
  65. package/package.json +1 -1
package/README.md CHANGED
@@ -179,6 +179,117 @@ RollBuilder (d20, d6, roll(), etc.)
179
179
  └─ .toPMF() ──► PMF
180
180
  ```
181
181
 
182
+ **Crits double the damage dice, never the flats.** An `onHit` payload with no `onCrit` override
183
+ crits with its dice doubled: `roll(2, d6).plus(5)` and the string `"2d6+5"` both crit as `4d6 + 5`.
184
+ A pool doubles inside, then pools — `roll(2, d6).plus(3).keepHighestAll(2, 1)` crits as
185
+ `roll(4, d6).plus(3).keepHighestAll(2, 1)` (mean 18.93), not as the whole pool rolled twice (22.74).
186
+ Rider damage doubles the same way; a bare `PMF` has no dice, so it is added as-is. `onCrit(...)` and
187
+ `noCrit()` stay explicit, and a damage string containing an attack or save check (`AC`/`DC`) throws
188
+ when used as a payload that doubles (`onHit`, `doubleDice()`); as a rider it is added as-is on a
189
+ crit, like the attack or save builder it stands for.
190
+ An attack string with no crit clause crits too: `"(d20 + 8 AC 16) * (2d6)"` rolls its natural 20 as
191
+ `4d6`, like `d20.plus(8).ac(16).onHit(roll(2, d6))`; a `crit (…)` clause still wins. A term after
192
+ the payload joined by `+`, `*`, `**`, `/` or `//` applies only where the attack deals damage (the
193
+ grammar reads left to right, and `+` adds to non-zero totals), so it is part of the payload:
194
+ `(d20 + 5 AC 15) * (1d8) + 1d6` crits as `2d8 + 2d6`, like `onHit(roll(1, d8).plus(roll(1, d6)))`,
195
+ and keeps its hit, crit and miss labels. After a `crit (…)` clause the term is added to the crit as
196
+ written. The crit rate reads the check's natural die, its one d20 wherever it sits in the sum (with
197
+ no d20, its largest die), through bonus to-hit dice, advantage (`d20 > d20`, `d20!`, `2kh1d20`),
198
+ disadvantage (`2kl1(1d20)`), elven accuracy (`3kh1(1d20)`) and halfling luck (`hd20`,
199
+ `d20 reroll 1`), for `crit` and `xcrit N` alike: `(d20 > d20 + 5 + 1d4 AC 15) * (2d6)` crits at
200
+ 39/400, like `d20.withAdvantage().plus(5).plus(d4)`, `(1d4 + d20 + 5 AC 15)` crits on the d20, not
201
+ the d4, and `(d20 + d100 AC 60)` on the d20, not the d100 (61/2000). A max or min against another
202
+ die or a number crits where the natural 20 is the value kept: `(d20 > d4 + 5 AC 10)` on every
203
+ natural 20, `(d20 < 15 + 5 AC 10)` never. An `&` mix crits where one of its sides rolls its natural
204
+ 20, at that side's share of the mix, in either order and with the AC gate on either side:
205
+ `(d4 & d20 AC 5) * (1d6) crit (2d6)` and `(d4 & (d20 AC 5)) * (1d6)` both crit at 1/24. A die on
206
+ the AC side is the target's roll, never the natural roll. A check with no single natural die
207
+ (`2d20`, `d20 + d20`, `d20 + d20 + d100`, `2kh2(1d20)`, advantage over a total like `(d20 + 1d4)!`,
208
+ `d20 + 5 > d20`, a reroll, repeat, keep or double advantage of a mix with a smaller die in it, like
209
+ `(d20 & d4) reroll 1`, `1(d20 & d4)`, `2kh1(d20 & d4)` or `(d20 & d4)!!`) throws `crit rate cannot
210
+ be computed exactly …` when it would crit, and so does an `xcrit N` wider than the die. A check with
211
+ no die at all (`(15 AC 12) * (1d6)`, `(25 AC d20) * (1d6)`) has no natural roll, so it never crits,
212
+ and a `crit (…)` or `xcrit N` clause on it is inert: `(15 AC 12) * (1d6) crit (2d6)` means 3.5, like
213
+ `roll.flat(15).ac(12).onHit(d6)`, which emits that string.
214
+
215
+ **`&` shapes that are refused.** An `&` mix weights each side by its count of outcomes, so these
216
+ shapes have no single reading and throw rather than return a number that depends on how they are
217
+ spelled:
218
+
219
+ - `&` with dice on either side inside a payload that doubles on a crit: an attack string with no
220
+ crit clause (`(d20 + 5 AC 15) * (1d6 & 3)`, or a trailing `+ (1d4 & 2)`), `onHit("1d6 & 3")`, a
221
+ rider's auto-crit and `doubleDice()`. Doubling the dice also changes each side's share of the
222
+ mix (`1d6 & 3` → `2d6 & 3` moves the flat's share from 1/7 to 1/37), so this throws
223
+ `AmbiguousCritDoublingError`. Give the crit explicitly: `… * (1d6 & 3) crit (2d6 & 3)` parses.
224
+ - `&` with an attack already split into crit, miss or save outcomes (`((d20 AC 5) * (1d6)) & d4`,
225
+ `(d20 + 5 AC 15) * (1d6) & 3`), `&` of a saving throw with anything but another saving throw
226
+ (`d4 & (d20 DC 12)`), and a crit, save, pc or miss clause right after an `&` mix
227
+ (`(d20 + 5 AC 15) & (1d6) crit (2d6)`). Mix the checks before the payload instead:
228
+ `((d20 AC 10) & (d20 AC 15)) * (1d6)`.
229
+ - A reroll, repeat, keep or double advantage of a mix with a smaller die in it, when it would crit
230
+ (listed above). `(1(d20 & d4) AC 5) * (1d6) crit (2d6)` and `(2kh1(d20 & d4) AC 15) * (1d6) crit
231
+ (2d6)` were exact in 0.11.0 (119/48 and 2093/1152) and now throw. Spell the second as
232
+ `((d20 & d4)! AC 15)`, which gives the same exact value.
233
+
234
+ A parsed attack still differs from the builder in these cases:
235
+
236
+ - `parse()` has no natural-1 miss and no natural-20 hit: `(d20+30 AC 5) * (1d8)` means 4.725, the
237
+ builder 4.5.
238
+ - A payload the doubling rewrite cannot read (`d4d6`, a nested check) is added to the crit as-is;
239
+ the builder's `onHit` throws.
240
+ - A term joined by an op that also changes a miss (`~+`, `-`, `>`, `<`, `=`, `reroll`, `!`) loses
241
+ the attack's crit and miss labels, so riders never see its crit. So do a clause after a trailing
242
+ term (`… * (2d6) + 3 miss (1d6)`) and a repeat wrapper around an attack string
243
+ (`2((d20 + 5 AC 15) * (2d6))`). An `&` there throws (see above).
244
+
245
+ **Crits on keeps.** A keep-highest-of-1 payload, meaning "roll it N times, keep the best", doubles
246
+ its dice inside each trial on a crit. For example, `roll(2,d6).keepHighest(2,1).plus(3)` crits as
247
+ `2kh1(4d6) + 3` (18.9334, the same as the pooled `keepHighestAll(2,1)`), and the parsed
248
+ `2kh1(2d6)+3` crits as `2kh1(4d6)+3`. Other shapes have more than one reasonable doubled meaning,
249
+ so `doubleDice()`/`scaleDice()` throws an Error rather than guessing. The throw names the shape and
250
+ asks for an explicit crit. The shapes that throw are: a per-die keep with K >= 2
251
+ (`roll(4,d6).keepHighest(4,3)`, which used to crit at 88.98); any keepLowest (`3kl1`, `2kl1`); a
252
+ `bestOf()` that is a keep, or becomes one when doubled (`roll(4,d6).bestOf(3)`,
253
+ `roll(2,d6).bestOf(3)`); a non-d20 die rolled with advantage, disadvantage or elven accuracy
254
+ (`d6.withAdvantage()`, which used to crit at the hit's mean); a parsed `NkhK(...)` with K >= 2 or
255
+ any `NklK(...)`, including when nested (`4kh3d6`, `3kh2(2d6+1)`, `2kh1(4kh3(1d6))`); a parsed
256
+ min of two dice terms (`d6 < d6`); and a parsed `&` mix with dice on either side (`1d6 & 3`). This
257
+ covers an attack's auto-crit (`resolve()` and `toExpression()` throw), a rider's auto-crit, and an
258
+ attack string with no crit clause
259
+ (`(d20+5 AC 12) * (4kh3(1d6))` now fails to parse). You can fix any of these with `onCrit(...)`, a
260
+ rider's `critDamage`, a `crit (...)` clause, or `noCrit()`. The attack check's own d20 advantage is
261
+ never doubled and is unaffected. Three things still do not throw: `keepHighestAll`/`keepLowestAll`
262
+ pools, which double inside and then pool as before; a flat cap or floor like `2d6 < 9` or `3>d6`;
263
+ and `diceMatchInfo()`, whose crit descriptor is `null` for such an attack (its hit side is unchanged).
264
+
265
+ **Builder semantics.** `roll(N, X)` is N independent copies of X, so `roll(2, d6.keepHighest(2, 1))`
266
+ is two best-of-two d6 (161/18), and a group of zero dice rolls nothing (`roll(0, d6).plus(3)` is 3).
267
+ A roll type applies to each die of its group: `roll(2, d6).withAdvantage()` is two advantaged d6.
268
+ `explode(k)` needs a finite cap k, sides must be finite and not negative (`roll(2, 0)` is no die),
269
+ `scaleResult()` needs a finite numerator and a non-zero finite denominator, and `ac()`, `dc()`,
270
+ `critOn()`, `minimumDamageDie()`, `rerollDamage()` and a `withCheck()` result need finite numbers;
271
+ each throws, naming the argument. Per-die keeps have one reading each: `roll(1, d).keepHighest(T, K)`
272
+ keeps K of T dice, `roll(N, d).keepHighest(N, K)` keeps K of the N dice, and
273
+ `roll(N, d).keepHighest(T, 1)` is the best of T rolls of the whole group (`keepLowest(T, 1)` with
274
+ T ≠ N the worst); any other keep on several dice throws `AmbiguousKeepError`, so use
275
+ `keepHighestAll`/`keepLowestAll` or a keep on one die. `minus(X)` subtracts X's flat along with its
276
+ dice. `half()`, `scaleResult()` and `maxOf()` keep their transform under `plus()`/`minus()`.
277
+ A check's natural roll is its d20 wherever it sits in the sum (with no d20, its largest die);
278
+ `withAdvantage()`/`withDisadvantage()`/`withElvenAccuracy()` apply to that d20 whatever the call
279
+ order; a natural roll of more than one die (`roll(2, d20).ac(15)`) throws; a check with no die
280
+ (`flat(15).ac(12)`) compares its total with the target and never crits; a natural 20 always crits,
281
+ including under `alwaysHits()` and `alwaysCrits()`. A parsed string cannot be a check
282
+ (`d("d20+5").ac(15)` throws `ParsedCheckError`); spell it with the builder or as a full attack string.
283
+ `rerollDamage(k)` rerolls a face when its kept value, after any `minimumDamageDie` floor, is below
284
+ a fresh die's expected value, and never lowers a payload's own `reroll` or `minimum`.
285
+
286
+ **Strings from builders.** `toExpression()` prints a string that `parse()` reads back to the
287
+ builder's own distribution: a term after the first is parenthesised when it is an expression of its
288
+ own, `~+` joins a term to a running total that can be 0, and an attack always carries its crit clause
289
+ (`noCrit()` prints `xcrit0 (<hit payload>)`, which crits on no natural face). Explode, pool-wide explode, `scaleResult(…, "round")`,
290
+ fractional scale factors, `plusSeparateDamage()` and `halfOnMiss()` have no spelling and throw.
291
+ Strings have no natural-1 miss or natural-20 hit.
292
+
182
293
  ### Core Class Flow
183
294
 
184
295
  ```
@@ -317,6 +428,49 @@ const query = new DiceQuery(pmf);
317
428
  console.log("DPR:", query.mean());
318
429
  ```
319
430
 
431
+ #### Grammar
432
+
433
+ Spaces are ignored and letters may be any case. Every binary operator has the **same precedence and
434
+ associates left to right**: `1d6 + 2 * 3` is `(1d6 + 2) * 3`. Parenthesise to group.
435
+
436
+ | Syntax | Meaning |
437
+ | --- | --- |
438
+ | `7`, `d6`, `3d6`, `hd20` | A number, a die, a sum of dice, a die whose 1 is rerolled once (halfling luck, `d20 reroll 1`). |
439
+ | `N(X)`, `(X)d6` | N independent copies of X, summed. The count may be rolled: `(1d4)d6`. A count of 0 is 0 (`0d6`, `(1d4 - 1)d6` has P(0) = 1/4); a count that can be negative throws. |
440
+ | `NkhK(X)`, `NklK(X)` | The sum of the K highest (lowest) of N independent copies of X, exact for any X: `4kh3d6`, `2kl1(1d20)`, `4kh1(2d20)`. `2kl1(2d6)` keeps the lower of two 2d6 *sums*. |
441
+ | `X + Y` | Adds Y where the total so far is not 0, so a miss (0) stays 0: `(d20 + 5 AC 15) * (1d8) + 1d6`. Inside a check total (left of `AC`/`DC`) `+` always adds, so `d20 - 5 + 1d4 AC 1` adds the d4 on a natural 5 too. |
442
+ | `X ~+ Y` | Always adds. |
443
+ | `X - Y`, `-X` | Subtracts. A leading `-` negates the argument after it, repeat included: `-2d6` is `-(2d6)`, `1d6 + -3` is `1d6 - 3`, `-1d8 + 1d6` has mean -1. There is no unary `+`. |
444
+ | `X * Y` | Y where X is not 0, else 0: the hit gate of `(check) * (damage)`. |
445
+ | `X ** Y` | The product. |
446
+ | `X / Y`, `X // Y` | Division rounding up, rounding down (toward -∞). A divisor that can be 0 throws. |
447
+ | `X > Y`, `X < Y` | The max, the min: `d20 > d20` is advantage, `3>d6` a floor of 3. |
448
+ | `X!` | The max of two independent copies of X. |
449
+ | `X = Y` | 1 where X equals Y, else 0. |
450
+ | `X & Y` | A mix weighted by each side's count of outcomes (see the refused shapes above). |
451
+ | `X reroll R` | Rolls X, and on a result in the face set R rolls X again and keeps the second roll. |
452
+ | `X AC T`, `X DC T` | An attack check (X where X ≥ T, else 0) and a saving throw (0 on a save, 1 on a failure). |
453
+ | `… crit (Y)`, `… xcritN (Y)`, `… miss (Y)`, `… save half`, `… pc` | Outcome clauses after an attack's or a save's payload. `xcrit0 (Y)` never crits: every landing is a hit. |
454
+
455
+ **Rerolls.** `reroll N` rerolls the face N only; `reroll dN` rerolls every face from 1 to N; the
456
+ builder's `.reroll(N)` rerolls every face up to N, like `reroll dN`. On a d6 they differ from N = 2
457
+ on: `d6 reroll 2` is 15/4, `d6 reroll d2` and `d6.reroll(2)` are 25/6. `reroll d0` rerolls
458
+ nothing. A reroll applies to the whole value on its left, so `2d6 reroll 1` rerolls the *total*,
459
+ which is never 1 (mean 7); spell a per-die reroll as `2(d6 reroll 1)` (47/6), and several of them as
460
+ `1(d8 reroll 1) + 2(d6 reroll 1)` (613/48). Each result keeps its own probability, so a reroll of a
461
+ sum, a max or a floor is exact: `2d6 reroll 2` is 257/36, `(d20 > d20) reroll 1` is 221713/16000.
462
+ The reroll decides on the raw face and a minimum applies after: `3>(d6 reroll 1)` (what
463
+ `roll(1, d6).reroll(1).minimum(3)` prints) is 25/6.
464
+
465
+ **Labels.** An attack labels its outcomes `hit`, `crit`, `missNone` and `missDamage`. A save labels a
466
+ failed save `saveFail` (whatever the payload rolls, 0 included), a halved success `saveHalf`, and a
467
+ success with no `save half` `missNone`, like the builder's `onSaveFailure()`. A landed hit whose
468
+ payload deals 0 is still `hit` (a 0-damage crit is `crit`), like the builder; only a miss is
469
+ `missNone`.
470
+
471
+ A `d0` has no faces: it is only a face set (`reroll d0`); rolled on its own it throws, as does a
472
+ string the grammar cannot read, always as a `DiceParseError`.
473
+
320
474
  ### Error Handling
321
475
 
322
476
  `parse()` throws a `DiceParseError` (a subclass of `Error`) for invalid input.
@@ -335,14 +489,15 @@ try {
335
489
  ```
336
490
 
337
491
  For UI code that parses on every keystroke, `tryParse()` returns an empty PMF
338
- instead of throwing, and accepts a bare integer — which the grammar rejects, but
339
- a half-typed damage field is one for a keystroke or two:
492
+ instead of throwing, and also reads an integer with a leading `+`, which the
493
+ grammar rejects — a half-typed damage field is one for a keystroke or two:
340
494
 
341
495
  ```ts
342
496
  import { tryParse } from "@yipe/dice";
343
497
 
344
498
  tryParse("1d6 + 2").mean(); // 5.5
345
- tryParse("-3").mean(); // -3 — signed integers, which the grammar rejects
499
+ tryParse("+7").mean(); // 7 — a leading `+`, which the grammar rejects
500
+ tryParse("-3").mean(); // -3 — the same as parse("-3")
346
501
  tryParse("0x10").mass(); // 0 — decimal only
347
502
  tryParse("1d").mass(); // 0 — empty PMF
348
503
  ```
@@ -400,7 +555,8 @@ chance of 0.015 instead of 0.1225.
400
555
  |---|---|---|
401
556
  | `onFirstHit` | once, on the first attack that lands — doubled if it crit | Sneak Attack |
402
557
  | `onAnyCrit` | once, if any attack crit | Divine Smite |
403
- | `onAnyMiss` | once, if any attack missed | Unerring Accuracy, Lucky |
558
+ | `onAnyMiss` | once, if any attack missed; runs after every attack | a reroll no grant reaches |
559
+ | `onFirstMiss` | once, on the first attack that missed; runs right after it | Unerring Accuracy, Lucky |
404
560
  | `onEveryHit` | once per attack that lands | Hunter's Mark, Hex, Rage |
405
561
  | `otherwise` | when the rider before it did *not* | flurry of blows if you didn't smite |
406
562
 
@@ -434,6 +590,10 @@ turn([sword, sword]).onEveryHit(flat(2)); // Rage
434
590
  turn([dagger, dagger]).onAnyCrit(roll(4, d8)).otherwise([unarmed, unarmed]); // smite, or flurry
435
591
  ```
436
592
 
593
+ A list of attacks deals their exact summed damage, but it is one payload, not attacks that each
594
+ land: it is not a watchable attack. Naming it in `of` throws `not-an-attack`, and it never joins a
595
+ later rider's default `of`. To watch each strike, give each strike its own rider.
596
+
437
597
  #### The hard build
438
598
 
439
599
  A goliath rogue/monk/paladin, every trigger at once:
@@ -444,7 +604,7 @@ const goliath = turn([dagger, dagger])
444
604
  .onFirstHit(d10) // fire's burn
445
605
  .onAnyCrit(roll(2, d8)) // divine smite
446
606
  .otherwise([unarmed, unarmed]) // flurry of blows, if the smite didn't happen
447
- .onEveryHit(d6); // hunter's mark
607
+ .onEveryHit(d6); // hunter's mark, on the daggers
448
608
 
449
609
  goliath.mean(); // 39.5903
450
610
  goliath.toQuery().damageAttributionChartModel();
@@ -453,7 +613,8 @@ goliath.toQuery().damageAttributionChartModel();
453
613
  Two things that would be easy to get wrong are handled for you. Riders sharing a trigger resolve
454
614
  **jointly** — sneak attack and fire's burn fire together or not at all, which shows up in the spread
455
615
  even though it never moves the mean. And `otherwise()` binds to the rider immediately before it, so
456
- the smite and the flurry are two branches of one decision and can never both land.
616
+ the smite and the flurry are two branches of one decision and can never both land. The mark
617
+ watches the two daggers: the flurry is a list rider, which no rider watches.
457
618
 
458
619
  #### Asking questions
459
620
 
@@ -467,6 +628,104 @@ t.toQuery().probTotalAtLeast(20); // 0.5062 — P(20+ damage)
467
628
  t.toQuery().percentiles([0.25, 0.5, 0.75]); // [15, 20, 24]
468
629
  ```
469
630
 
631
+ #### Once per turn: keep the better damage roll
632
+
633
+ `onFirstHit` also takes a **transform** instead of damage. `keepBestDamage()` rolls the attack's own
634
+ base payload a second time and keeps the better total — once per turn, on the first attack that
635
+ lands. No dice are restated, and one call covers every attack in the turn:
636
+
637
+ ```ts
638
+ import { keepBestDamage } from "@yipe/dice/builder";
639
+
640
+ const attack = d20.plus(5).ac(12).onHit(roll(2, d6).plus(3));
641
+
642
+ turn([attack, attack]).mean(); // 14.7000
643
+ turn([attack, attack]).onFirstHit(keepBestDamage()).mean(); // 15.9849
644
+ ```
645
+
646
+ What it rerolls is the **base payload**: a crit transforms the doubled crit dice, while
647
+ `plusSeparateDamage` channels, `every-hit` riders and the miss branch are never rerolled. Spending
648
+ on the first landing is a policy, and not the best one — a player who sees a high roll holds the
649
+ reroll — so the number is a lower bound. `keepBestDamage().ifBelow({ hit, crit })` spends only when
650
+ the base payload total (dice plus the payload's own flat bonus, excluding separate-damage channels)
651
+ is below the threshold for that mode. When no later attack it watches can still land — the last
652
+ one, or an earlier one whose watched reroll can no longer fire — it spends on any landing.
653
+ `ifBelow({ hit: 10, crit: 18 })` reaches the optimum on the turn above, and since the threshold
654
+ reads the payload's own values it works on a parsed string or a bare `PMF` too.
655
+ On a tie the original roll is kept; the total is the same, so that only matters to a dice-match
656
+ trigger reading the same attack.
657
+ `fireProbability(id)` reports P(spent). A second transform over any of the same attacks throws
658
+ `duplicate-substitute`, and a transform passed to any other verb throws `unsupported-trigger`.
659
+
660
+ #### Conditions: advantage, disadvantage and crits granted by earlier attacks
661
+
662
+ The same verbs take a **grant** — a modifier with a lifetime — for the attack rolls after it:
663
+
664
+ ```ts
665
+ import { advantage, disadvantage, turn } from "@yipe/dice/builder";
666
+
667
+ const sword = d20.plus(5).ac(12).onHit(roll(1, d8).plus(3));
668
+
669
+ // A hit gives the next attack advantage. The next attack uses it up even on a miss, and passes it
670
+ // on if it lands.
671
+ turn([sword, sword]).onEveryHit(advantage().untilNextAttack()).mean(); // 12.202125
672
+ turn([sword, sword, sword]).onEveryHit(advantage().untilNextAttack()).mean(); // 19.192196
673
+
674
+ // A hit gives advantage for the rest of the turn if the target fails a save; each landing tries again.
675
+ turn([axe, axe]).onEveryHit(advantage().untilEndOfTurn(), { save: d20.plus(2).dc(15) });
676
+
677
+ // Once per turn, on the first hit, 40% of the time: advantage, and every later hit is a crit.
678
+ turn([fist, fist, fist]).onFirstHit(advantage().critOnHit().untilEndOfTurn(), { chance: 0.4 });
679
+
680
+ // One save, two grants: advantage for the melee attacks, disadvantage for the ranged ones.
681
+ turn().attack(axe, { tag: "melee" }).attack(bow, { tag: "ranged" })
682
+ .onEveryHit([advantage().untilEndOfTurn().to("melee"),
683
+ disadvantage().untilEndOfTurn().to("ranged")], { of: ["melee"], save: dc });
684
+ ```
685
+
686
+ `advantage()`, `disadvantage()` and `critOnHit()` are not grants until they get a lifetime, so
687
+ `onEveryHit(advantage())` does not compile. A grant combines with the reading attack's own roll
688
+ type by cancellation — advantage and disadvantage together roll flat — and a net advantage rolls
689
+ three dice for an attack built with `threeDiceAdvantage()`. `critOnHit` makes every landing a crit;
690
+ a natural 1 still misses. `.to(…)` takes ids or tags; left out, every later attack roll reads the
691
+ grant, rerolls and bonus attacks included. An `onAnyMiss` reroll resolves after every attack, so one
692
+ that would read a grant, or apply one, throws `unsupported-trigger`: use `onFirstMiss`, which
693
+ resolves right after the miss and reads the grants in force there.
694
+
695
+ `chance` or `save` gates the grants only: damage in the same call (`[d8, advantage()…]`) lands
696
+ whatever the save does. `onEveryHit` rolls the save again on each landing until it takes, and
697
+ `onFirstHit` rolls it once. An application whose grants are all `untilEndOfTurn` and all already
698
+ in force rolls no further save; one with any `untilNextAttack` grant always rolls.
699
+ `onSave` applies other grants on the success branch of the same roll:
700
+ `{ chance: 0.4, onSave: advantage().untilNextAttack() }`. `fireProbability(id)` reports P(the grants
701
+ were applied where a later attack reads them). In plain data it is `TurnSpec.conditions`, with the
702
+ `save` already turned into its `chance`.
703
+
704
+ #### Rerolls, sweeping AC, and naming attacks
705
+
706
+ A reroll is an attack-shaped rider. `onFirstMiss(attack)` resolves right after the attack that
707
+ missed, so anything that reads order — a `first-hit` rider's crit mode, a granted advantage — sees
708
+ it where it happened. A rider or transform added *after* an `onAnyMiss` / `onFirstMiss` reroll
709
+ watches it without being told:
710
+
711
+ ```ts
712
+ turn([sword, sword])
713
+ .onFirstMiss(sword) // reroll the first miss
714
+ .onFirstHit(roll(1, d10)) // watches both attacks and the reroll
715
+ ```
716
+
717
+ `vsAC(ac)` rebuilds every attack with an AC — including rerolls and bonus attacks carried by
718
+ riders — for a DPR-by-AC sweep, leaving saves untouched:
719
+
720
+ ```ts
721
+ const base = turn([sword, sword]).onFirstHit(roll(3, d6));
722
+ [12, 14, 16, 18].map((ac) => base.vsAC(ac).mean());
723
+ ```
724
+
725
+ `attack(source, { tag })` and `attacks(n, source, { tag })` name a group of attacks, and an `of`
726
+ entry that is not an id expands to every attack with that tag — so reordering a turn cannot
727
+ silently retarget a rider the way a positional `attack 2` can.
728
+
470
729
  #### Ids, errors, and plain data
471
730
 
472
731
  Nothing above needs an `id`: attacks and riders get `attack 1`, `rider 2`, … in declaration order,
@@ -482,12 +741,17 @@ paladin.riderIds; // ["smite"]
482
741
 
483
742
  Every construction path validates immediately and throws a `TurnSpecError` whose `code` —
484
743
  `unknown-id`, `cycle`, `not-an-attack`, `duplicate-id`, `self-reference`, `unused-crit-damage`,
485
- `too-many-groups` — maps
744
+ `too-many-groups`, `no-dice-descriptor`, `duplicate-substitute`, `attack-after-rider`,
745
+ `no-rebindable-source`, `unsupported-trigger`, `unsupported-policy`, `too-many-flags` — maps
486
746
  straight onto a UI field state. A bad `of` fails at the call that introduced it, not later at
487
747
  `.mean()`.
488
748
 
489
749
  Each `onX` method takes an optional `{ id, of, critDamage }`, where `of` picks which attacks the
490
- rider watches and defaults to all of them. All of them are sugar over `rider()`, which takes the
750
+ rider watches. Left out, it is filled in at that call: the attacks declared so far, plus any reroll
751
+ declared so far. So declare attacks first — `.attack()` after such a rider throws
752
+ `attack-after-rider` rather than silently leaving the new attack out. `Turn.from` fills an omitted
753
+ `of` the same way — every attack plus the rerolls, for a rider the rerolls listed before it — so
754
+ both spellings of a turn watch the same sources. All of them are sugar over `rider()`, which takes the
491
755
  trigger as plain data — and `Trigger` is JSON-safe, so a UI can persist one and hand it straight
492
756
  back:
493
757
 
@@ -529,6 +793,12 @@ console.table(query.toChartSeries());
529
793
  └─────────┴────┴──────────┘
530
794
  ```
531
795
 
796
+ Percentiles and `quantile()` land on the exact bin, so a d20's median is 10. `missChance()` is the
797
+ probability that at least one attack misses; for "every attack misses" use
798
+ `probExactlyK(["missNone", "missDamage"], n)`. `turn()`, `Turn.from()` and `resolve()` keep every
799
+ reachable damage value (their `eps` defaults to 0). `setCachingEnabled(false)` turns off and
800
+ empties every internal cache; PMFs returned from a cache have frozen bins.
801
+
532
802
  ## 🧪 Running Examples
533
803
 
534
804
  This repository includes example scripts:
@@ -1,10 +1,26 @@
1
1
  import { PMF } from "../pmf/pmf.js";
2
2
  import { AttackBuilder } from "./attack.js";
3
3
  import { AlwaysCritBuilder, RollBuilder } from "./roll.js";
4
+ import type { RollConfig, RollType } from "./types.js";
4
5
  export interface AttackConfig {
5
6
  ac: number;
6
7
  critThreshold: number;
8
+ advantageDice: 2 | 3;
7
9
  }
10
+ /**
11
+ * Combines a source's own roll type with granted advantage/disadvantage flags by cancellation —
12
+ * `{advantage, disadvantage}` from any combination of source and grants resolve to `advantage` /
13
+ * `disadvantage` / `flat`. `advantageDice` governs how many dice a NET advantage rolls, whether
14
+ * the advantage came from `rollType` itself or from `flags`; an own `elven accuracy` roll type
15
+ * always rolls three. Pure; independently testable against the full 4×2×4 table.
16
+ */
17
+ export declare function combine(rollType: RollType, advantageDice: 2 | 3, flags: {
18
+ advantage: boolean;
19
+ disadvantage: boolean;
20
+ }): {
21
+ rollType: RollType;
22
+ dice: number;
23
+ };
8
24
  export declare class ACBuilder extends RollBuilder {
9
25
  readonly attackConfig: AttackConfig;
10
26
  constructor(baseRoll: RollBuilder, ac: number, attackConfig?: AttackConfig);
@@ -16,11 +32,38 @@ export declare class ACBuilder extends RollBuilder {
16
32
  onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;
17
33
  onHit(count: number, sides: number, modifier: number): AttackBuilder;
18
34
  get critThreshold(): number;
35
+ /**
36
+ * Three-dice advantage is an attacker property, not a roll type. Setting it now applies
37
+ * whenever the NET result later resolves to advantage — whether this source already has
38
+ * advantage baked in, or advantage is granted afterward (via `withCheck`).
39
+ */
40
+ threeDiceAdvantage(): ACBuilder;
41
+ /**
42
+ * The dice configs with `advantageDice` mechanically folded in (`elven accuracy` in place of
43
+ * `advantage` when the net roll should use 3 dice). Shared by every consumer that needs the
44
+ * REAL resolvable dice rather than the raw stored config: {@link getRootDieConfig} (read by
45
+ * `rollType`/`resolveRootD20`) and {@link alwaysCrits} (whose target class has no override).
46
+ */
47
+ private resolvedConfigs;
48
+ getRootDieConfig(): RollConfig | undefined;
19
49
  cacheKey(): string | null;
50
+ /** Sets the crit threshold: a natural roll at or above it is a crit. */
20
51
  critOn(threshold: number): ACBuilder;
21
52
  alwaysCrits(): AlwaysCritBuilder;
53
+ /**
54
+ * `(<to-hit total> AC <ac>)`: the natural roll as {@link toPMF} reads it (one root die, rolled with
55
+ * the resolved roll type, so three-dice advantage prints `d20 > d20 > d20`), its flats and every
56
+ * bonus die.
57
+ */
22
58
  toExpression(): string;
59
+ /**
60
+ * The raw to-hit PMF: the check's total where it reaches the AC and 0 where it does not, with no
61
+ * natural-1 miss or natural-20 hit (`d20.plus(20).ac(15).toPMF()` never shows 0, although the
62
+ * attack misses on a natural 1). The attack outcome — natural rules, crits — comes from
63
+ * `onHit(...).resolve()`.
64
+ */
23
65
  toPMF(eps?: number): PMF;
24
- copy(): ACBuilder;
66
+ /** Accepts a replacement AC, so `vsAC` can rebind the check without rebuilding the roll. */
67
+ copy(ac?: number): ACBuilder;
25
68
  }
26
69
  //# sourceMappingURL=ac.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ac.d.ts","sourceRoot":"","sources":["../../src/builder/ac.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;CACvB;AACD,qBAAa,SAAU,SAAQ,WAAW;IACxC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAEpC,YAAY,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY,EAQzE;IAMD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC;IAClC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC;IAClC,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC;IACvC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC;IACtD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC;IACnD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC;IACxE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC;IAMrE,IAAI,aAAa,IAAI,MAAM,CAE1B;IAEQ,QAAQ,IAAI,MAAM,GAAG,IAAI,CAGjC;IAGD,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAMnC;IAED,WAAW,IAAI,iBAAiB,CAS/B;IAGQ,YAAY,IAAI,MAAM,CAM9B;IAEQ,KAAK,CAAC,GAAG,GAAE,MAAU,GAAG,GAAG,CAsBnC;IAEQ,IAAI,IAAI,SAAS,CAOzB;CACF"}
1
+ {"version":3,"file":"ac.d.ts","sourceRoot":"","sources":["../../src/builder/ac.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,OAAO,EAAE,iBAAiB,EAAoB,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IAItB,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CACrB,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,CAAC,GAAG,CAAC,EACpB,KAAK,EAAE;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,GACnD;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAUtC;AAED,qBAAa,SAAU,SAAQ,WAAW;IACxC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAEpC,YAAY,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY,EAQzE;IAED,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC;IAClC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAAC;IAClC,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC;IACvC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,aAAa,CAAC;IACtD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC;IACnD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC;IACxE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC;IAMrE,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;;;OAIG;IACH,kBAAkB,IAAI,SAAS,CAM9B;IAED;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAiBd,gBAAgB,IAAI,UAAU,GAAG,SAAS,CAIlD;IAEQ,QAAQ,IAAI,MAAM,GAAG,IAAI,CAKjC;IAED,wEAAwE;IACxE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAOnC;IAED,WAAW,IAAI,iBAAiB,CAU/B;IAED;;;;OAIG;IACM,YAAY,IAAI,MAAM,CAE9B;IAED;;;;;OAKG;IACM,KAAK,CAAC,GAAG,GAAE,MAAU,GAAG,GAAG,CAsBnC;IAED,4FAA4F;IACnF,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAQpC;CACF"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Throws for a non-finite builder argument, naming it. NaN reads "got NaN" unless the call site has
3
+ * its own, earlier NaN message. Internal: not re-exported from the package.
4
+ */
5
+ export declare function requireFinite(value: number, what: string): void;
6
+ //# sourceMappingURL=arguments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arguments.d.ts","sourceRoot":"","sources":["../../src/builder/arguments.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE/D"}
@@ -1,8 +1,32 @@
1
1
  import { PMF } from "../.js";
2
2
  import type { DieNode, ExpressionNode } from "./nodes.js";
3
- import type { RollBuilder } from "./roll.js";
3
+ import { type RollBuilder } from "./roll.js";
4
4
  import type { RollConfig, RollType } from "./types.js";
5
+ /** Clears the single-die and d20-lift PMF caches (reached through `clearRollCache`). */
6
+ export declare function clearDieCaches(): void;
5
7
  export declare function dieNodeFromConfig(cfg: RollConfig): DieNode;
8
+ /**
9
+ * Thrown for a per-die `keepHighest(T, K)`/`keepLowest(T, K)` on a group of N > 1 dice whose
10
+ * reading is not unique: "keep K of T rolls of the whole N-dice sum" and "keep K of T single dice"
11
+ * give different numbers, and nothing in the call says which is meant.
12
+ */
13
+ export declare class AmbiguousKeepError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ type KeepSpec = NonNullable<RollConfig["keep"]>;
17
+ /**
18
+ * How a per-die keep on a group of `count` d`sides` reads:
19
+ *
20
+ * - `"dice"`: keep K of T single dice. Used for one die (`d6.keepHighest(3, 2)`), for K >= 2 of
21
+ * the group's own N dice (`roll(4, d6).keepHighest(4, 3)`), and for `bestOf(k)`.
22
+ * - `"trials"`: roll the whole N-dice group T times and keep K of those sums. Used for K = 1:
23
+ * `roll(2, d6).keepHighest(2, 1)` is the better of two 2d6, and `keepLowest(T, 1)` with T != N
24
+ * is the worse of T rolls of the group.
25
+ *
26
+ * Every other shape on N > 1 dice (K >= 2 with T != N, and `keepLowest(N, 1)`, whose single-die
27
+ * and whole-group readings differ) throws an {@link AmbiguousKeepError} naming both spellings.
28
+ */
29
+ export declare function perDieKeepReading(count: number, sides: number, keep: KeepSpec, synthesizedBestOf?: boolean): "dice" | "trials";
6
30
  export declare function astFromRollConfigs(configs: readonly RollConfig[]): ExpressionNode | undefined;
7
31
  export declare function resolve(node: ExpressionNode, eps?: number): PMF;
8
32
  export declare function pmfFromRollBuilder(rb: RollBuilder, eps?: number): PMF;
@@ -26,14 +50,19 @@ export declare function pmfFromRollBuilder(rb: RollBuilder, eps?: number): PMF;
26
50
  */
27
51
  export declare function resolveD20Roll(die: DieNode, rollType: RollType | undefined): PMF;
28
52
  /**
29
- * Resolve a check builder's root die (the to-hit/save d20, or whatever die it wraps), honoring
30
- * that die's reroll/minimum/explode, then lift by its `rollType`. The single entry point every
31
- * attack/save check builder ({@link ACBuilder}, {@link AttackBuilder}, {@link AlwaysHitBuilder},
32
- * {@link AlwaysCritBuilder}, `DCBuilder`, save `resolveProbabilities`) should use instead of
33
- * reaching for `d20RollPMF(rollType, baseReroll > 0)` directly — that 2-argument summary silently
34
- * drops `minimum`/`explode` on the root config.
53
+ * Resolve a check builder's natural roll (its d20, or with no d20 its largest die: see
54
+ * {@link naturalRollIndex}), honoring that die's reroll/minimum/explode, then lift by its
55
+ * `rollType`. The single entry point every attack/save check builder ({@link ACBuilder},
56
+ * {@link AttackBuilder}, {@link AlwaysHitBuilder}, {@link AlwaysCritBuilder}, `DCBuilder`, save
57
+ * `resolveProbabilities`) uses for the natural roll; the other dice are bonus dice.
58
+ *
59
+ * A check with no die has no natural roll: this returns a certain 0, so its total is its flat
60
+ * modifier plus any bonus dice and no natural-1/natural-20 rule can apply. A natural roll of more
61
+ * than one die (`roll(2, d20)`, or two equal top dice like `d20.plus(d20)`) has no single natural
62
+ * 1 or 20 and throws.
35
63
  */
36
64
  export declare function resolveRootD20(check: RollBuilder): PMF;
37
65
  export declare function resolveSingleDie(die: DieNode, eps?: number): PMF;
38
66
  export declare function getASTSignature(node: ExpressionNode): string;
67
+ export {};
39
68
  //# sourceMappingURL=ast.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/builder/ast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,GAAG,EAAE,MAAM,KAAK,CAAC;AAGpC,OAAO,KAAK,EAIV,OAAO,EACP,cAAc,EAIf,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAOpD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAW1D;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,cAAc,GAAG,SAAS,CAwI5B;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,GAAE,MAAmB,GAAG,GAAG,CAgG3E;AAED,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,WAAW,EACf,GAAG,GAAE,MAAmB,GACvB,GAAG,CAGL;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,GAAG,CA8BhF;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,CAOtD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,GAAE,MAAmB,GAAG,GAAG,CAsE5E;AAqTD,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAsD5D"}
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/builder/ast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG1B,OAAO,KAAK,EAIV,OAAO,EACP,cAAc,EAIf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAOpD,wFAAwF;AACxF,wBAAgB,cAAc,IAAI,IAAI,CAGrC;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAa1D;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAO,EAAE,MAAM,EAI1B;CACF;AAED,KAAK,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,QAAQ,EACd,iBAAiB,UAAQ,GACxB,MAAM,GAAG,QAAQ,CAgBnB;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,cAAc,GAAG,SAAS,CAsF5B;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,GAAE,MAAmB,GAAG,GAAG,CAmG3E;AAED,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,WAAW,EACf,GAAG,GAAE,MAAmB,GACvB,GAAG,CAGL;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,GAAG,CA8BhF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,CAgBtD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,GAAE,MAAmB,GAAG,GAAG,CA2E5E;AA6UD,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAwD5D"}