@arcane-engine/runtime 0.1.0 → 0.2.1

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 (47) hide show
  1. package/package.json +4 -2
  2. package/src/agent/protocol.ts +35 -1
  3. package/src/agent/types.ts +98 -13
  4. package/src/particles/emitter.test.ts +323 -0
  5. package/src/particles/emitter.ts +409 -0
  6. package/src/particles/index.ts +25 -0
  7. package/src/particles/types.ts +236 -0
  8. package/src/pathfinding/astar.ts +27 -0
  9. package/src/pathfinding/types.ts +39 -0
  10. package/src/physics/aabb.ts +55 -8
  11. package/src/rendering/animation.ts +73 -0
  12. package/src/rendering/audio.ts +29 -9
  13. package/src/rendering/camera.ts +28 -4
  14. package/src/rendering/input.ts +45 -9
  15. package/src/rendering/lighting.ts +29 -3
  16. package/src/rendering/loop.ts +16 -3
  17. package/src/rendering/sprites.ts +24 -1
  18. package/src/rendering/text.ts +52 -6
  19. package/src/rendering/texture.ts +22 -4
  20. package/src/rendering/tilemap.ts +36 -4
  21. package/src/rendering/types.ts +37 -19
  22. package/src/rendering/validate.ts +48 -3
  23. package/src/state/error.ts +21 -2
  24. package/src/state/observe.ts +40 -9
  25. package/src/state/prng.ts +88 -10
  26. package/src/state/query.ts +115 -15
  27. package/src/state/store.ts +42 -11
  28. package/src/state/transaction.ts +116 -12
  29. package/src/state/types.ts +31 -5
  30. package/src/systems/system.ts +77 -5
  31. package/src/systems/types.ts +52 -6
  32. package/src/testing/harness.ts +103 -5
  33. package/src/testing/mock-renderer.test.ts +16 -20
  34. package/src/tweening/chain.test.ts +191 -0
  35. package/src/tweening/chain.ts +103 -0
  36. package/src/tweening/easing.test.ts +134 -0
  37. package/src/tweening/easing.ts +288 -0
  38. package/src/tweening/helpers.test.ts +185 -0
  39. package/src/tweening/helpers.ts +166 -0
  40. package/src/tweening/index.ts +76 -0
  41. package/src/tweening/tween.test.ts +322 -0
  42. package/src/tweening/tween.ts +296 -0
  43. package/src/tweening/types.ts +134 -0
  44. package/src/ui/colors.ts +129 -0
  45. package/src/ui/index.ts +1 -0
  46. package/src/ui/primitives.ts +44 -5
  47. package/src/ui/types.ts +41 -2
@@ -1,9 +1,34 @@
1
1
  import type { Vec2 } from "./types.ts";
2
2
 
3
- /** A predicate function for filtering */
3
+ /**
4
+ * A predicate function for filtering state queries.
5
+ * Returns true if the item matches the filter criteria.
6
+ * Build predicates using combinators: {@link lt}, {@link gt}, {@link eq}, {@link oneOf}, etc.
7
+ */
4
8
  export type Predicate<T> = (item: T) => boolean;
5
9
 
6
- /** Query an array at a path, with optional filtering */
10
+ /**
11
+ * Query state at a dot-separated path, with optional filtering.
12
+ * Pure function — does not modify the state.
13
+ *
14
+ * If the value at the path is an array, returns matching elements.
15
+ * If the value is a single value, wraps it in an array.
16
+ * If the path doesn't exist, returns an empty array.
17
+ *
18
+ * The filter can be a predicate function, or an object where each key-value pair
19
+ * must match (values can be predicates or literal values).
20
+ * Supports `*` wildcards in paths to query across array elements.
21
+ *
22
+ * @param state - The state to query.
23
+ * @param path - Dot-separated path (e.g., "enemies", "player.inventory"). Use `*` for wildcards.
24
+ * @param filter - Optional predicate function or property-matching object.
25
+ * @returns Readonly array of matching results.
26
+ *
27
+ * @example
28
+ * const alive = query(state, "enemies", { alive: true });
29
+ * const nearby = query(state, "enemies", within({ x: 5, y: 5 }, 3));
30
+ * const names = query(state, "enemies.*.name");
31
+ */
7
32
  export function query<S, R = unknown>(
8
33
  state: S,
9
34
  path: string,
@@ -36,12 +61,30 @@ export function query<S, R = unknown>(
36
61
  }) as readonly R[];
37
62
  }
38
63
 
39
- /** Get a single value at a path */
64
+ /**
65
+ * Get a single value at a dot-separated path. Pure function.
66
+ * Returns undefined if the path doesn't exist.
67
+ *
68
+ * @param state - The state to read from.
69
+ * @param path - Dot-separated path (e.g., "player.hp", "config.difficulty").
70
+ * @returns The value at the path, or undefined if not found.
71
+ *
72
+ * @example
73
+ * const hp = get(state, "player.hp"); // number | undefined
74
+ */
40
75
  export function get<S, R = unknown>(state: S, path: string): R | undefined {
41
76
  return getByPath(state, path) as R | undefined;
42
77
  }
43
78
 
44
- /** Check existence at a path, optionally with a predicate */
79
+ /**
80
+ * Check if a value exists at a path, optionally testing it with a predicate.
81
+ * Pure function. Returns false if the path doesn't exist or if the predicate fails.
82
+ *
83
+ * @param state - The state to check.
84
+ * @param path - Dot-separated path (e.g., "player.weapon").
85
+ * @param predicate - Optional predicate to test the value against.
86
+ * @returns True if the value exists (and passes the predicate, if provided).
87
+ */
45
88
  export function has<S>(
46
89
  state: S,
47
90
  path: string,
@@ -55,42 +98,84 @@ export function has<S>(
55
98
 
56
99
  // --- Filter combinators ---
57
100
 
58
- /** Less than */
101
+ /**
102
+ * Create a predicate that tests if a number is less than the given value.
103
+ *
104
+ * @param value - The threshold to compare against.
105
+ * @returns A predicate returning true if item < value.
106
+ */
59
107
  export function lt(value: number): Predicate<number> {
60
108
  return (item: number) => item < value;
61
109
  }
62
110
 
63
- /** Greater than */
111
+ /**
112
+ * Create a predicate that tests if a number is greater than the given value.
113
+ *
114
+ * @param value - The threshold to compare against.
115
+ * @returns A predicate returning true if item > value.
116
+ */
64
117
  export function gt(value: number): Predicate<number> {
65
118
  return (item: number) => item > value;
66
119
  }
67
120
 
68
- /** Less than or equal */
121
+ /**
122
+ * Create a predicate that tests if a number is less than or equal to the given value.
123
+ *
124
+ * @param value - The threshold to compare against.
125
+ * @returns A predicate returning true if item <= value.
126
+ */
69
127
  export function lte(value: number): Predicate<number> {
70
128
  return (item: number) => item <= value;
71
129
  }
72
130
 
73
- /** Greater than or equal */
131
+ /**
132
+ * Create a predicate that tests if a number is greater than or equal to the given value.
133
+ *
134
+ * @param value - The threshold to compare against.
135
+ * @returns A predicate returning true if item >= value.
136
+ */
74
137
  export function gte(value: number): Predicate<number> {
75
138
  return (item: number) => item >= value;
76
139
  }
77
140
 
78
- /** Strict equality */
141
+ /**
142
+ * Create a predicate that tests for strict equality (===) with the given value.
143
+ *
144
+ * @param value - The value to compare against.
145
+ * @returns A predicate returning true if item === value.
146
+ */
79
147
  export function eq<T>(value: T): Predicate<T> {
80
148
  return (item: T) => item === value;
81
149
  }
82
150
 
83
- /** Not equal */
151
+ /**
152
+ * Create a predicate that tests for strict inequality (!==) with the given value.
153
+ *
154
+ * @param value - The value to compare against.
155
+ * @returns A predicate returning true if item !== value.
156
+ */
84
157
  export function neq<T>(value: T): Predicate<T> {
85
158
  return (item: T) => item !== value;
86
159
  }
87
160
 
88
- /** Value is one of the given options */
161
+ /**
162
+ * Create a predicate that tests if a value is one of the given options (using Array.includes).
163
+ *
164
+ * @param values - The allowed values to match against.
165
+ * @returns A predicate returning true if item is in the values list.
166
+ */
89
167
  export function oneOf<T>(...values: T[]): Predicate<T> {
90
168
  return (item: T) => values.includes(item);
91
169
  }
92
170
 
93
- /** Position within radius of a center point */
171
+ /**
172
+ * Create a predicate that tests if a Vec2 position is within a circular radius
173
+ * of a center point. Uses squared distance for efficiency (no sqrt).
174
+ *
175
+ * @param center - Center point of the circle.
176
+ * @param radius - Radius of the circle. Must be >= 0.
177
+ * @returns A predicate returning true if the position is within the circle (inclusive).
178
+ */
94
179
  export function within(center: Vec2, radius: number): Predicate<Vec2> {
95
180
  return (pos: Vec2) => {
96
181
  const dx = pos.x - center.x;
@@ -99,17 +184,32 @@ export function within(center: Vec2, radius: number): Predicate<Vec2> {
99
184
  };
100
185
  }
101
186
 
102
- /** Combine predicates: all must pass */
187
+ /**
188
+ * Combine multiple predicates with logical AND. All predicates must pass.
189
+ *
190
+ * @param predicates - Predicates to combine.
191
+ * @returns A predicate returning true only if every predicate passes.
192
+ */
103
193
  export function allOf<T>(...predicates: Predicate<T>[]): Predicate<T> {
104
194
  return (item: T) => predicates.every((p) => p(item));
105
195
  }
106
196
 
107
- /** Combine predicates: any must pass */
197
+ /**
198
+ * Combine multiple predicates with logical OR. At least one predicate must pass.
199
+ *
200
+ * @param predicates - Predicates to combine.
201
+ * @returns A predicate returning true if any predicate passes.
202
+ */
108
203
  export function anyOf<T>(...predicates: Predicate<T>[]): Predicate<T> {
109
204
  return (item: T) => predicates.some((p) => p(item));
110
205
  }
111
206
 
112
- /** Negate a predicate */
207
+ /**
208
+ * Negate a predicate. Returns the logical NOT of the original predicate.
209
+ *
210
+ * @param predicate - The predicate to negate.
211
+ * @returns A predicate returning true when the original returns false, and vice versa.
212
+ */
113
213
  export function not<T>(predicate: Predicate<T>): Predicate<T> {
114
214
  return (item: T) => !predicate(item);
115
215
  }
@@ -6,47 +6,78 @@ import { query, get, has } from "./query.ts";
6
6
  import type { PathPattern, ObserverCallback, Unsubscribe } from "./observe.ts";
7
7
  import { createObserverRegistry } from "./observe.ts";
8
8
 
9
- /** The game store: ties state + transactions + observers together */
9
+ /**
10
+ * The game store: central coordination point for state management.
11
+ * Ties together state, transactions, queries, and observers.
12
+ * Created via {@link createStore}.
13
+ *
14
+ * - `getState()` - Returns the current state as a deep readonly snapshot.
15
+ * - `dispatch(mutations)` - Apply mutations atomically, update state, notify observers.
16
+ * - `observe(pattern, callback)` - Subscribe to state changes matching a path pattern.
17
+ * - `query(path, filter?)` - Query arrays or values in current state.
18
+ * - `get(path)` - Get a single value from current state.
19
+ * - `has(path, predicate?)` - Check existence in current state.
20
+ * - `replaceState(state)` - Replace the entire state (for deserialization / time travel).
21
+ * - `getHistory()` - Get the transaction history for recording/replay.
22
+ */
10
23
  export type GameStore<S> = Readonly<{
11
- /** Current state (readonly snapshot) */
24
+ /** Returns the current state as a deep readonly snapshot. */
12
25
  getState: () => DeepReadonly<S>;
13
26
 
14
- /** Apply mutations as a transaction, update state, notify observers */
27
+ /** Apply mutations atomically. Updates state and notifies observers on success. */
15
28
  dispatch: (mutations: readonly Mutation<S>[]) => TransactionResult<S>;
16
29
 
17
- /** Subscribe to state changes at a path pattern */
30
+ /** Subscribe to state changes matching a path pattern. Returns an unsubscribe function. */
18
31
  observe: <T = unknown>(
19
32
  pattern: PathPattern,
20
33
  callback: ObserverCallback<T>,
21
34
  ) => Unsubscribe;
22
35
 
23
- /** Query current state */
36
+ /** Query arrays or values at a path, with optional filtering. */
24
37
  query: <R = unknown>(
25
38
  path: string,
26
39
  filter?: Predicate<R> | Record<string, unknown>,
27
40
  ) => readonly R[];
28
41
 
29
- /** Get a value from current state */
42
+ /** Get a single value from current state by path. Returns undefined if not found. */
30
43
  get: <R = unknown>(path: string) => R | undefined;
31
44
 
32
- /** Check existence in current state */
45
+ /** Check if a value exists at a path, optionally testing with a predicate. */
33
46
  has: (path: string, predicate?: Predicate<unknown>) => boolean;
34
47
 
35
- /** Replace the entire state (for deserialization / time travel) */
48
+ /** Replace the entire state (for deserialization / time travel). Does not trigger observers. */
36
49
  replaceState: (state: S) => void;
37
50
 
38
- /** Get the transaction history (for recording/replay) */
51
+ /** Get the transaction history as an ordered list of TransactionRecords. */
39
52
  getHistory: () => readonly TransactionRecord<S>[];
40
53
  }>;
41
54
 
42
- /** A recorded transaction for replay */
55
+ /**
56
+ * A recorded transaction for replay and debugging.
57
+ * Stored in the store's history, accessible via getHistory().
58
+ *
59
+ * - `timestamp` - When the transaction was applied (Date.now() milliseconds).
60
+ * - `mutations` - The mutations that were applied in this transaction.
61
+ * - `diff` - The computed diff of changes from this transaction.
62
+ */
43
63
  export type TransactionRecord<S> = Readonly<{
44
64
  timestamp: number;
45
65
  mutations: readonly Mutation<S>[];
46
66
  diff: Diff;
47
67
  }>;
48
68
 
49
- /** Create a game store with initial state */
69
+ /**
70
+ * Create a new game store with initial state, transactions, and observers.
71
+ * The store is the central coordination point for game state management.
72
+ *
73
+ * @param initialState - The initial state object. Becomes the starting state for all queries.
74
+ * @returns A GameStore with getState, dispatch, observe, query, get, has, replaceState, and getHistory.
75
+ *
76
+ * @example
77
+ * const store = createStore({ player: { x: 0, y: 0, hp: 100 }, enemies: [] });
78
+ * store.dispatch([set("player.x", 10)]);
79
+ * console.log(store.getState().player.x); // 10
80
+ */
50
81
  export function createStore<S>(initialState: S): GameStore<S> {
51
82
  let state: S = initialState;
52
83
  const observers = createObserverRegistry<S>();
@@ -1,7 +1,16 @@
1
1
  import type { ArcaneError } from "./error.ts";
2
2
  import { createError } from "./error.ts";
3
3
 
4
- /** A mutation: a named, describable, applicable state change */
4
+ /**
5
+ * A mutation: a named, describable, applicable state change.
6
+ * Created by mutation primitives ({@link set}, {@link update}, {@link push}, etc.)
7
+ * and applied atomically via {@link transaction}.
8
+ *
9
+ * - `type` - Mutation kind: "set", "update", "push", or "remove".
10
+ * - `path` - Dot-separated path to the target value (e.g., "player.hp").
11
+ * - `description` - Human-readable description of what this mutation does.
12
+ * - `apply` - Pure function that takes state and returns new state with the mutation applied.
13
+ */
5
14
  export type Mutation<S> = Readonly<{
6
15
  type: string;
7
16
  path: string;
@@ -11,7 +20,21 @@ export type Mutation<S> = Readonly<{
11
20
 
12
21
  // --- Core mutation primitives ---
13
22
 
14
- /** Set a value at a path */
23
+ /**
24
+ * Create a mutation that sets a value at a dot-separated path.
25
+ * Pure function — returns a Mutation object, does not modify state directly.
26
+ * Apply via {@link transaction} or {@link GameStore.dispatch}.
27
+ *
28
+ * @param path - Dot-separated path (e.g., "player.hp", "enemies.0.alive").
29
+ * @param value - The value to set at the path.
30
+ * @returns A Mutation that can be applied in a transaction.
31
+ *
32
+ * @example
33
+ * const result = transaction(state, [
34
+ * set("player.hp", 80),
35
+ * set("player.position.x", 10),
36
+ * ]);
37
+ */
15
38
  export function set<S>(path: string, value: unknown): Mutation<S> {
16
39
  return {
17
40
  type: "set",
@@ -21,7 +44,15 @@ export function set<S>(path: string, value: unknown): Mutation<S> {
21
44
  };
22
45
  }
23
46
 
24
- /** Update a value at a path with a function */
47
+ /**
48
+ * Create a mutation that updates a value at a path using a transform function.
49
+ * The function receives the current value and returns the new value.
50
+ * Pure function — returns a Mutation object, does not modify state directly.
51
+ *
52
+ * @param path - Dot-separated path to the value (e.g., "player.hp").
53
+ * @param fn - Transform function: receives the current value, returns the new value.
54
+ * @returns A Mutation that can be applied in a transaction.
55
+ */
25
56
  export function update<S>(
26
57
  path: string,
27
58
  fn: (current: unknown) => unknown,
@@ -37,7 +68,14 @@ export function update<S>(
37
68
  };
38
69
  }
39
70
 
40
- /** Push an item onto an array at a path */
71
+ /**
72
+ * Create a mutation that pushes an item onto an array at a path.
73
+ * Throws during application if the value at the path is not an array.
74
+ *
75
+ * @param path - Dot-separated path to the array (e.g., "enemies", "player.inventory").
76
+ * @param item - The item to append to the array.
77
+ * @returns A Mutation that can be applied in a transaction.
78
+ */
41
79
  export function push<S>(path: string, item: unknown): Mutation<S> {
42
80
  return {
43
81
  type: "push",
@@ -53,7 +91,14 @@ export function push<S>(path: string, item: unknown): Mutation<S> {
53
91
  };
54
92
  }
55
93
 
56
- /** Remove items from an array at a path matching a predicate */
94
+ /**
95
+ * Create a mutation that removes items from an array at a path where the predicate returns true.
96
+ * Throws during application if the value at the path is not an array.
97
+ *
98
+ * @param path - Dot-separated path to the array.
99
+ * @param predicate - Function that returns true for items to remove.
100
+ * @returns A Mutation that can be applied in a transaction.
101
+ */
57
102
  export function removeWhere<S>(
58
103
  path: string,
59
104
  predicate: (item: unknown) => boolean,
@@ -76,7 +121,15 @@ export function removeWhere<S>(
76
121
  };
77
122
  }
78
123
 
79
- /** Remove a key from an object at a path */
124
+ /**
125
+ * Create a mutation that removes a key from an object.
126
+ * The last segment of the path is the key to remove; the preceding segments
127
+ * identify the parent object. Throws during application if the parent is not an object.
128
+ *
129
+ * @param path - Dot-separated path where the last segment is the key to remove
130
+ * (e.g., "player.buffs.shield" removes "shield" from player.buffs).
131
+ * @returns A Mutation that can be applied in a transaction.
132
+ */
80
133
  export function removeKey<S>(path: string): Mutation<S> {
81
134
  const segments = path.split(".");
82
135
  const key = segments.pop()!;
@@ -101,28 +154,54 @@ export function removeKey<S>(path: string): Mutation<S> {
101
154
 
102
155
  // --- Diff ---
103
156
 
104
- /** A single change entry */
157
+ /**
158
+ * A single change entry in a diff, representing one value that changed.
159
+ *
160
+ * - `path` - Dot-separated path to the changed value (e.g., "player.hp").
161
+ * - `from` - The previous value (undefined if the key was added).
162
+ * - `to` - The new value (undefined if the key was removed).
163
+ */
105
164
  export type DiffEntry = Readonly<{
106
165
  path: string;
107
166
  from: unknown;
108
167
  to: unknown;
109
168
  }>;
110
169
 
111
- /** All changes from a transaction */
170
+ /**
171
+ * All changes from a transaction, as a list of individual DiffEntry items.
172
+ * Empty entries array means no changes occurred.
173
+ *
174
+ * - `entries` - Ordered list of individual value changes.
175
+ */
112
176
  export type Diff = Readonly<{
113
177
  entries: readonly DiffEntry[];
114
178
  }>;
115
179
 
116
180
  // --- Transaction result ---
117
181
 
118
- /** An effect triggered by a state change (for observer/event routing) */
182
+ /**
183
+ * An effect triggered by a state change, for observer/event routing.
184
+ * Reserved for future use — currently transactions return an empty effects array.
185
+ *
186
+ * - `type` - Effect type identifier (e.g., "damage", "levelUp").
187
+ * - `source` - Identifier of the mutation or system that produced this effect.
188
+ * - `data` - Arbitrary payload data for the effect.
189
+ */
119
190
  export type Effect = Readonly<{
120
191
  type: string;
121
192
  source: string;
122
193
  data: Readonly<Record<string, unknown>>;
123
194
  }>;
124
195
 
125
- /** Result of executing a transaction */
196
+ /**
197
+ * Result of executing a transaction. Check `valid` before using the new state.
198
+ *
199
+ * - `state` - The resulting state. Equals the original state if the transaction failed.
200
+ * - `diff` - Changes that occurred. Empty if the transaction failed.
201
+ * - `effects` - Side effects produced (reserved for future use).
202
+ * - `valid` - Whether the transaction succeeded. If false, state is unchanged.
203
+ * - `error` - Structured error if `valid` is false. Undefined on success.
204
+ */
126
205
  export type TransactionResult<S> = Readonly<{
127
206
  state: S;
128
207
  diff: Diff;
@@ -131,7 +210,24 @@ export type TransactionResult<S> = Readonly<{
131
210
  error?: ArcaneError;
132
211
  }>;
133
212
 
134
- /** Apply mutations atomically. All succeed or all roll back. */
213
+ /**
214
+ * Apply mutations atomically to state. All succeed or all roll back.
215
+ * Pure function — returns a new state without modifying the original.
216
+ * If any mutation throws, the entire transaction fails and the original state is returned.
217
+ *
218
+ * @param state - The current state to apply mutations to.
219
+ * @param mutations - Ordered list of mutations to apply. Created via {@link set}, {@link update}, etc.
220
+ * @returns A TransactionResult with the new state, diff, and validity flag.
221
+ *
222
+ * @example
223
+ * const result = transaction(state, [
224
+ * set("player.hp", 80),
225
+ * update("player.xp", (xp: any) => xp + 50),
226
+ * ]);
227
+ * if (result.valid) {
228
+ * // Use result.state
229
+ * }
230
+ */
135
231
  export function transaction<S>(
136
232
  state: S,
137
233
  mutations: readonly Mutation<S>[],
@@ -166,7 +262,15 @@ export function transaction<S>(
166
262
  }
167
263
  }
168
264
 
169
- /** Compute the diff between two state trees */
265
+ /**
266
+ * Compute the diff between two state trees by recursively comparing all values.
267
+ * Pure function — does not modify either state tree.
268
+ * Used internally by {@link transaction}, but can also be called directly.
269
+ *
270
+ * @param before - The state before changes.
271
+ * @param after - The state after changes.
272
+ * @returns A Diff containing all individual value changes.
273
+ */
170
274
  export function computeDiff<S>(before: S, after: S): Diff {
171
275
  const entries: DiffEntry[] = [];
172
276
  diffRecursive(before, after, "", entries);
@@ -1,22 +1,48 @@
1
- /** Branded string type for entity identification */
1
+ /**
2
+ * Branded string type for entity identification.
3
+ * Uses TypeScript's structural branding to prevent plain strings from being
4
+ * used where an EntityId is expected. Create via {@link entityId} or {@link generateId}.
5
+ */
2
6
  export type EntityId = string & { readonly __entityId: true };
3
7
 
4
- /** Create an EntityId from a string */
8
+ /**
9
+ * Create an EntityId from a known string value.
10
+ * Use this for deterministic IDs (e.g., "player", "enemy_1").
11
+ * For random unique IDs, use {@link generateId} instead.
12
+ *
13
+ * @param id - The string to brand as an EntityId.
14
+ * @returns A branded EntityId.
15
+ */
5
16
  export function entityId(id: string): EntityId {
6
17
  return id as EntityId;
7
18
  }
8
19
 
9
- /** Generate a unique EntityId (uses crypto.randomUUID or counter in tests) */
20
+ /**
21
+ * Generate a unique EntityId using crypto.randomUUID().
22
+ * Each call produces a new UUID v4 string branded as EntityId.
23
+ * Use {@link entityId} instead when you need a deterministic, human-readable ID.
24
+ *
25
+ * @returns A new unique EntityId.
26
+ */
10
27
  export function generateId(): EntityId {
11
28
  return crypto.randomUUID() as EntityId;
12
29
  }
13
30
 
14
- /** 2D position vector (immutable) */
31
+ /**
32
+ * Immutable 2D vector. Used for positions, velocities, and directions.
33
+ *
34
+ * - `x` - Horizontal component (positive = right).
35
+ * - `y` - Vertical component (positive = down in screen coordinates).
36
+ */
15
37
  export type Vec2 = Readonly<{ x: number; y: number }>;
16
38
 
17
39
  type Primitive = string | number | boolean | null | undefined;
18
40
 
19
- /** Deep recursive readonly — enforces immutability at the type level */
41
+ /**
42
+ * Deep recursive readonly utility type. Enforces immutability at the type level
43
+ * by recursively wrapping all properties, arrays, Maps, and Sets as readonly.
44
+ * Applied to state returned by {@link GameStore.getState} to prevent accidental mutation.
45
+ */
20
46
  export type DeepReadonly<T> = T extends Primitive
21
47
  ? T
22
48
  : T extends (infer U)[]
@@ -1,7 +1,26 @@
1
1
  import type { Condition, Action, Rule, SystemDef, RuleResult, ExtendOptions } from "./types.ts";
2
2
  import { createError } from "../state/error.ts";
3
3
 
4
- /** Create a system definition from a name and list of rules. */
4
+ /**
5
+ * Create a system definition from a name and list of rules.
6
+ *
7
+ * A system is a named collection of rules that together define a game mechanic.
8
+ * Use {@link rule} to build rules, then combine them into a system.
9
+ *
10
+ * @typeParam S - The game state type.
11
+ * @param name - System name (e.g., "combat", "inventory").
12
+ * @param rules - Ordered array of rules belonging to this system.
13
+ * @returns An immutable {@link SystemDef}.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const combat = system("combat", [
18
+ * rule<GameState>("attack")
19
+ * .when((s, args) => s.player.hp > 0)
20
+ * .then((s, args) => ({ ...s, enemy: { ...s.enemy, hp: s.enemy.hp - 10 } })),
21
+ * ]);
22
+ * ```
23
+ */
5
24
  export function system<S>(name: string, rules: readonly Rule<S>[]): SystemDef<S> {
6
25
  return { name, rules };
7
26
  }
@@ -21,7 +40,24 @@ type RuleBuilderBase<S> = {
21
40
  };
22
41
  };
23
42
 
24
- /** Fluent builder for creating rules. */
43
+ /**
44
+ * Fluent builder for creating named rules.
45
+ *
46
+ * Chain `.when()` to add conditions and `.then()` to add actions.
47
+ * Use `.replaces()` to mark this rule as a replacement for an existing rule
48
+ * when used with {@link extend}.
49
+ *
50
+ * @typeParam S - The game state type.
51
+ * @param name - Unique rule name within the system.
52
+ * @returns A fluent builder with `.when()`, `.then()`, and `.replaces()` methods.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const attackRule = rule<GameState>("attack")
57
+ * .when((s) => s.player.hp > 0, (s) => s.enemy.hp > 0)
58
+ * .then((s, args) => ({ ...s, enemy: { ...s.enemy, hp: s.enemy.hp - 10 } }));
59
+ * ```
60
+ */
25
61
  export function rule<S>(name: string): RuleBuilderBase<S> {
26
62
  let replacesName: string | undefined;
27
63
 
@@ -59,7 +95,20 @@ export function rule<S>(name: string): RuleBuilderBase<S> {
59
95
  };
60
96
  }
61
97
 
62
- /** Find a rule by name, apply conditions, chain actions. */
98
+ /**
99
+ * Find a rule by name in a system, check its conditions, and execute its actions.
100
+ *
101
+ * If the rule is not found, returns `{ ok: false }` with an UNKNOWN_RULE error.
102
+ * If any condition fails, returns `{ ok: false }` with a CONDITION_FAILED error.
103
+ * Otherwise, chains all actions and returns `{ ok: true }` with the new state.
104
+ *
105
+ * @typeParam S - The game state type.
106
+ * @param sys - The system to search for the rule.
107
+ * @param ruleName - Name of the rule to apply.
108
+ * @param state - Current game state.
109
+ * @param args - Optional arguments passed to conditions and actions.
110
+ * @returns A {@link RuleResult} with the outcome and resulting state.
111
+ */
63
112
  export function applyRule<S>(
64
113
  sys: SystemDef<S>,
65
114
  ruleName: string,
@@ -102,7 +151,16 @@ export function applyRule<S>(
102
151
  return { ok: true, state: current, ruleName };
103
152
  }
104
153
 
105
- /** Return names of rules whose conditions are all met. */
154
+ /**
155
+ * Return names of rules whose conditions are all satisfied for the given state.
156
+ * Useful for presenting valid actions to a player or AI agent.
157
+ *
158
+ * @typeParam S - The game state type.
159
+ * @param sys - The system to query.
160
+ * @param state - Current game state to test conditions against.
161
+ * @param args - Optional arguments passed to condition functions.
162
+ * @returns Array of rule names that can currently be applied.
163
+ */
106
164
  export function getApplicableRules<S>(
107
165
  sys: SystemDef<S>,
108
166
  state: S,
@@ -113,7 +171,21 @@ export function getApplicableRules<S>(
113
171
  .map((r) => r.name);
114
172
  }
115
173
 
116
- /** Extend a system: replace rules by name, add new rules, remove rules by name. */
174
+ /**
175
+ * Create a new system by extending an existing one.
176
+ *
177
+ * Supports three operations:
178
+ * 1. **Replace** — new rules with `replaces` set swap out existing rules by name.
179
+ * 2. **Add** — new rules without `replaces` are appended to the end.
180
+ * 3. **Remove** — rules named in `options.remove` are excluded.
181
+ *
182
+ * The base system is not modified; a new {@link SystemDef} is returned.
183
+ *
184
+ * @typeParam S - The game state type.
185
+ * @param base - The system to extend.
186
+ * @param options - Rules to add/replace and rule names to remove.
187
+ * @returns A new system with the modifications applied.
188
+ */
117
189
  export function extend<S>(base: SystemDef<S>, options: ExtendOptions<S>): SystemDef<S> {
118
190
  const removeSet = new Set(options.remove ?? []);
119
191
  const newRules = options.rules ?? [];