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,366 @@
1
+ import { GRAPH_INPUT_TYPES, GRAPH_OPERATIONS, } from "../sdk/index.js";
2
+ import { graphValue, isGraphType } from "../sdk/graph-values.js";
3
+ const record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
4
+ const idPattern = /^[a-zA-Z][a-zA-Z0-9_.-]*$/;
5
+ const has = (values, value) => typeof value === "string" && values.includes(value);
6
+ const numeric = (type) => type !== "bool";
7
+ /** Typed acyclic graph validation; issue owners are the original extraction objects. */
8
+ export function compileGraphDeclaration(raw, expected, options = {}) {
9
+ const issues = [], g = record(raw) ? raw : {}, fallbackOwner = options.owner ?? g;
10
+ const error = (code, message, owner, field, suggestion = "Use the declared graph operation, matching types and existing node/parameter/texture IDs.") => issues.push({ code, message, owner, field, suggestion });
11
+ if (!record(raw))
12
+ error("TC_SCHEMA_TYPE", "graph must be an object.", fallbackOwner, "graph");
13
+ for (const key of Object.keys(g))
14
+ if (!["nodes", "output", "parameters"].includes(key))
15
+ error("TC_GRAPH_FIELD", "Unknown graph field " + key, g, key);
16
+ const parameters = [], parameterTypes = new Map();
17
+ if (g.parameters !== undefined && !Array.isArray(g.parameters))
18
+ error("TC_SCHEMA_TYPE", "parameters must be an array.", g, "parameters");
19
+ for (const rawParameter of Array.isArray(g.parameters) ? g.parameters : []) {
20
+ if (!record(rawParameter)) {
21
+ error("TC_GRAPH_PARAMETER", "Parameter must be an object.", g, "parameters");
22
+ continue;
23
+ }
24
+ const p = rawParameter;
25
+ for (const key of Object.keys(p))
26
+ if (!["name", "type", "default"].includes(key))
27
+ error("TC_GRAPH_FIELD", "Unknown parameter field " + key, p, key);
28
+ if (typeof p.name !== "string" || !idPattern.test(p.name)) {
29
+ error("TC_GRAPH_PARAMETER", "Parameter requires a valid name.", p, "name");
30
+ continue;
31
+ }
32
+ if (parameterTypes.has(p.name)) {
33
+ error("TC_GRAPH_PARAMETER", "Duplicate parameter " + p.name, p, "name");
34
+ continue;
35
+ }
36
+ if (!isGraphType(p.type)) {
37
+ error("TC_GRAPH_PARAMETER", "Unknown parameter type.", p, "type");
38
+ continue;
39
+ }
40
+ const value = graphValue(p.type, p.default);
41
+ if (value === null) {
42
+ error("TC_GRAPH_PARAMETER_VALUE", "Parameter default must match " +
43
+ p.type +
44
+ " and fit finite f32 values.", p, "default");
45
+ continue;
46
+ }
47
+ parameterTypes.set(p.name, p.type);
48
+ parameters.push({ name: p.name, type: p.type, default: value });
49
+ }
50
+ if (!Array.isArray(g.nodes))
51
+ error("TC_SCHEMA_TYPE", "nodes must be an array.", g, "nodes");
52
+ const lookup = new Map(), normalized = new Map(), dependencies = new Map(), consumers = new Map();
53
+ for (const rawNode of Array.isArray(g.nodes) ? g.nodes : []) {
54
+ if (!record(rawNode)) {
55
+ error("TC_SCHEMA_TYPE", "Graph node must be an object.", g, "nodes");
56
+ continue;
57
+ }
58
+ const n = rawNode;
59
+ if (typeof n.id !== "string" || !idPattern.test(n.id)) {
60
+ error("TC_SCHEMA_ID", "Graph node requires a valid ID.", n, "id");
61
+ continue;
62
+ }
63
+ if (lookup.has(n.id)) {
64
+ error("TC_GRAPH_DUPLICATE", "Duplicate graph node: " + n.id, n, "id");
65
+ continue;
66
+ }
67
+ lookup.set(n.id, n);
68
+ }
69
+ const requiredInputs = new Set(), textureIds = new Set(), textureSlots = Object.create(null), frameChannels = new Set(), historyChannels = new Set(), bufferTypes = Object.create(null);
70
+ for (const [name, n] of lookup) {
71
+ const op = n.op;
72
+ let arity = 0, fields = ["id", "op"], copy = { id: name, op };
73
+ if (isGraphType(op)) {
74
+ fields.push("value");
75
+ const value = graphValue(op, n.value);
76
+ if (value === null)
77
+ error("TC_GRAPH_VALUE", "Graph constant must match " + op + " and fit finite f32 values.", n, n.value === undefined ? "op" : "value");
78
+ copy.value = value;
79
+ }
80
+ else if (op === "time") {
81
+ // The fixed simulation clock is an engine input, never a wall-clock read.
82
+ }
83
+ else if (op === "input") {
84
+ fields.push("name");
85
+ copy.name = n.name;
86
+ if (typeof n.name !== "string" ||
87
+ !Object.hasOwn(GRAPH_INPUT_TYPES, n.name))
88
+ error("TC_GRAPH_CONTEXT", "Unknown graph input " + String(n.name), n, "name");
89
+ else {
90
+ const input = n.name;
91
+ if (options.inputs && !options.inputs.includes(input))
92
+ error("TC_GRAPH_CONTEXT", "Input " + input + " is unavailable in this graph domain.", n, "name");
93
+ }
94
+ }
95
+ else if (op === "parameter") {
96
+ fields.push("name");
97
+ copy.name = n.name;
98
+ if (typeof n.name !== "string" || !parameterTypes.has(n.name))
99
+ error("TC_GRAPH_PARAMETER", "Unknown parameter " + String(n.name), n, "name");
100
+ }
101
+ else if (op === "sampleTexture") {
102
+ arity = 1;
103
+ fields.push("texture");
104
+ const texture = typeof n.texture === "string"
105
+ ? n.texture
106
+ : record(n.texture) && n.texture.kind === "asset"
107
+ ? n.texture.id
108
+ : undefined;
109
+ if (typeof texture !== "string" ||
110
+ !idPattern.test(texture) ||
111
+ ((options.textures || options.textureSlots) &&
112
+ !options.textures?.has(texture) &&
113
+ !(typeof n.texture === "string" && options.textureSlots && Object.hasOwn(options.textureSlots, texture))))
114
+ error("TC_GRAPH_TEXTURE", options.domain === "compute" ? "Reference a named texture slot declared in defineCompute.textures." : "Reference a declared texture asset or render texture slot.", n, "texture");
115
+ if (typeof texture === "string" && options.textures?.has(texture) && options.textureSlots && Object.hasOwn(options.textureSlots, texture))
116
+ error("TC_GRAPH_TEXTURE", "Texture slot name conflicts with static asset ID: " + texture, n, "texture");
117
+ copy.texture = texture;
118
+ }
119
+ else if (op === "sampleFrame" || op === "sampleHistory") {
120
+ arity = 1;
121
+ fields.push("channel");
122
+ copy.channel = n.channel;
123
+ if (options.domain !== "postprocess")
124
+ error("TC_GRAPH_CONTEXT", "Frame and history samples are available only in postprocess graphs.", n, "op");
125
+ const channels = op === "sampleFrame" ? options.frameChannels : options.historyChannels;
126
+ if (typeof n.channel !== "string" || !channels?.has(n.channel))
127
+ error("TC_POSTPROCESS_CHANNEL", "Unknown or unavailable " + op + " channel: " + String(n.channel), n, "channel", "Frame reads must reference a scene channel or an earlier pass; history reads must reference a declared postprocess pass.");
128
+ }
129
+ else if (op === "readBuffer") {
130
+ arity = 1;
131
+ fields.push("buffer");
132
+ copy.buffer = n.buffer;
133
+ if (typeof n.buffer !== "string" ||
134
+ !idPattern.test(n.buffer) ||
135
+ !options.buffers ||
136
+ !Object.hasOwn(options.buffers, n.buffer))
137
+ error("TC_COMPUTE_BINDING", options.domain === "compute"
138
+ ? "Reference a declared compute buffer binding."
139
+ : "Reference a declared render buffer slot.", n, "buffer", options.domain === "compute"
140
+ ? "Add a matching name/type to defineCompute.buffers and bind a managed buffer at dispatch."
141
+ : "Add a matching name/type to " +
142
+ (options.domain === "postprocess" ? "definePostProcess.buffers" : "defineMaterial.buffers") +
143
+ " and bind a managed buffer before rendering.");
144
+ }
145
+ else if (op === "swizzle") {
146
+ arity = 1;
147
+ fields.push("components");
148
+ copy.components = n.components;
149
+ }
150
+ else if (op === "not" || has(GRAPH_OPERATIONS.unary, op))
151
+ arity = 1;
152
+ else if (has(GRAPH_OPERATIONS.binary, op) || op === "compose2")
153
+ arity = 2;
154
+ else if (has(GRAPH_OPERATIONS.ternary, op) || op === "compose3")
155
+ arity = 3;
156
+ else if (op === "compose4")
157
+ arity = 4;
158
+ else
159
+ error("TC_GRAPH_OP", "Unsupported graph operation: " + String(op), n, "op");
160
+ const inputs = arity
161
+ ? Array.isArray(n.inputs)
162
+ ? Array.from(n.inputs)
163
+ : []
164
+ : [];
165
+ if (arity) {
166
+ fields.push("inputs");
167
+ if (inputs.length !== arity ||
168
+ inputs.some((value) => typeof value !== "string"))
169
+ error("TC_GRAPH_ARITY", "Node " + name + " requires " + arity + " node IDs.", n, "inputs");
170
+ copy.inputs = inputs;
171
+ }
172
+ for (const key of Object.keys(n))
173
+ if (!fields.includes(key))
174
+ error("TC_GRAPH_FIELD", "Unknown " + String(op) + " node field " + key, n, key);
175
+ const deps = new Set();
176
+ for (const input of inputs) {
177
+ if (typeof input !== "string")
178
+ continue;
179
+ if (!lookup.has(input))
180
+ error("TC_GRAPH_REFERENCE", "Graph references missing node: " + input, n, "inputs");
181
+ else {
182
+ deps.add(input);
183
+ if (!consumers.has(input))
184
+ consumers.set(input, new Set());
185
+ consumers.get(input).add(name);
186
+ }
187
+ }
188
+ dependencies.set(name, deps);
189
+ normalized.set(name, copy);
190
+ }
191
+ const types = Object.create(null), order = [], ready = [...lookup.keys()].filter((name) => dependencies.get(name).size === 0);
192
+ for (let cursor = 0; cursor < ready.length; cursor++) {
193
+ const name = ready[cursor], n = lookup.get(name), op = n.op;
194
+ const inputIds = Array.isArray(n.inputs) ? n.inputs : [], ts = inputIds.map((id) => typeof id === "string" ? (types[id] ?? "float") : "float");
195
+ let result = "float";
196
+ const problem = (message) => error("TC_GRAPH_TYPE", message, n, "inputs");
197
+ const broadcast = () => {
198
+ if (ts.some((type) => !numeric(type))) {
199
+ problem("Numeric inputs are required.");
200
+ return "float";
201
+ }
202
+ const vectors = [...new Set(ts.filter((type) => type !== "float"))];
203
+ if (vectors.length > 1)
204
+ problem("Vector dimensions must agree.");
205
+ return vectors[0] ?? "float";
206
+ };
207
+ if (isGraphType(op))
208
+ result = op;
209
+ else if (op === "time")
210
+ result = "float";
211
+ else if (op === "input" &&
212
+ typeof n.name === "string" &&
213
+ Object.hasOwn(GRAPH_INPUT_TYPES, n.name))
214
+ result = GRAPH_INPUT_TYPES[n.name];
215
+ else if (op === "parameter")
216
+ result = parameterTypes.get(String(n.name)) ?? "float";
217
+ else if (op === "sampleTexture" || op === "sampleFrame" || op === "sampleHistory") {
218
+ result = "vec4";
219
+ if (ts[0] !== "vec2")
220
+ problem("Texture sampling requires vec2 UV coordinates.");
221
+ }
222
+ else if (op === "readBuffer") {
223
+ result = options.buffers && Object.hasOwn(options.buffers, String(n.buffer))
224
+ ? options.buffers[String(n.buffer)]
225
+ : "float";
226
+ if (ts[0] !== "float")
227
+ problem("Buffer index must be a float-valued expression; valid indices are finite in-range integers.");
228
+ }
229
+ else if (op === "swizzle") {
230
+ const components = n.components;
231
+ const valid = typeof components === "string" &&
232
+ (/^[xyzw]{1,4}$/.test(components) || /^[rgba]{1,4}$/.test(components));
233
+ const indices = typeof components === "string"
234
+ ? [...components].map((c) => "xyzw".includes(c) ? "xyzw".indexOf(c) : "rgba".indexOf(c))
235
+ : [];
236
+ if (!valid ||
237
+ !ts[0]?.startsWith("vec") ||
238
+ indices.some((index) => index >= Number(ts[0]?.slice(3))))
239
+ error("TC_GRAPH_TYPE", "Swizzle requires valid components of the source vector.", n, "components");
240
+ result = valid
241
+ ? components.length === 1
242
+ ? "float"
243
+ : ("vec" + components.length)
244
+ : "float";
245
+ }
246
+ else if (typeof op === "string" && op.startsWith("compose")) {
247
+ if (ts.some((type) => type !== "float"))
248
+ problem("Vector composition requires scalar inputs.");
249
+ result = ("vec" + op.slice(7));
250
+ }
251
+ else if (op === "and" || op === "or" || op === "not") {
252
+ if (ts.some((type) => type !== "bool"))
253
+ problem("Boolean inputs are required.");
254
+ result = "bool";
255
+ }
256
+ else if (op === "equal") {
257
+ if (ts[0] !== ts[1] || !["float", "bool"].includes(ts[0]))
258
+ problem("Equality requires equal scalar float or bool types.");
259
+ result = "bool";
260
+ }
261
+ else if (["lessThan", "lessEqual", "greaterThan", "greaterEqual"].includes(String(op))) {
262
+ if (ts.some((type) => type !== "float"))
263
+ problem("Comparison requires scalar float inputs.");
264
+ result = "bool";
265
+ }
266
+ else if (op === "select") {
267
+ if (ts[0] !== ts[1] || ts[2] !== "bool")
268
+ problem("select requires equal value types and a bool condition.");
269
+ result = ts[0] ?? "float";
270
+ }
271
+ else if (op === "dot" || op === "cross") {
272
+ if (ts[0] !== ts[1] ||
273
+ !ts[0]?.startsWith("vec") ||
274
+ (op === "cross" && ts[0] !== "vec3"))
275
+ problem("dot needs equal vectors; cross needs vec3 inputs.");
276
+ result = op === "dot" ? "float" : "vec3";
277
+ }
278
+ else if (has(GRAPH_OPERATIONS.unary, op)) {
279
+ if (!numeric(ts[0] ?? "bool") ||
280
+ (op === "normalize" && !ts[0]?.startsWith("vec")))
281
+ problem("Unary operation requires numeric input; normalize requires a vector.");
282
+ result = op === "length" ? "float" : (ts[0] ?? "float");
283
+ }
284
+ else if (op === "add" || op === "subtract") {
285
+ if (ts[0] !== ts[1] || !numeric(ts[0] ?? "bool"))
286
+ problem("add/subtract require equal numeric types.");
287
+ result = ts[0] ?? "float";
288
+ }
289
+ else if (op === "mix") {
290
+ if (ts[0] !== ts[1] ||
291
+ !numeric(ts[0] ?? "bool") ||
292
+ (ts[2] !== "float" && ts[2] !== ts[0]))
293
+ problem("mix requires equal numeric values and scalar/matching-vector weight.");
294
+ result = ts[0] ?? "float";
295
+ }
296
+ else if (has(GRAPH_OPERATIONS.binary, op) ||
297
+ has(GRAPH_OPERATIONS.ternary, op))
298
+ result = broadcast();
299
+ types[name] = result;
300
+ order.push(normalized.get(name));
301
+ for (const consumer of consumers.get(name) ?? []) {
302
+ const deps = dependencies.get(consumer);
303
+ deps.delete(name);
304
+ if (!deps.size)
305
+ ready.push(consumer);
306
+ }
307
+ }
308
+ for (const [name, n] of lookup)
309
+ if (!Object.hasOwn(types, name))
310
+ error("TC_GRAPH_CYCLE", "Graph cycle at node: " + name, n, "inputs", "Graphs must be acyclic.");
311
+ const output = typeof g.output === "string" ? g.output : "";
312
+ if (!lookup.has(output))
313
+ error("TC_GRAPH_REFERENCE", "Graph references missing output: " + output, g, "output");
314
+ const outputType = types[output] ?? "float";
315
+ if (outputType !== expected)
316
+ error("TC_GRAPH_TYPE", "Expected " + expected + " graph output, received " + outputType + ".", g, "output");
317
+ // Validate all nodes, but bind/evaluate only the output dependency closure.
318
+ const reachable = new Set(), pending = [output];
319
+ while (pending.length) {
320
+ const name = pending.pop();
321
+ if (reachable.has(name))
322
+ continue;
323
+ const node = normalized.get(name);
324
+ if (!node)
325
+ continue;
326
+ reachable.add(name);
327
+ if ("inputs" in node)
328
+ pending.push(...node.inputs);
329
+ if (node.op === "input")
330
+ requiredInputs.add(node.name);
331
+ if (node.op === "readBuffer" && options.buffers && Object.hasOwn(options.buffers, node.buffer))
332
+ bufferTypes[node.buffer] = options.buffers[node.buffer];
333
+ if (node.op === "sampleFrame")
334
+ frameChannels.add(node.channel);
335
+ if (node.op === "sampleHistory")
336
+ historyChannels.add(node.channel);
337
+ if (node.op === "sampleTexture") {
338
+ const name = node.texture;
339
+ textureIds.add(name);
340
+ if (options.textureSlots && Object.hasOwn(options.textureSlots, name))
341
+ textureSlots[name] = { ...options.textureSlots[name] };
342
+ }
343
+ }
344
+ const nodes = order.filter((node) => reachable.has(node.id));
345
+ const graph = {
346
+ nodes,
347
+ output,
348
+ outputType,
349
+ nodeTypes: Object.fromEntries(nodes.map((node) => [node.id, types[node.id]])),
350
+ };
351
+ if (parameters.length)
352
+ graph.parameters = parameters;
353
+ if (requiredInputs.size)
354
+ graph.requiredInputs = [...requiredInputs];
355
+ if (textureIds.size)
356
+ graph.textureIds = [...textureIds];
357
+ if (Object.keys(textureSlots).length)
358
+ graph.textureSlots = { ...textureSlots };
359
+ if (Object.keys(bufferTypes).length)
360
+ graph.bufferTypes = { ...bufferTypes };
361
+ if (frameChannels.size)
362
+ graph.frameChannels = [...frameChannels];
363
+ if (historyChannels.size)
364
+ graph.historyChannels = [...historyChannels];
365
+ return { graph, issues };
366
+ }