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,260 @@
1
+ export const BSDF_MIN_ROUGHNESS = 0.045;
2
+ const PI = Math.PI;
3
+ const ZERO = [0, 0, 0];
4
+ const clamp = (x, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, x));
5
+ const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
6
+ const scale = (v, s) => [
7
+ v[0] * s,
8
+ v[1] * s,
9
+ v[2] * s,
10
+ ];
11
+ const add = (a, b) => [
12
+ a[0] + b[0],
13
+ a[1] + b[1],
14
+ a[2] + b[2],
15
+ ];
16
+ const cross = (a, b) => [
17
+ a[1] * b[2] - a[2] * b[1],
18
+ a[2] * b[0] - a[0] * b[2],
19
+ a[0] * b[1] - a[1] * b[0],
20
+ ];
21
+ function unit(v) {
22
+ const largest = Math.max(...v.map(Math.abs));
23
+ if (largest === 0)
24
+ return ZERO;
25
+ const scaled = [v[0] / largest, v[1] / largest, v[2] / largest];
26
+ return scale(scaled, 1 / Math.sqrt(dot(scaled, scaled)));
27
+ }
28
+ function halfVector(v, l) {
29
+ if (dot(v, l) < -0.5) {
30
+ // For unit V,L this equals (1-dot(V,L))*(V+L), with less cancellation.
31
+ const h = cross(cross(v, l), add(v, scale(l, -1)));
32
+ if (Math.max(...h.map(Math.abs)) > 0)
33
+ return unit(h);
34
+ }
35
+ return unit(add(v, l));
36
+ }
37
+ function finite(value, label) {
38
+ if (!Number.isFinite(value) ||
39
+ !Number.isFinite(Math.fround(value)) ||
40
+ (value !== 0 && Math.fround(value) === 0))
41
+ throw new RangeError(`TC_BSDF_INPUT: ${label} must be finite and f32-representable`);
42
+ }
43
+ function vector(value, label) {
44
+ if (value.length !== 3)
45
+ throw new RangeError(`TC_BSDF_INPUT: ${label} must have 3 components`);
46
+ for (let i = 0; i < 3; i++)
47
+ finite(value[i], label);
48
+ }
49
+ function validate(normal, view, base, roughness, metalness) {
50
+ vector(normal, "normal");
51
+ vector(view, "view");
52
+ vector(base, "base");
53
+ if (base.some((v) => v < 0 || v > 1))
54
+ throw new RangeError("TC_BSDF_INPUT: linear base albedo must be in [0,1]; HDR radiance is not albedo");
55
+ finite(roughness, "roughness");
56
+ finite(metalness, "metalness");
57
+ }
58
+ const alphaOf = (r) => clamp(r, BSDF_MIN_ROUGHNESS) ** 2;
59
+ const f0Of = (base, m, ior, intensity = 1, color = [1, 1, 1]) => {
60
+ const ratio = (ior - 1) / (ior + 1), dielectric = ior === 1.5 ? 0.04 : ratio * ratio;
61
+ return base.map((c, i) => Math.min(dielectric * color[i], 1) * intensity * (1 - m) + c * m);
62
+ };
63
+ function validateIor(ior) {
64
+ finite(ior, "ior");
65
+ if (ior <= 0)
66
+ throw new RangeError("TC_BSDF_INPUT: ior must be positive");
67
+ }
68
+ function validateSpecular(intensity, color) {
69
+ finite(intensity, 'specularIntensity');
70
+ vector(color, 'specularColor');
71
+ if (intensity < 0 || intensity > 1 || color.some((c) => c < 0 || c > 1))
72
+ throw new RangeError('TC_BSDF_INPUT: specular intensity and color must be in [0,1]');
73
+ }
74
+ function fresnel(f0, cosine, f90 = 1) {
75
+ const f = (1 - clamp(cosine)) ** 5;
76
+ return f0.map((c) => c + (f90 - c) * f);
77
+ }
78
+ function smithRoot(alpha, cosine) {
79
+ return Math.sqrt(alpha * alpha + (1 - alpha * alpha) * cosine * cosine);
80
+ }
81
+ function distribution(n, h, alpha) {
82
+ const nh = clamp(dot(n, h)), perpendicular = cross(n, h);
83
+ // Algebraically Three's 1 + (alpha^2 - 1) NoH^2, without peak cancellation.
84
+ const denominator = dot(perpendicular, perpendicular) + alpha * alpha * nh * nh;
85
+ return (alpha * alpha) / (PI * denominator * denominator);
86
+ }
87
+ function probability(base, f0, m, nv, f90 = 1) {
88
+ const fv = fresnel(f0, nv, f90);
89
+ const specular = (fv[0] + fv[1] + fv[2]) / 3;
90
+ const diffuse = ((1 - m) * (base[0] + base[1] + base[2])) / 3;
91
+ if (diffuse === 0)
92
+ return 1;
93
+ // Neither nonzero lobe may disappear from the proposal, including at grazing.
94
+ return clamp(specular / (specular + diffuse), 0.05, 0.95);
95
+ }
96
+ function evaluate(n, v, l, base, r, m, ior, intensity, color) {
97
+ const nv = clamp(dot(n, v)), nl = clamp(dot(n, l));
98
+ if (nv <= 0 || nl <= 0)
99
+ return [0, 0, 0];
100
+ const h = halfVector(v, l), alpha = alphaOf(r), f0 = f0Of(base, m, ior, intensity, color), f90 = intensity * (1 - m) + m;
101
+ const f = fresnel(f0, dot(v, h), f90), fv = fresnel(f0, nv, f90), fl = fresnel(f0, nl, f90);
102
+ const visibility = 0.5 /
103
+ Math.max(nl * smithRoot(alpha, nv) + nv * smithRoot(alpha, nl), 1e-30);
104
+ const dv = distribution(n, h, alpha) * visibility;
105
+ return base.map((c, i) => f[i] * dv + (c * (1 - m) * (1 - fv[i]) * (1 - fl[i])) / PI);
106
+ }
107
+ function pdf(n, v, l, base, r, m, ior, intensity, color) {
108
+ const nv = clamp(dot(n, v)), nl = clamp(dot(n, l));
109
+ if (nv <= 0 || nl <= 0)
110
+ return 0;
111
+ const h = halfVector(v, l), alpha = alphaOf(r);
112
+ const p = probability(base, f0Of(base, m, ior, intensity, color), m, nv, intensity * (1 - m) + m);
113
+ // Visible-normal PDF D*G1(v)*VoH/NoV and reflection Jacobian 1/(4*VoH).
114
+ const specular = distribution(n, h, alpha) / (2 * (nv + smithRoot(alpha, nv)));
115
+ return p * specular + ((1 - p) * nl) / PI;
116
+ }
117
+ /** Returns BRDF f only, in inverse steradians, with no cosine or incident radiance. */
118
+ export function evaluateBsdf(normal, view, light, base, roughness, metalness, ior = 1.5, intensity = 1, color = [1, 1, 1]) {
119
+ validate(normal, view, base, roughness, metalness);
120
+ validateIor(ior);
121
+ validateSpecular(intensity, color);
122
+ vector(light, "light");
123
+ return evaluate(unit(normal), unit(view), unit(light), base, roughness, clamp(metalness), ior, intensity, color);
124
+ }
125
+ /** Solid-angle mixture density. Its upper-hemisphere integral may be less than 1. */
126
+ export function bsdfPdf(normal, view, light, base, roughness, metalness, ior = 1.5, intensity = 1, color = [1, 1, 1]) {
127
+ validate(normal, view, base, roughness, metalness);
128
+ validateIor(ior);
129
+ validateSpecular(intensity, color);
130
+ vector(light, "light");
131
+ return pdf(unit(normal), unit(view), unit(light), base, roughness, clamp(metalness), ior, intensity, color);
132
+ }
133
+ /** random.x selects a lobe; random.yz sample it. Null events are NOT resampled. */
134
+ export function sampleBsdf(normal, view, base, roughness, metalness, random, ior = 1.5, intensity = 1, color = [1, 1, 1]) {
135
+ validate(normal, view, base, roughness, metalness);
136
+ validateIor(ior);
137
+ validateSpecular(intensity, color);
138
+ vector(random, "random");
139
+ if (random.some((v) => v < 0 || v >= 1))
140
+ throw new RangeError("TC_BSDF_INPUT: random must have independent uniform components in [0,1)");
141
+ const zero = () => ({
142
+ direction: [0, 0, 0],
143
+ weight: [0, 0, 0],
144
+ pdf: 0,
145
+ });
146
+ const n = unit(normal), v = unit(view), nv = clamp(dot(n, v));
147
+ if (nv <= 0)
148
+ return zero();
149
+ const m = clamp(metalness), alpha = alphaOf(roughness);
150
+ const t = unit(cross(Math.abs(n[2]) < 0.999 ? [0, 0, 1] : [1, 0, 0], n));
151
+ const b = cross(n, t);
152
+ const world = (local) => unit(add(add(scale(t, local[0]), scale(b, local[1])), scale(n, local[2])));
153
+ const radius = Math.sqrt(random[1]), phi = 2 * PI * random[2];
154
+ const x = radius * Math.cos(phi);
155
+ const y = radius * Math.sin(phi);
156
+ let direction;
157
+ if (random[0] < probability(base, f0Of(base, m, ior, intensity, color), m, nv, intensity * (1 - m) + m)) {
158
+ // Dupuy/Benyoub 2023: exact VNDF via a spherical cap, no view-aligned basis.
159
+ const vh = unit([alpha * dot(v, t), alpha * dot(v, b), nv]);
160
+ const z = 1 - random[1] * (1 + vh[2]);
161
+ const sinTheta = Math.sqrt(Math.max(0, 1 - z * z));
162
+ const nh = add([sinTheta * Math.cos(phi), sinTheta * Math.sin(phi), z], vh);
163
+ const h = world(unit([alpha * nh[0], alpha * nh[1], Math.max(0, nh[2])]));
164
+ direction = unit(add(scale(h, 2 * dot(v, h)), scale(v, -1)));
165
+ }
166
+ else {
167
+ direction = world([x, y, Math.sqrt(Math.max(0, 1 - random[1]))]);
168
+ }
169
+ const nl = dot(n, direction);
170
+ if (nl <= 0)
171
+ return zero();
172
+ const density = pdf(n, v, direction, base, roughness, m, ior, intensity, color);
173
+ if (density <= 0)
174
+ return zero();
175
+ const f = evaluate(n, v, direction, base, roughness, m, ior, intensity, color);
176
+ return { direction, weight: scale(f, nl / density), pdf: density };
177
+ }
178
+ /** Binding-free WGSL; all helper names are namespaced for surface/path composition. */
179
+ export const BSDF_SHADER = /* wgsl */ String.raw `
180
+ struct BsdfSample { direction:vec3f, weight:vec3f, pdf:f32, }
181
+ fn bsdfUnit(v:vec3f)->vec3f {
182
+ let largest=max(max(abs(v.x),abs(v.y)),abs(v.z));
183
+ if(largest==0.0){return vec3f(0);}
184
+ let s=v/largest;return s/sqrt(dot(s,s));
185
+ }
186
+ fn bsdfHalfVector(v:vec3f,l:vec3f)->vec3f {
187
+ if(dot(v,l)<-0.5){let h=cross(cross(v,l),v-l);if(max(max(abs(h.x),abs(h.y)),abs(h.z))>0.0){return bsdfUnit(h);}}
188
+ return bsdfUnit(v+l);
189
+ }
190
+ fn bsdfAlpha(r:f32)->f32 {let roughness=clamp(r,0.045,1.0);return roughness*roughness;}
191
+ fn bsdfF0Specular(base:vec3f,m:f32,ior:f32,intensity:f32,color:vec3f)->vec3f {let ratio=(ior-1.0)/(ior+1.0);let dielectric=select(ratio*ratio,0.04,ior==1.5);return min(vec3f(dielectric)*color,vec3f(1))*intensity*(1.0-m)+base*m;}
192
+ fn bsdfF0Ior(base:vec3f,m:f32,ior:f32)->vec3f {return bsdfF0Specular(base,m,ior,1.0,vec3f(1));}
193
+ fn bsdfF0(base:vec3f,m:f32)->vec3f {return bsdfF0Ior(base,m,1.5);}
194
+ fn bsdfFresnelSpecular(f0:vec3f,cosine:f32,f90:f32)->vec3f {
195
+ let x=1.0-clamp(cosine,0.0,1.0);let x2=x*x;return f0+(vec3f(f90)-f0)*(x2*x2*x);
196
+ }
197
+ fn bsdfFresnel(f0:vec3f,cosine:f32)->vec3f {return bsdfFresnelSpecular(f0,cosine,1.0);}
198
+ fn bsdfSmithRoot(alpha:f32,cosine:f32)->f32 {
199
+ let a2=alpha*alpha;return sqrt(a2+(1.0-a2)*cosine*cosine);
200
+ }
201
+ fn bsdfDistribution(n:vec3f,h:vec3f,alpha:f32)->f32 {
202
+ let nh=clamp(dot(n,h),0.0,1.0);let perpendicular=cross(n,h);
203
+ let denominator=dot(perpendicular,perpendicular)+alpha*alpha*nh*nh;
204
+ return alpha*alpha/(3.141592653589793*denominator*denominator);
205
+ }
206
+ fn bsdfProbabilitySpecular(base:vec3f,f0:vec3f,m:f32,nv:f32,f90:f32)->f32 {
207
+ let fv=bsdfFresnelSpecular(f0,nv,f90);let specular=(fv.x+fv.y+fv.z)/3.0;
208
+ let diffuse=(1.0-m)*(base.x+base.y+base.z)/3.0;
209
+ if(diffuse==0.0){return 1.0;}
210
+ return clamp(specular/(specular+diffuse),0.05,0.95);
211
+ }
212
+ fn bsdfProbability(base:vec3f,f0:vec3f,m:f32,nv:f32)->f32 {return bsdfProbabilitySpecular(base,f0,m,nv,1.0);}
213
+ fn bsdfEvaluateSpecular(normal:vec3f,view:vec3f,light:vec3f,base:vec3f,roughness:f32,metalness:f32,ior:f32,intensity:f32,color:vec3f)->vec3f {
214
+ let n=bsdfUnit(normal);let v=bsdfUnit(view);let l=bsdfUnit(light);
215
+ let nv=clamp(dot(n,v),0.0,1.0);let nl=clamp(dot(n,l),0.0,1.0);
216
+ if(nv<=0.0||nl<=0.0){return vec3f(0);}
217
+ let m=clamp(metalness,0.0,1.0);let h=bsdfHalfVector(v,l);let alpha=bsdfAlpha(roughness);let f0=bsdfF0Specular(base,m,ior,intensity,color);let f90=intensity*(1.0-m)+m;
218
+ let f=bsdfFresnelSpecular(f0,dot(v,h),f90);let fv=bsdfFresnelSpecular(f0,nv,f90);let fl=bsdfFresnelSpecular(f0,nl,f90);
219
+ let visibility=0.5/max(nl*bsdfSmithRoot(alpha,nv)+nv*bsdfSmithRoot(alpha,nl),1e-30);
220
+ return f*(bsdfDistribution(n,h,alpha)*visibility)+base*(1.0-m)*(vec3f(1)-fv)*(vec3f(1)-fl)/3.141592653589793;
221
+ }
222
+ fn bsdfPdfSpecular(normal:vec3f,view:vec3f,light:vec3f,base:vec3f,roughness:f32,metalness:f32,ior:f32,intensity:f32,color:vec3f)->f32 {
223
+ let n=bsdfUnit(normal);let v=bsdfUnit(view);let l=bsdfUnit(light);
224
+ let nv=clamp(dot(n,v),0.0,1.0);let nl=clamp(dot(n,l),0.0,1.0);
225
+ if(nv<=0.0||nl<=0.0){return 0.0;}
226
+ let m=clamp(metalness,0.0,1.0);let h=bsdfHalfVector(v,l);let alpha=bsdfAlpha(roughness);
227
+ let p=bsdfProbabilitySpecular(base,bsdfF0Specular(base,m,ior,intensity,color),m,nv,intensity*(1.0-m)+m);
228
+ let specular=bsdfDistribution(n,h,alpha)/(2.0*(nv+bsdfSmithRoot(alpha,nv)));
229
+ return p*specular+(1.0-p)*nl/3.141592653589793;
230
+ }
231
+ fn bsdfWorld(n:vec3f,t:vec3f,b:vec3f,local:vec3f)->vec3f {
232
+ return bsdfUnit(t*local.x+b*local.y+n*local.z);
233
+ }
234
+ fn bsdfSampleSpecular(normal:vec3f,view:vec3f,base:vec3f,roughness:f32,metalness:f32,random:vec3f,ior:f32,intensity:f32,color:vec3f)->BsdfSample {
235
+ let zero=BsdfSample(vec3f(0),vec3f(0),0.0);
236
+ let n=bsdfUnit(normal);let v=bsdfUnit(view);let nv=clamp(dot(n,v),0.0,1.0);
237
+ if(nv<=0.0){return zero;}
238
+ let m=clamp(metalness,0.0,1.0);let alpha=bsdfAlpha(roughness);
239
+ var up=vec3f(1,0,0);if(abs(n.z)<0.999){up=vec3f(0,0,1);}
240
+ let t=bsdfUnit(cross(up,n));let b=cross(n,t);
241
+ let radius=sqrt(random.y);let phi=6.283185307179586*random.z;
242
+ let x=radius*cos(phi);let y=radius*sin(phi);var direction:vec3f;
243
+ if(random.x<bsdfProbabilitySpecular(base,bsdfF0Specular(base,m,ior,intensity,color),m,nv,intensity*(1.0-m)+m)) {
244
+ let vh=bsdfUnit(vec3f(alpha*dot(v,t),alpha*dot(v,b),nv));
245
+ let z=1.0-random.y*(1.0+vh.z);let sinTheta=sqrt(max(0.0,1.0-z*z));
246
+ let nh=vec3f(sinTheta*cos(phi),sinTheta*sin(phi),z)+vh;
247
+ let h=bsdfWorld(n,t,b,bsdfUnit(vec3f(alpha*nh.x,alpha*nh.y,max(0.0,nh.z))));
248
+ direction=bsdfUnit(2.0*dot(v,h)*h-v);
249
+ } else {direction=bsdfWorld(n,t,b,vec3f(x,y,sqrt(max(0.0,1.0-random.y))));}
250
+ let nl=dot(n,direction);if(nl<=0.0){return zero;}
251
+ let density=bsdfPdfSpecular(n,v,direction,base,roughness,m,ior,intensity,color);if(density<=0.0){return zero;}
252
+ return BsdfSample(direction,bsdfEvaluateSpecular(n,v,direction,base,roughness,m,ior,intensity,color)*(nl/density),density);
253
+ }
254
+ fn bsdfEvaluateIor(n:vec3f,v:vec3f,l:vec3f,base:vec3f,r:f32,m:f32,ior:f32)->vec3f{return bsdfEvaluateSpecular(n,v,l,base,r,m,ior,1.0,vec3f(1));}
255
+ fn bsdfPdfIor(n:vec3f,v:vec3f,l:vec3f,base:vec3f,r:f32,m:f32,ior:f32)->f32{return bsdfPdfSpecular(n,v,l,base,r,m,ior,1.0,vec3f(1));}
256
+ fn bsdfSampleIor(n:vec3f,v:vec3f,base:vec3f,r:f32,m:f32,random:vec3f,ior:f32)->BsdfSample{return bsdfSampleSpecular(n,v,base,r,m,random,ior,1.0,vec3f(1));}
257
+ fn bsdfEvaluate(n:vec3f,v:vec3f,l:vec3f,base:vec3f,r:f32,m:f32)->vec3f{return bsdfEvaluateIor(n,v,l,base,r,m,1.5);}
258
+ fn bsdfPdf(n:vec3f,v:vec3f,l:vec3f,base:vec3f,r:f32,m:f32)->f32{return bsdfPdfIor(n,v,l,base,r,m,1.5);}
259
+ fn bsdfSample(n:vec3f,v:vec3f,base:vec3f,r:f32,m:f32,random:vec3f)->BsdfSample{return bsdfSampleIor(n,v,base,r,m,random,1.5);}
260
+ `;
@@ -0,0 +1,88 @@
1
+ import { SDK_VERSION } from "../sdk/index.js";
2
+ import { createSimulation } from "../experiments/simulation/index.js";
3
+ import { createWorldProbe } from "../experiments/world/index.js";
4
+ /** Only the display envelope is checked here; real owners validate their state before commit. */
5
+ export function validateBrowserCheckpoint(value, resources) {
6
+ const saved = structuredClone(value);
7
+ if (!saved ||
8
+ saved.format !== "pelican.browser" ||
9
+ saved.version !== 1 ||
10
+ saved.engineVersion !== SDK_VERSION ||
11
+ !Number.isSafeInteger(saved.tick) ||
12
+ saved.tick < 0 ||
13
+ !saved.runtime ||
14
+ saved.runtime.scheduler?.tick !== saved.tick ||
15
+ !Number.isFinite(saved.cameraPhase) ||
16
+ !Number.isFinite(saved.frameAccumulator) ||
17
+ saved.frameAccumulator < 0)
18
+ throw new Error("TC_BROWSER_SNAPSHOT: Invalid or incompatible runtime/display checkpoint.");
19
+ if (JSON.stringify(saved.resources) !== JSON.stringify(resources))
20
+ throw new Error("TC_BROWSER_RESOURCE_VERSION: Reloaded content requires a matching checkpoint.");
21
+ if (!Array.isArray(saved.inputLog) ||
22
+ saved.inputLog.some((input) => !input ||
23
+ !Number.isSafeInteger(input.tick) ||
24
+ input.tick < 0 ||
25
+ input.tick > saved.tick ||
26
+ typeof input.key !== "string" ||
27
+ !input.key.length ||
28
+ input.key.length > 64))
29
+ throw new Error("TC_BROWSER_SNAPSHOT_INPUT: Invalid input trace.");
30
+ if (!Array.isArray(saved.topology) ||
31
+ saved.topology.some((position) => !Array.isArray(position) ||
32
+ position.length !== 3 ||
33
+ position.some((value) => typeof value !== "number" || !Number.isFinite(value))))
34
+ throw new Error("TC_BROWSER_SNAPSHOT_GEOMETRY: Invalid probe positions.");
35
+ return saved;
36
+ }
37
+ /** M0 CPU probes only, not M1 production physics/world modules. Never mutate live owners here. */
38
+ export async function prepareCheckpointProbes(saved) {
39
+ let simulation;
40
+ let world;
41
+ try {
42
+ if (saved.simulation !== undefined) {
43
+ if (!saved.simulation ||
44
+ !["rope", "cloth", "softbody", "fluid"].includes(saved.simulation.kind))
45
+ throw new Error("TC_BROWSER_SNAPSHOT_SIMULATION: Invalid simulation probe.");
46
+ simulation = createSimulation(saved.simulation.kind);
47
+ simulation.restore(saved.simulation);
48
+ }
49
+ if (saved.world !== undefined) {
50
+ if (!saved.world || typeof saved.world.config !== "object")
51
+ throw new Error("TC_BROWSER_SNAPSHOT_WORLD: Invalid world probe.");
52
+ world = createWorldProbe(saved.world.config);
53
+ await world.restore(saved.world);
54
+ }
55
+ return {
56
+ simulation,
57
+ world,
58
+ dispose() {
59
+ simulation?.dispose();
60
+ world?.dispose();
61
+ },
62
+ };
63
+ }
64
+ catch (error) {
65
+ simulation?.dispose();
66
+ world?.dispose();
67
+ throw error;
68
+ }
69
+ }
70
+ /** M1 uses World.records(); indexed/paged world queries are a later optimization. */
71
+ export function selectInspectRecords(records, options) {
72
+ const filtered = options.entity === undefined
73
+ ? records
74
+ : records.filter((record) => record.id === options.entity);
75
+ if (options.entity !== undefined && !filtered.length)
76
+ throw new Error(`TC_BROWSER_ENTITY_MISSING: Entity ${options.entity} does not exist.`);
77
+ const offset = options.offset ?? 0;
78
+ const limit = options.limit ?? 100;
79
+ return {
80
+ records: filtered.slice(offset, offset + limit),
81
+ paging: {
82
+ total: filtered.length,
83
+ offset,
84
+ limit,
85
+ returned: Math.min(limit, Math.max(0, filtered.length - offset)),
86
+ },
87
+ };
88
+ }