@arcane-engine/runtime 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/package.json +4 -2
- package/src/agent/protocol.ts +35 -1
- package/src/agent/types.ts +98 -13
- package/src/particles/emitter.test.ts +323 -0
- package/src/particles/emitter.ts +409 -0
- package/src/particles/index.ts +25 -0
- package/src/particles/types.ts +236 -0
- package/src/pathfinding/astar.ts +27 -0
- package/src/pathfinding/types.ts +39 -0
- package/src/physics/aabb.ts +55 -8
- package/src/rendering/animation.ts +73 -0
- package/src/rendering/audio.ts +29 -9
- package/src/rendering/camera.ts +28 -4
- package/src/rendering/input.ts +45 -9
- package/src/rendering/lighting.ts +29 -3
- package/src/rendering/loop.ts +16 -3
- package/src/rendering/sprites.ts +24 -1
- package/src/rendering/text.ts +52 -6
- package/src/rendering/texture.ts +22 -4
- package/src/rendering/tilemap.ts +36 -4
- package/src/rendering/types.ts +37 -19
- package/src/rendering/validate.ts +48 -3
- package/src/state/error.ts +21 -2
- package/src/state/observe.ts +40 -9
- package/src/state/prng.ts +88 -10
- package/src/state/query.ts +115 -15
- package/src/state/store.ts +42 -11
- package/src/state/transaction.ts +116 -12
- package/src/state/types.ts +31 -5
- package/src/systems/system.ts +77 -5
- package/src/systems/types.ts +52 -6
- package/src/testing/harness.ts +103 -5
- package/src/testing/mock-renderer.test.ts +16 -20
- package/src/tweening/chain.test.ts +191 -0
- package/src/tweening/chain.ts +103 -0
- package/src/tweening/easing.test.ts +134 -0
- package/src/tweening/easing.ts +288 -0
- package/src/tweening/helpers.test.ts +185 -0
- package/src/tweening/helpers.ts +166 -0
- package/src/tweening/index.ts +76 -0
- package/src/tweening/tween.test.ts +322 -0
- package/src/tweening/tween.ts +296 -0
- package/src/tweening/types.ts +134 -0
- package/src/ui/colors.ts +129 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/primitives.ts +44 -5
- package/src/ui/types.ts +41 -2
package/src/rendering/text.ts
CHANGED
|
@@ -4,25 +4,41 @@ import { getCamera } from "./camera.ts";
|
|
|
4
4
|
|
|
5
5
|
// --- Types ---
|
|
6
6
|
|
|
7
|
+
/** Descriptor for a bitmap font backed by a texture atlas. */
|
|
7
8
|
export type BitmapFont = {
|
|
9
|
+
/** Texture handle containing the font glyph atlas. */
|
|
8
10
|
textureId: TextureId;
|
|
11
|
+
/** Width of each glyph cell in pixels. */
|
|
9
12
|
glyphW: number;
|
|
13
|
+
/** Height of each glyph cell in pixels. */
|
|
10
14
|
glyphH: number;
|
|
15
|
+
/** Number of glyph columns in the atlas. */
|
|
11
16
|
columns: number;
|
|
17
|
+
/** Number of glyph rows in the atlas. */
|
|
12
18
|
rows: number;
|
|
19
|
+
/** ASCII code of the first glyph in the atlas. Default: 32 (space). */
|
|
13
20
|
firstChar: number;
|
|
14
21
|
};
|
|
15
22
|
|
|
23
|
+
/** Options for {@link drawText} and {@link measureText}. */
|
|
16
24
|
export type TextOptions = {
|
|
25
|
+
/** Font to use. Default: built-in 8x8 CP437 bitmap font via getDefaultFont(). */
|
|
17
26
|
font?: BitmapFont;
|
|
27
|
+
/** Scale multiplier for glyph size. Default: 1. A value of 2 draws at 16x16. */
|
|
18
28
|
scale?: number;
|
|
29
|
+
/** RGBA tint color for the text (0.0-1.0 per channel). Default: white. */
|
|
19
30
|
tint?: { r: number; g: number; b: number; a: number };
|
|
31
|
+
/** Draw order layer. Default: 100 (above most game sprites). */
|
|
20
32
|
layer?: number;
|
|
33
|
+
/** If true, position is in screen pixels (HUD). If false, position is in world units. Default: false. */
|
|
21
34
|
screenSpace?: boolean;
|
|
22
35
|
};
|
|
23
36
|
|
|
37
|
+
/** Result of {@link measureText}. Dimensions in pixels (before camera transform). */
|
|
24
38
|
export type TextMeasurement = {
|
|
39
|
+
/** Total width in pixels (text.length * glyphW * scale). */
|
|
25
40
|
width: number;
|
|
41
|
+
/** Total height in pixels (glyphH * scale). */
|
|
26
42
|
height: number;
|
|
27
43
|
};
|
|
28
44
|
|
|
@@ -54,6 +70,15 @@ let defaultFont: BitmapFont | null = null;
|
|
|
54
70
|
|
|
55
71
|
/**
|
|
56
72
|
* Create a bitmap font descriptor from a texture atlas.
|
|
73
|
+
* The atlas should contain a grid of equally-sized glyphs in ASCII order.
|
|
74
|
+
*
|
|
75
|
+
* @param textureId - Texture handle of the font atlas (from loadTexture()).
|
|
76
|
+
* @param glyphW - Width of each glyph cell in pixels.
|
|
77
|
+
* @param glyphH - Height of each glyph cell in pixels.
|
|
78
|
+
* @param columns - Number of glyph columns in the atlas.
|
|
79
|
+
* @param rows - Number of glyph rows in the atlas.
|
|
80
|
+
* @param firstChar - ASCII code of the first glyph in the atlas. Default: 32 (space).
|
|
81
|
+
* @returns BitmapFont descriptor for use with drawText().
|
|
57
82
|
*/
|
|
58
83
|
export function loadFont(
|
|
59
84
|
textureId: TextureId,
|
|
@@ -67,8 +92,10 @@ export function loadFont(
|
|
|
67
92
|
}
|
|
68
93
|
|
|
69
94
|
/**
|
|
70
|
-
* Get the default 8x8 bitmap font, lazily initialized.
|
|
95
|
+
* Get the default built-in 8x8 CP437 bitmap font, lazily initialized.
|
|
71
96
|
* In headless mode returns a dummy font (textureId 0).
|
|
97
|
+
*
|
|
98
|
+
* @returns The built-in BitmapFont (8x8 glyphs, 16 columns, 6 rows, ASCII 32-127).
|
|
72
99
|
*/
|
|
73
100
|
export function getDefaultFont(): BitmapFont {
|
|
74
101
|
if (defaultFont !== null) return defaultFont;
|
|
@@ -100,8 +127,12 @@ export function getDefaultFont(): BitmapFont {
|
|
|
100
127
|
}
|
|
101
128
|
|
|
102
129
|
/**
|
|
103
|
-
* Measure the pixel dimensions of a text string.
|
|
104
|
-
* Pure math
|
|
130
|
+
* Measure the pixel dimensions of a text string without drawing it.
|
|
131
|
+
* Pure math -- works in headless mode.
|
|
132
|
+
*
|
|
133
|
+
* @param text - The string to measure.
|
|
134
|
+
* @param options - Font and scale options. Default font and scale 1 if omitted.
|
|
135
|
+
* @returns Width and height in pixels.
|
|
105
136
|
*/
|
|
106
137
|
export function measureText(
|
|
107
138
|
text: string,
|
|
@@ -116,9 +147,24 @@ export function measureText(
|
|
|
116
147
|
}
|
|
117
148
|
|
|
118
149
|
/**
|
|
119
|
-
* Draw a text string using the sprite pipeline.
|
|
120
|
-
*
|
|
121
|
-
*
|
|
150
|
+
* Draw a text string using the sprite pipeline (one sprite per character).
|
|
151
|
+
* Must be called every frame. No-op in headless mode.
|
|
152
|
+
*
|
|
153
|
+
* @param text - The string to draw.
|
|
154
|
+
* @param x - X position (screen pixels if screenSpace, world units otherwise).
|
|
155
|
+
* @param y - Y position (screen pixels if screenSpace, world units otherwise).
|
|
156
|
+
* @param options - Font, scale, tint, layer, and screenSpace options.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* // Draw HUD text at the top-left of the screen
|
|
160
|
+
* drawText("HP: 100", 10, 10, { scale: 2, screenSpace: true });
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* // Draw world-space text above an entity
|
|
164
|
+
* drawText("Enemy", enemy.x, enemy.y - 12, {
|
|
165
|
+
* tint: { r: 1, g: 0.3, b: 0.3, a: 1 },
|
|
166
|
+
* layer: 50,
|
|
167
|
+
* });
|
|
122
168
|
*/
|
|
123
169
|
export function drawText(
|
|
124
170
|
text: string,
|
package/src/rendering/texture.ts
CHANGED
|
@@ -5,9 +5,16 @@ const hasRenderOps =
|
|
|
5
5
|
typeof (globalThis as any).Deno?.core?.ops?.op_load_texture === "function";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Load a texture from a PNG file path. Returns
|
|
9
|
-
* Caches by path
|
|
8
|
+
* Load a texture from a PNG file path. Returns an opaque texture handle.
|
|
9
|
+
* Caches by path -- loading the same path twice returns the same handle.
|
|
10
10
|
* Returns 0 (no texture) in headless mode.
|
|
11
|
+
*
|
|
12
|
+
* @param path - File path to a PNG image (relative to game entry file or absolute).
|
|
13
|
+
* @returns Texture handle for use with drawSprite(), createTilemap(), etc.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const playerTex = loadTexture("assets/player.png");
|
|
17
|
+
* drawSprite({ textureId: playerTex, x: 0, y: 0, w: 32, h: 32 });
|
|
11
18
|
*/
|
|
12
19
|
export function loadTexture(path: string): TextureId {
|
|
13
20
|
if (!hasRenderOps) return 0;
|
|
@@ -15,9 +22,20 @@ export function loadTexture(path: string): TextureId {
|
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
/**
|
|
18
|
-
* Create a solid-color
|
|
19
|
-
*
|
|
25
|
+
* Create a 1x1 solid-color texture. Useful for rectangles, placeholder sprites,
|
|
26
|
+
* and UI elements. Cached by name -- creating the same name twice returns the same handle.
|
|
20
27
|
* Returns 0 (no texture) in headless mode.
|
|
28
|
+
*
|
|
29
|
+
* @param name - Unique name for caching (e.g. "red", "health-green").
|
|
30
|
+
* @param r - Red channel, 0-255.
|
|
31
|
+
* @param g - Green channel, 0-255.
|
|
32
|
+
* @param b - Blue channel, 0-255.
|
|
33
|
+
* @param a - Alpha channel, 0-255. Default: 255 (fully opaque).
|
|
34
|
+
* @returns Texture handle for use with drawSprite().
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* const redTex = createSolidTexture("red", 255, 0, 0);
|
|
38
|
+
* drawSprite({ textureId: redTex, x: 10, y: 10, w: 50, h: 50 });
|
|
21
39
|
*/
|
|
22
40
|
export function createSolidTexture(
|
|
23
41
|
name: string,
|
package/src/rendering/tilemap.ts
CHANGED
|
@@ -4,7 +4,14 @@ const hasRenderOps =
|
|
|
4
4
|
typeof (globalThis as any).Deno !== "undefined" &&
|
|
5
5
|
typeof (globalThis as any).Deno?.core?.ops?.op_create_tilemap === "function";
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Create a tilemap backed by a texture atlas. Returns an opaque TilemapId handle.
|
|
9
|
+
* The tilemap stores a grid of tile IDs that map to sub-regions of the atlas texture.
|
|
10
|
+
* Returns 0 in headless mode.
|
|
11
|
+
*
|
|
12
|
+
* @param opts - Tilemap configuration (atlas texture, grid size, tile size, atlas layout).
|
|
13
|
+
* @returns Tilemap handle for use with setTile(), getTile(), drawTilemap().
|
|
14
|
+
*/
|
|
8
15
|
export function createTilemap(opts: TilemapOptions): TilemapId {
|
|
9
16
|
if (!hasRenderOps) return 0;
|
|
10
17
|
return (globalThis as any).Deno.core.ops.op_create_tilemap(
|
|
@@ -17,7 +24,16 @@ export function createTilemap(opts: TilemapOptions): TilemapId {
|
|
|
17
24
|
);
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Set a tile at grid position (gx, gy).
|
|
29
|
+
* Tile IDs correspond to positions in the texture atlas (left-to-right, top-to-bottom).
|
|
30
|
+
* Tile ID 0 = empty (not drawn). No-op in headless mode.
|
|
31
|
+
*
|
|
32
|
+
* @param id - Tilemap handle from createTilemap().
|
|
33
|
+
* @param gx - Grid X position (column). 0 = leftmost.
|
|
34
|
+
* @param gy - Grid Y position (row). 0 = topmost.
|
|
35
|
+
* @param tileId - Tile index in the atlas (1-based). 0 = empty/clear.
|
|
36
|
+
*/
|
|
21
37
|
export function setTile(
|
|
22
38
|
id: TilemapId,
|
|
23
39
|
gx: number,
|
|
@@ -28,13 +44,29 @@ export function setTile(
|
|
|
28
44
|
(globalThis as any).Deno.core.ops.op_set_tile(id, gx, gy, tileId);
|
|
29
45
|
}
|
|
30
46
|
|
|
31
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Get the tile ID at grid position (gx, gy).
|
|
49
|
+
* Returns 0 if out of bounds or in headless mode.
|
|
50
|
+
*
|
|
51
|
+
* @param id - Tilemap handle from createTilemap().
|
|
52
|
+
* @param gx - Grid X position (column).
|
|
53
|
+
* @param gy - Grid Y position (row).
|
|
54
|
+
* @returns Tile ID at the given position, or 0 if empty/out of bounds.
|
|
55
|
+
*/
|
|
32
56
|
export function getTile(id: TilemapId, gx: number, gy: number): number {
|
|
33
57
|
if (!hasRenderOps) return 0;
|
|
34
58
|
return (globalThis as any).Deno.core.ops.op_get_tile(id, gx, gy);
|
|
35
59
|
}
|
|
36
60
|
|
|
37
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Draw all visible tiles as sprites. Only draws tiles within the camera viewport (culled).
|
|
63
|
+
* Must be called every frame. No-op in headless mode.
|
|
64
|
+
*
|
|
65
|
+
* @param id - Tilemap handle from createTilemap().
|
|
66
|
+
* @param x - World X offset for the tilemap origin. Default: 0.
|
|
67
|
+
* @param y - World Y offset for the tilemap origin. Default: 0.
|
|
68
|
+
* @param layer - Draw order layer. Default: 0.
|
|
69
|
+
*/
|
|
38
70
|
export function drawTilemap(
|
|
39
71
|
id: TilemapId,
|
|
40
72
|
x: number = 0,
|
package/src/rendering/types.ts
CHANGED
|
@@ -1,54 +1,72 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Opaque handle to a loaded texture. Returned by {@link loadTexture} or {@link createSolidTexture}.
|
|
3
|
+
* A value of 0 means "no texture" (headless mode fallback).
|
|
4
|
+
*/
|
|
2
5
|
export type TextureId = number;
|
|
3
6
|
|
|
4
|
-
/** Options for drawing a sprite. */
|
|
7
|
+
/** Options for drawing a sprite via {@link drawSprite}. */
|
|
5
8
|
export type SpriteOptions = {
|
|
6
9
|
/** Texture handle from loadTexture() or createSolidTexture(). */
|
|
7
10
|
textureId: TextureId;
|
|
8
|
-
/** World X position (top-left corner). */
|
|
11
|
+
/** World X position (top-left corner of sprite). */
|
|
9
12
|
x: number;
|
|
10
|
-
/** World Y position (top-left corner). */
|
|
13
|
+
/** World Y position (top-left corner of sprite). */
|
|
11
14
|
y: number;
|
|
12
|
-
/** Width in world units. */
|
|
15
|
+
/** Width in world units (pixels at zoom 1). */
|
|
13
16
|
w: number;
|
|
14
|
-
/** Height in world units. */
|
|
17
|
+
/** Height in world units (pixels at zoom 1). */
|
|
15
18
|
h: number;
|
|
16
|
-
/** Draw order layer
|
|
19
|
+
/** Draw order layer. Lower values are drawn first (behind). Default: 0. Use 100+ for HUD elements. */
|
|
17
20
|
layer?: number;
|
|
18
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* UV sub-rectangle for atlas/sprite-sheet textures.
|
|
23
|
+
* All values are normalized 0.0-1.0 (fraction of full texture).
|
|
24
|
+
* Default: full texture `{ x: 0, y: 0, w: 1, h: 1 }`.
|
|
25
|
+
*/
|
|
19
26
|
uv?: { x: number; y: number; w: number; h: number };
|
|
20
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* RGBA tint color multiplied with the texture color.
|
|
29
|
+
* Each channel is 0.0-1.0. Default: white `{ r: 1, g: 1, b: 1, a: 1 }` (no tint).
|
|
30
|
+
*/
|
|
21
31
|
tint?: { r: number; g: number; b: number; a: number };
|
|
22
32
|
};
|
|
23
33
|
|
|
24
|
-
/** Camera state. */
|
|
34
|
+
/** Camera state returned by {@link getCamera}. */
|
|
25
35
|
export type CameraState = {
|
|
36
|
+
/** Camera center X position in world units. */
|
|
26
37
|
x: number;
|
|
38
|
+
/** Camera center Y position in world units. */
|
|
27
39
|
y: number;
|
|
40
|
+
/** Zoom level. 1.0 = default, >1.0 = zoomed in, <1.0 = zoomed out. */
|
|
28
41
|
zoom: number;
|
|
29
42
|
};
|
|
30
43
|
|
|
31
|
-
/** Mouse position. */
|
|
44
|
+
/** Mouse position in screen or world coordinates. */
|
|
32
45
|
export type MousePosition = {
|
|
46
|
+
/** X position in pixels (screen) or world units (world). */
|
|
33
47
|
x: number;
|
|
48
|
+
/** Y position in pixels (screen) or world units (world). */
|
|
34
49
|
y: number;
|
|
35
50
|
};
|
|
36
51
|
|
|
37
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Opaque handle to a tilemap. Returned by {@link createTilemap}.
|
|
54
|
+
* A value of 0 means "no tilemap" (headless mode fallback).
|
|
55
|
+
*/
|
|
38
56
|
export type TilemapId = number;
|
|
39
57
|
|
|
40
|
-
/** Options for creating a tilemap. */
|
|
58
|
+
/** Options for creating a tilemap via {@link createTilemap}. */
|
|
41
59
|
export type TilemapOptions = {
|
|
42
|
-
/** Texture atlas handle from loadTexture(). */
|
|
60
|
+
/** Texture atlas handle from loadTexture(). Must be a valid TextureId. */
|
|
43
61
|
textureId: number;
|
|
44
|
-
/** Grid width in tiles. */
|
|
62
|
+
/** Grid width in tiles. Must be a positive integer. */
|
|
45
63
|
width: number;
|
|
46
|
-
/** Grid height in tiles. */
|
|
64
|
+
/** Grid height in tiles. Must be a positive integer. */
|
|
47
65
|
height: number;
|
|
48
|
-
/** Size of each tile in world units. */
|
|
66
|
+
/** Size of each tile in world units (pixels at zoom 1). Must be positive. */
|
|
49
67
|
tileSize: number;
|
|
50
|
-
/** Number of columns in the texture atlas. */
|
|
68
|
+
/** Number of tile columns in the texture atlas. Must be a positive integer. */
|
|
51
69
|
atlasColumns: number;
|
|
52
|
-
/** Number of rows in the texture atlas. */
|
|
70
|
+
/** Number of tile rows in the texture atlas. Must be a positive integer. */
|
|
53
71
|
atlasRows: number;
|
|
54
72
|
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime validation for rendering API calls
|
|
3
|
-
* Add
|
|
2
|
+
* Runtime validation for rendering API calls.
|
|
3
|
+
* Add validation to catch type errors and invalid values early in development.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/** Options controlling how validation errors are reported. */
|
|
6
7
|
export interface ValidationOptions {
|
|
8
|
+
/** If true, throw on validation failure. Default: true. */
|
|
7
9
|
throwOnError?: boolean;
|
|
10
|
+
/** If true, log errors to console.error. Default: false. */
|
|
8
11
|
logErrors?: boolean;
|
|
9
12
|
}
|
|
10
13
|
|
|
@@ -13,6 +16,15 @@ const defaultOptions: ValidationOptions = {
|
|
|
13
16
|
logErrors: false,
|
|
14
17
|
};
|
|
15
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Validate that a value is a finite number (not NaN, not Infinity).
|
|
21
|
+
*
|
|
22
|
+
* @param api - Name of the calling API (for error messages).
|
|
23
|
+
* @param param - Name of the parameter being validated.
|
|
24
|
+
* @param value - The value to validate.
|
|
25
|
+
* @param options - Error reporting options.
|
|
26
|
+
* @returns true if valid, false or throws if invalid.
|
|
27
|
+
*/
|
|
16
28
|
export function validateNumber(
|
|
17
29
|
api: string,
|
|
18
30
|
param: string,
|
|
@@ -37,6 +49,14 @@ export function validateNumber(
|
|
|
37
49
|
return true;
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Validate that a value is a valid Color object with r, g, b channels in 0.0-1.0.
|
|
54
|
+
*
|
|
55
|
+
* @param api - Name of the calling API (for error messages).
|
|
56
|
+
* @param color - The color object to validate.
|
|
57
|
+
* @param options - Error reporting options.
|
|
58
|
+
* @returns true if valid, false or throws if invalid.
|
|
59
|
+
*/
|
|
40
60
|
export function validateColor(
|
|
41
61
|
api: string,
|
|
42
62
|
color: any,
|
|
@@ -67,6 +87,13 @@ export function validateColor(
|
|
|
67
87
|
return true;
|
|
68
88
|
}
|
|
69
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Validate drawText options (x, y, size must be valid numbers, color must be valid).
|
|
92
|
+
*
|
|
93
|
+
* @param opts - The text options object to validate.
|
|
94
|
+
* @param options - Error reporting options.
|
|
95
|
+
* @returns true if valid, false or throws if invalid.
|
|
96
|
+
*/
|
|
70
97
|
export function validateTextOptions(
|
|
71
98
|
opts: any,
|
|
72
99
|
options: ValidationOptions = defaultOptions
|
|
@@ -87,6 +114,17 @@ export function validateTextOptions(
|
|
|
87
114
|
return true;
|
|
88
115
|
}
|
|
89
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Validate drawRect parameters (x, y, w, h must be finite numbers, color must be valid).
|
|
119
|
+
*
|
|
120
|
+
* @param x - X position to validate.
|
|
121
|
+
* @param y - Y position to validate.
|
|
122
|
+
* @param w - Width to validate.
|
|
123
|
+
* @param h - Height to validate.
|
|
124
|
+
* @param opts - Optional rect options with color.
|
|
125
|
+
* @param options - Error reporting options.
|
|
126
|
+
* @returns true if valid, false or throws if invalid.
|
|
127
|
+
*/
|
|
90
128
|
export function validateRectParams(
|
|
91
129
|
x: any,
|
|
92
130
|
y: any,
|
|
@@ -119,7 +157,14 @@ function handleError(message: string, options: ValidationOptions): boolean {
|
|
|
119
157
|
return false;
|
|
120
158
|
}
|
|
121
159
|
|
|
122
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Wrap a rendering function with automatic parameter validation.
|
|
162
|
+
* If validation fails, the wrapped function is not called.
|
|
163
|
+
*
|
|
164
|
+
* @param fn - The rendering function to wrap.
|
|
165
|
+
* @param validator - Validation function that receives the same args. Returns true if valid.
|
|
166
|
+
* @returns Wrapped function that validates before calling the original.
|
|
167
|
+
*/
|
|
123
168
|
export function withValidation<T extends (...args: any[]) => any>(
|
|
124
169
|
fn: T,
|
|
125
170
|
validator: (...args: Parameters<T>) => boolean
|
package/src/state/error.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Structured error type for the Arcane engine.
|
|
3
|
+
* Provides machine-readable codes, human-readable messages, and actionable context.
|
|
4
|
+
* Follows the error design in docs/api-design.md.
|
|
5
|
+
*
|
|
6
|
+
* - `code` - Machine-readable error code (e.g., "TRANSACTION_FAILED", "INVALID_PATH").
|
|
7
|
+
* - `message` - Human-readable error description.
|
|
8
|
+
* - `context.action` - What operation was being attempted.
|
|
9
|
+
* - `context.reason` - Why the operation failed.
|
|
10
|
+
* - `context.state` - Optional snapshot of relevant state at failure time.
|
|
11
|
+
* - `context.suggestion` - Optional suggestion for how to fix the error.
|
|
12
|
+
*/
|
|
2
13
|
export type ArcaneError = Readonly<{
|
|
3
14
|
code: string;
|
|
4
15
|
message: string;
|
|
@@ -10,7 +21,15 @@ export type ArcaneError = Readonly<{
|
|
|
10
21
|
}>;
|
|
11
22
|
}>;
|
|
12
23
|
|
|
13
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Create an ArcaneError with structured context.
|
|
26
|
+
* Pure function — returns a new frozen error object.
|
|
27
|
+
*
|
|
28
|
+
* @param code - Machine-readable error code (e.g., "TRANSACTION_FAILED").
|
|
29
|
+
* @param message - Human-readable error description.
|
|
30
|
+
* @param context - Structured context with action, reason, and optional suggestion.
|
|
31
|
+
* @returns A new ArcaneError object.
|
|
32
|
+
*/
|
|
14
33
|
export function createError(
|
|
15
34
|
code: string,
|
|
16
35
|
message: string,
|
package/src/state/observe.ts
CHANGED
|
@@ -1,36 +1,62 @@
|
|
|
1
1
|
import type { Diff } from "./transaction.ts";
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Callback invoked when an observed path changes.
|
|
5
|
+
* Receives the new value, old value, and context about the change.
|
|
6
|
+
*
|
|
7
|
+
* @param newValue - The value after the change.
|
|
8
|
+
* @param oldValue - The value before the change.
|
|
9
|
+
* @param context - Metadata about the change (path, full diff).
|
|
10
|
+
*/
|
|
4
11
|
export type ObserverCallback<T = unknown> = (
|
|
5
12
|
newValue: T,
|
|
6
13
|
oldValue: T,
|
|
7
14
|
context: ObserverContext,
|
|
8
15
|
) => void;
|
|
9
16
|
|
|
10
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Context provided to observer callbacks when a change is detected.
|
|
19
|
+
*
|
|
20
|
+
* - `path` - The specific path that changed (e.g., "player.hp").
|
|
21
|
+
* - `diff` - The full Diff from the transaction that triggered this notification.
|
|
22
|
+
*/
|
|
11
23
|
export type ObserverContext = Readonly<{
|
|
12
24
|
path: string;
|
|
13
25
|
diff: Diff;
|
|
14
26
|
}>;
|
|
15
27
|
|
|
16
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Function returned by observe() to unsubscribe from further notifications.
|
|
30
|
+
* Call it to stop receiving callbacks for that subscription.
|
|
31
|
+
*/
|
|
17
32
|
export type Unsubscribe = () => void;
|
|
18
33
|
|
|
19
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Pattern for matching state paths. Supports `*` wildcards at any segment position.
|
|
36
|
+
* Examples: "player.hp", "enemies.*.hp", "*.position".
|
|
37
|
+
* Each `*` matches exactly one path segment.
|
|
38
|
+
*/
|
|
20
39
|
export type PathPattern = string;
|
|
21
40
|
|
|
22
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Observer registry that manages subscriptions and dispatches change notifications.
|
|
43
|
+
* Created via {@link createObserverRegistry}. Used internally by GameStore.
|
|
44
|
+
*
|
|
45
|
+
* - `observe` - Subscribe to changes matching a path pattern. Returns an Unsubscribe function.
|
|
46
|
+
* - `notify` - Dispatch notifications to all matching observers for a given diff.
|
|
47
|
+
* - `clear` - Remove all observers (useful for cleanup/reset).
|
|
48
|
+
*/
|
|
23
49
|
export type ObserverRegistry<S> = Readonly<{
|
|
24
|
-
/** Subscribe to changes at a path pattern */
|
|
50
|
+
/** Subscribe to changes at a path pattern. Returns an unsubscribe function. */
|
|
25
51
|
observe: <T = unknown>(
|
|
26
52
|
pattern: PathPattern,
|
|
27
53
|
callback: ObserverCallback<T>,
|
|
28
54
|
) => Unsubscribe;
|
|
29
55
|
|
|
30
|
-
/** Notify all matching observers after a transaction commits */
|
|
56
|
+
/** Notify all matching observers after a transaction commits. */
|
|
31
57
|
notify: (oldState: S, newState: S, diff: Diff) => void;
|
|
32
58
|
|
|
33
|
-
/** Remove all observers */
|
|
59
|
+
/** Remove all observers. */
|
|
34
60
|
clear: () => void;
|
|
35
61
|
}>;
|
|
36
62
|
|
|
@@ -39,7 +65,12 @@ type Subscription = {
|
|
|
39
65
|
callback: ObserverCallback;
|
|
40
66
|
};
|
|
41
67
|
|
|
42
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Create a new observer registry for tracking state change subscriptions.
|
|
70
|
+
* Used internally by {@link createStore} to power the store's observe() method.
|
|
71
|
+
*
|
|
72
|
+
* @returns A new ObserverRegistry with observe, notify, and clear methods.
|
|
73
|
+
*/
|
|
43
74
|
export function createObserverRegistry<S>(): ObserverRegistry<S> {
|
|
44
75
|
const subscriptions = new Set<Subscription>();
|
|
45
76
|
|
package/src/state/prng.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Opaque PRNG state using the xoshiro128** algorithm.
|
|
3
|
+
* Serializable and deterministic — the same seed always produces the same sequence.
|
|
4
|
+
* All PRNG functions are pure: they take a PRNGState and return a new PRNGState.
|
|
5
|
+
* Create via {@link seed}.
|
|
6
|
+
*
|
|
7
|
+
* - `__brand` - Type brand, always "PRNGState".
|
|
8
|
+
* - `seed` - The original seed value used to initialize this state.
|
|
9
|
+
* - `s0`, `s1`, `s2`, `s3` - Internal 32-bit state words. Do not modify directly.
|
|
10
|
+
*/
|
|
2
11
|
export type PRNGState = Readonly<{
|
|
3
12
|
readonly __brand: "PRNGState";
|
|
4
13
|
seed: number;
|
|
@@ -8,7 +17,18 @@ export type PRNGState = Readonly<{
|
|
|
8
17
|
s3: number;
|
|
9
18
|
}>;
|
|
10
19
|
|
|
11
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Create a seeded PRNG state. The same seed always produces the same random sequence.
|
|
22
|
+
* Uses splitmix32 to initialize the xoshiro128** internal state.
|
|
23
|
+
*
|
|
24
|
+
* @param n - The seed value. Truncated to a 32-bit integer.
|
|
25
|
+
* @returns A new PRNGState ready for use with {@link rollDice}, {@link randomInt}, etc.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const rng = seed(42);
|
|
29
|
+
* const [value, rng2] = randomInt(rng, 1, 6);
|
|
30
|
+
* // value is deterministic for seed 42
|
|
31
|
+
*/
|
|
12
32
|
export function seed(n: number): PRNGState {
|
|
13
33
|
// Initialize state from seed using splitmix32
|
|
14
34
|
let s = n | 0;
|
|
@@ -30,17 +50,33 @@ export function seed(n: number): PRNGState {
|
|
|
30
50
|
};
|
|
31
51
|
}
|
|
32
52
|
|
|
33
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Branded string type for dice notation (e.g., "2d6+3", "1d20", "3d8-1").
|
|
55
|
+
* Format: `NdS` or `NdS+M` / `NdS-M` where N=count, S=sides, M=modifier.
|
|
56
|
+
*/
|
|
34
57
|
export type DiceNotation = string & { readonly __dice: true };
|
|
35
58
|
|
|
36
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Parsed dice specification. Created by {@link parseDice} or passed directly to {@link rollDice}.
|
|
61
|
+
*
|
|
62
|
+
* - `count` - Number of dice to roll (the N in NdS). Must be >= 1.
|
|
63
|
+
* - `sides` - Number of sides per die (the S in NdS). Must be >= 1.
|
|
64
|
+
* - `modifier` - Added to the total after all dice are summed. Can be negative.
|
|
65
|
+
*/
|
|
37
66
|
export type DiceSpec = Readonly<{
|
|
38
67
|
count: number;
|
|
39
68
|
sides: number;
|
|
40
69
|
modifier: number;
|
|
41
70
|
}>;
|
|
42
71
|
|
|
43
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Parse a dice notation string into a DiceSpec.
|
|
74
|
+
* Pure function. Throws if the notation is invalid.
|
|
75
|
+
*
|
|
76
|
+
* @param notation - Dice notation string (e.g., "2d6+3", "1d20", "3d8-1").
|
|
77
|
+
* @returns Parsed DiceSpec with count, sides, and modifier.
|
|
78
|
+
* @throws Error if notation doesn't match the `NdS` or `NdS+M` / `NdS-M` format.
|
|
79
|
+
*/
|
|
44
80
|
export function parseDice(notation: string): DiceSpec {
|
|
45
81
|
const match = notation.match(/^(\d+)d(\d+)([+-]\d+)?$/);
|
|
46
82
|
if (!match) {
|
|
@@ -55,7 +91,22 @@ export function parseDice(notation: string): DiceSpec {
|
|
|
55
91
|
};
|
|
56
92
|
}
|
|
57
93
|
|
|
58
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Roll dice deterministically using the PRNG. Pure function — returns the result
|
|
96
|
+
* and a new PRNGState without modifying the original.
|
|
97
|
+
*
|
|
98
|
+
* Accepts either a DiceSpec object or a dice notation string (e.g., "2d6+3").
|
|
99
|
+
* Each die is rolled individually using {@link randomInt}, then summed with the modifier.
|
|
100
|
+
*
|
|
101
|
+
* @param rng - Current PRNG state.
|
|
102
|
+
* @param spec - A DiceSpec or dice notation string (e.g., "1d20", "2d6+3").
|
|
103
|
+
* @returns A tuple of [total roll result, new PRNGState].
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* const rng = seed(42);
|
|
107
|
+
* const [damage, rng2] = rollDice(rng, "2d6+3");
|
|
108
|
+
* const [toHit, rng3] = rollDice(rng2, "1d20");
|
|
109
|
+
*/
|
|
59
110
|
export function rollDice(
|
|
60
111
|
rng: PRNGState,
|
|
61
112
|
spec: DiceSpec | string,
|
|
@@ -73,7 +124,15 @@ export function rollDice(
|
|
|
73
124
|
return [total, current];
|
|
74
125
|
}
|
|
75
126
|
|
|
76
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Generate a random integer in the range [min, max] (inclusive on both ends).
|
|
129
|
+
* Pure function — returns the value and a new PRNGState.
|
|
130
|
+
*
|
|
131
|
+
* @param rng - Current PRNG state.
|
|
132
|
+
* @param min - Minimum value (inclusive).
|
|
133
|
+
* @param max - Maximum value (inclusive). Must be >= min.
|
|
134
|
+
* @returns A tuple of [random integer, new PRNGState].
|
|
135
|
+
*/
|
|
77
136
|
export function randomInt(
|
|
78
137
|
rng: PRNGState,
|
|
79
138
|
min: number,
|
|
@@ -85,14 +144,26 @@ export function randomInt(
|
|
|
85
144
|
return [value, next];
|
|
86
145
|
}
|
|
87
146
|
|
|
88
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Generate a random float in the range [0, 1) (inclusive of 0, exclusive of 1).
|
|
149
|
+
* Pure function — returns the value and a new PRNGState.
|
|
150
|
+
*
|
|
151
|
+
* @param rng - Current PRNG state.
|
|
152
|
+
* @returns A tuple of [random float in [0,1), new PRNGState].
|
|
153
|
+
*/
|
|
89
154
|
export function randomFloat(rng: PRNGState): [number, PRNGState] {
|
|
90
155
|
const next = advance(rng);
|
|
91
156
|
const result = (xoshiro128ss(rng) >>> 0) / 4294967296;
|
|
92
157
|
return [result, next];
|
|
93
158
|
}
|
|
94
159
|
|
|
95
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* Pick one random element from an array. Pure function — does not modify the array.
|
|
162
|
+
*
|
|
163
|
+
* @param rng - Current PRNG state.
|
|
164
|
+
* @param items - Non-empty array to pick from.
|
|
165
|
+
* @returns A tuple of [randomly selected item, new PRNGState].
|
|
166
|
+
*/
|
|
96
167
|
export function randomPick<T>(
|
|
97
168
|
rng: PRNGState,
|
|
98
169
|
items: readonly T[],
|
|
@@ -101,7 +172,14 @@ export function randomPick<T>(
|
|
|
101
172
|
return [items[index], next];
|
|
102
173
|
}
|
|
103
174
|
|
|
104
|
-
/**
|
|
175
|
+
/**
|
|
176
|
+
* Shuffle an array using Fisher-Yates algorithm. Pure function — returns a new
|
|
177
|
+
* shuffled array without modifying the original.
|
|
178
|
+
*
|
|
179
|
+
* @param rng - Current PRNG state.
|
|
180
|
+
* @param items - Array to shuffle.
|
|
181
|
+
* @returns A tuple of [new shuffled array, new PRNGState].
|
|
182
|
+
*/
|
|
105
183
|
export function shuffle<T>(
|
|
106
184
|
rng: PRNGState,
|
|
107
185
|
items: readonly T[],
|