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,346 @@
1
+ import * as THREE from "three/webgpu";
2
+ import { cubeTexture, equirectUV, float, screenUV, texture, vec3, vec4, } from "three/tsl";
3
+ export class EnvironmentCaptureError extends Error {
4
+ code;
5
+ textureUuid;
6
+ constructor(code, textureUuid, message, options) {
7
+ super(code + ": " + message, options);
8
+ this.code = code;
9
+ this.textureUuid = textureUuid;
10
+ this.name = "EnvironmentCaptureError";
11
+ }
12
+ }
13
+ function describe(input) {
14
+ const flags = input;
15
+ const unsupported = (message) => {
16
+ throw new EnvironmentCaptureError("TC_ENVIRONMENT_UNSUPPORTED", input.uuid, message);
17
+ };
18
+ if (flags.isCompressedTexture ||
19
+ flags.isCompressedCubeTexture ||
20
+ flags.isDataArrayTexture ||
21
+ flags.isData3DTexture ||
22
+ flags.isVideoTexture ||
23
+ flags.isDepthTexture ||
24
+ input.format !== THREE.RGBAFormat ||
25
+ ![THREE.UnsignedByteType, THREE.HalfFloatType, THREE.FloatType].some((type) => type === input.type) ||
26
+ ![
27
+ THREE.NoColorSpace,
28
+ THREE.LinearSRGBColorSpace,
29
+ THREE.SRGBColorSpace,
30
+ ].some((space) => space === input.colorSpace) ||
31
+ input.premultiplyAlpha)
32
+ unsupported("Use an uncompressed RGBA byte/half/float environment in linear-sRGB or sRGB without premultiplied alpha.");
33
+ let width, height;
34
+ if (flags.isCubeTexture) {
35
+ if (input.mapping !== THREE.CubeReflectionMapping)
36
+ unsupported("CubeTexture requires CubeReflectionMapping.");
37
+ const faces = input.image;
38
+ if (!Array.isArray(faces) || faces.length !== 6)
39
+ unsupported("CubeTexture must have six ready faces.");
40
+ const size = faces[0]?.image ?? faces[0];
41
+ const side = size?.width ?? 0;
42
+ if (!faces.every((face) => {
43
+ const image = face?.image ?? face;
44
+ return image?.width === side && image?.height === side;
45
+ }))
46
+ unsupported("CubeTexture faces must be ready, square and equal-sized.");
47
+ width = side * 4;
48
+ height = side * 2;
49
+ }
50
+ else {
51
+ if (input.mapping !== THREE.EquirectangularReflectionMapping)
52
+ unsupported("Use EquirectangularReflectionMapping or the locked WebGPU PMREM CubeTexture. Legacy CubeUV atlases are not supported by r187 TSL.");
53
+ const image = input.image;
54
+ width = image?.width ?? 0;
55
+ height = image?.height ?? 0;
56
+ }
57
+ if (!Number.isSafeInteger(width) ||
58
+ !Number.isSafeInteger(height) ||
59
+ width <= 0 ||
60
+ height <= 0 ||
61
+ !Number.isSafeInteger(width * height * 16))
62
+ throw new EnvironmentCaptureError("TC_ENVIRONMENT_DIMENSIONS", input.uuid, "Supply a ready environment with positive integral dimensions that fit RGBA32F storage.");
63
+ return {
64
+ version: input.version,
65
+ source: input.source,
66
+ sourceVersion: input.source.version,
67
+ image: input.image,
68
+ pmremVersion: input.pmremVersion,
69
+ mapping: input.mapping,
70
+ width,
71
+ height,
72
+ sampling: [
73
+ input.type,
74
+ input.format,
75
+ input.colorSpace,
76
+ input.flipY,
77
+ input.wrapS,
78
+ input.wrapT,
79
+ input.magFilter,
80
+ input.minFilter,
81
+ ].join(":"),
82
+ };
83
+ }
84
+ function same(a, b) {
85
+ return Object.keys(a).every((key) => a[key] === b[key]);
86
+ }
87
+ function submit(renderer, target, material) {
88
+ const viewport = renderer.getViewport(new THREE.Vector4());
89
+ const scissor = renderer.getScissor(new THREE.Vector4());
90
+ const state = {
91
+ target: renderer.getRenderTarget(),
92
+ face: renderer.getActiveCubeFace(),
93
+ mip: renderer.getActiveMipmapLevel(),
94
+ toneMapping: renderer.toneMapping,
95
+ exposure: renderer.toneMappingExposure,
96
+ colorSpace: renderer.outputColorSpace,
97
+ autoClear: renderer.autoClear,
98
+ autoClearColor: renderer.autoClearColor,
99
+ autoClearDepth: renderer.autoClearDepth,
100
+ autoClearStencil: renderer.autoClearStencil,
101
+ scissorTest: renderer.getScissorTest(),
102
+ mrt: renderer.getMRT(),
103
+ renderObject: renderer.getRenderObjectFunction(),
104
+ opaque: renderer.opaque,
105
+ transparent: renderer.transparent,
106
+ };
107
+ try {
108
+ renderer.setRenderTarget(target);
109
+ renderer.setMRT(null);
110
+ renderer.setRenderObjectFunction(null);
111
+ renderer.toneMapping = THREE.NoToneMapping;
112
+ renderer.toneMappingExposure = 1;
113
+ renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
114
+ // The opaque full-screen triangle writes every texel; no clear is needed.
115
+ renderer.autoClear = false;
116
+ renderer.opaque = true;
117
+ renderer.transparent = true;
118
+ new THREE.QuadMesh(material).render(renderer);
119
+ return renderer.readRenderTargetPixelsAsync(target, 0, 0, target.width, target.height);
120
+ }
121
+ finally {
122
+ renderer.setRenderTarget(state.target, state.face, state.mip);
123
+ renderer.setMRT(state.mrt);
124
+ renderer.setRenderObjectFunction(state.renderObject);
125
+ renderer.toneMapping = state.toneMapping;
126
+ renderer.toneMappingExposure = state.exposure;
127
+ renderer.outputColorSpace = state.colorSpace;
128
+ renderer.autoClear = state.autoClear;
129
+ renderer.autoClearColor = state.autoClearColor;
130
+ renderer.autoClearDepth = state.autoClearDepth;
131
+ renderer.autoClearStencil = state.autoClearStencil;
132
+ renderer.opaque = state.opaque;
133
+ renderer.transparent = state.transparent;
134
+ renderer.setViewport(viewport.x, viewport.y, viewport.z, viewport.w, viewport.minDepth ?? 0, viewport.maxDepth ?? 1);
135
+ renderer.setScissor(scissor);
136
+ renderer.setScissorTest(state.scissorTest);
137
+ }
138
+ }
139
+ /** Renderer-local GPU capture. Call between renders, after await renderer.init(). */
140
+ export function createEnvironmentCapture(renderer) {
141
+ const entries = new Map();
142
+ const diagnostics = new Map();
143
+ let disposed = false, reads = 0, hits = 0, shared = 0, allocated = 0, released = 0;
144
+ const error = (code, input, message, cause) => {
145
+ const failure = new EnvironmentCaptureError(code, input?.uuid ?? null, message, cause === undefined ? undefined : { cause });
146
+ diagnostics.set(input?.uuid ?? "renderer", {
147
+ code,
148
+ textureUuid: failure.textureUuid,
149
+ message,
150
+ });
151
+ return failure;
152
+ };
153
+ const cancel = (input, entry, code) => {
154
+ if (entries.get(input) === entry)
155
+ entries.delete(input);
156
+ entry.unlisten();
157
+ if (entry.active) {
158
+ entry.active = false;
159
+ entry.reject(error(code, input, "Capture invalidated; submit a fresh capture when the source and renderer are ready."));
160
+ }
161
+ entry.release();
162
+ };
163
+ const clear = () => {
164
+ for (const [input, entry] of entries)
165
+ cancel(input, entry, "TC_ENVIRONMENT_CANCELLED");
166
+ diagnostics.clear();
167
+ };
168
+ return {
169
+ capture(input) {
170
+ if (input === null)
171
+ return Promise.resolve(null);
172
+ if (disposed)
173
+ return Promise.reject(error("TC_ENVIRONMENT_DISPOSED", input, "Create a new capture provider after dispose()."));
174
+ let revision;
175
+ try {
176
+ revision = describe(input);
177
+ }
178
+ catch (failure) {
179
+ const previous = entries.get(input);
180
+ if (previous)
181
+ cancel(input, previous, "TC_ENVIRONMENT_STALE");
182
+ if (failure instanceof EnvironmentCaptureError) {
183
+ diagnostics.set(input.uuid, {
184
+ code: failure.code,
185
+ textureUuid: input.uuid,
186
+ message: failure.message,
187
+ });
188
+ return Promise.reject(failure);
189
+ }
190
+ return Promise.reject(error("TC_ENVIRONMENT_UNSUPPORTED", input, "Environment metadata is not readable.", failure));
191
+ }
192
+ let entry = entries.get(input);
193
+ if (entry && !same(entry.revision, revision)) {
194
+ cancel(input, entry, "TC_ENVIRONMENT_STALE");
195
+ entry = undefined;
196
+ }
197
+ if (entry) {
198
+ hits++;
199
+ if (entry.active)
200
+ shared++;
201
+ }
202
+ else {
203
+ if (!renderer.backend
204
+ .isWebGPUBackend ||
205
+ !renderer.hasInitialized())
206
+ return Promise.reject(error("TC_ENVIRONMENT_RENDERER", input, "Use an initialized WebGPU renderer; await renderer.init() before capture()."));
207
+ const limit = renderer.backend.device?.limits.maxTextureDimension2D;
208
+ if (limit !== undefined &&
209
+ (revision.width > limit || revision.height > limit))
210
+ return Promise.reject(error("TC_ENVIRONMENT_DIMENSIONS", input, "The equirectangular output exceeds this GPU's maxTextureDimension2D; use a smaller source."));
211
+ let resolve;
212
+ let reject;
213
+ const promise = new Promise((yes, no) => {
214
+ resolve = yes;
215
+ reject = no;
216
+ });
217
+ entry = {
218
+ revision,
219
+ promise,
220
+ reject,
221
+ release: () => { },
222
+ unlisten: () => { },
223
+ active: true,
224
+ };
225
+ entries.set(input, entry);
226
+ const current = entry;
227
+ const onDispose = () => cancel(input, current, "TC_ENVIRONMENT_SOURCE_DISPOSED");
228
+ input.addEventListener("dispose", onDispose);
229
+ current.unlisten = () => input.removeEventListener("dispose", onDispose);
230
+ let target, material;
231
+ current.release = () => {
232
+ if (target) {
233
+ target.dispose();
234
+ target = undefined;
235
+ released++;
236
+ }
237
+ if (material) {
238
+ material.dispose();
239
+ material = undefined;
240
+ released++;
241
+ }
242
+ };
243
+ const fail = (cause) => {
244
+ if (!current.active)
245
+ return;
246
+ current.active = false;
247
+ if (entries.get(input) === current)
248
+ entries.delete(input);
249
+ current.unlisten();
250
+ current.release();
251
+ reject(cause instanceof EnvironmentCaptureError
252
+ ? cause
253
+ : error("TC_ENVIRONMENT_READ_FAILED", input, "GPU environment capture failed; inspect the cause and retry after restoring GPU/source availability.", cause));
254
+ };
255
+ try {
256
+ target = new THREE.RenderTarget(revision.width, revision.height, {
257
+ type: THREE.FloatType,
258
+ format: THREE.RGBAFormat,
259
+ colorSpace: THREE.LinearSRGBColorSpace,
260
+ depthBuffer: false,
261
+ stencilBuffer: false,
262
+ minFilter: THREE.NearestFilter,
263
+ magFilter: THREE.NearestFilter,
264
+ generateMipmaps: false,
265
+ });
266
+ allocated++;
267
+ material = new THREE.NodeMaterial();
268
+ allocated++;
269
+ material.depthTest = false;
270
+ material.depthWrite = false;
271
+ material.blending = THREE.NoBlending;
272
+ material.toneMapped = false;
273
+ // WebGPU screenUV.y and readback rows both start at the top.
274
+ const theta = screenUV.x.sub(0.5).mul(2 * Math.PI);
275
+ const phi = screenUV.y.mul(Math.PI);
276
+ const direction = vec3(phi.sin().mul(theta.cos()), phi.cos(), phi.sin().mul(theta.sin()));
277
+ const environment = input.isCubeTexture
278
+ ? cubeTexture(input, direction)
279
+ : texture(input, equirectUV(direction));
280
+ environment.updateMatrix = false;
281
+ const sample = environment.level(float(0));
282
+ material.fragmentNode = vec4(sample.rgb, 1);
283
+ reads++;
284
+ const readback = submit(renderer, target, material);
285
+ void Promise.resolve(readback)
286
+ .then((pixels) => {
287
+ if (!current.active)
288
+ return;
289
+ try {
290
+ if (!same(revision, describe(input)))
291
+ throw error("TC_ENVIRONMENT_STALE", input, "The texture changed during readback; capture the new revision.");
292
+ if (!(pixels instanceof Float32Array) ||
293
+ pixels.length !== revision.width * revision.height * 4)
294
+ throw error("TC_ENVIRONMENT_READ_LAYOUT", input, "Expected tightly packed RGBA32F from the locked WebGPU readback API.");
295
+ const data = pixels.slice();
296
+ if (!data.every(Number.isFinite))
297
+ throw error("TC_ENVIRONMENT_NONFINITE", input, "Environment radiance contains NaN or infinity; repair the source HDR texture.");
298
+ const result = Object.freeze({
299
+ uuid: input.uuid,
300
+ sourceVersion: revision.version,
301
+ width: revision.width,
302
+ height: revision.height,
303
+ data,
304
+ });
305
+ current.result = result;
306
+ current.active = false;
307
+ current.release();
308
+ diagnostics.delete(input.uuid);
309
+ resolve(result);
310
+ }
311
+ catch (cause) {
312
+ fail(cause);
313
+ }
314
+ }, fail)
315
+ .catch(fail);
316
+ }
317
+ catch (cause) {
318
+ fail(cause);
319
+ }
320
+ }
321
+ // A shared read/promise never shares the mutable cache buffer with callers.
322
+ return entry.promise.then((result) => Object.freeze({ ...result, data: result.data.slice() }));
323
+ },
324
+ inspect() {
325
+ return {
326
+ disposed,
327
+ gpuReads: reads,
328
+ cacheHits: hits,
329
+ sharedPending: shared,
330
+ entries: entries.size,
331
+ pending: Array.from(entries.values()).filter((entry) => entry.active)
332
+ .length,
333
+ listeners: entries.size,
334
+ resources: { allocated, released, live: allocated - released },
335
+ retainedBytes: Array.from(entries.values()).reduce((sum, entry) => sum + (entry.result?.data.byteLength ?? 0), 0),
336
+ layout: "linear-srgb/rgba32f/equirect/north-first",
337
+ diagnostics: Array.from(diagnostics.values(), (item) => ({ ...item })),
338
+ };
339
+ },
340
+ clear,
341
+ dispose() {
342
+ clear();
343
+ disposed = true;
344
+ },
345
+ };
346
+ }