@dice-o-rolla/dice-renderer 0.1.0 → 0.2.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
@@ -9,4 +9,8 @@ implementation is available from `@dice-o-rolla/dice-renderer-three`.
9
9
  import type { DiceRenderer, RendererTheme } from '@dice-o-rolla/dice-renderer';
10
10
  ```
11
11
 
12
+ The package also exports `VisualPresetRegistry`. Preset descriptors contain geometry, scaling,
13
+ labels, value mapping, and optional opaque asset IDs, but never load skins or sounds. A renderer is
14
+ not required to depend on `@dice-o-rolla/dice-assets`.
15
+
12
16
  Licensed under Apache-2.0. See `LICENSE`, `NOTICE`, and `THIRD_PARTY_NOTICES.md` in the package.
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export type { DiceRenderer, RenderDieState, RendererTheme, RendererViewport, RenderTransform, } from './types.js';
2
+ export { createVisualPresetDescriptor, VisualPresetRegistry, type RegisterVisualPresetOptions, type VisualPresetDescriptor, type VisualPresetMetadataValue, } from './visual-preset-registry.js';
package/dist/index.js CHANGED
@@ -0,0 +1 @@
1
+ export { createVisualPresetDescriptor, VisualPresetRegistry, } from './visual-preset-registry.js';
package/dist/types.d.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import type { QuaternionLike, Vector3Like } from '@dice-o-rolla/dice-core';
2
+ import type { VisualPresetDescriptor } from './visual-preset-registry.js';
2
3
  export interface RenderTransform {
3
4
  readonly position: Vector3Like;
4
5
  readonly quaternion: QuaternionLike;
5
6
  }
6
7
  export interface RenderDieState {
7
8
  readonly id: string;
9
+ readonly presetId: string;
8
10
  readonly geometryId: string;
11
+ readonly scale: number;
9
12
  readonly faceLabels?: Readonly<Record<number, string | number>>;
10
13
  readonly previous: RenderTransform;
11
14
  readonly current: RenderTransform;
@@ -24,6 +27,8 @@ export interface RendererTheme {
24
27
  }
25
28
  export interface DiceRenderer {
26
29
  initialize(): Promise<void> | void;
30
+ registerPreset(preset: VisualPresetDescriptor): void;
31
+ unregisterPreset(id: string): void;
27
32
  createDie(state: RenderDieState): void;
28
33
  updateDie(state: RenderDieState): void;
29
34
  removeDie(id: string): void;
@@ -0,0 +1,26 @@
1
+ export type VisualPresetMetadataValue = string | number | boolean;
2
+ export interface VisualPresetDescriptor {
3
+ readonly id: string;
4
+ readonly dieType: string;
5
+ readonly geometryId: string;
6
+ readonly scale?: number;
7
+ readonly skinId?: string;
8
+ readonly soundPackId?: string;
9
+ readonly faceLabels?: Readonly<Record<number, string | number>>;
10
+ readonly valueMap?: Readonly<Record<number, number>>;
11
+ readonly metadata?: Readonly<Record<string, VisualPresetMetadataValue>>;
12
+ }
13
+ export interface RegisterVisualPresetOptions {
14
+ readonly replace?: boolean;
15
+ }
16
+ export declare function createVisualPresetDescriptor(source: VisualPresetDescriptor): VisualPresetDescriptor;
17
+ export declare class VisualPresetRegistry {
18
+ #private;
19
+ constructor(presets?: readonly VisualPresetDescriptor[]);
20
+ get revision(): number;
21
+ register(source: VisualPresetDescriptor, options?: RegisterVisualPresetOptions): VisualPresetDescriptor;
22
+ unregister(id: string): VisualPresetDescriptor | undefined;
23
+ get(id: string): VisualPresetDescriptor | undefined;
24
+ has(id: string): boolean;
25
+ list(): readonly VisualPresetDescriptor[];
26
+ }
@@ -0,0 +1,100 @@
1
+ const PRESET_ID_PATTERN = /^[a-z0-9][a-z0-9._:-]*$/i;
2
+ function assertIdentifier(value, name) {
3
+ if (!PRESET_ID_PATTERN.test(value)) {
4
+ throw new RangeError(`${name} must be a non-empty portable identifier`);
5
+ }
6
+ }
7
+ function cloneNumericRecord(source, name) {
8
+ if (source === undefined)
9
+ return undefined;
10
+ const copy = {};
11
+ for (const [rawKey, value] of Object.entries(source)) {
12
+ const key = Number(rawKey);
13
+ if (!Number.isSafeInteger(key) || key <= 0) {
14
+ throw new RangeError(`${name} keys must be positive safe integers`);
15
+ }
16
+ if (typeof value === 'number' && !Number.isSafeInteger(value)) {
17
+ throw new RangeError(`${name} numeric values must be safe integers`);
18
+ }
19
+ copy[key] = value;
20
+ }
21
+ return Object.freeze(copy);
22
+ }
23
+ function cloneMetadata(source) {
24
+ if (source === undefined)
25
+ return undefined;
26
+ const copy = {};
27
+ for (const [key, value] of Object.entries(source)) {
28
+ if (key.length === 0)
29
+ throw new RangeError('metadata keys must not be empty');
30
+ if (typeof value === 'number' && !Number.isFinite(value)) {
31
+ throw new RangeError('metadata numeric values must be finite');
32
+ }
33
+ copy[key] = value;
34
+ }
35
+ return Object.freeze(copy);
36
+ }
37
+ export function createVisualPresetDescriptor(source) {
38
+ assertIdentifier(source.id, 'id');
39
+ assertIdentifier(source.dieType, 'dieType');
40
+ assertIdentifier(source.geometryId, 'geometryId');
41
+ if (source.skinId !== undefined)
42
+ assertIdentifier(source.skinId, 'skinId');
43
+ if (source.soundPackId !== undefined)
44
+ assertIdentifier(source.soundPackId, 'soundPackId');
45
+ const scale = source.scale ?? 1;
46
+ if (!Number.isFinite(scale) || scale <= 0) {
47
+ throw new RangeError('scale must be a positive finite number');
48
+ }
49
+ const faceLabels = cloneNumericRecord(source.faceLabels, 'faceLabels');
50
+ const valueMap = cloneNumericRecord(source.valueMap, 'valueMap');
51
+ const metadata = cloneMetadata(source.metadata);
52
+ return Object.freeze({
53
+ id: source.id,
54
+ dieType: source.dieType,
55
+ geometryId: source.geometryId,
56
+ scale,
57
+ ...(source.skinId === undefined ? {} : { skinId: source.skinId }),
58
+ ...(source.soundPackId === undefined ? {} : { soundPackId: source.soundPackId }),
59
+ ...(faceLabels === undefined ? {} : { faceLabels }),
60
+ ...(valueMap === undefined ? {} : { valueMap }),
61
+ ...(metadata === undefined ? {} : { metadata }),
62
+ });
63
+ }
64
+ export class VisualPresetRegistry {
65
+ #presets = new Map();
66
+ #revision = 0;
67
+ constructor(presets = []) {
68
+ for (const preset of presets)
69
+ this.register(preset);
70
+ }
71
+ get revision() {
72
+ return this.#revision;
73
+ }
74
+ register(source, options = {}) {
75
+ const preset = createVisualPresetDescriptor(source);
76
+ if (this.#presets.has(preset.id) && options.replace !== true) {
77
+ throw new Error(`Visual preset "${preset.id}" is already registered`);
78
+ }
79
+ this.#presets.set(preset.id, preset);
80
+ this.#revision += 1;
81
+ return preset;
82
+ }
83
+ unregister(id) {
84
+ const preset = this.#presets.get(id);
85
+ if (preset === undefined)
86
+ return undefined;
87
+ this.#presets.delete(id);
88
+ this.#revision += 1;
89
+ return preset;
90
+ }
91
+ get(id) {
92
+ return this.#presets.get(id);
93
+ }
94
+ has(id) {
95
+ return this.#presets.has(id);
96
+ }
97
+ list() {
98
+ return Object.freeze(Array.from(this.#presets.values()));
99
+ }
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dice-o-rolla/dice-renderer",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Renderer-neutral dice snapshots, themes, and rendering lifecycle contracts.",
5
5
  "keywords": [
6
6
  "dice",
@@ -8,7 +8,16 @@
8
8
  "renderer",
9
9
  "typescript"
10
10
  ],
11
+ "homepage": "https://github.com/creepiest-space/dice-o-rolla#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/creepiest-space/dice-o-rolla/issues"
14
+ },
11
15
  "license": "Apache-2.0",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/creepiest-space/dice-o-rolla.git",
19
+ "directory": "packages/dice-renderer"
20
+ },
12
21
  "files": [
13
22
  "dist",
14
23
  "README.md",
@@ -31,7 +40,7 @@
31
40
  "registry": "https://registry.npmjs.org/"
32
41
  },
33
42
  "dependencies": {
34
- "@dice-o-rolla/dice-core": "0.1.0"
43
+ "@dice-o-rolla/dice-core": "0.2.0"
35
44
  },
36
45
  "engines": {
37
46
  "node": ">=20.0.0"