pelican-engine 0.0.0-stage → 0.3.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 (192) hide show
  1. package/AGENTS.md +147 -0
  2. package/LICENSE +22 -0
  3. package/README.md +129 -2
  4. package/distribution.json +197 -0
  5. package/docs/AI-WORKFLOW.md +517 -0
  6. package/docs/ARCHITECTURE.md +268 -0
  7. package/docs/CAPABILITIES.md +264 -0
  8. package/docs/DECISIONS.md +107 -0
  9. package/docs/M3-USAGE.md +192 -0
  10. package/docs/PELICAN-MIGRATION.md +38 -0
  11. package/docs/RENDER-BACKENDS.md +50 -0
  12. package/docs/ROADMAP.md +239 -0
  13. package/docs/THIRD-PARTY-NOTICES.md +151 -0
  14. package/docs/api/REFERENCE.md +1435 -0
  15. package/docs/api/index.json +1139 -0
  16. package/docs/npm/DISTRIBUTION.md +130 -0
  17. package/docs/npm/GAME-AUTHOR.md +147 -0
  18. package/engine.manifest.json +15 -0
  19. package/lib/assets/audio.js +67 -0
  20. package/lib/assets/errors.js +36 -0
  21. package/lib/assets/glb.js +277 -0
  22. package/lib/assets/index.js +53 -0
  23. package/lib/assets/jpeg.js +199 -0
  24. package/lib/assets/model.js +558 -0
  25. package/lib/assets/optimize.js +95 -0
  26. package/lib/assets/texture.js +187 -0
  27. package/lib/compiler/distribution.js +17 -0
  28. package/lib/compiler/extract.js +425 -0
  29. package/lib/compiler/graph.js +366 -0
  30. package/lib/compiler/index.js +574 -0
  31. package/lib/compiler/normalize.js +1194 -0
  32. package/lib/compiler/source-map.js +25 -0
  33. package/lib/compiler/types.js +2 -0
  34. package/lib/compiler/version.js +93 -0
  35. package/lib/experiments/simulation/index.js +2 -0
  36. package/lib/experiments/simulation/math.js +41 -0
  37. package/lib/experiments/simulation/probe.js +215 -0
  38. package/lib/experiments/simulation/scenes.js +192 -0
  39. package/lib/experiments/simulation/solver.js +346 -0
  40. package/lib/experiments/simulation/types.js +1 -0
  41. package/lib/experiments/world/adapters.js +30 -0
  42. package/lib/experiments/world/geometry.js +180 -0
  43. package/lib/experiments/world/index.js +2 -0
  44. package/lib/experiments/world/probe.js +288 -0
  45. package/lib/experiments/world/types.js +1 -0
  46. package/lib/experiments/world/world.js +599 -0
  47. package/lib/navigation/index.js +157 -0
  48. package/lib/physics/index.js +909 -0
  49. package/lib/protocol/index.js +92 -0
  50. package/lib/renderer/acceleration.js +609 -0
  51. package/lib/renderer/browser.js +2015 -0
  52. package/lib/renderer/bsdf.js +260 -0
  53. package/lib/renderer/checkpoint.js +88 -0
  54. package/lib/renderer/compute-executor.js +1034 -0
  55. package/lib/renderer/compute-texture-assets.js +49 -0
  56. package/lib/renderer/compute-view-contract.js +1 -0
  57. package/lib/renderer/denoiser.js +361 -0
  58. package/lib/renderer/dielectric.js +360 -0
  59. package/lib/renderer/embedded-image-pixels.js +368 -0
  60. package/lib/renderer/emissive-lights.js +67 -0
  61. package/lib/renderer/environment-importance.js +55 -0
  62. package/lib/renderer/environment.js +346 -0
  63. package/lib/renderer/gameplay.js +464 -0
  64. package/lib/renderer/geometry.js +103 -0
  65. package/lib/renderer/graph-expressions.js +485 -0
  66. package/lib/renderer/graph-tsl.js +141 -0
  67. package/lib/renderer/graph.js +214 -0
  68. package/lib/renderer/managed-texture-sampling.js +29 -0
  69. package/lib/renderer/material-graphs.js +297 -0
  70. package/lib/renderer/medium.js +348 -0
  71. package/lib/renderer/models.js +1053 -0
  72. package/lib/renderer/motion-shader.js +40 -0
  73. package/lib/renderer/path-shader.js +196 -0
  74. package/lib/renderer/pathtrace.js +324 -0
  75. package/lib/renderer/postprocess-contract.js +1 -0
  76. package/lib/renderer/postprocess.js +494 -0
  77. package/lib/renderer/render-buffers.js +132 -0
  78. package/lib/renderer/render-textures.js +155 -0
  79. package/lib/renderer/startup.js +50 -0
  80. package/lib/renderer/surface-capture.js +199 -0
  81. package/lib/renderer/surface-gpu.js +270 -0
  82. package/lib/renderer/surface-motion.js +84 -0
  83. package/lib/renderer/surface-ray-shader.js +91 -0
  84. package/lib/renderer/surface-shader.js +88 -0
  85. package/lib/renderer/surface-tracer.js +1720 -0
  86. package/lib/renderer/surface.js +1425 -0
  87. package/lib/renderer/temporal-filter.js +512 -0
  88. package/lib/renderer/texture-pixels.js +207 -0
  89. package/lib/renderer/traversal-nodes.js +54 -0
  90. package/lib/renderer/ui.js +446 -0
  91. package/lib/renderer/vertex-bounds.js +127 -0
  92. package/lib/renderer/vertex-contract.js +1 -0
  93. package/lib/renderer/vertex-graphs.js +197 -0
  94. package/lib/renderer/volume-shader.js +50 -0
  95. package/lib/runtime/compute-contract.js +1 -0
  96. package/lib/runtime/compute-service.js +674 -0
  97. package/lib/runtime/contracts.js +1 -0
  98. package/lib/runtime/error.js +37 -0
  99. package/lib/runtime/events.js +189 -0
  100. package/lib/runtime/game-services.js +772 -0
  101. package/lib/runtime/host.js +839 -0
  102. package/lib/runtime/material-parameters.js +145 -0
  103. package/lib/runtime/plugins.js +1 -0
  104. package/lib/runtime/postprocess-contract.js +1 -0
  105. package/lib/runtime/postprocess.js +189 -0
  106. package/lib/runtime/prefabs.js +13 -0
  107. package/lib/runtime/random.js +88 -0
  108. package/lib/runtime/render-buffer-contract.js +1 -0
  109. package/lib/runtime/render-buffers.js +243 -0
  110. package/lib/runtime/render-capabilities.js +17 -0
  111. package/lib/runtime/render-settings.js +249 -0
  112. package/lib/runtime/render-texture-contract.js +1 -0
  113. package/lib/runtime/render-textures.js +271 -0
  114. package/lib/runtime/resources.js +648 -0
  115. package/lib/runtime/scheduler.js +765 -0
  116. package/lib/runtime/storage.js +169 -0
  117. package/lib/runtime/world.js +594 -0
  118. package/lib/schema/index.js +24 -0
  119. package/lib/sdk/graph-values.js +16 -0
  120. package/lib/sdk/index.d.ts +800 -0
  121. package/lib/sdk/index.js +82 -0
  122. package/lib/tools/api-index.js +113 -0
  123. package/lib/tools/browser-session.js +89 -0
  124. package/lib/tools/cli.js +513 -0
  125. package/lib/tools/compress-dist.js +59 -0
  126. package/lib/tools/control-client.js +153 -0
  127. package/lib/tools/control-server.js +633 -0
  128. package/lib/tools/diagnostic-code.js +3 -0
  129. package/lib/tools/doctor.js +496 -0
  130. package/lib/tools/npm/browser-cli.js +6 -0
  131. package/lib/tools/npm/entry.js +40 -0
  132. package/lib/tools/npm/layout.js +195 -0
  133. package/lib/tools/setup.js +153 -0
  134. package/licenses/@dimforge/rapier2d-compat/LICENSE +201 -0
  135. package/licenses/@dimforge/rapier3d-compat/LICENSE +201 -0
  136. package/licenses/@gltf-transform/core/LICENSE.md +21 -0
  137. package/licenses/@gltf-transform/extensions/LICENSE.md +21 -0
  138. package/licenses/@gltf-transform/functions/LICENSE.md +21 -0
  139. package/licenses/@img/colour/LICENSE.md +82 -0
  140. package/licenses/@img/sharp-win32-x64/LICENSE +191 -0
  141. package/licenses/@oxc-project/types/LICENSE +22 -0
  142. package/licenses/@rolldown/pluginutils/LICENSE +21 -0
  143. package/licenses/@tweenjs/tween.js/LICENSE +23 -0
  144. package/licenses/@types/ndarray/LICENSE +21 -0
  145. package/licenses/@types/node/LICENSE +21 -0
  146. package/licenses/@types/stats.js/LICENSE +21 -0
  147. package/licenses/@types/three/@dimforge/rapier3d-compat/LICENSE +201 -0
  148. package/licenses/@types/three/LICENSE +21 -0
  149. package/licenses/@types/webxr/LICENSE +21 -0
  150. package/licenses/@webgpu/types/LICENSE +26 -0
  151. package/licenses/THIRD-PARTY-NOTICES.md +139 -0
  152. package/licenses/cwise-compiler/LICENSE +22 -0
  153. package/licenses/detect-libc/LICENSE +201 -0
  154. package/licenses/esbuild/LICENSE.md +21 -0
  155. package/licenses/fast-png/LICENSE +21 -0
  156. package/licenses/fdir/LICENSE +7 -0
  157. package/licenses/fflate/LICENSE +21 -0
  158. package/licenses/iobuffer/LICENSE +22 -0
  159. package/licenses/iota-array/LICENSE +22 -0
  160. package/licenses/is-buffer/LICENSE +21 -0
  161. package/licenses/ktx-parse/LICENSE +21 -0
  162. package/licenses/lightningcss/LICENSE +373 -0
  163. package/licenses/lightningcss-win32-x64-msvc/LICENSE +373 -0
  164. package/licenses/meshoptimizer/LICENSE.md +21 -0
  165. package/licenses/nanoid/LICENSE +20 -0
  166. package/licenses/ndarray/LICENSE +22 -0
  167. package/licenses/ndarray-lanczos/LICENSE +21 -0
  168. package/licenses/ndarray-ops/LICENSE +22 -0
  169. package/licenses/ndarray-pixels/LICENSE +21 -0
  170. package/licenses/ngraph.path/LICENSE +21 -0
  171. package/licenses/picocolors/LICENSE +15 -0
  172. package/licenses/picomatch/LICENSE +21 -0
  173. package/licenses/playwright/LICENSE +202 -0
  174. package/licenses/playwright/NOTICE +5 -0
  175. package/licenses/playwright-core/LICENSE +202 -0
  176. package/licenses/playwright-core/NOTICE +5 -0
  177. package/licenses/postcss/LICENSE +20 -0
  178. package/licenses/prettier/LICENSE +7 -0
  179. package/licenses/property-graph/LICENSE +21 -0
  180. package/licenses/rolldown/LICENSE +25 -0
  181. package/licenses/semver/LICENSE +15 -0
  182. package/licenses/sharp/LICENSE +191 -0
  183. package/licenses/source-map-js/LICENSE +28 -0
  184. package/licenses/three/LICENSE +21 -0
  185. package/licenses/tinyglobby/LICENSE +21 -0
  186. package/licenses/tsx/LICENSE +21 -0
  187. package/licenses/typescript/LICENSE.txt +55 -0
  188. package/licenses/undici-types/LICENSE +21 -0
  189. package/licenses/uniq/LICENSE +22 -0
  190. package/licenses/vite/LICENSE.md +2084 -0
  191. package/package.json +49 -4
  192. package/web/index.html +34 -0
@@ -0,0 +1,594 @@
1
+ import { ComponentStorage, cloneSchema, fail, identifier, record, values, } from "./storage.js";
2
+ const TRANSFORM = {
3
+ position: { type: "vec3", default: [0, 0, 0] },
4
+ rotation: { type: "vec3", default: [0, 0, 0] },
5
+ scale: { type: "vec3", default: [1, 1, 1] },
6
+ };
7
+ let worldSerial = 0;
8
+ function capacityValue(value) {
9
+ if (!Number.isSafeInteger(value) || value < 1 || value > 0x7fffffff)
10
+ fail("CAPACITY", "Capacity must be a positive int32.");
11
+ return value;
12
+ }
13
+ function empty(capacity) {
14
+ return {
15
+ capacity,
16
+ count: 0,
17
+ length: 0,
18
+ free: [],
19
+ ids: new Array(capacity),
20
+ prefabs: new Map(),
21
+ byId: new Map(),
22
+ alive: new Uint8Array(capacity),
23
+ generations: new Float64Array(capacity),
24
+ parents: new Int32Array(capacity).fill(-1),
25
+ children: new Array(capacity),
26
+ definitions: new Map(),
27
+ stores: new Map(),
28
+ transforms: new ComponentStorage(TRANSFORM, capacity),
29
+ };
30
+ }
31
+ function grow(state) {
32
+ const capacity = capacityValue(Math.min(0x7fffffff, state.capacity * 2));
33
+ if (capacity === state.capacity)
34
+ fail("CAPACITY", "Entity index capacity is exhausted.");
35
+ const alive = new Uint8Array(capacity);
36
+ alive.set(state.alive);
37
+ const generations = new Float64Array(capacity);
38
+ generations.set(state.generations);
39
+ const parents = new Int32Array(capacity).fill(-1);
40
+ parents.set(state.parents);
41
+ state.transforms.grow(capacity);
42
+ for (const storage of state.stores.values())
43
+ storage.grow(capacity);
44
+ state.alive = alive;
45
+ state.generations = generations;
46
+ state.parents = parents;
47
+ state.ids.length = capacity;
48
+ state.children.length = capacity;
49
+ state.capacity = capacity;
50
+ }
51
+ function copyState(source) {
52
+ return {
53
+ ...source,
54
+ free: [...source.free],
55
+ ids: [...source.ids],
56
+ prefabs: new Map(source.prefabs),
57
+ byId: new Map(source.byId),
58
+ alive: source.alive.slice(),
59
+ generations: source.generations.slice(),
60
+ parents: source.parents.slice(),
61
+ children: source.children.map((set) => set && new Set(set)),
62
+ definitions: new Map(source.definitions),
63
+ stores: new Map([...source.stores].map(([id, store]) => [id, store.clone()])),
64
+ transforms: source.transforms.clone(),
65
+ };
66
+ }
67
+ function register(state, input) {
68
+ record(input, "Component definition");
69
+ identifier(input.id, "Component id");
70
+ if (input.id === "Transform")
71
+ fail("RESERVED", "Transform is built in and cannot be registered.");
72
+ if (state.definitions.has(input.id))
73
+ fail("DUPLICATE", `Component ${input.id} is already registered.`);
74
+ if (input.version !== undefined &&
75
+ (!Number.isSafeInteger(input.version) || input.version < 1)) {
76
+ fail("VERSION", `Component ${input.id} version must be a positive safe integer.`);
77
+ }
78
+ const schema = cloneSchema(input.schema);
79
+ state.definitions.set(input.id, {
80
+ id: input.id,
81
+ schema,
82
+ ...(input.version === undefined ? {} : { version: input.version }),
83
+ });
84
+ state.stores.set(input.id, new ComponentStorage(schema, state.capacity));
85
+ }
86
+ function store(state, id) {
87
+ identifier(id, "Component id");
88
+ if (id === "Transform")
89
+ return state.transforms;
90
+ return (state.stores.get(id) ??
91
+ fail("COMPONENT", `Component ${id} is not registered.`));
92
+ }
93
+ function normalize(state, input, strict = false) {
94
+ record(input, "Entity");
95
+ identifier(input.id, "Entity id");
96
+ if (input.prefab !== undefined)
97
+ identifier(input.prefab, "Prefab id");
98
+ if (input.parent !== undefined)
99
+ identifier(input.parent, "Parent id");
100
+ if (strict &&
101
+ (input.transform === undefined || input.components === undefined))
102
+ fail("SNAPSHOT", "Entity records require transform and components.");
103
+ const transform = values(TRANSFORM, (input.transform === undefined ? {} : input.transform), true, strict);
104
+ const components = input.components === undefined ? {} : input.components;
105
+ record(components, `Entity ${input.id} components`);
106
+ const entries = Object.entries(components).map(([id, data]) => {
107
+ if (id === "Transform")
108
+ fail("RESERVED", "Use the entity transform property, not a Transform component entry.");
109
+ return [id, values(store(state, id).schema, data, true, strict)];
110
+ });
111
+ return {
112
+ id: input.id,
113
+ ...(input.prefab === undefined ? {} : { prefab: input.prefab }),
114
+ ...(input.parent === undefined ? {} : { parent: input.parent }),
115
+ transform,
116
+ components: Object.fromEntries(entries),
117
+ };
118
+ }
119
+ function put(state, entity, generation, parent = -1) {
120
+ if (state.byId.has(entity.id))
121
+ fail("DUPLICATE", `Entity ${entity.id} already exists.`);
122
+ if (state.free.length === 0 && state.length === state.capacity)
123
+ grow(state);
124
+ const index = state.free.pop() ?? state.length++;
125
+ state.ids[index] = entity.id;
126
+ if (entity.prefab !== undefined)
127
+ state.prefabs.set(index, entity.prefab);
128
+ state.byId.set(entity.id, index);
129
+ state.alive[index] = 1;
130
+ state.generations[index] = generation;
131
+ state.parents[index] = parent;
132
+ state.children[index] = new Set();
133
+ if (parent !== -1)
134
+ state.children[parent].add(index);
135
+ state.transforms.write(index, entity.transform);
136
+ for (const [id, data] of Object.entries(entity.components))
137
+ state.stores.get(id).write(index, data);
138
+ state.count++;
139
+ return index;
140
+ }
141
+ /** Child deletion is intentionally cascading, including descendants without components. */
142
+ function subtree(state, root) {
143
+ const result = [];
144
+ const pending = [root];
145
+ while (pending.length) {
146
+ const index = pending.pop();
147
+ result.push(index);
148
+ for (const child of state.children[index])
149
+ pending.push(child);
150
+ }
151
+ return result;
152
+ }
153
+ function erase(state, indices) {
154
+ for (let n = indices.length - 1; n >= 0; n--) {
155
+ const index = indices[n], parent = state.parents[index];
156
+ if (parent !== -1)
157
+ state.children[parent].delete(index);
158
+ state.byId.delete(state.ids[index]);
159
+ state.ids[index] = undefined;
160
+ state.prefabs.delete(index);
161
+ state.alive[index] = 0;
162
+ state.parents[index] = -1;
163
+ state.children[index].clear();
164
+ state.transforms.remove(index);
165
+ for (const storage of state.stores.values())
166
+ storage.remove(index);
167
+ state.free.push(index);
168
+ state.count--;
169
+ }
170
+ }
171
+ export function createWorld(options = {}) {
172
+ record(options, "World options");
173
+ const world = ++worldSerial;
174
+ if (!Number.isSafeInteger(world))
175
+ fail("GENERATION", "World identity space is exhausted.");
176
+ const initialCapacity = capacityValue(options.initialCapacity ?? 64);
177
+ let generation = 0, revision = 0, disposed = false;
178
+ let active;
179
+ let running = false;
180
+ let pending = [];
181
+ const freshGeneration = () => {
182
+ if (generation >= Number.MAX_SAFE_INTEGER)
183
+ fail("GENERATION", "Handle generation space is exhausted.");
184
+ return ++generation;
185
+ };
186
+ const ensure = () => {
187
+ if (disposed)
188
+ fail("DISPOSED", "World has been disposed.");
189
+ };
190
+ const outsideSystem = () => {
191
+ ensure();
192
+ if (running)
193
+ fail("STRUCTURAL", "Systems must queue structural changes; lifecycle operations run outside systems.");
194
+ };
195
+ // Omitted individual access lists retain legacy behavior. An explicit [] grants nothing;
196
+ // only an explicitly listed write grants read access to that component.
197
+ const read = (component, access = active) => {
198
+ if (access?.reads !== undefined &&
199
+ !access.reads.includes(component) &&
200
+ !access.writes?.includes(component)) {
201
+ fail("ACCESS_READ", `System has no read access to ${component}.`);
202
+ }
203
+ };
204
+ const write = (component, access = active) => {
205
+ if (access?.writes !== undefined && !access.writes.includes(component))
206
+ fail("ACCESS_WRITE", `System has no write access to ${component}.`);
207
+ };
208
+ const structural = (access = active) => {
209
+ if (access?.structural === false)
210
+ fail("ACCESS_STRUCTURAL", "System has no structural access.");
211
+ };
212
+ const lookup = (state, ref) => {
213
+ if (typeof ref === "string")
214
+ return (state.byId.get(ref) ?? fail("ENTITY", `Entity ${ref} does not exist.`));
215
+ if (!ref ||
216
+ ref.world !== world ||
217
+ !Number.isSafeInteger(ref.index) ||
218
+ ref.index < 0 ||
219
+ ref.index >= state.length ||
220
+ state.alive[ref.index] !== 1 ||
221
+ state.generations[ref.index] !== ref.generation) {
222
+ fail("HANDLE", "Entity handle is stale or belongs to another world.");
223
+ }
224
+ return ref.index;
225
+ };
226
+ const handle = (state, index) => Object.freeze({ world, index, generation: state.generations[index] });
227
+ // Validate all entities (including forward parents and cycles) before allocating live state.
228
+ const build = (definitions, inputs, strict, capacity) => {
229
+ if (!Array.isArray(definitions) || !Array.isArray(inputs))
230
+ fail("SNAPSHOT", "Components and entities must be arrays.");
231
+ const result = empty(capacity);
232
+ for (const definition of definitions)
233
+ register(result, definition);
234
+ const entities = Array.from(inputs, (input) => normalize(result, input, strict));
235
+ const indices = new Map();
236
+ entities.forEach((entity, index) => {
237
+ if (indices.has(entity.id))
238
+ fail("DUPLICATE", `Duplicate entity ${entity.id}.`);
239
+ indices.set(entity.id, index);
240
+ });
241
+ const parents = entities.map((entity) => entity.parent === undefined
242
+ ? -1
243
+ : (indices.get(entity.parent) ??
244
+ fail("PARENT", `Missing parent ${entity.parent}.`)));
245
+ const colors = new Uint8Array(entities.length);
246
+ for (let index = 0; index < entities.length; index++) {
247
+ if (colors[index] === 2)
248
+ continue;
249
+ let current = index;
250
+ const path = [];
251
+ while (current !== -1 && colors[current] !== 2) {
252
+ if (colors[current] === 1)
253
+ fail("PARENT_CYCLE", `Parent cycle at ${entities[current].id}.`);
254
+ colors[current] = 1;
255
+ path.push(current);
256
+ current = parents[current];
257
+ }
258
+ for (const visited of path)
259
+ colors[visited] = 2;
260
+ }
261
+ if (generation > Number.MAX_SAFE_INTEGER - entities.length)
262
+ fail("GENERATION", "Handle generation space is exhausted.");
263
+ for (const entity of entities)
264
+ put(result, entity, freshGeneration());
265
+ for (let index = 0; index < parents.length; index++) {
266
+ result.parents[index] = parents[index];
267
+ if (parents[index] !== -1)
268
+ result.children[parents[index]].add(index);
269
+ }
270
+ return result;
271
+ };
272
+ let state = build(options.components ?? [], options.entities ?? [], false, initialCapacity);
273
+ const spawn = (target, input, access) => {
274
+ const normalized = normalize(target, input);
275
+ if (target.byId.has(normalized.id))
276
+ fail("DUPLICATE", `Entity ${normalized.id} already exists.`);
277
+ const parent = normalized.parent === undefined
278
+ ? -1
279
+ : (target.byId.get(normalized.parent) ??
280
+ fail("PARENT", `Missing parent ${normalized.parent}.`));
281
+ write("Transform", access);
282
+ for (const name of Object.keys(normalized.components))
283
+ write(name, access);
284
+ return put(target, normalized, freshGeneration(), parent);
285
+ };
286
+ const add = (target, ref, component, data, access) => {
287
+ if (component === "Transform")
288
+ fail("RESERVED", "Transform cannot be added or removed.");
289
+ write(component, access);
290
+ const index = lookup(target, ref), storage = store(target, component);
291
+ if (storage.presence[index])
292
+ fail("DUPLICATE", `Entity already has ${component}.`);
293
+ const normalized = values(storage.schema, data, true);
294
+ storage.write(index, normalized);
295
+ };
296
+ const remove = (target, ref, component, access) => {
297
+ if (component === "Transform")
298
+ fail("RESERVED", "Transform cannot be added or removed.");
299
+ write(component, access);
300
+ const index = lookup(target, ref), storage = store(target, component);
301
+ if (!storage.presence[index])
302
+ fail("MISSING_COMPONENT", `Entity does not have ${component}.`);
303
+ storage.remove(index);
304
+ };
305
+ const destroy = (target, ref, access) => {
306
+ const indices = subtree(target, lookup(target, ref));
307
+ write("Transform", access);
308
+ for (const [name, storage] of target.stores) {
309
+ if (indices.some((index) => storage.presence[index]))
310
+ write(name, access);
311
+ }
312
+ erase(target, indices);
313
+ };
314
+ const readAll = () => {
315
+ read("Transform");
316
+ for (const name of state.stores.keys())
317
+ read(name);
318
+ };
319
+ const records = () => {
320
+ const result = [];
321
+ for (let index = 0; index < state.length; index++) {
322
+ if (!state.alive[index])
323
+ continue;
324
+ const parent = state.parents[index];
325
+ result.push({
326
+ id: state.ids[index],
327
+ ...(state.prefabs.has(index)
328
+ ? { prefab: state.prefabs.get(index) }
329
+ : {}),
330
+ ...(parent === -1 ? {} : { parent: state.ids[parent] }),
331
+ transform: state.transforms.read(index),
332
+ components: Object.fromEntries([...state.stores]
333
+ .filter(([, storage]) => storage.presence[index])
334
+ .map(([name, storage]) => [name, storage.read(index)])),
335
+ });
336
+ }
337
+ return result;
338
+ };
339
+ const api = {
340
+ registerComponent(definition) {
341
+ outsideSystem();
342
+ register(state, definition);
343
+ revision++;
344
+ },
345
+ spawn(input) {
346
+ outsideSystem();
347
+ const index = spawn(state, input);
348
+ revision++;
349
+ return handle(state, index);
350
+ },
351
+ destroy(entity) {
352
+ outsideSystem();
353
+ destroy(state, entity);
354
+ revision++;
355
+ },
356
+ resolve(id) {
357
+ ensure();
358
+ identifier(id, "Entity id");
359
+ return handle(state, lookup(state, id));
360
+ },
361
+ id(entity) {
362
+ ensure();
363
+ return state.ids[lookup(state, entity)];
364
+ },
365
+ isAlive(entity) {
366
+ ensure();
367
+ if (typeof entity === "string")
368
+ return state.byId.has(entity);
369
+ return (!!entity &&
370
+ entity.world === world &&
371
+ Number.isSafeInteger(entity.index) &&
372
+ entity.index >= 0 &&
373
+ entity.index < state.length &&
374
+ state.alive[entity.index] === 1 &&
375
+ state.generations[entity.index] === entity.generation);
376
+ },
377
+ getTransform(entity) {
378
+ ensure();
379
+ read("Transform");
380
+ return state.transforms.read(lookup(state, entity));
381
+ },
382
+ setTransform(entity, patch) {
383
+ ensure();
384
+ write("Transform");
385
+ const index = lookup(state, entity);
386
+ const data = values(TRANSFORM, patch, false);
387
+ state.transforms.write(index, data);
388
+ revision++;
389
+ },
390
+ getComponent(entity, component) {
391
+ ensure();
392
+ read(component);
393
+ const index = lookup(state, entity), storage = store(state, component);
394
+ if (!storage.presence[index])
395
+ fail("MISSING_COMPONENT", `Entity does not have ${component}.`);
396
+ return storage.read(index);
397
+ },
398
+ setComponent(entity, component, patch) {
399
+ ensure();
400
+ write(component);
401
+ const index = lookup(state, entity), storage = store(state, component);
402
+ if (!storage.presence[index])
403
+ fail("MISSING_COMPONENT", `Entity does not have ${component}.`);
404
+ const data = values(storage.schema, patch, false);
405
+ storage.write(index, data);
406
+ revision++;
407
+ },
408
+ addComponent(entity, component, data = {}) {
409
+ outsideSystem();
410
+ add(state, entity, component, data);
411
+ revision++;
412
+ },
413
+ removeComponent(entity, component) {
414
+ outsideSystem();
415
+ remove(state, entity, component);
416
+ revision++;
417
+ },
418
+ query(...components) {
419
+ ensure();
420
+ const columns = [...new Set(components)].map((name) => {
421
+ read(name);
422
+ return store(state, name).presence;
423
+ });
424
+ const result = [];
425
+ for (let index = 0; index < state.length; index++) {
426
+ if (state.alive[index] &&
427
+ columns.every((column) => column[index] === 1))
428
+ result.push(state.ids[index]);
429
+ }
430
+ return result;
431
+ },
432
+ records() {
433
+ ensure();
434
+ readAll();
435
+ return records();
436
+ },
437
+ queue(command) {
438
+ ensure();
439
+ structural();
440
+ record(command, "World command");
441
+ if (!["spawn", "destroy", "addComponent", "removeComponent"].includes(command.type))
442
+ fail("COMMAND", "Unknown structural command.");
443
+ // Capture payload and permissions now, not mutable caller-owned references at flush time.
444
+ let captured;
445
+ try {
446
+ captured = structuredClone(command);
447
+ }
448
+ catch {
449
+ return fail("COMMAND", "Command payload must be cloneable data.");
450
+ }
451
+ if (command.type === "spawn") {
452
+ write("Transform");
453
+ record(command.entity, "Spawn entity");
454
+ for (const name of Object.keys(command.entity.components ?? {}))
455
+ write(name);
456
+ }
457
+ else if (command.type === "addComponent" ||
458
+ command.type === "removeComponent")
459
+ write(command.component);
460
+ pending.push({
461
+ command: captured,
462
+ access: active && {
463
+ ...(active.reads === undefined ? {} : { reads: [...active.reads] }),
464
+ ...(active.writes === undefined
465
+ ? {}
466
+ : { writes: [...active.writes] }),
467
+ structural: active.structural,
468
+ },
469
+ });
470
+ },
471
+ flush() {
472
+ outsideSystem();
473
+ if (pending.length === 0)
474
+ return;
475
+ const candidate = copyState(state);
476
+ const previousGeneration = generation;
477
+ try {
478
+ for (const { command, access } of pending) {
479
+ structural(access);
480
+ switch (command.type) {
481
+ case "spawn":
482
+ spawn(candidate, command.entity, access);
483
+ break;
484
+ case "destroy":
485
+ destroy(candidate, command.entity, access);
486
+ break;
487
+ case "addComponent":
488
+ add(candidate, command.entity, command.component, command.data === undefined ? {} : command.data, access);
489
+ break;
490
+ case "removeComponent":
491
+ remove(candidate, command.entity, command.component, access);
492
+ break;
493
+ }
494
+ }
495
+ }
496
+ catch (error) {
497
+ generation = previousGeneration;
498
+ // Failed batches are discarded; retaining an invalid queue would poison future frames.
499
+ pending = [];
500
+ throw error;
501
+ }
502
+ state = candidate;
503
+ pending = [];
504
+ revision++;
505
+ },
506
+ runSystem(id, access, operation) {
507
+ ensure();
508
+ identifier(id, "System id");
509
+ if (running)
510
+ fail("SYSTEM_NESTED", "Nested systems are not supported.");
511
+ if (typeof operation !== "function" ||
512
+ operation.constructor.name === "AsyncFunction")
513
+ fail("SYSTEM_ASYNC", "Systems must be synchronous functions.");
514
+ if (access !== undefined) {
515
+ record(access, "System access");
516
+ for (const list of [access.reads, access.writes]) {
517
+ if (list !== undefined &&
518
+ (!Array.isArray(list) ||
519
+ Array.from(list).some((name) => typeof name !== "string" || name.trim().length === 0)))
520
+ fail("ACCESS", "Access lists must contain nonempty component names.");
521
+ }
522
+ if (access.structural !== undefined &&
523
+ typeof access.structural !== "boolean")
524
+ fail("ACCESS", "structural must be boolean.");
525
+ }
526
+ active = access && {
527
+ ...access,
528
+ reads: access.reads && [...access.reads],
529
+ writes: access.writes && [...access.writes],
530
+ };
531
+ running = true;
532
+ try {
533
+ const result = operation();
534
+ if (result !== null &&
535
+ (typeof result === "object" || typeof result === "function") &&
536
+ typeof result.then === "function") {
537
+ // Consume a rejected native promise so rejecting asynchronous systems does not
538
+ // produce an unrelated unhandled-rejection failure in the host process.
539
+ if (result instanceof Promise)
540
+ void result.catch(() => { });
541
+ fail("SYSTEM_ASYNC", "Systems cannot return asynchronous work.");
542
+ }
543
+ return result;
544
+ }
545
+ finally {
546
+ running = false;
547
+ active = undefined;
548
+ }
549
+ },
550
+ snapshot() {
551
+ ensure();
552
+ readAll();
553
+ return {
554
+ format: "pelican.world",
555
+ version: 1,
556
+ components: [...state.definitions.values()].map((definition) => ({
557
+ ...definition,
558
+ schema: cloneSchema(definition.schema),
559
+ })),
560
+ entities: records(),
561
+ };
562
+ },
563
+ restore(snapshot) {
564
+ outsideSystem();
565
+ record(snapshot, "World snapshot");
566
+ if (snapshot.format !== "pelican.world" || snapshot.version !== 1)
567
+ fail("VERSION", "Unsupported world snapshot format/version.");
568
+ const candidate = build(snapshot.components, snapshot.entities, true, initialCapacity);
569
+ state = candidate;
570
+ pending = [];
571
+ revision++;
572
+ },
573
+ inspect() {
574
+ return {
575
+ entities: state.count,
576
+ capacity: state.capacity,
577
+ components: state.stores.size,
578
+ pendingCommands: pending.length,
579
+ revision,
580
+ disposed,
581
+ };
582
+ },
583
+ dispose() {
584
+ if (disposed)
585
+ return;
586
+ outsideSystem();
587
+ state = empty(0);
588
+ pending = [];
589
+ disposed = true;
590
+ revision++;
591
+ },
592
+ };
593
+ return api;
594
+ }
@@ -0,0 +1,24 @@
1
+ export const CONTENT_FORMAT_VERSION = 1;
2
+ /** Generate JSON Schema from the exact SDK component declaration, never a parallel hand-maintained model. */
3
+ export function componentJSONSchema(definition) {
4
+ const properties = Object.fromEntries(Object.entries(definition.schema).map(([name, field]) => [
5
+ name,
6
+ field.type === "vec3"
7
+ ? {
8
+ type: "array",
9
+ items: { type: "number" },
10
+ minItems: 3,
11
+ maxItems: 3,
12
+ default: [...field.default],
13
+ }
14
+ : { type: field.type, default: field.default },
15
+ ]));
16
+ return {
17
+ $schema: "https://json-schema.org/draft/2020-12/schema",
18
+ title: definition.id,
19
+ type: "object",
20
+ additionalProperties: false,
21
+ required: Object.keys(properties),
22
+ properties,
23
+ };
24
+ }
@@ -0,0 +1,16 @@
1
+ import { GRAPH_TYPES } from "./index.js";
2
+ /** Internal shared value contract; public declarations remain in the SDK entry. */
3
+ export function isGraphType(value) {
4
+ return typeof value === "string" && GRAPH_TYPES.includes(value);
5
+ }
6
+ export function graphValue(type, value) {
7
+ const number = (v) => typeof v === "number" && Number.isFinite(v) && Number.isFinite(Math.fround(v));
8
+ if (type === "bool")
9
+ return typeof value === "boolean" ? value : null;
10
+ if (type === "float")
11
+ return number(value) ? value : null;
12
+ const size = Number(type.slice(3));
13
+ if (!Array.isArray(value) || value.length !== size || !Array.from(value).every(number))
14
+ return null;
15
+ return Object.freeze(Array.from(value));
16
+ }