roll-parser 3.0.0-beta.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.
- package/CHANGELOG.md +142 -9
- package/MIGRATION.md +219 -0
- package/README.md +1026 -154
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +81 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/format.d.ts +15 -3
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +18 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/main.d.ts +36 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +83 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/errors.d.ts +332 -17
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +141 -0
- package/dist/errors.js.map +1 -0
- package/dist/evaluator/die.d.ts +27 -0
- package/dist/evaluator/die.d.ts.map +1 -0
- package/dist/evaluator/die.js +19 -0
- package/dist/evaluator/die.js.map +1 -0
- package/dist/evaluator/env.d.ts +89 -0
- package/dist/evaluator/env.d.ts.map +1 -0
- package/dist/evaluator/env.js +11 -0
- package/dist/evaluator/env.js.map +1 -0
- package/dist/evaluator/evaluator.d.ts +41 -75
- package/dist/evaluator/evaluator.d.ts.map +1 -1
- package/dist/evaluator/evaluator.js +914 -0
- package/dist/evaluator/evaluator.js.map +1 -0
- package/dist/evaluator/modifiers/compare.js +15 -0
- package/dist/evaluator/modifiers/compare.js.map +1 -0
- package/dist/evaluator/modifiers/crit-threshold.d.ts +57 -5
- package/dist/evaluator/modifiers/crit-threshold.d.ts.map +1 -1
- package/dist/evaluator/modifiers/crit-threshold.js +42 -0
- package/dist/evaluator/modifiers/crit-threshold.js.map +1 -0
- package/dist/evaluator/modifiers/die-bound.d.ts +29 -0
- package/dist/evaluator/modifiers/die-bound.d.ts.map +1 -0
- package/dist/evaluator/modifiers/die-bound.js +17 -0
- package/dist/evaluator/modifiers/die-bound.js.map +1 -0
- package/dist/evaluator/modifiers/explode.d.ts +13 -5
- package/dist/evaluator/modifiers/explode.d.ts.map +1 -1
- package/dist/evaluator/modifiers/explode.js +105 -0
- package/dist/evaluator/modifiers/explode.js.map +1 -0
- package/dist/evaluator/modifiers/flags.d.ts +47 -0
- package/dist/evaluator/modifiers/flags.d.ts.map +1 -0
- package/dist/evaluator/modifiers/flags.js +25 -0
- package/dist/evaluator/modifiers/flags.js.map +1 -0
- package/dist/evaluator/modifiers/keep-drop.d.ts +15 -29
- package/dist/evaluator/modifiers/keep-drop.d.ts.map +1 -1
- package/dist/evaluator/modifiers/keep-drop.js +82 -0
- package/dist/evaluator/modifiers/keep-drop.js.map +1 -0
- package/dist/evaluator/modifiers/reroll.d.ts +12 -4
- package/dist/evaluator/modifiers/reroll.d.ts.map +1 -1
- package/dist/evaluator/modifiers/reroll.js +68 -0
- package/dist/evaluator/modifiers/reroll.js.map +1 -0
- package/dist/evaluator/modifiers/sort.d.ts +5 -1
- package/dist/evaluator/modifiers/sort.d.ts.map +1 -1
- package/dist/evaluator/modifiers/sort.js +13 -0
- package/dist/evaluator/modifiers/sort.js.map +1 -0
- package/dist/evaluator/modifiers/success-count.d.ts +6 -7
- package/dist/evaluator/modifiers/success-count.d.ts.map +1 -1
- package/dist/evaluator/modifiers/success-count.js +25 -0
- package/dist/evaluator/modifiers/success-count.js.map +1 -0
- package/dist/index.d.ts +33 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2560
- package/dist/index.js.map +1 -26
- package/dist/lexer/lexer.d.ts +50 -5
- package/dist/lexer/lexer.d.ts.map +1 -1
- package/dist/lexer/lexer.js +260 -0
- package/dist/lexer/lexer.js.map +1 -0
- package/dist/lexer/tokens.d.ts +31 -6
- package/dist/lexer/tokens.d.ts.map +1 -1
- package/dist/lexer/tokens.js +42 -0
- package/dist/lexer/tokens.js.map +1 -0
- package/dist/parser/ast.d.ts +324 -165
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js +52 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/guards.d.ts +106 -0
- package/dist/parser/guards.d.ts.map +1 -0
- package/dist/parser/guards.js +121 -0
- package/dist/parser/guards.js.map +1 -0
- package/dist/parser/parser.d.ts +118 -14
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +751 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/render.d.ts +95 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +227 -0
- package/dist/render.js.map +1 -0
- package/dist/rng/mock.d.ts +73 -12
- package/dist/rng/mock.d.ts.map +1 -1
- package/dist/rng/mock.js +30 -0
- package/dist/rng/mock.js.map +1 -0
- package/dist/rng/seeded.d.ts +141 -9
- package/dist/rng/seeded.d.ts.map +1 -1
- package/dist/rng/seeded.js +138 -0
- package/dist/rng/seeded.js.map +1 -0
- package/dist/rng/types.d.ts +57 -0
- package/dist/rng/types.d.ts.map +1 -1
- package/dist/rng/types.js +2 -0
- package/dist/rng/types.js.map +1 -0
- package/dist/roll.d.ts +58 -28
- package/dist/roll.d.ts.map +1 -1
- package/dist/roll.js +8 -0
- package/dist/roll.js.map +1 -0
- package/dist/testing.d.ts +5 -4
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +2 -41
- package/dist/testing.js.map +1 -11
- package/dist/types.d.ts +349 -47
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +93 -34
- package/src/cli/args.ts +66 -10
- package/src/cli/format.ts +37 -26
- package/src/cli/index.ts +27 -84
- package/src/cli/main.ts +129 -0
- package/src/errors.ts +480 -27
- package/src/evaluator/die.ts +51 -0
- package/src/evaluator/env.ts +105 -0
- package/src/evaluator/evaluator.ts +693 -434
- package/src/evaluator/modifiers/crit-threshold.ts +96 -14
- package/src/evaluator/modifiers/die-bound.ts +48 -0
- package/src/evaluator/modifiers/explode.ts +70 -62
- package/src/evaluator/modifiers/flags.ts +78 -0
- package/src/evaluator/modifiers/keep-drop.ts +129 -127
- package/src/evaluator/modifiers/reroll.ts +44 -56
- package/src/evaluator/modifiers/sort.ts +21 -2
- package/src/evaluator/modifiers/success-count.ts +24 -12
- package/src/index.ts +56 -35
- package/src/lexer/lexer.ts +107 -34
- package/src/lexer/tokens.ts +31 -6
- package/src/parser/ast.ts +333 -346
- package/src/parser/guards.ts +248 -0
- package/src/parser/parser.ts +419 -242
- package/src/render.ts +392 -0
- package/src/rng/mock.ts +74 -13
- package/src/rng/seeded.ts +299 -64
- package/src/rng/types.ts +57 -0
- package/src/roll.ts +64 -47
- package/src/testing.ts +5 -9
- package/src/types.ts +353 -46
- package/src/version.ts +2 -0
- package/dist/cli.js +0 -2608
- package/dist/cli.js.map +0 -28
- package/dist/evaluator/index.d.ts +0 -8
- package/dist/evaluator/index.d.ts.map +0 -1
- package/dist/rng/index.d.ts +0 -8
- package/dist/rng/index.d.ts.map +0 -1
- package/src/evaluator/index.ts +0 -14
- package/src/rng/index.ts +0 -8
package/dist/rng/seeded.d.ts
CHANGED
|
@@ -1,33 +1,165 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Seedable RNG using
|
|
2
|
+
* Seedable RNG using the xoshiro128** algorithm.
|
|
3
3
|
*
|
|
4
4
|
* @module rng/seeded
|
|
5
5
|
*/
|
|
6
6
|
import type { RNG } from './types.js';
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* A snapshot of {@link SeededRNG}'s internal state: a format version followed
|
|
9
|
+
* by the four state words as unsigned 32-bit integers. Pass one back to the
|
|
10
|
+
* constructor to resume the exact sequence it was taken from.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* Opaque and bound to the major version, the same contract seeds carry: the
|
|
13
|
+
* words mean nothing outside the engine that produced them, and a release that
|
|
14
|
+
* changes that engine bumps the leading version. A snapshot from a different
|
|
15
|
+
* version is rejected with an `INCOMPATIBLE_RNG_STATE` error rather than
|
|
16
|
+
* resumed under the wrong semantics — safe to hold in memory or serialize for
|
|
17
|
+
* a save file, and loud rather than silent across an upgrade.
|
|
18
|
+
*
|
|
19
|
+
* @category RNG
|
|
20
|
+
*/
|
|
21
|
+
export type RngState = readonly [version: number, s0: number, s1: number, s2: number, s3: number];
|
|
22
|
+
/**
|
|
23
|
+
* Seedable pseudo-random number generator using xoshiro128**. The default
|
|
24
|
+
* randomness source — `roll(notation)` builds one per call, and
|
|
25
|
+
* `roll(notation, { seed })` builds one from your seed.
|
|
26
|
+
*
|
|
27
|
+
* Period 2^128 - 1. Every seed is stringified and hashed with cyrb128 into
|
|
28
|
+
* the full 128-bit state, so numeric seeds keep all 53 bits and unrelated
|
|
29
|
+
* strings are overwhelmingly unlikely to share a state; an omitted seed hashes
|
|
30
|
+
* `Date.now()` together with two `Math.random()` draws, roughly 100 bits of
|
|
31
|
+
* width rather than 32 — wide enough that concurrently created generators are
|
|
32
|
+
* very unlikely to collide, though unpredictability stays bounded by the host
|
|
33
|
+
* engine's `Math.random()` seeding. The first 8 draws are discarded as a run-in.
|
|
34
|
+
*
|
|
35
|
+
* Stringifying means `42` and `'42'` are the same seed — the two forms share
|
|
36
|
+
* one namespace, which matters when seeds arrive from a CLI flag or JSON.
|
|
37
|
+
*
|
|
38
|
+
* {@link state} snapshots the state words as a versioned {@link RngState}, and
|
|
39
|
+
* passing one to the constructor resumes that exact sequence — restore copies
|
|
40
|
+
* the words verbatim, skipping both the hash and the run-in. A snapshot from
|
|
41
|
+
* another format version throws `INCOMPATIBLE_RNG_STATE`; within the current
|
|
42
|
+
* version the type is the contract, and a hand-built tuple is coerced to 32
|
|
43
|
+
* bits per word rather than rejected.
|
|
44
|
+
*
|
|
45
|
+
* Reproducibility guarantee: the same seed and the same notation produce the
|
|
46
|
+
* same dice for the lifetime of a major version, and `RngState` carries that
|
|
47
|
+
* binding. The one exception is a genuine distribution bug — bias, faulty
|
|
48
|
+
* rejection sampling — which may change the mapping in a minor release, never
|
|
49
|
+
* silently in a patch, and always with a `BREAKING` changelog note. The
|
|
50
|
+
* sequence is *not* cryptographically secure. To pin a roll beyond that,
|
|
51
|
+
* persist the {@link RollResult} rather than re-deriving it from a seed.
|
|
12
52
|
*
|
|
13
53
|
* @example
|
|
14
54
|
* ```typescript
|
|
55
|
+
* import { SeededRNG, roll } from 'roll-parser';
|
|
56
|
+
*
|
|
15
57
|
* // Same seed = same sequence
|
|
16
|
-
* const
|
|
17
|
-
* const
|
|
18
|
-
*
|
|
58
|
+
* const a = new SeededRNG('test-seed');
|
|
59
|
+
* const b = new SeededRNG('test-seed');
|
|
60
|
+
* a.nextInt(1, 6) === b.nextInt(1, 6); // true
|
|
61
|
+
*
|
|
62
|
+
* // An injected instance keeps advancing across rolls; `{ seed }` restarts
|
|
63
|
+
* // the stream on every call.
|
|
64
|
+
* const rng = new SeededRNG('demo');
|
|
65
|
+
* roll('1d20', { rng }).total; // 1
|
|
66
|
+
* roll('1d20', { rng }).total; // 20 — the stream moved on
|
|
67
|
+
* roll('1d20', { seed: 'demo' }).total; // 1, every single time
|
|
19
68
|
* ```
|
|
69
|
+
*
|
|
70
|
+
* @category RNG
|
|
20
71
|
*/
|
|
21
72
|
export declare class SeededRNG implements RNG {
|
|
22
73
|
private s0;
|
|
23
74
|
private s1;
|
|
24
75
|
private s2;
|
|
25
76
|
private s3;
|
|
26
|
-
constructor(seed?: string | number);
|
|
77
|
+
constructor(seed?: string | number | RngState);
|
|
27
78
|
private initState;
|
|
28
|
-
|
|
79
|
+
/** All-zero is a fixed point for xoshiro — at least one word must be non-zero. */
|
|
80
|
+
private guardZeroState;
|
|
81
|
+
/**
|
|
82
|
+
* Returns the current state as a format version followed by four unsigned
|
|
83
|
+
* 32-bit words. Feeding the snapshot back to the constructor resumes this
|
|
84
|
+
* exact sequence; the source instance is untouched, and the two then advance
|
|
85
|
+
* independently.
|
|
86
|
+
*
|
|
87
|
+
* The words are {@link RngState} — opaque, restorable within the major
|
|
88
|
+
* version that produced them, and rejected with `INCOMPATIBLE_RNG_STATE`
|
|
89
|
+
* outside it.
|
|
90
|
+
*
|
|
91
|
+
* @returns A snapshot of the version and the four state words
|
|
92
|
+
*
|
|
93
|
+
* @example Replay a roll that was never seeded
|
|
94
|
+
* ```typescript
|
|
95
|
+
* import { SeededRNG, roll } from 'roll-parser';
|
|
96
|
+
*
|
|
97
|
+
* const rng = new SeededRNG();
|
|
98
|
+
* const snapshot = rng.state();
|
|
99
|
+
*
|
|
100
|
+
* const first = roll('1d20', { rng });
|
|
101
|
+
* const replay = roll('1d20', { rng: new SeededRNG(snapshot) });
|
|
102
|
+
* first.total === replay.total; // true
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* Not a fork primitive. A restored generator replays the parent's stream, so
|
|
106
|
+
* children taken at different points are the same sequence at an offset, not
|
|
107
|
+
* independent substreams — derive a seed per entity instead.
|
|
108
|
+
*
|
|
109
|
+
* @example Per-entity streams — derive seeds, do not fork state
|
|
110
|
+
* ```typescript
|
|
111
|
+
* import { SeededRNG } from 'roll-parser';
|
|
112
|
+
*
|
|
113
|
+
* const goblin = new SeededRNG('world:goblin');
|
|
114
|
+
* const orc = new SeededRNG('world:orc');
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
state(): RngState;
|
|
118
|
+
/**
|
|
119
|
+
* Normalizes the constructor seed into a string. Numbers are stringified
|
|
120
|
+
* rather than coerced to uint32, so all 53 exact bits reach the hash.
|
|
121
|
+
*/
|
|
122
|
+
private toSeedString;
|
|
123
|
+
/** cyrb128 — hashes the seed into all four state words at once. */
|
|
124
|
+
private hashSeed;
|
|
29
125
|
private nextUint32;
|
|
126
|
+
/**
|
|
127
|
+
* Returns a float in `[0, 1)`, derived from one uint32 draw. Resolution is
|
|
128
|
+
* 2^-32, not the full 2^-53 a double can hold.
|
|
129
|
+
*
|
|
130
|
+
* Not used by the evaluator — dice go through {@link nextInt}.
|
|
131
|
+
*
|
|
132
|
+
* @returns A float in `[0, 1)`
|
|
133
|
+
*/
|
|
30
134
|
next(): number;
|
|
135
|
+
/**
|
|
136
|
+
* Returns an integer in the inclusive range `[min, max]`, uniformly
|
|
137
|
+
* distributed — rejection sampling removes the modulo bias a plain
|
|
138
|
+
* `% range` would introduce.
|
|
139
|
+
*
|
|
140
|
+
* Bounds handling, in order:
|
|
141
|
+
* - `min > max` is normalized by swapping, so `nextInt(6, 1)` behaves as
|
|
142
|
+
* `nextInt(1, 6)`. (The mock RNG throws instead; see {@link RNG.nextInt}.)
|
|
143
|
+
* - `min === max` returns that value without consuming a draw.
|
|
144
|
+
* - Ranges wider than 2^32 use two draws composed into a 53-bit value.
|
|
145
|
+
* - Ranges wider than 2^53 cannot be sampled exactly and throw a
|
|
146
|
+
* `RangeError` rather than silently skewing.
|
|
147
|
+
*
|
|
148
|
+
* @param min - Lower bound, inclusive
|
|
149
|
+
* @param max - Upper bound, inclusive
|
|
150
|
+
* @returns An integer in `[min, max]`
|
|
151
|
+
* @throws {RangeError} If `max - min + 1` exceeds 2^53
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* import { SeededRNG } from 'roll-parser';
|
|
156
|
+
*
|
|
157
|
+
* const rng = new SeededRNG('demo');
|
|
158
|
+
* rng.nextInt(1, 6); // 1..6
|
|
159
|
+
* rng.nextInt(3, 3); // 3, always
|
|
160
|
+
* rng.nextInt(1, Number.MAX_SAFE_INTEGER); // fine — two-draw path
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
31
163
|
nextInt(min: number, max: number): number;
|
|
32
164
|
/**
|
|
33
165
|
* Unbiased sampling in `[0, range)` for ranges above 2^32, built from two
|
package/dist/rng/seeded.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seeded.d.ts","sourceRoot":"","sources":["../../src/rng/seeded.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"seeded.d.ts","sourceRoot":"","sources":["../../src/rng/seeded.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;AAiFlG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,qBAAa,SAAU,YAAW,GAAG;IAKnC,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,EAAE,CAAS;IAEnB,YAAY,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,EAwB5C;IAED,OAAO,CAAC,SAAS;IAKjB,kFAAkF;IAClF,OAAO,CAAC,cAAc;IAMtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,KAAK,IAAI,QAAQ,CAEhB;IAED;;;OAGG;IACH,OAAO,CAAC,YAAY;IAMpB,mEAAmE;IACnE,OAAO,CAAC,QAAQ;IA+BhB,OAAO,CAAC,UAAU;IAmBlB;;;;;;;OAOG;IACH,IAAI,IAAI,MAAM,CAEb;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAyBxC;IAED;;;;;OAKG;IACH,OAAO,CAAC,eAAe;CAexB"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { RollParserError } from '../errors.js';
|
|
2
|
+
const RNG_STATE_VERSION = 1;
|
|
3
|
+
const RNG_STATE_LENGTH = 5;
|
|
4
|
+
const rotl = (value, bits) => (value << bits) | (value >>> (32 - bits));
|
|
5
|
+
const WARMUP_DRAWS = 8;
|
|
6
|
+
const UINT32_SPACE = 0x100000000;
|
|
7
|
+
const MAX_EXACT_INT = 2 ** 53;
|
|
8
|
+
const HIGH_DRAW_SCALE = 0x200000;
|
|
9
|
+
const LOW_DRAW_SHIFT = 11;
|
|
10
|
+
const CYRB128_BASIS_1 = 1779033703;
|
|
11
|
+
const CYRB128_BASIS_2 = 3144134277;
|
|
12
|
+
const CYRB128_BASIS_3 = 1013904242;
|
|
13
|
+
const CYRB128_BASIS_4 = 2773480762;
|
|
14
|
+
const SCRAMBLE_MULTIPLIER_1 = 5;
|
|
15
|
+
const SCRAMBLE_ROTATION = 7;
|
|
16
|
+
const SCRAMBLE_MULTIPLIER_2 = 9;
|
|
17
|
+
const STATE_SHIFT = 9;
|
|
18
|
+
const STATE_ROTATION = 11;
|
|
19
|
+
const CYRB128_MULTIPLIER_1 = 597399067;
|
|
20
|
+
const CYRB128_MULTIPLIER_2 = 2869860233;
|
|
21
|
+
const CYRB128_MULTIPLIER_3 = 951274213;
|
|
22
|
+
const CYRB128_MULTIPLIER_4 = 2716044179;
|
|
23
|
+
function assertRestorable(state) {
|
|
24
|
+
if (state.length !== RNG_STATE_LENGTH) {
|
|
25
|
+
throw new RollParserError(`Incompatible RngState: expected ${RNG_STATE_LENGTH} values, received ${state.length}`, 'INCOMPATIBLE_RNG_STATE');
|
|
26
|
+
}
|
|
27
|
+
if (state[0] !== RNG_STATE_VERSION) {
|
|
28
|
+
throw new RollParserError(`Incompatible RngState version: expected ${RNG_STATE_VERSION}, received ${state[0]}`, 'INCOMPATIBLE_RNG_STATE');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class SeededRNG {
|
|
32
|
+
s0;
|
|
33
|
+
s1;
|
|
34
|
+
s2;
|
|
35
|
+
s3;
|
|
36
|
+
constructor(seed) {
|
|
37
|
+
this.s0 = 0;
|
|
38
|
+
this.s1 = 0;
|
|
39
|
+
this.s2 = 0;
|
|
40
|
+
this.s3 = 0;
|
|
41
|
+
if (seed !== null && typeof seed === 'object') {
|
|
42
|
+
assertRestorable(seed);
|
|
43
|
+
this.s0 = seed[1] | 0;
|
|
44
|
+
this.s1 = seed[2] | 0;
|
|
45
|
+
this.s2 = seed[3] | 0;
|
|
46
|
+
this.s3 = seed[4] | 0;
|
|
47
|
+
this.guardZeroState();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.initState(seed);
|
|
51
|
+
for (let i = 0; i < WARMUP_DRAWS; i++) {
|
|
52
|
+
this.nextUint32();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
initState(seed) {
|
|
56
|
+
this.hashSeed(this.toSeedString(seed));
|
|
57
|
+
this.guardZeroState();
|
|
58
|
+
}
|
|
59
|
+
guardZeroState() {
|
|
60
|
+
if (this.s0 === 0 && this.s1 === 0 && this.s2 === 0 && this.s3 === 0) {
|
|
61
|
+
this.s0 = 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
state() {
|
|
65
|
+
return [RNG_STATE_VERSION, this.s0 >>> 0, this.s1 >>> 0, this.s2 >>> 0, this.s3 >>> 0];
|
|
66
|
+
}
|
|
67
|
+
toSeedString(seed) {
|
|
68
|
+
if (seed == null)
|
|
69
|
+
return `${Date.now()}-${Math.random()}-${Math.random()}`;
|
|
70
|
+
return String(seed);
|
|
71
|
+
}
|
|
72
|
+
hashSeed(str) {
|
|
73
|
+
let h1 = CYRB128_BASIS_1;
|
|
74
|
+
let h2 = CYRB128_BASIS_2;
|
|
75
|
+
let h3 = CYRB128_BASIS_3;
|
|
76
|
+
let h4 = CYRB128_BASIS_4;
|
|
77
|
+
for (let i = 0; i < str.length; i++) {
|
|
78
|
+
const k = str.charCodeAt(i);
|
|
79
|
+
h1 = h2 ^ Math.imul(h1 ^ k, CYRB128_MULTIPLIER_1);
|
|
80
|
+
h2 = h3 ^ Math.imul(h2 ^ k, CYRB128_MULTIPLIER_2);
|
|
81
|
+
h3 = h4 ^ Math.imul(h3 ^ k, CYRB128_MULTIPLIER_3);
|
|
82
|
+
h4 = h1 ^ Math.imul(h4 ^ k, CYRB128_MULTIPLIER_4);
|
|
83
|
+
}
|
|
84
|
+
h1 = Math.imul(h3 ^ (h1 >>> 18), CYRB128_MULTIPLIER_1);
|
|
85
|
+
h2 = Math.imul(h4 ^ (h2 >>> 22), CYRB128_MULTIPLIER_2);
|
|
86
|
+
h3 = Math.imul(h1 ^ (h3 >>> 17), CYRB128_MULTIPLIER_3);
|
|
87
|
+
h4 = Math.imul(h2 ^ (h4 >>> 19), CYRB128_MULTIPLIER_4);
|
|
88
|
+
const mixed = h1 ^ h2 ^ h3 ^ h4;
|
|
89
|
+
this.s0 = mixed;
|
|
90
|
+
this.s1 = h2 ^ mixed;
|
|
91
|
+
this.s2 = h3 ^ mixed;
|
|
92
|
+
this.s3 = h4 ^ mixed;
|
|
93
|
+
}
|
|
94
|
+
nextUint32() {
|
|
95
|
+
const scrambled = rotl(Math.imul(this.s1, SCRAMBLE_MULTIPLIER_1), SCRAMBLE_ROTATION);
|
|
96
|
+
const result = Math.imul(scrambled, SCRAMBLE_MULTIPLIER_2) >>> 0;
|
|
97
|
+
const t = this.s1 << STATE_SHIFT;
|
|
98
|
+
this.s2 ^= this.s0;
|
|
99
|
+
this.s3 ^= this.s1;
|
|
100
|
+
this.s1 ^= this.s2;
|
|
101
|
+
this.s0 ^= this.s3;
|
|
102
|
+
this.s2 ^= t;
|
|
103
|
+
this.s3 = rotl(this.s3, STATE_ROTATION);
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
next() {
|
|
107
|
+
return this.nextUint32() / UINT32_SPACE;
|
|
108
|
+
}
|
|
109
|
+
nextInt(min, max) {
|
|
110
|
+
const lo = min > max ? max : min;
|
|
111
|
+
const hi = min > max ? min : max;
|
|
112
|
+
const range = hi - lo + 1;
|
|
113
|
+
if (range <= 1) {
|
|
114
|
+
return lo;
|
|
115
|
+
}
|
|
116
|
+
if (range > UINT32_SPACE) {
|
|
117
|
+
return lo + this.nextBoundedWide(range);
|
|
118
|
+
}
|
|
119
|
+
const threshold = (UINT32_SPACE - range) % range;
|
|
120
|
+
let value;
|
|
121
|
+
do {
|
|
122
|
+
value = this.nextUint32();
|
|
123
|
+
} while (value < threshold);
|
|
124
|
+
return lo + (value % range);
|
|
125
|
+
}
|
|
126
|
+
nextBoundedWide(range) {
|
|
127
|
+
if (range > MAX_EXACT_INT) {
|
|
128
|
+
throw new RangeError(`nextInt range ${range} exceeds 2^53 and cannot be sampled exactly`);
|
|
129
|
+
}
|
|
130
|
+
const limit = Math.floor(MAX_EXACT_INT / range) * range;
|
|
131
|
+
let value;
|
|
132
|
+
do {
|
|
133
|
+
value = this.nextUint32() * HIGH_DRAW_SCALE + (this.nextUint32() >>> LOW_DRAW_SHIFT);
|
|
134
|
+
} while (value >= limit);
|
|
135
|
+
return value % range;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=seeded.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seeded.js","sourceRoot":"","sources":["../../src/rng/seeded.ts"],"names":[],"mappings":"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"}
|
package/dist/rng/types.d.ts
CHANGED
|
@@ -7,6 +7,57 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* Random Number Generator interface for dice rolling.
|
|
10
|
+
*
|
|
11
|
+
* Guaranteed by every implementation:
|
|
12
|
+
*
|
|
13
|
+
* - `next()` returns a float in `[0, 1)`.
|
|
14
|
+
* - `nextInt(min, max)` returns an integer in `[min, max]` when `min <= max`.
|
|
15
|
+
* - `nextInt(n, n)` returns `n`.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately NOT guaranteed: behavior when `min > max`. The two shipped
|
|
18
|
+
* implementations differ, and each difference is load-bearing — see
|
|
19
|
+
* {@link nextInt}. Evaluator code never inverts its bounds, so callers should
|
|
20
|
+
* treat inverted bounds as a programming error rather than an API.
|
|
21
|
+
*
|
|
22
|
+
* The evaluator only ever calls `nextInt`, once per die, left to right;
|
|
23
|
+
* `next` exists for implementations that want a float source of their own.
|
|
24
|
+
* Two shipped implementations satisfy the interface — {@link SeededRNG} and
|
|
25
|
+
* the mock from `roll-parser/testing` — and anything structurally compatible
|
|
26
|
+
* works, so a crypto-backed or table-driven generator drops straight in.
|
|
27
|
+
*
|
|
28
|
+
* @example A crypto-backed RNG
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { roll, type RNG } from 'roll-parser';
|
|
31
|
+
*
|
|
32
|
+
* const cryptoRng: RNG = {
|
|
33
|
+
* next: () => crypto.getRandomValues(new Uint32Array(1))[0]! / 2 ** 32,
|
|
34
|
+
* nextInt: (min, max) => min + Math.floor(cryptoRng.next() * (max - min + 1)),
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* roll('4d6kh3', { rng: cryptoRng }).total; // 3..18
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @example Wrapping an RNG to log every draw
|
|
41
|
+
* ```typescript
|
|
42
|
+
* import { SeededRNG, roll, type RNG } from 'roll-parser';
|
|
43
|
+
*
|
|
44
|
+
* function withLog(inner: RNG, log: number[]): RNG {
|
|
45
|
+
* return {
|
|
46
|
+
* next: () => inner.next(),
|
|
47
|
+
* nextInt: (min, max) => {
|
|
48
|
+
* const value = inner.nextInt(min, max);
|
|
49
|
+
* log.push(value);
|
|
50
|
+
* return value;
|
|
51
|
+
* },
|
|
52
|
+
* };
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* const draws: number[] = [];
|
|
56
|
+
* roll('4d6kh3', { rng: withLog(new SeededRNG('demo'), draws) });
|
|
57
|
+
* draws; // [4, 3, 3, 3] — every face, including the dropped one
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @category RNG
|
|
10
61
|
*/
|
|
11
62
|
export type RNG = {
|
|
12
63
|
/**
|
|
@@ -16,6 +67,12 @@ export type RNG = {
|
|
|
16
67
|
/**
|
|
17
68
|
* Returns a random integer in the inclusive range [min, max].
|
|
18
69
|
*
|
|
70
|
+
* Inverted bounds (`min > max`) are implementation-defined:
|
|
71
|
+
* `SeededRNG` normalizes by swapping them, so `nextInt(6, 1)` yields the
|
|
72
|
+
* same sequence as `nextInt(1, 6)`; the mock RNG from `roll-parser/testing`
|
|
73
|
+
* raises `RangeError`, because a scripted value can never satisfy an empty
|
|
74
|
+
* range and silently accepting it would hide a miscounted test sequence.
|
|
75
|
+
*
|
|
19
76
|
* @param min - Minimum value (inclusive)
|
|
20
77
|
* @param max - Maximum value (inclusive)
|
|
21
78
|
*/
|
package/dist/rng/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rng/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rng/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAM,MAAM,GAAG,GAAG;IAChB;;OAEG;IACH,IAAI,IAAI,MAAM,CAAC;IAEf;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/rng/types.ts"],"names":[],"mappings":""}
|
package/dist/roll.d.ts
CHANGED
|
@@ -4,48 +4,78 @@
|
|
|
4
4
|
* @module roll
|
|
5
5
|
*/
|
|
6
6
|
import type { RNG } from './rng/types.js';
|
|
7
|
-
import type { RollResult } from './types.js';
|
|
7
|
+
import type { EvaluationOptions, RollResult } from './types.js';
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Everything {@link roll} accepts on top of the shared {@link EvaluationOptions}:
|
|
10
|
+
* a randomness source, given either as a ready-made {@link RNG} or as a seed.
|
|
11
|
+
*
|
|
12
|
+
* @category Core
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { roll, SeededRNG } from 'roll-parser';
|
|
17
|
+
*
|
|
18
|
+
* roll('4d6', { seed: 'character-1' }); // reproducible
|
|
19
|
+
* roll('4d6', { rng: new SeededRNG(42) }); // rng wins over seed
|
|
20
|
+
* roll('1d20+@str', { context: { str: 4 } });
|
|
21
|
+
* ```
|
|
10
22
|
*/
|
|
11
|
-
export type RollOptions = {
|
|
12
|
-
/**
|
|
23
|
+
export type RollOptions = EvaluationOptions & {
|
|
24
|
+
/**
|
|
25
|
+
* Randomness source. Takes precedence over `seed` — when both are given,
|
|
26
|
+
* `seed` is ignored.
|
|
27
|
+
*/
|
|
13
28
|
rng?: RNG;
|
|
14
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Seed for a fresh `SeededRNG`. Equal seeds replay the same die sequence for
|
|
31
|
+
* the same notation. Ignored when `rng` is set.
|
|
32
|
+
*/
|
|
15
33
|
seed?: string | number;
|
|
16
|
-
/** Maximum total dice allowed per evaluation (default: 10,000) */
|
|
17
|
-
maxDice?: number;
|
|
18
|
-
/** Maximum explosion iterations allowed per die (default: 1,000) */
|
|
19
|
-
maxExplodeIterations?: number;
|
|
20
|
-
/** Maximum reroll iterations allowed per die (default: 1,000) */
|
|
21
|
-
maxRerollIterations?: number;
|
|
22
|
-
/** Variable context for `@name` / `@{name}` references (default: empty) */
|
|
23
|
-
context?: Record<string, number>;
|
|
24
|
-
/** Behavior when a referenced variable is missing from context (default: 'throw') */
|
|
25
|
-
onMissingVariable?: 'throw' | 'zero';
|
|
26
34
|
};
|
|
27
35
|
/**
|
|
28
|
-
* Parses and evaluates a dice notation string
|
|
36
|
+
* Parses and evaluates a dice notation string in one call — the main entry
|
|
37
|
+
* point of the library.
|
|
29
38
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* @
|
|
39
|
+
* Equivalent to `evaluate(parse(notation), rng, { notation })`. Each call
|
|
40
|
+
* builds a fresh `SeededRNG` unless `options.rng` is supplied, so reuse
|
|
41
|
+
* {@link parse} + {@link evaluate} directly when rolling the same notation in
|
|
42
|
+
* a loop.
|
|
43
|
+
*
|
|
44
|
+
* @param notation - Dice notation, e.g. `'2d6+3'` or `'4d6kh3'`
|
|
45
|
+
* @param options - RNG or seed, plus the shared {@link EvaluationOptions}
|
|
46
|
+
* @returns Complete {@link RollResult} with total, per-die results and the
|
|
47
|
+
* structured `parts` tree
|
|
48
|
+
* @throws {LexerError} On an invalid character
|
|
49
|
+
* @throws {ParseError} On invalid syntax
|
|
50
|
+
* @throws {EvaluatorError} On a limit breach or an impossible expression
|
|
51
|
+
* @throws {RollParserError} `INVALID_EVALUATION_LIMIT` when a supplied limit is
|
|
52
|
+
* not an integer in range — raised before any die is rolled
|
|
53
|
+
* @throws {RollParserError} `INVALID_NOTATION_TYPE` when `notation` is not a
|
|
54
|
+
* string, so `isRollParserError` still filters untrusted input completely
|
|
33
55
|
*
|
|
34
56
|
* @example
|
|
35
57
|
* ```typescript
|
|
58
|
+
* import { roll } from 'roll-parser';
|
|
59
|
+
*
|
|
36
60
|
* // Random roll
|
|
37
|
-
*
|
|
38
|
-
*
|
|
61
|
+
* roll('2d6+3').total; // 5..15
|
|
62
|
+
*
|
|
63
|
+
* // Seeded — same seed, same sequence
|
|
64
|
+
* roll('2d6+3', { seed: 'demo' }).rendered; // '2d6[1, 6] + 3 = 10'
|
|
65
|
+
* roll('2d6+3', { seed: 'demo' }).total; // 10
|
|
66
|
+
* ```
|
|
39
67
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
68
|
+
* @example Deterministic tests with the testing mock
|
|
69
|
+
* ```typescript
|
|
70
|
+
* import { roll } from 'roll-parser';
|
|
71
|
+
* import { createMockRng } from 'roll-parser/testing';
|
|
44
72
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* result.
|
|
73
|
+
* const result = roll('4d6kh3', { rng: createMockRng([3, 6, 2, 5]) });
|
|
74
|
+
* result.total; // 14
|
|
75
|
+
* result.rendered; // '4d6[3, 6, ~~2~~, 5] = 14'
|
|
48
76
|
* ```
|
|
77
|
+
*
|
|
78
|
+
* @category Core
|
|
49
79
|
*/
|
|
50
80
|
export declare function roll(notation: string, options?: RollOptions): RollResult;
|
|
51
81
|
//# sourceMappingURL=roll.d.ts.map
|
package/dist/roll.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roll.d.ts","sourceRoot":"","sources":["../src/roll.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"roll.d.ts","sourceRoot":"","sources":["../src/roll.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG;IAC5C;;;OAGG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC;IACV;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,UAAU,CAK5E"}
|
package/dist/roll.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { evaluate } from './evaluator/evaluator.js';
|
|
2
|
+
import { parse } from './parser/parser.js';
|
|
3
|
+
import { SeededRNG } from './rng/seeded.js';
|
|
4
|
+
export function roll(notation, options = {}) {
|
|
5
|
+
const { rng, seed, ...limits } = options;
|
|
6
|
+
return evaluate(parse(notation), rng ?? new SeededRNG(seed), { ...limits, notation });
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=roll.js.map
|
package/dist/roll.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roll.js","sourceRoot":"","sources":["../src/roll.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AA6E5C,MAAM,UAAU,IAAI,CAAC,QAAgB,EAAE,OAAO,GAAgB,EAAE;IAE9D,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IAEzC,OAAO,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,GAAG,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;AACxF,CAAC"}
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test utilities for roll-parser consumers.
|
|
3
3
|
*
|
|
4
|
-
* Import from `roll-parser/testing` for deterministic dice testing.
|
|
4
|
+
* Import from `roll-parser/testing` for deterministic dice testing. The full
|
|
5
|
+
* TSDoc lives on the implementations in `./rng/mock.ts`; this entry point is
|
|
6
|
+
* a plain re-export barrel.
|
|
5
7
|
*
|
|
6
8
|
* @module testing
|
|
7
9
|
*/
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
export declare const MockRNGExhaustedError: typeof _MockRNGExhaustedError;
|
|
10
|
+
export { createMockRng, MockRNGExhaustedError } from './rng/mock.js';
|
|
11
|
+
export type { RNG } from './rng/types.js';
|
|
11
12
|
//# sourceMappingURL=testing.d.ts.map
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACrE,YAAY,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/testing.js
CHANGED
|
@@ -1,41 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
consumed;
|
|
4
|
-
constructor(consumed) {
|
|
5
|
-
super(`MockRNG exhausted: consumed ${consumed} values, no more available`);
|
|
6
|
-
this.name = "MockRNGExhaustedError";
|
|
7
|
-
this.consumed = consumed;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
function createMockRng(values) {
|
|
11
|
-
let index = 0;
|
|
12
|
-
const getNext = () => {
|
|
13
|
-
const value = values[index];
|
|
14
|
-
if (value === undefined) {
|
|
15
|
-
throw new MockRNGExhaustedError(index);
|
|
16
|
-
}
|
|
17
|
-
index++;
|
|
18
|
-
return value;
|
|
19
|
-
};
|
|
20
|
-
return {
|
|
21
|
-
next: getNext,
|
|
22
|
-
nextInt: (min, max) => {
|
|
23
|
-
const value = getNext();
|
|
24
|
-
if (value < min || value > max) {
|
|
25
|
-
throw new RangeError(`MockRNG value ${value} is out of bounds [${min}, ${max}]`);
|
|
26
|
-
}
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// src/testing.ts
|
|
33
|
-
var createMockRng2 = createMockRng;
|
|
34
|
-
var MockRNGExhaustedError2 = MockRNGExhaustedError;
|
|
35
|
-
export {
|
|
36
|
-
createMockRng2 as createMockRng,
|
|
37
|
-
MockRNGExhaustedError2 as MockRNGExhaustedError
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
//# debugId=916ECEA68894E64864756E2164756E21
|
|
41
|
-
//# sourceMappingURL=testing.js.map
|
|
1
|
+
export { createMockRng, MockRNGExhaustedError } from './rng/mock.js';
|
|
2
|
+
//# sourceMappingURL=testing.js.map
|
package/dist/testing.js.map
CHANGED
|
@@ -1,11 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/rng/mock.ts", "../src/testing.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\n * Mock RNG for deterministic testing.\n *\n * @module rng/mock\n */\n\nimport type { RNG } from './types.js';\n\n/**\n * Error thrown when MockRNG exhausts its predefined values.\n *\n * This is intentional behavior to catch incorrect roll counts in tests.\n * If you see this error, your test is consuming more random values than expected.\n */\nexport class MockRNGExhaustedError extends Error {\n readonly consumed: number;\n\n constructor(consumed: number) {\n super(`MockRNG exhausted: consumed ${consumed} values, no more available`);\n this.name = 'MockRNGExhaustedError';\n this.consumed = consumed;\n }\n}\n\n/**\n * Creates a mock RNG that returns predefined values in sequence.\n *\n * IMPORTANT: Throws MockRNGExhaustedError when all values are consumed.\n * This behavior catches incorrect roll counts in tests - it never wraps around.\n *\n * @param values - Array of values to return (dice results for nextInt, floats for next)\n * @returns RNG instance returning predefined values\n *\n * @example\n * ```typescript\n * const rng = createMockRng([4, 2, 6]);\n * rng.nextInt(1, 6); // Returns 4\n * rng.nextInt(1, 6); // Returns 2\n * rng.nextInt(1, 6); // Returns 6\n * rng.nextInt(1, 6); // Throws MockRNGExhaustedError\n * ```\n */\nexport function createMockRng(values: number[]): RNG {\n let index = 0;\n\n const getNext = (): number => {\n const value = values[index];\n if (value === undefined) {\n throw new MockRNGExhaustedError(index);\n }\n index++;\n return value;\n };\n\n return {\n next: getNext,\n nextInt: (min: number, max: number): number => {\n const value = getNext();\n if (value < min || value > max) {\n throw new RangeError(`MockRNG value ${value} is out of bounds [${min}, ${max}]`);\n }\n return value;\n },\n };\n}\n",
|
|
6
|
-
"/**\n * Test utilities for roll-parser consumers.\n *\n * Import from `roll-parser/testing` for deterministic dice testing.\n *\n * @module testing\n */\n\n// Direct value exports force the bundler to inline the code\nimport {\n MockRNGExhaustedError as _MockRNGExhaustedError,\n createMockRng as _createMockRng,\n} from './rng/mock.js';\n\nexport const createMockRng = _createMockRng;\nexport const MockRNGExhaustedError = _MockRNGExhaustedError;\n"
|
|
7
|
-
],
|
|
8
|
-
"mappings": ";AAcO,MAAM,8BAA8B,MAAM;AAAA,EACtC;AAAA,EAET,WAAW,CAAC,UAAkB;AAAA,IAC5B,MAAM,+BAA+B,oCAAoC;AAAA,IACzE,KAAK,OAAO;AAAA,IACZ,KAAK,WAAW;AAAA;AAEpB;AAoBO,SAAS,aAAa,CAAC,QAAuB;AAAA,EACnD,IAAI,QAAQ;AAAA,EAEZ,MAAM,UAAU,MAAc;AAAA,IAC5B,MAAM,QAAQ,OAAO;AAAA,IACrB,IAAI,UAAU,WAAW;AAAA,MACvB,MAAM,IAAI,sBAAsB,KAAK;AAAA,IACvC;AAAA,IACA;AAAA,IACA,OAAO;AAAA;AAAA,EAGT,OAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,KAAa,QAAwB;AAAA,MAC7C,MAAM,QAAQ,QAAQ;AAAA,MACtB,IAAI,QAAQ,OAAO,QAAQ,KAAK;AAAA,QAC9B,MAAM,IAAI,WAAW,iBAAiB,2BAA2B,QAAQ,MAAM;AAAA,MACjF;AAAA,MACA,OAAO;AAAA;AAAA,EAEX;AAAA;;;ACjDK,IAAM,iBAAgB;AACtB,IAAM,yBAAwB;",
|
|
9
|
-
"debugId": "916ECEA68894E64864756E2164756E21",
|
|
10
|
-
"names": []
|
|
11
|
-
}
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|