@dice-o-rolla/dice-engine 0.1.1 → 0.3.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/README.md CHANGED
@@ -19,7 +19,9 @@ engine.destroy();
19
19
  ```
20
20
 
21
21
  The browser entry point initializes Rapier WASM, creates the Three.js renderer, uses Web Crypto for
22
- throw generation, and releases partially initialized resources if composition fails.
22
+ throw generation, and releases partially initialized resources if composition fails. Concurrent
23
+ `initialize()` calls coalesce, while every roll promise terminates by settlement, cancellation,
24
+ timeout, or failure.
23
25
 
24
26
  ## Custom adapters
25
27
 
@@ -32,6 +34,49 @@ await engine.initialize();
32
34
 
33
35
  The main entry point depends on domain contracts rather than concrete Rapier or Three.js types.
34
36
 
37
+ ## Deterministic simulation and replay
38
+
39
+ `simulate()` runs the configured physics adapter with a per-call seeded random source and returns a
40
+ JSON-serializable `PhysicalRollTrace`. It does not render or emit the ordinary roll lifecycle events.
41
+ Set `captureFrames: true` when the trace will be animated; otherwise the trace contains only its
42
+ terminal frame.
43
+
44
+ ```ts
45
+ const trace = await engine.simulate('2d20kh1', {
46
+ seed: 2026,
47
+ captureFrames: true,
48
+ frameIntervalSteps: 2,
49
+ });
50
+
51
+ await engine.replay(trace, {
52
+ theme: { material: 'matte', roughness: 0.85 },
53
+ signal: abortController.signal,
54
+ });
55
+ ```
56
+
57
+ `replay()` consumes the captured transforms without stepping physics. Its optional theme is applied
58
+ through the normal engine theme state, and the terminal dice remain rendered until the next roll,
59
+ replay, `clear()`, or `destroy()`. Simulation and replay require an idle engine because the facade
60
+ owns one physics world, renderer, and frame scheduler. Replay cancellation rejects with
61
+ `RollCancelledError`.
62
+
63
+ Traces include producer and physics profile metadata, initial throw conditions, definition
64
+ fingerprints, collision/impact events, and the immutable logical result. Replay validates registered
65
+ definitions, final orientation-derived faces, and the aggregate total before rendering. Default
66
+ trace limits are 1,200 frames, 60,000 die samples, and 20,000 events; customize them through
67
+ `DiceEngineOptions.traceLimits`. `TraceLimitExceededError` identifies the rejected dimension.
68
+
69
+ ## Visual presets and optional effects
70
+
71
+ `registerVisualPreset()` associates a logical die with a validated physical geometry, scale, face
72
+ labels, and optional value map. `skinId` and `soundPackId` are opaque application-owned references;
73
+ the engine does not load assets. Skins and sound definitions belong in the optional
74
+ `@dice-o-rolla/dice-assets` package, which is not a dependency of the engine.
75
+
76
+ The engine emits `die:spawn` and `die:remove` lifecycle events. Collision events are opt-in through
77
+ `DiceEngineOptions.collisionEvents`, bounded by `maxEventsPerFrame`, and suitable for an external
78
+ sound or effects adapter.
79
+
35
80
  ## Supported notation
36
81
 
37
82
  The initial grammar supports standard polyhedral expressions and integer modifiers, including:
@@ -39,11 +84,23 @@ The initial grammar supports standard polyhedral expressions and integer modifie
39
84
  ```text
40
85
  d20
41
86
  4d6 + 2
87
+ 4d6kh3
88
+ 2d20kl1
89
+ 5d20s{1=-2,17..19=1,20=2}
42
90
  d%
43
91
  d100
44
92
  d66
45
93
  ```
46
94
 
95
+ Keep/drop rolls retain every physical die in `result.dice` and expose the selection through
96
+ `included`. Score maps expose each contribution through `score`; unlisted faces contribute zero.
97
+ Selection is applied before scoring, followed by integer modifiers. Paired `d%`, `d100`, and `d66`
98
+ terms currently reject keep/drop and score operations.
99
+
100
+ Engine-produced dice also contain immutable `provenance`: stable term, logical-die, and physical-die
101
+ coordinates plus the settled face, inclusion state, and contribution. Consumers can explain a
102
+ total without reparsing notation or depending on renderer state.
103
+
47
104
  Default resource limits reject oversized notation, more than 50 logical or physical dice, and more
48
105
  than eight pending rolls. Consumers may lower or explicitly raise these limits through
49
106
  `DiceEngineOptions.limits` after testing their target devices.
@@ -52,7 +109,13 @@ than eight pending rolls. Consumers may lower or explicitly raise these limits t
52
109
 
53
110
  Client-side results are not authoritative for rankings, prizes, or wagering. Call `destroy()` when
54
111
  the engine is no longer needed to release frame scheduling, observers, physics resources, WebGL
55
- resources, and the renderer canvas.
112
+ resources, and the renderer canvas. `clear()` keeps the engine reusable; `destroy()` is idempotent
113
+ and final. See the canonical
114
+ [lifecycle and runtime contract](https://github.com/creepiest-space/dice-o-rolla/blob/main/docs/engine.md).
115
+ Consumers upgrading from `0.1` should also review the
116
+ [0.2 migration notes](https://github.com/creepiest-space/dice-o-rolla/blob/main/docs/migration-0.2.md).
117
+ Consumers upgrading from `0.2` should review the
118
+ [0.3 migration notes](https://github.com/creepiest-space/dice-o-rolla/blob/main/docs/migration-0.3.md).
56
119
 
57
120
  ## License
58
121
 
@@ -1,16 +1,23 @@
1
1
  import { TypedEventEmitter } from '@dice-o-rolla/dice-core';
2
2
  import type { RollResult } from '@dice-o-rolla/dice-core';
3
- import type { RendererViewport } from '@dice-o-rolla/dice-renderer';
4
- import type { DiceEngineEvents, DiceEngineFacade, DiceEngineOptions, DiceTheme, RollOptions } from './types.js';
3
+ import { type RendererViewport, type VisualPresetDescriptor } from '@dice-o-rolla/dice-renderer';
4
+ import type { DiceEngineEvents, DiceEngineFacade, DiceEngineOptions, DiceTheme, PhysicalRollTrace, RegisterEngineVisualPresetOptions, ReplayOptions, RollOptions, SimulateOptions } from './types.js';
5
+ import { type PhysicalDieType } from './visual-presets.js';
5
6
  export declare class DiceEngine extends TypedEventEmitter<DiceEngineEvents> implements DiceEngineFacade {
6
7
  #private;
7
8
  constructor(options: DiceEngineOptions);
8
9
  initialize(): Promise<void>;
9
10
  roll(notation: string, options?: RollOptions): Promise<RollResult>;
11
+ simulate(notation: string, options: SimulateOptions): Promise<PhysicalRollTrace>;
12
+ replay(trace: PhysicalRollTrace, options?: ReplayOptions): Promise<void>;
10
13
  cancel(sessionId?: string): boolean;
11
14
  clear(): void;
12
15
  resize(viewport: RendererViewport): void;
13
16
  setTheme(theme: Partial<DiceTheme>): DiceTheme;
14
17
  get theme(): DiceTheme;
18
+ registerVisualPreset(source: VisualPresetDescriptor, options?: RegisterEngineVisualPresetOptions): VisualPresetDescriptor;
19
+ unregisterVisualPreset(id: string): boolean;
20
+ setVisualPreset(dieType: PhysicalDieType, presetId: string): void;
21
+ getVisualPreset(dieType: PhysicalDieType): VisualPresetDescriptor;
15
22
  destroy(): void;
16
23
  }