roll-parser 3.0.0 → 3.1.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 (74) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/MIGRATION.md +73 -1
  3. package/README.md +85 -19
  4. package/dist/cli/format.d.ts.map +1 -1
  5. package/dist/cli/format.js +8 -7
  6. package/dist/cli/format.js.map +1 -1
  7. package/dist/evaluator/die.d.ts +2 -1
  8. package/dist/evaluator/die.d.ts.map +1 -1
  9. package/dist/evaluator/die.js.map +1 -1
  10. package/dist/evaluator/env.d.ts +31 -0
  11. package/dist/evaluator/env.d.ts.map +1 -1
  12. package/dist/evaluator/env.js.map +1 -1
  13. package/dist/evaluator/evaluator.d.ts.map +1 -1
  14. package/dist/evaluator/evaluator.js +29 -21
  15. package/dist/evaluator/evaluator.js.map +1 -1
  16. package/dist/evaluator/modifiers/crit-threshold.d.ts +57 -4
  17. package/dist/evaluator/modifiers/crit-threshold.d.ts.map +1 -1
  18. package/dist/evaluator/modifiers/crit-threshold.js +27 -8
  19. package/dist/evaluator/modifiers/crit-threshold.js.map +1 -1
  20. package/dist/evaluator/modifiers/die-bound.d.ts +5 -2
  21. package/dist/evaluator/modifiers/die-bound.d.ts.map +1 -1
  22. package/dist/evaluator/modifiers/die-bound.js +6 -3
  23. package/dist/evaluator/modifiers/die-bound.js.map +1 -1
  24. package/dist/evaluator/modifiers/explode.d.ts.map +1 -1
  25. package/dist/evaluator/modifiers/explode.js +7 -5
  26. package/dist/evaluator/modifiers/explode.js.map +1 -1
  27. package/dist/evaluator/modifiers/flags.d.ts +10 -0
  28. package/dist/evaluator/modifiers/flags.d.ts.map +1 -1
  29. package/dist/evaluator/modifiers/flags.js +7 -0
  30. package/dist/evaluator/modifiers/flags.js.map +1 -1
  31. package/dist/evaluator/modifiers/keep-drop.d.ts +4 -2
  32. package/dist/evaluator/modifiers/keep-drop.d.ts.map +1 -1
  33. package/dist/evaluator/modifiers/keep-drop.js +7 -7
  34. package/dist/evaluator/modifiers/keep-drop.js.map +1 -1
  35. package/dist/evaluator/modifiers/reroll.d.ts.map +1 -1
  36. package/dist/evaluator/modifiers/reroll.js +16 -10
  37. package/dist/evaluator/modifiers/reroll.js.map +1 -1
  38. package/dist/evaluator/modifiers/sort.d.ts +1 -1
  39. package/dist/evaluator/modifiers/sort.d.ts.map +1 -1
  40. package/dist/evaluator/modifiers/sort.js +3 -3
  41. package/dist/evaluator/modifiers/sort.js.map +1 -1
  42. package/dist/evaluator/modifiers/success-count.d.ts +5 -2
  43. package/dist/evaluator/modifiers/success-count.d.ts.map +1 -1
  44. package/dist/evaluator/modifiers/success-count.js +7 -6
  45. package/dist/evaluator/modifiers/success-count.js.map +1 -1
  46. package/dist/parser/ast.d.ts +12 -7
  47. package/dist/parser/ast.d.ts.map +1 -1
  48. package/dist/parser/ast.js.map +1 -1
  49. package/dist/render.d.ts +95 -0
  50. package/dist/render.d.ts.map +1 -0
  51. package/dist/render.js +227 -0
  52. package/dist/render.js.map +1 -0
  53. package/dist/types.d.ts +49 -9
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js.map +1 -1
  56. package/dist/version.d.ts +1 -1
  57. package/dist/version.js +1 -1
  58. package/package.json +11 -1
  59. package/src/cli/format.ts +15 -23
  60. package/src/evaluator/die.ts +2 -1
  61. package/src/evaluator/env.ts +32 -0
  62. package/src/evaluator/evaluator.ts +48 -21
  63. package/src/evaluator/modifiers/crit-threshold.ts +95 -10
  64. package/src/evaluator/modifiers/die-bound.ts +13 -4
  65. package/src/evaluator/modifiers/explode.ts +12 -6
  66. package/src/evaluator/modifiers/flags.ts +17 -0
  67. package/src/evaluator/modifiers/keep-drop.ts +9 -5
  68. package/src/evaluator/modifiers/reroll.ts +20 -11
  69. package/src/evaluator/modifiers/sort.ts +9 -3
  70. package/src/evaluator/modifiers/success-count.ts +21 -6
  71. package/src/parser/ast.ts +12 -7
  72. package/src/render.ts +392 -0
  73. package/src/types.ts +49 -9
  74. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.1.0] - 2026-08-08
11
+
12
+ ### Added
13
+
14
+ - `roll-parser/render`, a subpath export holding `renderBreakdown(result, marks?)`, `DieMarks`, and `MARKDOWN_MARKS`. It rebuilds the `RollResult.rendered` breakdown from `RollResult.parts` with markers the caller chooses, so consumers targeting anything other than Discord markdown no longer regex-parse the baked string. Six slots — `dropped`, `success`, `failure`, `critical`, `fumble`, and `droppedGroup` — compose in a fixed order; `critical`/`fumble` read the `DieResult` booleans, which `rendered` has no marker for. With no `marks` the output is byte-identical to `rendered`, pinned by a property test. Budgeted separately at 2 kB, leaving the `index.js` and `{ roll }` budgets to the library ([#292](https://github.com/edloidas/roll-parser/issues/292))
15
+
16
+ ### Changed
17
+
18
+ - The `explode`, `reroll`, and `successCount` members of `RollPart` now carry `rolls: DieResult[]`, the pool the modifier produced — mirroring what `sort` already did. Standard and penetrating explosions and both reroll forms append dice that appear nowhere under `target`, so the part tree could not previously describe its own output ([#292](https://github.com/edloidas/roll-parser/issues/292))
19
+
20
+ Reading `parts` is unaffected. Three things do change:
21
+
22
+ - Code that **constructs** one of those three parts stops compiling — `TS2322: Property 'rolls' is missing in type … but required`. Add the pool, or widen the annotation.
23
+ - Deep-equality assertions against those parts need the new field.
24
+ - `--json` and `JSON.stringify(result)` payloads grow for expressions using explode, reroll, or success counting, since the pool is serialized on the modifier as well as its target — roughly +40% to +96% depending on pool size. Expressions without those modifiers are unchanged.
25
+
26
+ - performance: the per-die `isVersusDc` check is now skipped entirely for notation that cannot carry a `'dc'` tag, `sortDice` scans before allocating instead of running an unconditional `filter`, and `countSuccesses` filters DC dice up front rather than guarding inside the loop. Sort notation had no benchmark coverage, which is why the regression shipped unseen; `4d6sd`, `10d10sd`, `100d6sa`, and `10d10sd>=6f1` now have bench cases ([#281](https://github.com/edloidas/roll-parser/issues/281))
27
+
28
+ ### Fixed
29
+
30
+ - Bare `cs`/`cf` now judge the natural face (`initialResult ?? result`) rather than a value a preceding modifier rewrote, matching what the documentation always claimed. Compound explode also stopped clobbering the raw face recorded by an earlier `minN`/`maxN` clamp, which made `1d6min6!!cs` report a false critical. Explicit thresholds deliberately keep reading `result`, so the two kinds can disagree on one die ([#288](https://github.com/edloidas/roll-parser/issues/288))
31
+ - `cs`/`cf` thresholds now apply to dice minted by explode and reroll, which previously fell back to the built-in default rule. Each resolved rule is recorded per die and inherited by appended and replacement dice, so a further explode or reroll inherits it in turn. Overriding one side no longer moves the other: `1d6cf>5!p` keeps the `critical` that plain `1d6!p` gives the appended die ([#289](https://github.com/edloidas/roll-parser/issues/289))
32
+ - Chained same-kind bounds (`1d6min2min3`) no longer push duplicate `'min'`/`'max'` modifiers onto a die, and `initialResult` still comes from the first bound that moved it. The same duplication is fixed for `'success'`/`'failure'` on a group counted after its members (`{4d6>=5}>=1`) and for `'meta'` on nested meta operands (`((1d2)d4)d6`) ([#290](https://github.com/edloidas/roll-parser/issues/290))
33
+ - CLI `--verbose` no longer mangles a dropped group sub-roll nested inside another dropped group sub-roll. `{{1d6, 1d8}kh1, {1d10, 1d12}kh1}kh1` rendered as `({)1d10[1](, 1d12[3]})`; the marker rewrite was a regex over the flat string and stopped at the first inner `~~`. It now reads the structure and emits `({(1d10[1]), 1d12[3]})` ([#292](https://github.com/edloidas/roll-parser/issues/292))
34
+ - Demo site: fractional totals are rounded to four decimals — falling back to exponential where rounding would show a non-zero total as `0` — with the exact value on a `title` attribute, instead of overflowing the result card ([#285](https://github.com/edloidas/roll-parser/issues/285))
35
+ - Demo site: the total count-up is gated on `Number.isSafeInteger`, so an astronomical total lands without animating rather than interpolating through to `0` ([#286](https://github.com/edloidas/roll-parser/issues/286))
36
+
37
+ ### Documentation
38
+
39
+ - `MIGRATION.md` gains a 3.0.0 → 3.1.0 section covering the `RollPart` field, the three ways it can surface, and the new render subpath. The file is now ordered newest-first with an index, and `README.md` points at it for every upgrade path rather than only for 2.x ([#292](https://github.com/edloidas/roll-parser/issues/292))
40
+ - `DieResult.critical` and `DieResult.fumble` now scope the max-face/1 default to `sides > 1`, so neither claims to fire on `d1` or Fate dice, and the `FateDiceNode` / `createFateDieResult` TSDoc no longer states the flags are never set on Fate dice — an explicit `4dFcs>0` or `4dFcf=-1` does set them ([#291](https://github.com/edloidas/roll-parser/issues/291))
41
+ - The README no longer implies success counting rejects every enclosing expression — it is terminal only against direct wrapping
42
+ - The README performance table gained a `4d6sd` row and links the published benchmark trend, and the Node badge reads from the published `engines` range
43
+
10
44
  ## [3.0.0] - 2026-08-06
11
45
 
12
46
  First stable release of the v3 rewrite. This section covers what changed since
@@ -183,7 +217,8 @@ Dice mechanics (Stage 2):
183
217
  - Dice count safety limit via `maxDice` option (default 10,000), enforced across the whole expression to prevent DoS via additive groups like `5000d6+5000d6` ([#19](https://github.com/edloidas/roll-parser/issues/19))
184
218
  - Parser and evaluator correctness: duplicate `kept` modifier entries, implicit modifier count defaulting to 1 (`4d6kh` → `4d6kh1`), `critical` flag suppression when `sides === 1`, negative `--seed` CLI values ([#21](https://github.com/edloidas/roll-parser/issues/21))
185
219
 
186
- [Unreleased]: https://github.com/edloidas/roll-parser/compare/v3.0.0...HEAD
220
+ [Unreleased]: https://github.com/edloidas/roll-parser/compare/v3.1.0...HEAD
221
+ [3.1.0]: https://github.com/edloidas/roll-parser/releases/tag/v3.1.0
187
222
  [3.0.0]: https://github.com/edloidas/roll-parser/releases/tag/v3.0.0
188
223
  [3.0.0-beta.0]: https://github.com/edloidas/roll-parser/releases/tag/v3.0.0-beta.0
189
224
  [3.0.0-alpha.0]: https://github.com/edloidas/roll-parser/releases/tag/v3.0.0-alpha.0
package/MIGRATION.md CHANGED
@@ -1,4 +1,76 @@
1
- # Migrating from v2 to v3
1
+ # Migration notes
2
+
3
+ Newest first. [Upgrading from 3.0.0 to 3.1.0](#upgrading-from-300-to-310) ·
4
+ [Migrating from v2 to v3](#migrating-from-v2-to-v3) ·
5
+ [Upgrading from a v3 pre-release](#upgrading-from-a-v3-pre-release)
6
+
7
+ ## Upgrading from 3.0.0 to 3.1.0
8
+
9
+ Nothing was removed and no behaviour changed: `total`, `expression`, `rendered`,
10
+ `rolls`, and `degree` are byte-identical to 3.0.0 for every expression. The
11
+ whole of this section is about one added field.
12
+
13
+ **`RollPart` gained `rolls` on three variants.** The `explode`, `reroll`, and
14
+ `successCount` members now carry `rolls: DieResult[]` — the pool the modifier
15
+ produced — joining `sort`, which always had it. They needed it: standard and
16
+ penetrating explosions and both reroll forms *append* dice, and those dice
17
+ appeared nowhere in the part tree, so a part could not describe its own output.
18
+
19
+ <!-- readme-test: skip -->
20
+ ```typescript
21
+ // 3.0.0 — the explosion die is missing from the tree
22
+ roll('2d6!').parts.target.rolls.length; // 2, but `rendered` shows three dice
23
+
24
+ // 3.1.0 — the modifier carries the pool it actually rendered
25
+ roll('2d6!').parts.rolls.length; // 3
26
+ ```
27
+
28
+ Per the [versioning policy](README.md#versioning), adding a field to a returned
29
+ object is a minor. Reading `parts` is unaffected and needs no change. Three
30
+ things can still bite:
31
+
32
+ - **Constructing one of those parts stops compiling** — `TS2322: Property
33
+ 'rolls' is missing in type … but required`. This is only reachable in test
34
+ fixtures and mocks, since no library function accepts a `RollPart`. Add the
35
+ pool, or widen the annotation.
36
+ - **Deep-equality assertions against those parts fail.** Add `rolls` to the
37
+ expected object, or assert on the fields you care about instead of the whole
38
+ part.
39
+ - **`--json` and `JSON.stringify(result)` payloads grow** for expressions using
40
+ explode, reroll, or success counting, because the pool is serialized on the
41
+ modifier as well as on its target — roughly +40% to +96% depending on pool
42
+ size. Expressions without those modifiers are unchanged. Budget for it if you
43
+ log, cache, or ship results over a wire.
44
+
45
+ **The CLI renders nested dropped sub-rolls correctly.** `--verbose` on a dropped
46
+ group sub-roll nested inside another one used to emit mismatched delimiters —
47
+ `{{(1d6[1]), 1d8[4]}, ({)1d10[1](, 1d12[3]})}`. It now emits
48
+ `{{(1d6[1]), 1d8[4]}, ({(1d10[1]), 1d12[3]})}`. Anything parsing that output
49
+ should be reading `--json` instead.
50
+
51
+ **New, optional: `roll-parser/render`.** A subpath export whose
52
+ `renderBreakdown(result, marks?)` rebuilds the breakdown from `result.parts`
53
+ with markers you choose, so you no longer have to regex the markdown out of
54
+ `rendered`. With no marks its output is byte-identical to `rendered`, which
55
+ makes it a drop-in starting point:
56
+
57
+ ```typescript
58
+ import { roll } from 'roll-parser';
59
+ import { renderBreakdown } from 'roll-parser/render';
60
+ import { createMockRng } from 'roll-parser/testing';
61
+
62
+ const result = roll('4d6kh3', { rng: createMockRng([3, 6, 2, 5]) });
63
+
64
+ renderBreakdown(result) === result.rendered; // true
65
+ renderBreakdown(result, { dropped: (_die, text) => `(${text})` });
66
+ // '4d6[3, 6, (2), 5] = 14'
67
+ ```
68
+
69
+ See [Custom markers](README.md#custom-markers) for the six slots and the order
70
+ they compose in. Nothing forces you to adopt it — `rendered` is unchanged and
71
+ is not deprecated.
72
+
73
+ ## Migrating from v2 to v3
2
74
 
3
75
  v3 is a full rewrite. Nothing in the 2.x API carries over — the entry points,
4
76
  the result shape, the error behaviour, and parts of the notation all changed. To
package/README.md CHANGED
@@ -12,8 +12,7 @@ Dice notation for tabletop RPGs, rolled into structured results.
12
12
  <a href="https://www.npmjs.com/package/roll-parser"><img src="https://img.shields.io/npm/v/roll-parser?color=cb3837" alt="npm version"></a>
13
13
  <a href="https://github.com/edloidas/roll-parser/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/edloidas/roll-parser/ci.yml?branch=master&label=CI" alt="CI status"></a>
14
14
  <a href="https://github.com/edloidas/roll-parser/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/roll-parser?color=blue" alt="MIT license"></a>
15
- <!-- TODO: switch to img.shields.io/node/v/roll-parser once 3.0.0 is npm `latest` — until then it reads 2.x engines -->
16
- <a href="https://nodejs.org/"><img src="https://img.shields.io/badge/node-%3E%3D22.12-5fa04e" alt="Node.js >= 22.12"></a>
15
+ <a href="https://nodejs.org/"><img src="https://img.shields.io/node/v/roll-parser?color=5fa04e" alt="Node.js >= 22.12"></a>
17
16
  </p>
18
17
 
19
18
  <p align="center">
@@ -98,7 +97,7 @@ roll('4d6kh3', { rng: createMockRng([3, 6, 2, 5]) }).total; // 14, every run
98
97
  - [Performance](#performance)
99
98
  - [Known limitations](#known-limitations)
100
99
  - [Versioning](#versioning)
101
- - [Upgrading from 2.x](#upgrading-from-2x)
100
+ - [Upgrading](#upgrading)
102
101
  - [Contributing](#contributing)
103
102
  - [License](#license)
104
103
 
@@ -144,10 +143,11 @@ as one request:
144
143
  `https://esm.sh/roll-parser` works the same way. Raw file URLs
145
144
  (`unpkg.com/roll-parser`) also work but fetch each module separately.
146
145
 
147
- ### Upgrading from 2.x
146
+ ### Upgrading
148
147
 
149
- v3 is a complete rewrite and the API is not compatible — see
150
- [MIGRATION.md](MIGRATION.md). To stay on the old line, pin `roll-parser@2.3.2`.
148
+ [MIGRATION.md](MIGRATION.md) covers every upgrade path, newest first 3.0.0 to
149
+ 3.1.0, v2 to v3, and the v3 pre-releases. v3 is a complete rewrite and the 2.x
150
+ API is not compatible; to stay on the old line, pin `roll-parser@2.3.2`.
151
151
 
152
152
  ## Notation reference
153
153
 
@@ -220,6 +220,16 @@ Chained keep/drop modifiers do **not** nest. `4d6kh3dl1` flattens into two
220
220
  specs applied independently to the same pool, with the dropped sets unioned —
221
221
  the Roll20 rule.
222
222
 
223
+ `cs` / `cf` covers the whole pool, including dice that `!`, `!p`, `r`, and `ro`
224
+ add after it — `1d6cs<2!` and `1d6!cs<2` flag the same dice. Two cases stay
225
+ order-sensitive. Modifiers that rewrite a die's value rather than add one (`!!`,
226
+ `minN`, `maxN`) are judged on whatever that value is when the threshold runs:
227
+ with `[6, 6, 3]`, `1d6cs>10!!` reports no critical while `1d6!!cs>10` reports
228
+ one, judging the compounded 15. And because `!p`'s added dice store one less
229
+ than they rolled, the side you *don't* override moves with position —
230
+ `1d6cf>5!p` keeps the critical that plain `1d6!p` gives the added 6, where
231
+ `1d6!pcf>5` does not.
232
+
223
233
  ### Pools and checks
224
234
 
225
235
  | Notation | Meaning |
@@ -231,8 +241,10 @@ the Roll20 rule.
231
241
  | `{a+b}khN` | Single-sub-roll group: keep/drop selects across the flattened pool |
232
242
 
233
243
  > [!IMPORTANT]
234
- > Success counting is **terminal** — nothing may wrap it, so `10d10>=6 + 2` is
235
- > a parse error. Put the arithmetic inside the threshold: `10d10>=(4+2)`.
244
+ > Success counting is **terminal** — no modifier or arithmetic applies to it
245
+ > directly, so `10d10>=6 + 2` is a parse error. Put the arithmetic inside the
246
+ > threshold, `10d10>=(4+2)`, or wrap the count in group braces to operate on its
247
+ > result: `{10d10>=6}+2`.
236
248
 
237
249
  > [!WARNING]
238
250
  > A bare `d` after a dice expression is rejected: `4d6d1` throws
@@ -342,9 +354,22 @@ describe(roll('4d6kh3 + 2', { rng: createMockRng([3, 6, 2, 5]) }).parts);
342
354
 
343
355
  Invariants worth relying on: `result.parts.total === result.total`,
344
356
  `successCount.total === successes - failures`, and every part's `rolls[]`
345
- sharing references with `result.rolls`. A `sort` part's `rolls[]` is its
346
- target's pool in sorted order — chained sorts (`4d6s sd`) nest, and the
347
- outermost one holds the order `rendered` shows.
357
+ sharing references with `result.rolls`.
358
+
359
+ Four variants carry a `rolls[]` of their own, holding the pool the modifier
360
+ produced rather than the one its target rolled — read those instead of
361
+ descending into `target` when you want what was rendered:
362
+
363
+ - `sort` — the target's pool in sorted order. Chained sorts (`4d6s sd`) nest,
364
+ and the outermost one holds the order `rendered` shows.
365
+ - `explode` — the expanded pool. Standard and penetrating explosions append
366
+ dice that exist nowhere under `target`.
367
+ - `reroll` — discarded intermediates and their replacements, both appended
368
+ rather than substituted.
369
+ - `successCount` — the tallied pool.
370
+
371
+ Unlike the pool on a `dice` part, those four keep `'meta'` dice, so filter them
372
+ before counting or displaying.
348
373
 
349
374
  Meta-expressions do **not** appear as nested parts. `(1d4)d6`, `4d6kh(1d2)`, and
350
375
  `1d6!>(1d2+3)` surface only their resolved numbers in the owning part; their
@@ -369,6 +394,36 @@ success-count modifiers, but **not** keep/drop — `4d6kh3` renders as `4d6[…]
369
394
  while `8d6!` renders as `8d6![…]`. Read `result.expression` when you need the
370
395
  modifier back.
371
396
 
397
+ #### Custom markers
398
+
399
+ Markdown is one dialect, and `rendered` bakes it in. `roll-parser/render` is a
400
+ ≈1.4 kB entry point that rebuilds the same breakdown from `result.parts` with
401
+ markers you choose — no regex round-trip through the baked string.
402
+
403
+ ```typescript
404
+ import { renderBreakdown } from 'roll-parser/render';
405
+
406
+ const result = roll('4d6kh3', { seed: 'demo' });
407
+
408
+ renderBreakdown(result); // '4d6[1, 6, ~~1~~, 3] = 10'
409
+ renderBreakdown(result, {}); // '4d6[1, 6, 1, 3] = 10'
410
+ renderBreakdown(result, {
411
+ dropped: (_die, text) => `<s>${text}</s>`,
412
+ critical: (_die, text) => `<b>${text}</b>`,
413
+ }); // '4d6[1, <b>6</b>, <s>1</s>, 3] = 10'
414
+ ```
415
+
416
+ Six slots are available — `dropped`, `success`, `failure`, `critical`,
417
+ `fumble`, and `droppedGroup` for a whole sub-roll struck by `{…}kh1`. An
418
+ omitted slot renders plain, so pass `{}` to strip markup entirely or spread
419
+ `MARKDOWN_MARKS` to keep the rest. `critical` and `fumble` read the
420
+ `DieResult` booleans, which `rendered` has no marker for at all.
421
+
422
+ Marks compose in a fixed order: `critical` then `fumble` innermost, then
423
+ exactly one of `dropped`, `success`, or `failure` — a dropped die is never
424
+ also shown as a success. With no marks the output is byte-identical to
425
+ `result.rendered`; a property test pins that.
426
+
372
427
  ## Randomness
373
428
 
374
429
  Every die is drawn through the `RNG` interface — no roll path bypasses the RNG
@@ -891,7 +946,8 @@ precedes them. Errors go to stderr; only the result goes to stdout.
891
946
  | `1d20` | 85 ns | 164 ns | **0.49 µs** (~2.0M rolls/s) |
892
947
  | `2d6+3` | 98 ns | 215 ns | **0.77 µs** |
893
948
  | `4d6kh3` | 122 ns | 245 ns | **1.2 µs** |
894
- | `10d10>=6f1` | 161 ns | 336 ns | **2.0 µs** |
949
+ | `4d6sd` | 101 ns | 211 ns | **0.84 µs** |
950
+ | `10d10>=6f1` | 161 ns | 336 ns | **1.6 µs** |
895
951
  | `100d6` | 82 ns | 168 ns | **2.6 µs** |
896
952
 
897
953
  The `roll` column pays for a fresh `SeededRNG` per call, which an injected RNG
@@ -907,13 +963,21 @@ Values are **p50**, from
907
963
  (`.gc('inner')`), taken as the per-record median of four full
908
964
  `bun run bench:json` passes, every row agreeing within 5% except
909
965
  `lex / 4d6kh3` at 7%. Measured 2026-08-05 on Bun
910
- 1.3.14, Apple M2 Pro, macOS, idle and on AC power. The `4d6kh3` and
911
- `10d10>=6f1` `roll` figures were re-measured 2026-08-06 over three passes
912
- agreeing within 4%, after per-die `'dc'` tag checks entered the keep/drop and
913
- success-count paths; the remaining rows re-measured within noise. Read them as
966
+ 1.3.14, Apple M2 Pro, macOS, idle and on AC power. Read them as
914
967
  two significant digits: another machine shifts every row, and a busy one
915
968
  inflates the heavy cases most.
916
969
 
970
+ The `10d10>=6f1` `roll` figure and the `4d6sd` row come from a later
971
+ five-pass measurement on the same machine, after the per-die `'dc'` tag checks
972
+ that had entered the keep/drop, success-count, and sort paths were hoisted out
973
+ of those loops. That session was not idle: `evaluate / 10d10>=6f1` and
974
+ `evaluate / 10d10sd` each read bimodally across passes with roughly 10% between
975
+ the two modes, so those two are quoted as "recovered to the pre-exclusion
976
+ range" rather than to a point value. The rows this hoist does not touch
977
+ (`1d20`, `2d6+3`, `4d6kh3`, `100d6`) re-measured within noise of the figures
978
+ above and are left as first measured, since the earlier idle session is the
979
+ better data for them.
980
+
917
981
  p50 rather than mean, because the mean here is effectively a GC-pause
918
982
  histogram and swings ±40% between processes. Every bench body is JIT-primed
919
983
  before measurement and pinned to mitata's batched sampling mode, so all cases
@@ -925,8 +989,10 @@ mis-times mid-weight cases by 10-30x.
925
989
  Run `bun run bench` for the full suite, or `bench:lex` / `bench:parse` /
926
990
  `bench:evaluate` / `bench:roll` for one stage. Cross-library numbers live in
927
991
  the [competitor suite](https://github.com/edloidas/roll-parser/tree/master/bench/competitors)
928
- (`bun run bench:competitors`). Bundle size is gated in CI by `size-limit`; the
929
- budgets live in `package.json`.
992
+ (`bun run bench:competitors`). Every push to `master` publishes its p50s to a
993
+ [trend chart](https://edloidas.io/roll-parser/dev/bench/), and CI comments on
994
+ any commit that regresses a case past 1.75x. Bundle size is gated in CI by
995
+ `size-limit`; the budgets live in `package.json`.
930
996
 
931
997
  ## Known limitations
932
998
 
@@ -940,7 +1006,7 @@ budgets live in `package.json`.
940
1006
  - **Threshold comparisons bind tight.** `1d6!>=5+2` parses as `(1d6!>=5)+2`;
941
1007
  parenthesize for a computed threshold, `1d6!>=(5+2)`. Success counts bind the
942
1008
  same way but are terminal, so `1d6>=5+2` errors outright — write
943
- `1d6>=(5+2)`.
1009
+ `1d6>=(5+2)`, or `{1d6>=5}+2` to add to the count itself.
944
1010
  - **`result.expression` substitutes meta-expressions with their resolved
945
1011
  values,** so it does not round-trip through `parse` when they are present:
946
1012
  `roll('(1d4)d6').expression` is `'4d6'` when the `1d4` rolled 4, and
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/cli/format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,8EAA8E;IAC9E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAYpF"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/cli/format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAe9C;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,8EAA8E;IAC9E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAYpF"}
@@ -1,3 +1,10 @@
1
+ import { renderBreakdown } from '../render.js';
2
+ const TERMINAL_MARKS = {
3
+ dropped: (_die, text) => `(${text})`,
4
+ success: (_die, text) => `[${text}]`,
5
+ failure: (_die, text) => `{${text}}`,
6
+ droppedGroup: (inner) => `(${inner})`,
7
+ };
1
8
  export function formatResult(result, options = {}) {
2
9
  const { json = false, verbose = false } = options;
3
10
  if (json) {
@@ -6,12 +13,6 @@ export function formatResult(result, options = {}) {
6
13
  if (!verbose) {
7
14
  return String(result.total);
8
15
  }
9
- return formatRendered(result.rendered);
10
- }
11
- function formatRendered(rendered) {
12
- return rendered
13
- .replace(/\*\*(-?\d+)\*\*/g, '[$1]')
14
- .replace(/__(-?\d+)__/g, '{$1}')
15
- .replace(/~~([^~]+)~~/g, '($1)');
16
+ return renderBreakdown(result, TERMINAL_MARKS);
16
17
  }
17
18
  //# sourceMappingURL=format.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/cli/format.ts"],"names":[],"mappings":"AAiCA,MAAM,UAAU,YAAY,CAAC,MAAkB,EAAE,OAAO,GAAkB,EAAE;IAC1E,MAAM,EAAE,IAAI,GAAG,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAElD,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AACzC,CAAC;AAiBD,SAAS,cAAc,CAAC,QAAgB;IACtC,OAAO,QAAQ;SACZ,OAAO,CAAC,kBAAkB,EAAE,MAAM,CAAC;SACnC,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC;SAC/B,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/cli/format.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,eAAe,EAAE,MAAM,cAAc,CAAC;AAS9D,MAAM,cAAc,GAAa;IAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG;IACpC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG;IACpC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG;IACpC,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG;CACtC,CAAC;AAyBF,MAAM,UAAU,YAAY,CAAC,MAAkB,EAAE,OAAO,GAAkB,EAAE;IAC1E,MAAM,EAAE,IAAI,GAAG,KAAK,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAElD,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,eAAe,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AACjD,CAAC"}
@@ -20,7 +20,8 @@ import type { DieModifier, DieResult } from '../types.js';
20
20
  export declare function createDieResult(sides: number, result: number, modifiers: DieModifier[]): DieResult;
21
21
  /**
22
22
  * Creates a Fate/Fudge die result. Uses `sides = 0` as a sentinel — Fate dice
23
- * have no max-face concept, so `critical` and `fumble` are always `false`.
23
+ * have no max-face concept, so `critical` and `fumble` start `false` and the
24
+ * default rule never sets them. An explicit `cs`/`cf` threshold still can.
24
25
  */
25
26
  export declare function createFateDieResult(result: number, modifiers: DieModifier[]): DieResult;
26
27
  //# sourceMappingURL=die.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"die.d.ts","sourceRoot":"","sources":["../../src/evaluator/die.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,WAAW,EAAE,GACvB,SAAS,CAUX;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,SAAS,CAQvF"}
1
+ {"version":3,"file":"die.d.ts","sourceRoot":"","sources":["../../src/evaluator/die.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,WAAW,EAAE,GACvB,SAAS,CAUX;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,SAAS,CAQvF"}
@@ -1 +1 @@
1
- {"version":3,"file":"die.js","sourceRoot":"","sources":["../../src/evaluator/die.ts"],"names":[],"mappings":"AAqBA,MAAM,UAAU,eAAe,CAC7B,KAAa,EACb,MAAc,EACd,SAAwB;IAIxB,OAAO;QACL,KAAK;QACL,MAAM;QACN,SAAS;QACT,QAAQ,EAAE,MAAM,KAAK,KAAK,IAAI,KAAK,GAAG,CAAC;QACvC,MAAM,EAAE,MAAM,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;KAClC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,SAAwB;IAC1E,OAAO;QACL,KAAK,EAAE,CAAC;QACR,MAAM;QACN,SAAS;QACT,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,KAAK;KACd,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"die.js","sourceRoot":"","sources":["../../src/evaluator/die.ts"],"names":[],"mappings":"AAqBA,MAAM,UAAU,eAAe,CAC7B,KAAa,EACb,MAAc,EACd,SAAwB;IAIxB,OAAO;QACL,KAAK;QACL,MAAM;QACN,SAAS;QACT,QAAQ,EAAE,MAAM,KAAK,KAAK,IAAI,KAAK,GAAG,CAAC;QACvC,MAAM,EAAE,MAAM,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;KAClC,CAAC;AACJ,CAAC;AAOD,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,SAAwB;IAC1E,OAAO;QACL,KAAK,EAAE,CAAC;QACR,MAAM;QACN,SAAS;QACT,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,KAAK;KACd,CAAC;AACJ,CAAC"}
@@ -8,6 +8,16 @@
8
8
  * @module evaluator/env
9
9
  */
10
10
  import type { ASTNode } from '../parser/ast.js';
11
+ import type { DieResult, ResolvedCritThreshold } from '../types.js';
12
+ /**
13
+ * A resolved `cs`/`cf` pair, recorded per die so that dice minted later by
14
+ * explode or reroll can be judged by the rule the user declared instead of
15
+ * the built-in default.
16
+ */
17
+ export type CritRule = {
18
+ readonly success: readonly ResolvedCritThreshold[];
19
+ readonly fail: readonly ResolvedCritThreshold[];
20
+ };
11
21
  /**
12
22
  * Per-evaluation shared environment (created once, shared across all branches).
13
23
  *
@@ -32,6 +42,27 @@ export type EvalEnv = {
32
42
  * left-chain check.
33
43
  */
34
44
  insideVersus: boolean;
45
+ /**
46
+ * `true` once a `vs` has tagged its DC dice `'dc'`. Lets every pool
47
+ * operation skip its per-die `isVersusDc` check outright — the tag exists in
48
+ * a small minority of notation, and the call inside the loop cost 11-38% on
49
+ * keep/drop, success, and sort notation that can never carry one (#281).
50
+ *
51
+ * ! Monotonic: set by `evalVersus`, never cleared. `insideVersus` is not a
52
+ * ! model for it — that flag resets in a `finally`, and it is false exactly
53
+ * ! when the tagged DC dice become visible to the enclosing pool.
54
+ */
55
+ hasVersusDc: boolean;
56
+ /**
57
+ * Crit rule per die, keyed on the `DieResult` object itself. Populated by
58
+ * `applyCritThresholds`; read by explode and reroll so a die they mint
59
+ * inherits the rule of the die it descended from.
60
+ *
61
+ * Out of band rather than a `DieResult` field because `DieResult` is public
62
+ * and serialized. `undefined` until the first `cs`/`cf` node — declared here
63
+ * so the shape stays stable for notation that has none.
64
+ */
65
+ critRules: WeakMap<DieResult, CritRule> | undefined;
35
66
  /**
36
67
  * User-supplied variable map for `@name` / `@{name}` references. Always
37
68
  * defined — `evaluate()` defaults to an empty object so lookups can be
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/evaluator/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,GAAG,MAAM,CAAC;CAC9C,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CASvF;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAEvE"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/evaluator/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACjD,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;;;;;OASG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;;;;OAQG;IACH,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,SAAS,CAAC;IACpD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,GAAG,MAAM,CAAC;CAC9C,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CASvF;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAEvE"}
@@ -1 +1 @@
1
- {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/evaluator/env.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAgD9C,MAAM,UAAU,UAAU,CAAC,GAAY,EAAE,KAAa,EAAE,QAAyB;IAC/E,IAAI,GAAG,CAAC,eAAe,GAAG,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;QAC9C,MAAM,IAAI,cAAc,CACtB,oBAAoB,GAAG,CAAC,eAAe,GAAG,KAAK,qBAAqB,GAAG,CAAC,OAAO,EAAE,EACjF,qBAAqB,EACrB,QAAQ,CACT,CAAC;IACJ,CAAC;IACD,GAAG,CAAC,eAAe,IAAI,KAAK,CAAC;AAC/B,CAAC;AAGD,MAAM,UAAU,SAAS,CAAC,GAAY,EAAE,QAAyB;IAC/D,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC"}
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/evaluator/env.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAgF9C,MAAM,UAAU,UAAU,CAAC,GAAY,EAAE,KAAa,EAAE,QAAyB;IAC/E,IAAI,GAAG,CAAC,eAAe,GAAG,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;QAC9C,MAAM,IAAI,cAAc,CACtB,oBAAoB,GAAG,CAAC,eAAe,GAAG,KAAK,qBAAqB,GAAG,CAAC,OAAO,EAAE,EACjF,qBAAqB,EACrB,QAAQ,CACT,CAAC;IACJ,CAAC;IACD,GAAG,CAAC,eAAe,IAAI,KAAK,CAAC;AAC/B,CAAC;AAGD,MAAM,UAAU,SAAS,CAAC,GAAY,EAAE,QAAyB;IAC/D,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/evaluator/evaluator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAiB,cAAc,EAAuC,MAAM,cAAc,CAAC;AAClG,OAAO,KAAK,EACV,OAAO,EAmBR,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EAIV,SAAS,EACT,eAAe,EAKf,UAAU,EACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,EAKL,8BAA8B,EAC/B,MAAM,wBAAwB,CAAC;AAQhC,OAAO,EAGL,6BAA6B,EAC9B,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAE,cAAc,EAAE,CAAC;AAM1B;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAmCvC,OAAO,EAAE,8BAA8B,EAAE,6BAA6B,EAAE,CAAC;AAMzE;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE;QACf,MAAM,EAAE,eAAe,CAAC;QACxB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;QAC5B,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AA++CF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,GAAE,eAAoB,GAAG,UAAU,CA4D1F"}
1
+ {"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/evaluator/evaluator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAiB,cAAc,EAAuC,MAAM,cAAc,CAAC;AAClG,OAAO,KAAK,EACV,OAAO,EAmBR,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EAIV,SAAS,EACT,eAAe,EAKf,UAAU,EACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,EAKL,8BAA8B,EAC/B,MAAM,wBAAwB,CAAC;AAShC,OAAO,EAGL,6BAA6B,EAC9B,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAE,cAAc,EAAE,CAAC;AAM1B;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAmCvC,OAAO,EAAE,8BAA8B,EAAE,6BAA6B,EAAE,CAAC;AAMzE;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE;QACf,MAAM,EAAE,eAAe,CAAC;QACxB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;QAC5B,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAogDF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,GAAE,eAAoB,GAAG,UAAU,CA8D1F"}