@erpg/dicecore 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 (55) hide show
  1. package/dist/index.cjs +9 -0
  2. package/dist/index.d.cts +1 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +9 -0
  5. package/dist/v3/cache.d.cts +23 -0
  6. package/dist/v3/cache.d.ts +23 -0
  7. package/dist/v3/compiler.d.cts +50 -0
  8. package/dist/v3/compiler.d.ts +50 -0
  9. package/dist/v3/engine.d.cts +7 -0
  10. package/dist/v3/engine.d.ts +7 -0
  11. package/dist/v3/errors.d.cts +37 -0
  12. package/dist/v3/errors.d.ts +37 -0
  13. package/dist/v3/executor.d.cts +11 -0
  14. package/dist/v3/executor.d.ts +11 -0
  15. package/dist/v3/freeze.d.cts +5 -0
  16. package/dist/v3/freeze.d.ts +5 -0
  17. package/dist/v3/index.d.cts +8 -0
  18. package/dist/v3/index.d.ts +8 -0
  19. package/dist/v3/math.d.cts +12 -0
  20. package/dist/v3/math.d.ts +12 -0
  21. package/dist/v3/normalization.d.cts +10 -0
  22. package/dist/v3/normalization.d.ts +10 -0
  23. package/dist/v3/roll-count-expression.d.cts +2 -0
  24. package/dist/v3/roll-count-expression.d.ts +2 -0
  25. package/dist/v3/runtime/budget.d.cts +57 -0
  26. package/dist/v3/runtime/budget.d.ts +57 -0
  27. package/dist/v3/runtime/context.d.cts +27 -0
  28. package/dist/v3/runtime/context.d.ts +27 -0
  29. package/dist/v3/runtime/index.d.cts +7 -0
  30. package/dist/v3/runtime/index.d.ts +7 -0
  31. package/dist/v3/runtime/journal.d.cts +23 -0
  32. package/dist/v3/runtime/journal.d.ts +23 -0
  33. package/dist/v3/runtime/limits.d.cts +25 -0
  34. package/dist/v3/runtime/limits.d.ts +25 -0
  35. package/dist/v3/runtime/mt19937.d.cts +23 -0
  36. package/dist/v3/runtime/mt19937.d.ts +23 -0
  37. package/dist/v3/runtime/replay.d.cts +38 -0
  38. package/dist/v3/runtime/replay.d.ts +38 -0
  39. package/dist/v3/runtime/xoshiro128ss.d.cts +14 -0
  40. package/dist/v3/runtime/xoshiro128ss.d.ts +14 -0
  41. package/dist/v3/syntax/ast.d.cts +132 -0
  42. package/dist/v3/syntax/ast.d.ts +132 -0
  43. package/dist/v3/syntax/index.d.cts +4 -0
  44. package/dist/v3/syntax/index.d.ts +4 -0
  45. package/dist/v3/syntax/parser.d.cts +7 -0
  46. package/dist/v3/syntax/parser.d.ts +7 -0
  47. package/dist/v3/syntax/scanner.d.cts +3 -0
  48. package/dist/v3/syntax/scanner.d.ts +3 -0
  49. package/dist/v3/syntax/tokens.d.cts +38 -0
  50. package/dist/v3/syntax/tokens.d.ts +38 -0
  51. package/dist/v3/types.d.cts +251 -0
  52. package/dist/v3/types.d.ts +251 -0
  53. package/licence.txt +23 -0
  54. package/package.json +105 -0
  55. package/readme.md +255 -0
@@ -0,0 +1,251 @@
1
+ import type { DiceRollError, SourceSpan } from './errors.js';
2
+ import type { DiceLimits } from './runtime/limits.js';
3
+ import type { RandomAlgorithm, ReplayDescriptor, SeedInput } from './runtime/replay.js';
4
+ export type DiceSides = number | 'F';
5
+ export type DiceState = 'compound' | 'critical-failure' | 'critical-success' | 'dropped' | 'exploded' | 'maximum' | 'minimum' | 'penetrated' | 'rerolled' | 'target-failure' | 'target-neutral' | 'target-success' | 'unique-rerolled';
6
+ export type GroupState = 'dropped' | 'sorted-ascending' | 'sorted-descending';
7
+ export interface DiceInspectionCost {
8
+ readonly staticDice: number;
9
+ readonly worstCaseGeneratedDice: number;
10
+ readonly worstCaseRandomCalls: number;
11
+ readonly totalStaticDice: number;
12
+ readonly totalWorstCaseGeneratedDice: number;
13
+ readonly totalWorstCaseRandomCalls: number;
14
+ }
15
+ export interface RollPlanGroup {
16
+ readonly id: string;
17
+ readonly sourceNodeId: string;
18
+ readonly kind: 'dice' | 'expression' | 'function' | 'group';
19
+ readonly notation: string;
20
+ readonly span: SourceSpan;
21
+ readonly childIds: readonly string[];
22
+ }
23
+ export interface RollPlan {
24
+ readonly type: 'roll-plan';
25
+ readonly schemaVersion: 3;
26
+ readonly compilerVersion: 1;
27
+ readonly planFingerprint: string;
28
+ readonly input: string;
29
+ readonly comment: string;
30
+ readonly notation: string;
31
+ readonly normalizedNotation: string;
32
+ readonly isMultiRoll: boolean;
33
+ readonly rollCount: number;
34
+ readonly groups: readonly RollPlanGroup[];
35
+ readonly cost: DiceInspectionCost;
36
+ }
37
+ export interface EntityRange {
38
+ readonly start: number;
39
+ readonly count: number;
40
+ }
41
+ export interface ResolvedDie {
42
+ readonly id: string;
43
+ readonly sourceNodeId: string;
44
+ readonly parentDieId: string | null;
45
+ readonly rollIndex: number;
46
+ readonly rollDieIndex: number;
47
+ readonly groupId: string;
48
+ readonly sides: DiceSides;
49
+ readonly rawValue: number;
50
+ readonly value: number;
51
+ readonly contribution: number;
52
+ readonly included: boolean;
53
+ readonly states: readonly DiceState[];
54
+ }
55
+ export interface ResolvedGroup {
56
+ readonly id: string;
57
+ readonly sourceNodeId: string;
58
+ readonly rollIndex: number;
59
+ readonly kind: 'dice' | 'expression' | 'function' | 'group';
60
+ readonly notation: string;
61
+ readonly span: SourceSpan;
62
+ readonly value: number;
63
+ readonly contribution: number;
64
+ readonly included: boolean;
65
+ readonly states: readonly GroupState[];
66
+ readonly childIds: readonly string[];
67
+ }
68
+ export interface PoolSummary {
69
+ readonly successes: number;
70
+ readonly failures: number;
71
+ readonly netSuccesses: number;
72
+ }
73
+ interface EventBase {
74
+ readonly sequence: number;
75
+ readonly rollIndex: number;
76
+ readonly sourceNodeId: string;
77
+ }
78
+ interface DieEventBase extends EventBase {
79
+ readonly subject: 'die';
80
+ readonly dieId: string;
81
+ readonly parentDieId: string | null;
82
+ }
83
+ interface GroupEventBase extends EventBase {
84
+ readonly subject: 'group';
85
+ readonly groupId: string;
86
+ }
87
+ export interface RollDiceEvent extends DieEventBase {
88
+ readonly type: 'roll';
89
+ readonly value: number;
90
+ }
91
+ export interface RerollDiceEvent extends DieEventBase {
92
+ readonly type: 'reroll';
93
+ readonly from: number;
94
+ readonly to: number;
95
+ readonly reason: 'reroll' | 'reroll-once' | 'unique' | 'unique-once';
96
+ }
97
+ export interface ExplodeDiceEvent extends DieEventBase {
98
+ readonly type: 'explode';
99
+ readonly childDieId: string;
100
+ readonly value: number;
101
+ readonly reason: 'explode' | 'compound' | 'penetrate';
102
+ }
103
+ export interface TransformDiceEvent extends DieEventBase {
104
+ readonly type: 'transform';
105
+ readonly from: number;
106
+ readonly to: number;
107
+ readonly reason: 'minimum' | 'maximum' | 'penetrate' | 'compound';
108
+ }
109
+ export interface TransformGroupEvent extends GroupEventBase {
110
+ readonly type: 'transform';
111
+ readonly from: readonly string[];
112
+ readonly to: readonly string[];
113
+ readonly reason: 'sort-ascending' | 'sort-descending';
114
+ }
115
+ export interface IncludeDiceEvent extends DieEventBase {
116
+ readonly type: 'include';
117
+ readonly contribution: number;
118
+ }
119
+ export interface IncludeGroupEvent extends GroupEventBase {
120
+ readonly type: 'include';
121
+ readonly value: number;
122
+ readonly contribution: number;
123
+ }
124
+ export interface ExcludeDiceEvent extends DieEventBase {
125
+ readonly type: 'exclude';
126
+ readonly reason: 'drop' | 'keep' | 'compound-absorbed';
127
+ }
128
+ export interface ExcludeGroupEvent extends GroupEventBase {
129
+ readonly type: 'exclude';
130
+ readonly value: number;
131
+ readonly reason: 'drop' | 'keep';
132
+ }
133
+ export interface ClassifyDiceEvent extends DieEventBase {
134
+ readonly type: 'classify';
135
+ readonly outcome: 'success' | 'failure' | 'neutral' | 'critical-success' | 'critical-failure';
136
+ }
137
+ export type DiceEvent = RollDiceEvent | RerollDiceEvent | ExplodeDiceEvent | TransformDiceEvent | TransformGroupEvent | IncludeDiceEvent | IncludeGroupEvent | ExcludeDiceEvent | ExcludeGroupEvent | ClassifyDiceEvent;
138
+ export interface ExecutionStats {
139
+ readonly rolls: number;
140
+ readonly initialDice: number;
141
+ readonly generatedDice: number;
142
+ readonly randomCalls: number;
143
+ readonly modifierSteps: number;
144
+ readonly events: number;
145
+ readonly resolvedGroups: number;
146
+ readonly resultItems: number;
147
+ }
148
+ export interface ResolvedRoll {
149
+ readonly index: number;
150
+ readonly total: number;
151
+ readonly pool: PoolSummary | null;
152
+ readonly diceRange: EntityRange;
153
+ readonly groupRange: EntityRange;
154
+ readonly eventRange: EntityRange;
155
+ }
156
+ interface RollResultBase {
157
+ readonly schemaVersion: 3;
158
+ readonly input: string;
159
+ readonly notation: string;
160
+ readonly normalizedNotation: string;
161
+ readonly comment: string;
162
+ readonly total: number;
163
+ readonly replay: ReplayDescriptor;
164
+ readonly stats: ExecutionStats;
165
+ readonly pool: PoolSummary | null;
166
+ }
167
+ export interface DiceRollResult extends RollResultBase {
168
+ readonly type: 'dice-roll';
169
+ readonly output: string;
170
+ readonly rolls: readonly ResolvedRoll[];
171
+ readonly groups: readonly ResolvedGroup[];
172
+ readonly dice: readonly ResolvedDie[];
173
+ readonly events: readonly DiceEvent[];
174
+ }
175
+ export interface ResolvedRollSummary {
176
+ readonly index: number;
177
+ readonly total: number;
178
+ readonly pool: PoolSummary | null;
179
+ }
180
+ export interface DiceRollSummary extends RollResultBase {
181
+ readonly type: 'dice-roll-summary';
182
+ readonly rolls: readonly ResolvedRollSummary[];
183
+ }
184
+ interface DiceNotationInspectionBase {
185
+ readonly type: 'dice-notation-inspection';
186
+ readonly input: string;
187
+ readonly notation: string;
188
+ readonly normalizedNotation: string;
189
+ readonly comment: string;
190
+ }
191
+ interface ValidDiceNotationInspection extends DiceNotationInspectionBase {
192
+ readonly isValid: true;
193
+ readonly plan: RollPlan;
194
+ readonly groups: readonly RollPlanGroup[];
195
+ readonly cost: DiceInspectionCost;
196
+ readonly error: null;
197
+ }
198
+ interface InvalidDiceNotationInspection extends DiceNotationInspectionBase {
199
+ readonly isValid: false;
200
+ readonly plan: null;
201
+ readonly groups: readonly RollPlanGroup[];
202
+ readonly cost: null;
203
+ readonly error: DiceRollError;
204
+ }
205
+ export type DiceNotationInspection = ValidDiceNotationInspection | InvalidDiceNotationInspection;
206
+ export type FreezeResultsMode = 'development' | 'always' | 'never';
207
+ export interface DiceCacheOptions {
208
+ readonly maxInputEntries?: number;
209
+ readonly maxProgramEntries?: number;
210
+ readonly maxProgramNodes?: number;
211
+ }
212
+ export interface DiceCacheStats {
213
+ readonly inputEntries: number;
214
+ readonly programEntries: number;
215
+ readonly programNodes: number;
216
+ readonly hits: number;
217
+ readonly misses: number;
218
+ readonly evictions: number;
219
+ }
220
+ export interface DiceEngineOptions {
221
+ readonly limits?: Partial<DiceLimits>;
222
+ readonly freezeResults?: FreezeResultsMode;
223
+ readonly cache?: false | DiceCacheOptions;
224
+ readonly randomAlgorithm?: RandomAlgorithm;
225
+ }
226
+ export interface CompileOptions {
227
+ readonly limits?: Partial<DiceLimits>;
228
+ }
229
+ type SeededRollOptions = {
230
+ readonly seed?: SeedInput;
231
+ readonly replay?: never;
232
+ readonly randomAlgorithm?: RandomAlgorithm;
233
+ };
234
+ type ReplayRollOptions = {
235
+ readonly seed?: never;
236
+ readonly replay: ReplayDescriptor;
237
+ readonly randomAlgorithm?: never;
238
+ };
239
+ export type RollOptions = CompileOptions & (SeededRollOptions | ReplayRollOptions);
240
+ export interface DiceEngine {
241
+ readonly limits: DiceLimits;
242
+ clearCache(): void;
243
+ compile(input: string, options?: CompileOptions): RollPlan;
244
+ getCacheStats(): DiceCacheStats;
245
+ inspect(input: string, options?: CompileOptions): DiceNotationInspection;
246
+ normalize(input: string): string;
247
+ roll(input: string | RollPlan, options?: RollOptions): DiceRollResult;
248
+ rollSummary(input: string | RollPlan, options?: RollOptions): DiceRollSummary;
249
+ verify(input: string, options?: CompileOptions): boolean;
250
+ }
251
+ export {};
package/licence.txt ADDED
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 - 2021 GreenImp Limited <greenimp.co.uk>
4
+ Copyright (c) 2021 - present GreenImp Media Limited <greenimp.co.uk>
5
+ Copyright (c) 2026 Arkanus
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "@erpg/dicecore",
3
+ "description": "ERPG dice core with ergonomic RPG notation, grouped rolls, structured UI data, deterministic replay, and safe parse-only inspection.",
4
+ "author": "Arkanus",
5
+ "version": "3.1.0",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "sideEffects": false,
9
+ "files": [
10
+ "dist",
11
+ "licence.txt",
12
+ "readme.md"
13
+ ],
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.js",
16
+ "exports": {
17
+ ".": {
18
+ "import": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "require": {
23
+ "types": "./dist/index.d.cts",
24
+ "default": "./dist/index.cjs"
25
+ }
26
+ },
27
+ "./package.json": "./package.json"
28
+ },
29
+ "types": "./dist/index.d.ts",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/arkanus-app/rpg-dice-roller.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/arkanus-app/rpg-dice-roller/issues"
36
+ },
37
+ "homepage": "https://github.com/arkanus-app/rpg-dice-roller",
38
+ "keywords": [
39
+ "dice",
40
+ "roller",
41
+ "rpg",
42
+ "roll",
43
+ "d&d",
44
+ "dnd",
45
+ "random",
46
+ "prng",
47
+ "roleplay",
48
+ "pathfinder",
49
+ "notation"
50
+ ],
51
+ "devDependencies": {
52
+ "@arethetypeswrong/cli": "^0.18.5",
53
+ "@eslint/js": "^9.39.2",
54
+ "@rollup/plugin-terser": "^1.0.0",
55
+ "@rollup/plugin-typescript": "^12.3.0",
56
+ "@types/node": "^24.0.0",
57
+ "@vitest/browser-playwright": "^4.1.10",
58
+ "@vitest/coverage-v8": "^4.1.10",
59
+ "eslint": "^9.39.2",
60
+ "fast-check": "^4.0.0",
61
+ "globals": "^16.0.0",
62
+ "jiti": "^2.0.0",
63
+ "publint": "^0.3.0",
64
+ "playwright": "^1.61.1",
65
+ "random-js": "^2.1.0",
66
+ "rollup": "^4.60.3",
67
+ "tslib": "^2.8.1",
68
+ "tsx": "^4.20.0",
69
+ "typescript": "^5.9.3",
70
+ "typescript-eslint": "^8.63.0",
71
+ "vite": "^8.1.4",
72
+ "vitest": "^4.1.10"
73
+ },
74
+ "dependencies": {},
75
+ "engines": {
76
+ "node": ">=22.0"
77
+ },
78
+ "scripts": {
79
+ "build": "npm run build:clean && npm run build:prepare && npm run build:bundle && npm run build:declaration",
80
+ "build:clean": "tsx ./scripts/clean.ts",
81
+ "build:prepare": "npm run typecheck && npm run lint",
82
+ "build:declaration": "tsx ./scripts/build-declarations.ts",
83
+ "build:bundle": "rollup --config=./rollup.config.mjs",
84
+ "benchmark": "node --expose-gc --import tsx ./scripts/benchmark.ts",
85
+ "benchmark:check": "npm run build && npm run benchmark -- --check --json",
86
+ "watch": "rollup --config=./rollup.config.mjs --watch",
87
+ "typecheck": "npm run typecheck:legacy && npm run typecheck:v3 && npm run typecheck:public-api && npm run typecheck:tooling",
88
+ "typecheck:legacy": "tsc -p tsconfig.json --pretty false",
89
+ "typecheck:v3": "tsc -p tsconfig.v3.json --pretty false",
90
+ "typecheck:public-api": "tsc -p tsconfig.type-tests.json --pretty false",
91
+ "typecheck:tooling": "tsc -p tsconfig.tools.json --pretty false",
92
+ "lint": "eslint . --max-warnings=0",
93
+ "lint:fix": "eslint . --fix --max-warnings=0",
94
+ "pretest": "npm run build:prepare",
95
+ "test": "vitest run",
96
+ "pretest:coverage": "npm run build:prepare",
97
+ "test:coverage": "vitest run --coverage",
98
+ "test:browser": "vitest run --config ./vitest.browser.config.ts",
99
+ "test:watch": "vitest",
100
+ "package:check": "npm run build && publint run . --strict && tsx ./scripts/check-package.ts",
101
+ "check": "npm run test:coverage && npm run package:check",
102
+ "prepare": "npm run build",
103
+ "prepublishOnly": "npm run check"
104
+ }
105
+ }
package/readme.md ADDED
@@ -0,0 +1,255 @@
1
+ # @erpg/dicecore 3.0.0
2
+
3
+ Núcleo de dados do ERPG para compilar, inspecionar e resolver notações de dados. A V3 é escrita em TypeScript estrito, não mantém estado global de RNG e entrega resultados `readonly`, JSON-safe e próprios para frontend, backend, automações e visualização 3D.
4
+
5
+ > A versão `3.0.0` está implementada neste repositório, mas este documento não indica que a publicação no npm ou a criação da tag já tenham ocorrido.
6
+
7
+ ## Requisitos e formatos
8
+
9
+ - Node.js 22 ou mais recente;
10
+ - browsers com ES2022 e `globalThis.crypto.getRandomValues`;
11
+ - ESM, CommonJS e declarações TypeScript;
12
+ - zero dependências de runtime no pacote publicado.
13
+
14
+ ```bash
15
+ npm install @erpg/dicecore
16
+ ```
17
+
18
+ ```ts
19
+ import { rollRpgDice } from '@erpg/dicecore';
20
+
21
+ const result = rollRpgDice('2d6+3', { seed: 'encontro-42' });
22
+ console.log(result.total, result.replay);
23
+ ```
24
+
25
+ ```js
26
+ const { rollRpgDice } = require('@erpg/dicecore');
27
+
28
+ const result = rollRpgDice('1d20+5', { seed: 'ataque-1' });
29
+ ```
30
+
31
+ Imports profundos, global UMD e as classes internas da V2 não fazem parte da API pública.
32
+
33
+ ## API pública
34
+
35
+ ```ts
36
+ import {
37
+ compileRpgDice,
38
+ createDiceEngine,
39
+ inspectRpgDiceNotation,
40
+ isDiceRollError,
41
+ isDiceRollErrorData,
42
+ normalizeRpgDiceNotation,
43
+ rollRpgDice,
44
+ rollRpgDiceSummary,
45
+ verifyRpgDiceNotation,
46
+ } from '@erpg/dicecore';
47
+
48
+ const plan = compileRpgDice('2#4d6kh3 [atributos]');
49
+ const result = rollRpgDice(plan, { seed: 'personagem-42' });
50
+
51
+ const inspection = inspectRpgDiceNotation('5d10>=8f=1');
52
+ if (inspection.isValid && inspection.plan) {
53
+ console.log(inspection.cost, rollRpgDice(inspection.plan).pool);
54
+ }
55
+
56
+ console.log(verifyRpgDiceNotation('d20+5')); // true
57
+ console.log(normalizeRpgDiceNotation('d + 2f')); // d20+2dF
58
+
59
+ try {
60
+ rollRpgDice('10001d6');
61
+ } catch (error: unknown) {
62
+ if (isDiceRollError(error)) {
63
+ console.error(error.code, error.span, error.details);
64
+ }
65
+ }
66
+ ```
67
+
68
+ As funções públicas são:
69
+
70
+ - `compileRpgDice(input, options?)`: normaliza, valida e compila uma vez para um `RollPlan` imutável;
71
+ - `createDiceEngine(options?)`: cria limites, cache de planos e política de congelamento isolados;
72
+ - `inspectRpgDiceNotation(input, options?)`: valida sem rolar e retorna plano, grupos e estimativa de custo;
73
+ - `rollRpgDice(inputOrPlan, options?)`: resolve a fórmula e sempre inclui um descritor de replay;
74
+ - `rollRpgDiceSummary(inputOrPlan, options?)`: resolve total, rolls, pool, replay e stats sem materializar dados, grupos, eventos ou output;
75
+ - `verifyRpgDiceNotation(input, options?)`: atalho booleano de validação;
76
+ - `normalizeRpgDiceNotation(input)`: aplica os atalhos de escrita do ERPG;
77
+ - `isDiceRollError(error)`: type guard para os erros estruturados da V3.
78
+ - `isDiceRollErrorData(value)`: valida erros transportados por JSON; restaure-os com `DiceRollError.fromJSON()`.
79
+
80
+ Um engine é indicado quando a aplicação precisa definir tetos próprios ou reutilizar planos com frequência:
81
+
82
+ ```ts
83
+ const dice = createDiceEngine({
84
+ limits: {
85
+ maxRolls: 20,
86
+ maxInitialDice: 500,
87
+ maxGeneratedDice: 1_000,
88
+ },
89
+ cache: {
90
+ maxInputEntries: 300,
91
+ maxProgramEntries: 100,
92
+ maxProgramNodes: 50_000,
93
+ },
94
+ randomAlgorithm: 'mt19937',
95
+ freezeResults: 'development',
96
+ });
97
+
98
+ const plan = dice.compile('3#2d20kh1+5');
99
+ const result = dice.roll(plan, {
100
+ seed: 'combate-17',
101
+ limits: { maxRolls: 3 },
102
+ });
103
+ ```
104
+
105
+ Limites informados em uma chamada podem apenas reduzir os tetos do engine. O plano original reutiliza a representação compilada; cópias via JSON ou spread são validadas e recompiladas pelo engine de destino antes da execução.
106
+
107
+ ## Limites de segurança
108
+
109
+ As opções usam `limits`, tanto no engine quanto por chamada:
110
+
111
+ ```ts
112
+ const result = rollRpgDice('3#2d6!', {
113
+ seed: 'teste-de-carga',
114
+ limits: {
115
+ maxInputLength: 4_096,
116
+ maxAstDepth: 64,
117
+ maxAstNodes: 10_000,
118
+ maxRolls: 3,
119
+ maxInitialDice: 100,
120
+ maxGeneratedDice: 500,
121
+ maxRandomCalls: 2_000,
122
+ maxEvents: 2_000,
123
+ maxSides: 4_294_967_296,
124
+ maxSeedLength: 1_024,
125
+ maxModifierSteps: 10_000,
126
+ maxResolvedGroups: 10_000,
127
+ maxResultItems: 25_000,
128
+ maxOutputLength: 100_000,
129
+ },
130
+ });
131
+ ```
132
+
133
+ Os padrões completos estão em `DEFAULT_DICE_LIMITS`. A inspeção calcula custo estático e pior caso; parser e executor também aplicam orçamentos duros durante a construção e a execução.
134
+
135
+ ## Resultado V3
136
+
137
+ ```ts
138
+ interface DiceRollResult {
139
+ readonly type: 'dice-roll';
140
+ readonly schemaVersion: 3;
141
+ readonly input: string;
142
+ readonly notation: string;
143
+ readonly normalizedNotation: string;
144
+ readonly comment: string;
145
+ readonly total: number;
146
+ readonly output: string;
147
+ readonly replay: ReplayDescriptor;
148
+ readonly stats: ExecutionStats;
149
+ readonly rolls: readonly ResolvedRoll[];
150
+ readonly groups: readonly ResolvedGroup[];
151
+ readonly dice: readonly ResolvedDie[];
152
+ readonly events: readonly DiceEvent[];
153
+ readonly pool: PoolSummary | null;
154
+ }
155
+ ```
156
+
157
+ - `rolls` separa cada execução de `N#formula` por ranges contíguos (`diceRange`, `groupRange`, `eventRange`); o `total` raiz é a soma de seus totais;
158
+ - `groups` resolve dados, expressões, funções e grupos com IDs e `SourceSpan` estáveis;
159
+ - `dice` separa face inicial (`rawValue`), valor final (`value`), contribuição e inclusão;
160
+ - `events` registra rolls, rerolls, explosões, transformações, inclusão, exclusão e classificação de dados e grupos na ordem do executor. Consumidores visuais devem reconstruir dependências por ID: o `roll` de um filho explosivo é registrado antes do evento `explode` que o liga ao pai;
161
+ - `pool` é `null` sem target e agrega sucessos/falhas quando a notação usa um target.
162
+
163
+ Veja o contrato completo em [docs/API_V3.md](docs/API_V3.md) e a atualização de consumidores em [docs/MIGRATION_V3.md](docs/MIGRATION_V3.md).
164
+
165
+ ## Seed e replay
166
+
167
+ Toda rolagem retorna:
168
+
169
+ ```ts
170
+ interface ReplayDescriptor {
171
+ readonly schemaVersion: 2;
172
+ readonly algorithm: 'mt19937' | 'xoshiro128ss';
173
+ readonly algorithmVersion: 1;
174
+ readonly executionVersion: 1;
175
+ readonly mathProfile: 'decimal12-v1';
176
+ readonly origin: 'provided-number' | 'provided-string' | 'crypto';
177
+ readonly seedMaterial: string; // 32 hex, sem a seed textual
178
+ readonly planFingerprint: string; // 32 hex
179
+ }
180
+ ```
181
+
182
+ Com `seed: string | number`, a mesma fórmula, seed de entrada e versão do core produzem a mesma sequência. Sem seed, o core gera 128 bits com `crypto.getRandomValues`; não há fallback para `Math.random`. O descritor retornado pode reproduzir exatamente qualquer uma das duas origens:
183
+
184
+ ```ts
185
+ const first = rollRpgDice('2d20kh1');
186
+ const repeated = rollRpgDice('2d20kh1', { replay: first.replay });
187
+ ```
188
+
189
+ `seed` e `replay` são mutuamente exclusivos. O replay já é vinculado ao fingerprint da fórmula; outra fórmula falha com `REPLAY_PLAN_MISMATCH`. MT19937 permanece o padrão e `xoshiro128ss` é opt-in.
190
+
191
+ ## Notação ERPG
192
+
193
+ A normalização preserva atalhos comuns:
194
+
195
+ - `d` → `d20`, `2d` → `2d20`;
196
+ - `f` → `4dF`, `2f` → `2dF`, `df` → `dF`;
197
+ - `ei6` → `!>=6`;
198
+ - `km` → `kl`, e `k`, `kh` ou `kl` sem quantidade recebem `1`;
199
+ - combinações simples como `+-`, `-+`, `++` e `--` são limpas;
200
+ - `N#formula` executa rolagens independentes; `N` também pode ser uma expressão matemática determinística, como `(3-1)#1d20` (com `()`, `{}` ou `[]` para agrupamento);
201
+ - comentários podem usar `[texto]`, `//`, `#` ou `/* ... */` conforme o contexto.
202
+
203
+ A sintaxe inclui dados padrão, percentuais e Fudge; aritmética e funções; grupos; keep/drop; reroll/unique; explode/compound/penetrate; min/max; critical; sort; e targets de sucesso/falha.
204
+
205
+ ```ts
206
+ rollRpgDice('4d6kh3');
207
+ rollRpgDice('1d%+2dF');
208
+ rollRpgDice('5d10>=8f=1');
209
+ rollRpgDice('{1d8,1d10}kh1');
210
+ rollRpgDice('ceil(1d6/2)+pow(2,3)');
211
+ ```
212
+
213
+ ## Integração com dice3dview
214
+
215
+ O `dicecore` decide o resultado; o `dice3dview` apenas o apresenta. A API de timeline recebe definições visuais e o journal resolvido sem interpretar a notação novamente:
216
+
217
+ ```ts
218
+ const result = rollRpgDice('2d6!kh2', { seed: 'cena-9' });
219
+ const supportedSides = new Set([2, 4, 6, 8, 10, 12, 20, 100]);
220
+ const visualDice = result.dice.filter(
221
+ (die) => typeof die.sides === 'number' && supportedSides.has(die.sides),
222
+ );
223
+ const visualIds = new Set(visualDice.map((die) => die.id));
224
+
225
+ await dice3dview.displayTimeline({
226
+ id: 'cena-9',
227
+ seed: 'cena-9',
228
+ dice: visualDice.map((die) => ({ id: die.id, sides: die.sides })),
229
+ events: result.events.filter(
230
+ (event) => event.subject === 'die' && visualIds.has(event.dieId),
231
+ ),
232
+ });
233
+
234
+ const scoring = result.dice.map((die) => ({
235
+ id: die.id,
236
+ parentDieId: die.parentDieId,
237
+ value: die.value,
238
+ included: die.included,
239
+ contribution: die.contribution,
240
+ }));
241
+ ```
242
+
243
+ O viewer pré-compila o journal inteiro porque o `roll` de um filho aparece antes do `explode` correspondente. Dados excluídos ainda podem ser animados, mas `included` e `contribution` governam a pontuação. `parentDieId` liga uma explosão ao dado que a originou. `transform` representa valor semântico: compound pode ultrapassar o número de faces e penetrate pode chegar a zero, portanto esses valores nunca devem ser usados como face física.
244
+
245
+ ## Desenvolvimento da V3
246
+
247
+ O parser e o MT19937 da V3 são implementações TypeScript do próprio pacote. Vitest, o perfil TypeScript estrito e as verificações do tarball fazem parte da toolchain. `dist/` é sempre gerado e não é versionado.
248
+
249
+ O núcleo V2 não é exportado pelo pacote V3. Ele permanece no repositório apenas como corpus de compatibilidade durante o desenvolvimento e pode ser removido quando a migração for encerrada.
250
+
251
+ ## Atribuição e licença
252
+
253
+ Este pacote é um derivado mantido pelo ERPG a partir do projeto open source `@dice-roller/rpg-dice-roller`, de GreenImp. A V3 substitui o parser e o runtime publicados por implementações próprias em TypeScript, mantendo o crédito e o aviso original em `licence.txt`.
254
+
255
+ Licença MIT.