@forgeax/engine-render 0.1.34 → 0.1.35

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 (243) hide show
  1. package/README.md +52 -3
  2. package/dist/assembly/device-feature-admission.d.ts +2 -2
  3. package/dist/assembly/host-contract.d.ts +3 -2
  4. package/dist/assembly/host-contract.d.ts.map +1 -1
  5. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  6. package/dist/assembly/render-target-host.d.ts +5 -0
  7. package/dist/assembly/render-target-host.d.ts.map +1 -1
  8. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  9. package/dist/assembly/webgpu-renderer.d.ts +1 -0
  10. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  11. package/dist/authoring.mjs +2 -4
  12. package/dist/authoring.mjs.map +1 -1
  13. package/dist/chunk-3XUBWRY2.mjs +1842 -0
  14. package/dist/chunk-3XUBWRY2.mjs.map +1 -0
  15. package/dist/{chunk-UI5SFODX.mjs → chunk-G6HDNGY7.mjs} +2329 -1776
  16. package/dist/chunk-G6HDNGY7.mjs.map +1 -0
  17. package/dist/{chunk-HEB564WF.mjs → chunk-LM5IE7GF.mjs} +4 -4
  18. package/dist/{chunk-HEB564WF.mjs.map → chunk-LM5IE7GF.mjs.map} +1 -1
  19. package/dist/{chunk-MKDIT2EM.mjs → chunk-LNIUIKQ7.mjs} +143 -246
  20. package/dist/chunk-LNIUIKQ7.mjs.map +1 -0
  21. package/dist/chunk-MXTWKZO4.mjs +3268 -0
  22. package/dist/chunk-MXTWKZO4.mjs.map +1 -0
  23. package/dist/{chunk-ODZWR6JQ.mjs → chunk-MZ4C7EHF.mjs} +2042 -27
  24. package/dist/chunk-MZ4C7EHF.mjs.map +1 -0
  25. package/dist/chunk-RIPAEGI6.mjs +48 -0
  26. package/dist/chunk-RIPAEGI6.mjs.map +1 -0
  27. package/dist/{chunk-OCDLEI6I.mjs → chunk-SGPUJW3F.mjs} +117 -5
  28. package/dist/chunk-SGPUJW3F.mjs.map +1 -0
  29. package/dist/{chunk-ELJFJ2BU.mjs → chunk-X2KA6WHM.mjs} +29 -4
  30. package/dist/chunk-X2KA6WHM.mjs.map +1 -0
  31. package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
  32. package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
  33. package/dist/cloud/frame-context.d.ts +2 -2
  34. package/dist/cloud/frame-context.d.ts.map +1 -1
  35. package/dist/construct-renderer.mjs +810 -2073
  36. package/dist/construct-renderer.mjs.map +1 -1
  37. package/dist/device/gpu-residency.d.ts +19 -19
  38. package/dist/device/gpu-residency.d.ts.map +1 -1
  39. package/dist/errors/render.d.ts +3 -2
  40. package/dist/errors/render.d.ts.map +1 -1
  41. package/dist/extract/material-context.d.ts +7 -0
  42. package/dist/extract/material-context.d.ts.map +1 -0
  43. package/dist/features/host.d.ts +5 -0
  44. package/dist/features/host.d.ts.map +1 -1
  45. package/dist/features/plan.d.ts +2 -0
  46. package/dist/features/plan.d.ts.map +1 -1
  47. package/dist/features/types.d.ts +4 -0
  48. package/dist/features/types.d.ts.map +1 -1
  49. package/dist/glyph-text-layout-system.d.ts +1 -1
  50. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  51. package/dist/gpu-driven/shadow-ownership.d.ts +3 -3
  52. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -1
  53. package/dist/ibl/frame-resources.d.ts.map +1 -1
  54. package/dist/ibl/skylight-bind-group.d.ts +4 -4
  55. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  56. package/dist/index.d.ts +3 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.mjs +530 -9
  59. package/dist/index.mjs.map +1 -1
  60. package/dist/instances.d.ts +3 -4
  61. package/dist/instances.d.ts.map +1 -1
  62. package/dist/internal.mjs +7 -8
  63. package/dist/internal.mjs.map +1 -1
  64. package/dist/pbr-pipeline.d.ts +3 -3
  65. package/dist/pbr-pipeline.d.ts.map +1 -1
  66. package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
  67. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
  68. package/dist/pipeline/standard-output/resources.d.ts +2 -2
  69. package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
  70. package/dist/publication/contract.d.ts +77 -0
  71. package/dist/publication/contract.d.ts.map +1 -0
  72. package/dist/publication/dependencies.d.ts +7 -0
  73. package/dist/publication/dependencies.d.ts.map +1 -0
  74. package/dist/publication/prepare-geometry.d.ts +9 -0
  75. package/dist/publication/prepare-geometry.d.ts.map +1 -0
  76. package/dist/publication/programs.d.ts +5 -0
  77. package/dist/publication/programs.d.ts.map +1 -0
  78. package/dist/publication/publisher.d.ts +33 -0
  79. package/dist/publication/publisher.d.ts.map +1 -0
  80. package/dist/publication/receiver.d.ts +29 -0
  81. package/dist/publication/receiver.d.ts.map +1 -0
  82. package/dist/publication/resource-scope.d.ts +18 -0
  83. package/dist/publication/resource-scope.d.ts.map +1 -0
  84. package/dist/publication/targets.d.ts +33 -0
  85. package/dist/publication/targets.d.ts.map +1 -0
  86. package/dist/publication/video.d.ts +5 -0
  87. package/dist/publication/video.d.ts.map +1 -0
  88. package/dist/record/fold-buckets.d.ts +3 -3
  89. package/dist/record/fold-buckets.d.ts.map +1 -1
  90. package/dist/record/frame-snapshot.d.ts +6 -6
  91. package/dist/record/frame-snapshot.d.ts.map +1 -1
  92. package/dist/record/frame.d.ts +5 -4
  93. package/dist/record/frame.d.ts.map +1 -1
  94. package/dist/record/helpers.d.ts +2 -2
  95. package/dist/record/helpers.d.ts.map +1 -1
  96. package/dist/record/main-pass-geometry.d.ts +2 -2
  97. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  98. package/dist/record/main-pass-material.d.ts +7 -9
  99. package/dist/record/main-pass-material.d.ts.map +1 -1
  100. package/dist/record/main-pass-sprite-draws.d.ts +3 -16
  101. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  102. package/dist/record/main-pass.d.ts.map +1 -1
  103. package/dist/record/prepared-material-bindings.d.ts +3 -3
  104. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  105. package/dist/record/render-context.d.ts +2 -2
  106. package/dist/record/render-context.d.ts.map +1 -1
  107. package/dist/record/sprite-instance-buffer.d.ts +31 -0
  108. package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
  109. package/dist/record/target-capture-graph.d.ts +19 -0
  110. package/dist/record/target-capture-graph.d.ts.map +1 -0
  111. package/dist/record/typed-frame-graph.d.ts +2 -13
  112. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  113. package/dist/record/view-ubo.d.ts.map +1 -1
  114. package/dist/record/volume-params.d.ts +2 -2
  115. package/dist/record/volume-params.d.ts.map +1 -1
  116. package/dist/recovery/render-system-candidate.d.ts +5 -5
  117. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  118. package/dist/render-contract.d.ts +4 -1
  119. package/dist/render-contract.d.ts.map +1 -1
  120. package/dist/render-system-extract-tail.d.ts.map +1 -1
  121. package/dist/render-system-extract.d.ts +2 -0
  122. package/dist/render-system-extract.d.ts.map +1 -1
  123. package/dist/render-system-presentation.d.ts +2 -2
  124. package/dist/render-system-presentation.d.ts.map +1 -1
  125. package/dist/render-system-projections.d.ts +3 -2
  126. package/dist/render-system-projections.d.ts.map +1 -1
  127. package/dist/render-system-residency.d.ts +2 -2
  128. package/dist/render-system-residency.d.ts.map +1 -1
  129. package/dist/render-system.d.ts +3 -7
  130. package/dist/render-system.d.ts.map +1 -1
  131. package/dist/scene/render-scene.d.ts +8 -4
  132. package/dist/scene/render-scene.d.ts.map +1 -1
  133. package/dist/scene/render-source.d.ts +42 -0
  134. package/dist/scene/render-source.d.ts.map +1 -0
  135. package/dist/scene/source-systems.d.ts +6 -0
  136. package/dist/scene/source-systems.d.ts.map +1 -0
  137. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  138. package/dist/systems/skin-palette-types.d.ts +10 -0
  139. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  140. package/dist/systems/transparent-dispatch.d.ts +2 -2
  141. package/dist/systems/transparent-dispatch.d.ts.map +1 -1
  142. package/dist/systems/transparent-sort-config.d.ts +2 -1
  143. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  144. package/dist/temporal/index.mjs +6 -7
  145. package/package.json +21 -21
  146. package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
  147. package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
  148. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +3 -4
  149. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
  150. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +0 -1
  151. package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
  152. package/src/__tests__/physical-clearcoat.integration.test.ts +23 -23
  153. package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
  154. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  155. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +1 -1
  156. package/src/assembly/device-feature-admission.ts +2 -2
  157. package/src/assembly/host-contract.ts +5 -2
  158. package/src/assembly/recovery/renderer-recover.ts +9 -0
  159. package/src/assembly/render-target-host.ts +57 -47
  160. package/src/assembly/renderer-facade.ts +3 -1
  161. package/src/assembly/webgpu-ready.ts +1 -1
  162. package/src/assembly/webgpu-renderer.ts +89 -61
  163. package/src/cloud/frame-context.ts +2 -2
  164. package/src/device/gpu-residency.ts +26 -25
  165. package/src/errors/render.ts +3 -1
  166. package/src/extract/material-context.ts +21 -0
  167. package/src/features/host.ts +13 -5
  168. package/src/features/plan.ts +2 -0
  169. package/src/features/types.ts +4 -0
  170. package/src/glyph-text-layout-system.ts +2 -2
  171. package/src/gpu-driven/shadow-ownership.ts +3 -3
  172. package/src/ibl/frame-resources.ts +0 -1
  173. package/src/ibl/skylight-bind-group.ts +13 -25
  174. package/src/index.ts +9 -0
  175. package/src/instances.ts +9 -6
  176. package/src/pbr-pipeline.ts +11 -17
  177. package/src/pipeline/standard-output/lut-gpu.ts +3 -3
  178. package/src/pipeline/standard-output/resources.ts +2 -2
  179. package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
  180. package/src/publication/__tests__/features.integration.test.ts +122 -0
  181. package/src/publication/__tests__/programs.unit.test.ts +25 -0
  182. package/src/publication/__tests__/publication.integration.test.ts +370 -0
  183. package/src/publication/__tests__/targets.integration.test.ts +120 -0
  184. package/src/publication/__tests__/video.browser.test.ts +50 -0
  185. package/src/publication/contract.ts +104 -0
  186. package/src/publication/dependencies.ts +54 -0
  187. package/src/publication/prepare-geometry.ts +94 -0
  188. package/src/publication/programs.ts +45 -0
  189. package/src/publication/publisher.ts +618 -0
  190. package/src/publication/receiver.ts +297 -0
  191. package/src/publication/resource-scope.ts +31 -0
  192. package/src/publication/targets.ts +182 -0
  193. package/src/publication/video.ts +33 -0
  194. package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
  195. package/src/record/__tests__/sprite-material-abi.unit.test.ts +8 -10
  196. package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
  197. package/src/record/fold-buckets.ts +8 -5
  198. package/src/record/frame-snapshot.ts +6 -6
  199. package/src/record/frame.ts +42 -22
  200. package/src/record/helpers.ts +2 -2
  201. package/src/record/main-pass-geometry.ts +21 -2
  202. package/src/record/main-pass-material.ts +37 -33
  203. package/src/record/main-pass-sprite-draws.ts +6 -152
  204. package/src/record/main-pass.ts +8 -7
  205. package/src/record/prepared-material-bindings.ts +11 -6
  206. package/src/record/render-context.ts +2 -2
  207. package/src/record/shadow-pass.ts +0 -3
  208. package/src/record/sprite-instance-buffer.ts +155 -0
  209. package/src/record/target-capture-graph.ts +258 -0
  210. package/src/record/typed-frame-graph.ts +6 -206
  211. package/src/record/view-ubo.ts +1 -1
  212. package/src/record/volume-params.ts +6 -6
  213. package/src/recovery/render-system-candidate.ts +10 -10
  214. package/src/render-contract.ts +6 -1
  215. package/src/render-system-extract-tail.ts +33 -17
  216. package/src/render-system-extract.ts +2 -0
  217. package/src/render-system-presentation.ts +2 -2
  218. package/src/render-system-projections.ts +3 -2
  219. package/src/render-system-residency.ts +2 -2
  220. package/src/render-system.ts +82 -53
  221. package/src/scene/render-scene.ts +110 -100
  222. package/src/scene/render-source.ts +90 -0
  223. package/src/scene/source-systems.ts +33 -0
  224. package/src/systems/skin-palette-allocator.ts +1 -0
  225. package/src/systems/skin-palette-types.ts +11 -0
  226. package/src/systems/transparent-dispatch.ts +2 -2
  227. package/src/systems/transparent-sort-config.ts +3 -1
  228. package/dist/chunk-BLXGONR2.mjs.map +0 -1
  229. package/dist/chunk-ELJFJ2BU.mjs.map +0 -1
  230. package/dist/chunk-ILQ5MEZF.mjs +0 -32
  231. package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
  232. package/dist/chunk-MKDIT2EM.mjs.map +0 -1
  233. package/dist/chunk-OCDLEI6I.mjs.map +0 -1
  234. package/dist/chunk-ODZWR6JQ.mjs.map +0 -1
  235. package/dist/chunk-RXZ3RGOU.mjs +0 -53
  236. package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
  237. package/dist/chunk-SHFUFJ5N.mjs +0 -1781
  238. package/dist/chunk-SHFUFJ5N.mjs.map +0 -1
  239. package/dist/chunk-UI5SFODX.mjs.map +0 -1
  240. package/dist/chunk-UINHLCZA.mjs +0 -2389
  241. package/dist/chunk-UINHLCZA.mjs.map +0 -1
  242. package/dist/chunk-WEX45HKH.mjs +0 -1524
  243. package/dist/chunk-WEX45HKH.mjs.map +0 -1
@@ -0,0 +1,1842 @@
1
+ import { freezeBarrelDistortionMapping } from './chunk-G6HDNGY7.mjs';
2
+ import { PROBE_BLEND_RECORD_CAPACITY, PROBE_BLEND_RECORD_BYTE_SIZE } from './chunk-RIPAEGI6.mjs';
3
+ import { GPU_BUFFER_USAGE_MAP_READ, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_COPY_SRC, GPU_TEXTURE_USAGE_COPY_SRC, GPU_TEXTURE_USAGE_COPY_DST, GPU_TEXTURE_USAGE_TEXTURE_BINDING, GPU_TEXTURE_USAGE_STORAGE_BINDING, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT } from './chunk-MXTWKZO4.mjs';
4
+ import { R_MIN, ok, err } from '@forgeax/engine-types';
5
+ import { err as err$1, ok as ok$1, RhiError } from '@forgeax/engine-rhi';
6
+
7
+ // src/ssr/hiz.ts
8
+ var SSR_HIZ_FORMAT = "r32float";
9
+ function validExtent(extent) {
10
+ return Number.isInteger(extent.width) && Number.isInteger(extent.height) && extent.width > 0 && extent.height > 0;
11
+ }
12
+ function requireExtent(extent) {
13
+ if (!validExtent(extent)) {
14
+ throw new RangeError("SSR Hi-Z extent must contain positive integer width and height");
15
+ }
16
+ }
17
+ function nextSsrHiZMipExtent(extent) {
18
+ requireExtent(extent);
19
+ return {
20
+ width: Math.max(1, Math.floor(extent.width / 2)),
21
+ height: Math.max(1, Math.floor(extent.height / 2))
22
+ };
23
+ }
24
+ function buildSsrHiZPlan(extent) {
25
+ requireExtent(extent);
26
+ const levels = [];
27
+ let current = { width: extent.width, height: extent.height };
28
+ let level = 0;
29
+ while (true) {
30
+ levels.push({ level, ...current });
31
+ if (current.width === 1 && current.height === 1) break;
32
+ current = nextSsrHiZMipExtent(current);
33
+ level += 1;
34
+ }
35
+ return Object.freeze({
36
+ extent: Object.freeze({ width: extent.width, height: extent.height }),
37
+ format: SSR_HIZ_FORMAT,
38
+ mipLevelCount: levels.length,
39
+ levels: Object.freeze(levels.map((entry) => Object.freeze(entry))),
40
+ graphSize: Object.freeze({ width: extent.width, height: extent.height })
41
+ });
42
+ }
43
+
44
+ // src/ssr/resources.ts
45
+ var SSR_TRACE_FORMAT = "rgba16float";
46
+ var SSR_R32FLOAT_BYTES_PER_PIXEL = 4;
47
+ var SSR_RGBA16FLOAT_BYTES_PER_PIXEL = 8;
48
+ var SSR_TEMPORAL_PARAMS_BYTES = 32;
49
+ var SSR_SPATIAL_MEMORY_BUDGET_BYTES = 45088768;
50
+ function halfExtent(extent) {
51
+ return {
52
+ width: Math.max(1, Math.floor(extent.width / 2)),
53
+ height: Math.max(1, Math.floor(extent.height / 2))
54
+ };
55
+ }
56
+ function byteCount(width, height, bytesPerPixel) {
57
+ const bytes = width * height * bytesPerPixel;
58
+ if (!Number.isSafeInteger(bytes)) {
59
+ throw new RangeError("SSR spatial descriptor byte count exceeds safe integer range");
60
+ }
61
+ return bytes;
62
+ }
63
+ function estimateSsrSpatialMemory(extent, options = {}) {
64
+ const half = halfExtent(extent);
65
+ const plan = buildSsrHiZPlan(half);
66
+ const temporal = options.temporal !== false;
67
+ const hizBytes = plan.levels.reduce(
68
+ (sum, level) => sum + byteCount(level.width, level.height, SSR_R32FLOAT_BYTES_PER_PIXEL),
69
+ 0
70
+ );
71
+ const traceBytes = byteCount(half.width, half.height, SSR_RGBA16FLOAT_BYTES_PER_PIXEL);
72
+ const hitReactivityBytes = byteCount(half.width, half.height, SSR_R32FLOAT_BYTES_PER_PIXEL);
73
+ const resolvedBytes = temporal ? plan.levels.reduce(
74
+ (sum, level) => sum + byteCount(level.width, level.height, SSR_RGBA16FLOAT_BYTES_PER_PIXEL),
75
+ 0
76
+ ) : 0;
77
+ const historyBytes = temporal ? half.width * half.height * 12 * 2 : 0;
78
+ const temporalParamsBytes = temporal ? SSR_TEMPORAL_PARAMS_BYTES : 0;
79
+ const fallbackInputBytes = byteCount(
80
+ extent.width,
81
+ extent.height,
82
+ SSR_RGBA16FLOAT_BYTES_PER_PIXEL
83
+ );
84
+ const ssrOwnedBytes = hizBytes + traceBytes + hitReactivityBytes + resolvedBytes + historyBytes + temporalParamsBytes + fallbackInputBytes;
85
+ return Object.freeze({
86
+ width: extent.width,
87
+ height: extent.height,
88
+ halfWidth: half.width,
89
+ halfHeight: half.height,
90
+ hizBytes,
91
+ traceBytes,
92
+ hitReactivityBytes,
93
+ resolvedBytes,
94
+ historyBytes,
95
+ temporalParamsBytes,
96
+ fallbackInputBytes,
97
+ ssrOwnedBytes,
98
+ budgetBytes: SSR_SPATIAL_MEMORY_BUDGET_BYTES,
99
+ withinBudget: ssrOwnedBytes <= SSR_SPATIAL_MEMORY_BUDGET_BYTES
100
+ });
101
+ }
102
+ function texture(graph, label, format, extent) {
103
+ const created = graph.createTexture(label, {
104
+ format,
105
+ size: { width: extent.width, height: extent.height },
106
+ domain: "linear-hdr"
107
+ });
108
+ if (!created.ok) return created;
109
+ const view = graph.view(created.value, { label: `${label}.view`, dimension: "2d" });
110
+ if (!view.ok) return view;
111
+ return ok({
112
+ texture: created.value,
113
+ view: view.value,
114
+ width: extent.width,
115
+ height: extent.height,
116
+ format
117
+ });
118
+ }
119
+ function createSsrHiZResources(graph, extent) {
120
+ const plan = buildSsrHiZPlan(halfExtent(extent));
121
+ const created = graph.createTexture("ssr-hiz", {
122
+ format: SSR_HIZ_FORMAT,
123
+ size: plan.graphSize,
124
+ mipLevelCount: plan.mipLevelCount
125
+ });
126
+ if (!created.ok) return created;
127
+ const levels = [];
128
+ for (const entry of plan.levels) {
129
+ const view = graph.view(created.value, {
130
+ label: `ssr-hiz.mip-${entry.level}.view`,
131
+ dimension: "2d",
132
+ baseMipLevel: entry.level,
133
+ mipLevelCount: 1
134
+ });
135
+ if (!view.ok) return view;
136
+ levels.push(view.value);
137
+ }
138
+ return ok({ plan, texture: created.value, levels: Object.freeze(levels) });
139
+ }
140
+ function createSsrSpatialResources(graph, extent) {
141
+ const hiz = createSsrHiZResources(graph, extent);
142
+ if (!hiz.ok) return hiz;
143
+ const traceExtent = halfExtent(extent);
144
+ const trace = texture(graph, "ssr-trace", SSR_TRACE_FORMAT, traceExtent);
145
+ if (!trace.ok) return trace;
146
+ const hitTexture = graph.createTexture("ssr-hit-reactivity", {
147
+ format: "r32float",
148
+ size: traceExtent
149
+ });
150
+ if (!hitTexture.ok) return hitTexture;
151
+ const hitReactivity = graph.view(hitTexture.value, {
152
+ label: "ssr-hit-reactivity.view",
153
+ dimension: "2d"
154
+ });
155
+ if (!hitReactivity.ok) return hitReactivity;
156
+ return ok({ hiz: hiz.value, trace: trace.value, hitReactivity: hitReactivity.value });
157
+ }
158
+
159
+ // src/inspection-types.ts
160
+ function projectLightInspection(input) {
161
+ return Object.freeze({
162
+ generation: input.generation,
163
+ candidate: input.candidate,
164
+ accepted: input.accepted,
165
+ lastKnownGood: input.lastKnownGood,
166
+ failure: input.failure,
167
+ failureKeys: [...input.failureKeys],
168
+ resourceCount: input.resourceCount,
169
+ uploadBytes: input.uploadBytes
170
+ });
171
+ }
172
+ function lightInspectionIdentity(topology, generation) {
173
+ return `${topology}:generation-${generation}`;
174
+ }
175
+ var emptyBloomInspection = () => ({
176
+ graphStatus: "empty",
177
+ enabled: false,
178
+ levelCount: 0,
179
+ levelDimensions: [],
180
+ downsamplePassCount: 0,
181
+ upsamplePassCount: 0,
182
+ targetCount: 0,
183
+ targetBytes: 0,
184
+ resourceCount: 0,
185
+ passCount: 0,
186
+ encodeCount: 0,
187
+ bindGroupCount: 0,
188
+ uploadCount: 0,
189
+ residentChildBytes: 0,
190
+ generation: 0,
191
+ state: "off"
192
+ });
193
+ function projectBarrelDistortionInspection(input) {
194
+ const effectiveMapping = input.effectiveMapping === void 0 ? void 0 : freezeBarrelDistortionMapping(input.effectiveMapping);
195
+ return Object.freeze({
196
+ effectiveMapping,
197
+ extent: effectiveMapping === void 0 ? void 0 : Object.freeze({ width: effectiveMapping.width, height: effectiveMapping.height }),
198
+ frameId: input.frameId,
199
+ deviceGeneration: input.deviceGeneration,
200
+ graphGeneration: input.graphGeneration,
201
+ lastKnownGood: input.lastKnownGood
202
+ });
203
+ }
204
+
205
+ // src/prepare/extended-lighting/state.ts
206
+ function createExtendedLightingState(generation) {
207
+ return {
208
+ enabled: false,
209
+ generation,
210
+ status: "empty",
211
+ candidate: void 0,
212
+ accepted: void 0,
213
+ lastKnownGood: void 0,
214
+ resourceCount: 0,
215
+ descriptorBytes: 0,
216
+ uploadCount: 0,
217
+ passCount: 0,
218
+ bindGroupCount: 0,
219
+ failure: void 0,
220
+ failureKeys: []
221
+ };
222
+ }
223
+ function promoteExtendedLightingCandidate(state, candidate) {
224
+ if (candidate === void 0) return state;
225
+ return {
226
+ ...state,
227
+ enabled: true,
228
+ generation: candidate.generation,
229
+ status: "accepted",
230
+ candidate: void 0,
231
+ accepted: candidate,
232
+ lastKnownGood: candidate,
233
+ resourceCount: 4,
234
+ descriptorBytes: candidate.descriptorBytes,
235
+ uploadCount: candidate.uploadCount
236
+ };
237
+ }
238
+ function recordExtendedLightingFailure(state, failure2) {
239
+ const key = `${failure2.detail.entity}:${failure2.detail.feature}:${failure2.detail.generation}`;
240
+ if (state.failureKeys.includes(key)) return state;
241
+ return {
242
+ ...state,
243
+ failure: failure2,
244
+ failureKeys: [...state.failureKeys, key]
245
+ };
246
+ }
247
+ function projectExtendedLightingInspection(state) {
248
+ const identity = (candidate) => candidate === void 0 ? void 0 : lightInspectionIdentity(candidate.topology, candidate.generation);
249
+ return projectLightInspection({
250
+ generation: state.generation,
251
+ candidate: identity(state.candidate),
252
+ accepted: identity(state.accepted),
253
+ lastKnownGood: identity(state.lastKnownGood),
254
+ failure: state.failure?.code,
255
+ failureKeys: state.failureKeys,
256
+ resourceCount: state.resourceCount,
257
+ uploadBytes: state.descriptorBytes
258
+ });
259
+ }
260
+ var SSR_HISTORY_FORMAT = "rgba16float";
261
+ var HISTORY_USAGE = GPU_TEXTURE_USAGE_COPY_SRC | GPU_TEXTURE_USAGE_COPY_DST | GPU_TEXTURE_USAGE_TEXTURE_BINDING | GPU_TEXTURE_USAGE_STORAGE_BINDING | GPU_TEXTURE_USAGE_RENDER_ATTACHMENT;
262
+ function positiveDimension(value, field) {
263
+ if (!Number.isSafeInteger(value) || value <= 0) {
264
+ throw new RangeError(`${field} must be a positive safe integer.`);
265
+ }
266
+ return value;
267
+ }
268
+ function createResources(device, scope, width, height) {
269
+ const halfWidth = Math.max(1, Math.floor(width / 2));
270
+ const halfHeight = Math.max(1, Math.floor(height / 2));
271
+ const descriptor = {
272
+ label: "ssr-history-rgba16float",
273
+ size: { width: halfWidth, height: halfHeight, depthOrArrayLayers: 1 },
274
+ format: SSR_HISTORY_FORMAT,
275
+ usage: HISTORY_USAGE,
276
+ mipLevelCount: 1,
277
+ sampleCount: 1,
278
+ dimension: "2d",
279
+ viewFormats: void 0,
280
+ textureBindingViewDimension: void 0
281
+ };
282
+ const childScope = scope.createChild(`${scope.owner}:ssr-history`);
283
+ const slots = [];
284
+ for (const index of [0, 1]) {
285
+ const texture2 = device.createTexture({
286
+ ...descriptor,
287
+ label: `ssr-history-rgba16float-${index}`
288
+ });
289
+ if (!texture2.ok) {
290
+ childScope.abandon();
291
+ return texture2;
292
+ }
293
+ childScope._adopt("texture", texture2.value, (value) => {
294
+ device.destroyTexture(value);
295
+ });
296
+ const view = device.createTextureView(texture2.value, {
297
+ label: `ssr-history-rgba16float-${index}.view`,
298
+ dimension: "2d",
299
+ baseMipLevel: 0,
300
+ mipLevelCount: 1,
301
+ baseArrayLayer: 0,
302
+ arrayLayerCount: 1
303
+ });
304
+ if (!view.ok) {
305
+ childScope.abandon();
306
+ return view;
307
+ }
308
+ const surfaceTexture = device.createTexture({
309
+ ...descriptor,
310
+ label: `ssr-history-surface-${index}`,
311
+ format: "rgba8unorm"
312
+ });
313
+ if (!surfaceTexture.ok) {
314
+ childScope.abandon();
315
+ return surfaceTexture;
316
+ }
317
+ childScope._adopt("texture", surfaceTexture.value, (value) => {
318
+ device.destroyTexture(value);
319
+ });
320
+ const surfaceView = device.createTextureView(surfaceTexture.value, { dimension: "2d" });
321
+ if (!surfaceView.ok) {
322
+ childScope.abandon();
323
+ return surfaceView;
324
+ }
325
+ slots.push({
326
+ texture: texture2.value,
327
+ view: view.value,
328
+ surfaceTexture: surfaceTexture.value,
329
+ surfaceView: surfaceView.value
330
+ });
331
+ }
332
+ const params = device.createBuffer({
333
+ label: "ssr-temporal-params",
334
+ size: 32,
335
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
336
+ mappedAtCreation: false
337
+ });
338
+ if (!params.ok) {
339
+ childScope.abandon();
340
+ return params;
341
+ }
342
+ childScope._adopt("buffer", params.value, (value) => {
343
+ device.destroyBuffer(value);
344
+ });
345
+ const cleared = device.queue.writeBuffer(params.value, 0, new Uint8Array(32));
346
+ if (!cleared.ok) {
347
+ childScope.abandon();
348
+ return cleared;
349
+ }
350
+ return ok({
351
+ width,
352
+ height,
353
+ halfWidth,
354
+ halfHeight,
355
+ bytes: halfWidth * halfHeight * 12 * 2,
356
+ descriptor,
357
+ slots,
358
+ paramsBuffer: params.value,
359
+ childScope
360
+ });
361
+ }
362
+ function failure(code, expected, hint, detail) {
363
+ return { code, expected, hint, ...{} };
364
+ }
365
+ var SsrHistoryOwner = class _SsrHistoryOwner {
366
+ constructor(device, scope, resources) {
367
+ this.device = device;
368
+ this.scope = scope;
369
+ this.activeResources = resources;
370
+ }
371
+ device;
372
+ scope;
373
+ activeResources;
374
+ retiringResources = /* @__PURE__ */ new Set();
375
+ retirementFencePending = false;
376
+ activeCandidate;
377
+ readIndex = 0;
378
+ valid = false;
379
+ state = "first-frame";
380
+ resetReason = "first-enable";
381
+ lastFailure;
382
+ resetCount = 0;
383
+ static create(input) {
384
+ const width = positiveDimension(input.width, "width");
385
+ const height = positiveDimension(input.height, "height");
386
+ const resources = createResources(input.device, input.scope, width, height);
387
+ return resources.ok ? ok(new _SsrHistoryOwner(input.device, input.scope, resources.value)) : resources;
388
+ }
389
+ get resources() {
390
+ if (this.activeResources === void 0) throw new Error("SSR history resources are retired.");
391
+ return this.activeResources;
392
+ }
393
+ beginFrame() {
394
+ if (this.activeResources === void 0 || this.state === "retired" || this.state === "disposed") {
395
+ return err(
396
+ failure(
397
+ "ssr-history-retired",
398
+ "SSR history resources are active",
399
+ "recreate the renderer-owned SSR history after device recovery"
400
+ )
401
+ );
402
+ }
403
+ if (this.activeCandidate !== void 0) {
404
+ return err(
405
+ failure(
406
+ "ssr-history-active",
407
+ "one SSR history candidate is active",
408
+ "commit or abort the active SSR history candidate before beginning another"
409
+ )
410
+ );
411
+ }
412
+ const writeSlot = this.valid ? this.readIndex === 0 ? 1 : 0 : 0;
413
+ const candidate = Object.freeze({
414
+ readSlot: this.valid ? this.readIndex : null,
415
+ writeSlot,
416
+ historyValid: this.valid && this.resetReason === void 0
417
+ });
418
+ this.activeCandidate = candidate;
419
+ this.state = this.valid ? "stable" : this.state === "aborted" ? "aborted" : "first-frame";
420
+ this.lastFailure = void 0;
421
+ return ok(candidate);
422
+ }
423
+ commitFrame(candidate) {
424
+ if (this.activeCandidate !== candidate) {
425
+ return err(
426
+ failure(
427
+ "ssr-history-not-active",
428
+ "commit the candidate returned by beginFrame",
429
+ "keep one candidate from beginFrame until the shared queue submit succeeds"
430
+ )
431
+ );
432
+ }
433
+ this.readIndex = candidate.writeSlot;
434
+ this.valid = true;
435
+ this.activeCandidate = void 0;
436
+ this.state = "stable";
437
+ this.resetReason = void 0;
438
+ this.lastFailure = void 0;
439
+ return ok(void 0);
440
+ }
441
+ abortFrame(candidate, stage) {
442
+ if (this.activeCandidate !== candidate) return;
443
+ this.activeCandidate = void 0;
444
+ this.state = "aborted";
445
+ this.lastFailure = stage;
446
+ }
447
+ reset(reason) {
448
+ this.activeCandidate = void 0;
449
+ this.valid = false;
450
+ this.readIndex = 0;
451
+ this.resetReason = reason;
452
+ this.lastFailure = void 0;
453
+ this.resetCount += 1;
454
+ if (this.activeResources !== void 0) this.state = "reset";
455
+ }
456
+ resize(width, height) {
457
+ const next = createResources(
458
+ this.device,
459
+ this.scope,
460
+ positiveDimension(width, "width"),
461
+ positiveDimension(height, "height")
462
+ );
463
+ if (!next.ok) return next;
464
+ if (this.activeResources !== void 0) this.retiringResources.add(this.activeResources);
465
+ this.activeResources = next.value;
466
+ this.activeCandidate = void 0;
467
+ this.valid = false;
468
+ this.readIndex = 0;
469
+ this.resetReason = "resize";
470
+ this.lastFailure = void 0;
471
+ this.resetCount += 1;
472
+ this.state = "reset";
473
+ return ok(void 0);
474
+ }
475
+ /** Retire pending allocations after the queue's already-submitted work. */
476
+ retireAfterFence(queue, onFailure) {
477
+ if (this.retirementFencePending) return;
478
+ if (this.retiringResources.size === 0 && this.activeResources !== void 0) {
479
+ this.retiringResources.add(this.activeResources);
480
+ this.activeCandidate = void 0;
481
+ this.valid = false;
482
+ this.state = "retiring";
483
+ }
484
+ const pending = [...this.retiringResources];
485
+ for (const resources of pending) resources.childScope.beginRetire();
486
+ if (pending.length === 0) return;
487
+ this.retirementFencePending = true;
488
+ const finish = () => {
489
+ this.retirementFencePending = false;
490
+ for (const resources of pending) {
491
+ this.retiringResources.delete(resources);
492
+ resources.childScope.retire();
493
+ if (this.activeResources === resources) this.activeResources = void 0;
494
+ }
495
+ if (this.activeResources === void 0 && this.retiringResources.size === 0) {
496
+ this.state = "retired";
497
+ }
498
+ if (this.retiringResources.size > 0) this.retireAfterFence(queue, onFailure);
499
+ };
500
+ void queue.onSubmittedWorkDone().then(finish, (cause) => {
501
+ onFailure(cause);
502
+ finish();
503
+ });
504
+ }
505
+ retire() {
506
+ this.retirementFencePending = false;
507
+ this.activeCandidate = void 0;
508
+ if (this.activeResources !== void 0) {
509
+ this.activeResources.childScope.retire();
510
+ this.activeResources = void 0;
511
+ }
512
+ for (const resources of this.retiringResources) resources.childScope.retire();
513
+ this.retiringResources.clear();
514
+ this.valid = false;
515
+ this.state = "retired";
516
+ }
517
+ dispose() {
518
+ if (this.state === "disposed") return;
519
+ this.retire();
520
+ this.state = "disposed";
521
+ }
522
+ inspect() {
523
+ const active = this.activeResources;
524
+ return Object.freeze({
525
+ state: this.state,
526
+ width: active?.width ?? 0,
527
+ height: active?.height ?? 0,
528
+ halfWidth: active?.halfWidth ?? 0,
529
+ halfHeight: active?.halfHeight ?? 0,
530
+ format: SSR_HISTORY_FORMAT,
531
+ historyCount: active === void 0 ? 0 : 2,
532
+ historyValid: this.valid,
533
+ readSlot: active === void 0 ? null : this.valid ? this.readIndex : null,
534
+ writeSlot: active === void 0 ? 0 : this.valid ? this.readIndex === 0 ? 1 : 0 : 0,
535
+ activeBytes: active?.bytes ?? 0,
536
+ candidateBytes: 0,
537
+ retiringBytes: [...this.retiringResources].filter((entry) => entry !== active).reduce((sum, entry) => sum + entry.bytes, 0),
538
+ resetCount: this.resetCount,
539
+ resetReason: this.resetReason,
540
+ lastFailure: this.lastFailure
541
+ });
542
+ }
543
+ };
544
+ function createSsrHistoryOwner(input) {
545
+ return SsrHistoryOwner.create(input);
546
+ }
547
+
548
+ // src/scene/visibility/occlusion-query-pool.ts
549
+ var OCCLUSION_QUERY_PAGE_COUNT = 3;
550
+ var OCCLUSION_QUERY_PAGE_INDEX_LIMIT = 4096;
551
+ var OCCLUSION_QUERY_RESULT_BYTES = 8;
552
+ var OCCLUSION_QUERY_PAGE_BYTES = OCCLUSION_QUERY_PAGE_INDEX_LIMIT * OCCLUSION_QUERY_RESULT_BYTES;
553
+ function identityKey(identity) {
554
+ return [
555
+ identity.viewKey,
556
+ identity.attachmentId,
557
+ identity.deviceGeneration,
558
+ identity.worldGeneration,
559
+ identity.primitiveSlot,
560
+ identity.slotGeneration
561
+ ].join("|");
562
+ }
563
+ var OcclusionQueryPool = class {
564
+ pages = Array.from(
565
+ { length: OCCLUSION_QUERY_PAGE_COUNT },
566
+ (_, index) => ({ index, nextQueryIndex: 0, pendingCount: 0, inFlightCount: 0 })
567
+ );
568
+ pending = /* @__PURE__ */ new Map();
569
+ tickets = /* @__PURE__ */ new Map();
570
+ nextReservationId = 1;
571
+ disposed = false;
572
+ reserve(identity) {
573
+ if (this.disposed) return void 0;
574
+ const page = this.pages.find(
575
+ (candidate) => candidate.inFlightCount === 0 && candidate.nextQueryIndex < OCCLUSION_QUERY_PAGE_INDEX_LIMIT
576
+ );
577
+ if (page === void 0 || page.nextQueryIndex >= OCCLUSION_QUERY_PAGE_INDEX_LIMIT) {
578
+ return void 0;
579
+ }
580
+ const reservation = Object.freeze({
581
+ ...identity,
582
+ pageIndex: page.index,
583
+ queryIndex: page.nextQueryIndex,
584
+ reservationId: this.nextReservationId++
585
+ });
586
+ page.nextQueryIndex += 1;
587
+ page.pendingCount += 1;
588
+ this.pending.set(reservation.reservationId, reservation);
589
+ return reservation;
590
+ }
591
+ publish(reservation, result) {
592
+ const pending = this.pending.get(reservation.reservationId);
593
+ if (pending === void 0 || this.disposed) {
594
+ return void 0;
595
+ }
596
+ if (!result.submitted) return void 0;
597
+ this.pending.delete(reservation.reservationId);
598
+ const page = this.pages[pending.pageIndex];
599
+ if (page !== void 0) {
600
+ page.pendingCount = Math.max(0, page.pendingCount - 1);
601
+ page.inFlightCount += 1;
602
+ }
603
+ const ticket = Object.freeze({ ...pending, submissionGeneration: result.submissionGeneration });
604
+ this.tickets.set(ticket.reservationId, ticket);
605
+ return ticket;
606
+ }
607
+ /** Return an unsubmitted reservation to its bounded page. */
608
+ cancel(reservation) {
609
+ const pending = this.pending.get(reservation.reservationId);
610
+ if (pending === void 0) return false;
611
+ this.pending.delete(reservation.reservationId);
612
+ const page = this.pages[pending.pageIndex];
613
+ if (page !== void 0) {
614
+ page.pendingCount = Math.max(0, page.pendingCount - 1);
615
+ this.releasePage(pending.pageIndex);
616
+ }
617
+ return true;
618
+ }
619
+ complete(ticket, _samples) {
620
+ const current = this.tickets.get(ticket.reservationId);
621
+ if (current !== void 0) {
622
+ this.tickets.delete(ticket.reservationId);
623
+ const page = this.pages[current.pageIndex];
624
+ if (page !== void 0) page.inFlightCount = Math.max(0, page.inFlightCount - 1);
625
+ this.releasePage(current.pageIndex);
626
+ }
627
+ if (this.disposed || current === void 0 || identityKey(current) !== identityKey(ticket)) {
628
+ return { status: "stale", visible: true };
629
+ }
630
+ return { status: "accepted", visible: true };
631
+ }
632
+ inspect() {
633
+ return {
634
+ pageCount: OCCLUSION_QUERY_PAGE_COUNT,
635
+ pageIndexLimit: OCCLUSION_QUERY_PAGE_INDEX_LIMIT,
636
+ resolveBytes: OCCLUSION_QUERY_PAGE_BYTES,
637
+ stagingBytes: OCCLUSION_QUERY_PAGE_BYTES,
638
+ availablePages: this.disposed ? 0 : this.pages.filter(
639
+ (page) => page.inFlightCount === 0 && page.nextQueryIndex < OCCLUSION_QUERY_PAGE_INDEX_LIMIT
640
+ ).length,
641
+ inFlight: this.tickets.size,
642
+ disposed: this.disposed
643
+ };
644
+ }
645
+ dispose() {
646
+ this.disposed = true;
647
+ this.pending.clear();
648
+ this.tickets.clear();
649
+ for (const page of this.pages) {
650
+ page.pendingCount = 0;
651
+ page.inFlightCount = 0;
652
+ }
653
+ }
654
+ releasePage(pageIndex) {
655
+ const page = this.pages[pageIndex];
656
+ if (page !== void 0 && page.pendingCount === 0 && page.inFlightCount === 0) {
657
+ page.nextQueryIndex = 0;
658
+ }
659
+ }
660
+ };
661
+
662
+ // src/scene/visibility/occlusion-pass.ts
663
+ function createOcclusionQueryResources(device, pageIndex) {
664
+ const querySet = device.createQuerySet({
665
+ label: `occlusion-page-${pageIndex}`,
666
+ type: "occlusion",
667
+ count: OCCLUSION_QUERY_PAGE_INDEX_LIMIT
668
+ });
669
+ if (!querySet.ok) return querySet;
670
+ const resolveBuffer = device.createBuffer({
671
+ label: `occlusion-page-${pageIndex}-resolve`,
672
+ size: OCCLUSION_QUERY_PAGE_BYTES,
673
+ usage: GPU_BUFFER_USAGE_QUERY_RESOLVE | GPU_BUFFER_USAGE_COPY_SRC
674
+ });
675
+ if (!resolveBuffer.ok) {
676
+ device.destroyQuerySet(querySet.value);
677
+ return resolveBuffer;
678
+ }
679
+ const stagingBuffer = device.createBuffer({
680
+ label: `occlusion-page-${pageIndex}-staging`,
681
+ size: OCCLUSION_QUERY_PAGE_BYTES,
682
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST
683
+ });
684
+ if (!stagingBuffer.ok) {
685
+ device.destroyBuffer(resolveBuffer.value);
686
+ device.destroyQuerySet(querySet.value);
687
+ return stagingBuffer;
688
+ }
689
+ return ok$1({
690
+ pageIndex,
691
+ querySet: querySet.value,
692
+ resolveBuffer: resolveBuffer.value,
693
+ stagingBuffer: stagingBuffer.value
694
+ });
695
+ }
696
+ function recordOcclusionResolve(encoder, resources, firstQuery, queryCount) {
697
+ if (!Number.isSafeInteger(firstQuery) || !Number.isSafeInteger(queryCount) || firstQuery < 0 || queryCount <= 0 || firstQuery + queryCount > OCCLUSION_QUERY_PAGE_INDEX_LIMIT) {
698
+ return err$1(
699
+ new RhiError({
700
+ code: "webgpu-runtime-error",
701
+ expected: "query range stays within one 4096-index page",
702
+ hint: "allocate a bounded page before recording occlusion resolve"
703
+ })
704
+ );
705
+ }
706
+ const resolved = encoder.resolveQuerySet(
707
+ resources.querySet,
708
+ firstQuery,
709
+ queryCount,
710
+ resources.resolveBuffer,
711
+ 0
712
+ );
713
+ if (!resolved.ok) return resolved;
714
+ encoder.copyBufferToBuffer(resources.resolveBuffer, resources.stagingBuffer, queryCount * 8);
715
+ return ok$1(void 0);
716
+ }
717
+
718
+ // src/scene/visibility/types.ts
719
+ function viewKeyId(key) {
720
+ return `${key.attachmentId}:${key.cameraEntity}:${key.viewRole}:${key.viewGeneration}`;
721
+ }
722
+ function primitiveKeyId(key) {
723
+ return `${key.attachmentId}:${key.worldGeneration}:${key.primitiveSlot}:${key.slotGeneration}`;
724
+ }
725
+ function viewKey(input) {
726
+ return Object.freeze({ ...input });
727
+ }
728
+ function primitiveKey(input) {
729
+ return Object.freeze({ ...input });
730
+ }
731
+
732
+ // src/scene/visibility/occlusion-runtime.ts
733
+ var PROXY_VERTEX_FLOATS_PER_CANDIDATE = 36 * 4;
734
+ var PROXY_GEOMETRY_BYTES = OCCLUSION_QUERY_PAGE_INDEX_LIMIT * PROXY_VERTEX_FLOATS_PER_CANDIDATE * 4;
735
+ var runtimeTestHooks;
736
+ function setOcclusionRuntimeTestHooks(hooks) {
737
+ runtimeTestHooks = hooks;
738
+ }
739
+ var OcclusionRenderRuntime = class {
740
+ constructor(device, facets, shaderModuleFactory, budget = facets.visibilityBudget()) {
741
+ this.device = device;
742
+ this.facets = facets;
743
+ this.shaderModuleFactory = shaderModuleFactory;
744
+ this.budget = budget;
745
+ const runtime = this;
746
+ this.proxyScratch = new Float32Array(
747
+ Math.min(this.budget.effectiveQueryBudget, OCCLUSION_QUERY_PAGE_INDEX_LIMIT) * PROXY_VERTEX_FLOATS_PER_CANDIDATE
748
+ );
749
+ this.projection = {
750
+ get querySet() {
751
+ return runtime.pendingFrame?.resources.querySet;
752
+ },
753
+ get queryIndex() {
754
+ return runtime.pendingFrame?.reservations[0]?.queryIndex ?? 0;
755
+ },
756
+ get queryCount() {
757
+ return runtime.pendingFrame?.reservations.length ?? 0;
758
+ },
759
+ get sampleCount() {
760
+ return runtime.pendingFrame?.sampleCount ?? 1;
761
+ },
762
+ get pageIndex() {
763
+ return runtime.pendingFrame?.reservations[0]?.pageIndex ?? 0;
764
+ },
765
+ get reservationId() {
766
+ return runtime.pendingFrame?.reservations[0]?.reservationId ?? 0;
767
+ },
768
+ encodeProxyBounds: (pass) => {
769
+ const pending = runtime.pendingFrame;
770
+ return pending === void 0 ? err$1(runtimeError("the current RenderSystem frame owns the occlusion candidate")) : runtime.encodeProxyBounds(pass, pending);
771
+ },
772
+ beginCandidate: (pass) => {
773
+ const pending = runtime.pendingFrame;
774
+ if (pending === void 0 || pending.endedCount >= pending.reservations.length) {
775
+ return err$1(runtimeError("the current RenderSystem frame owns the occlusion candidate"));
776
+ }
777
+ return pass.beginOcclusionQuery(pending.reservations[pending.endedCount]?.queryIndex ?? 0);
778
+ },
779
+ endCandidate: (pass) => {
780
+ const pending = runtime.pendingFrame;
781
+ if (pending === void 0 || pending.endedCount >= pending.reservations.length) {
782
+ return err$1(runtimeError("the current RenderSystem frame owns the occlusion candidate"));
783
+ }
784
+ const ended = pass.endOcclusionQuery();
785
+ if (ended.ok) pending.endedCount += 1;
786
+ return ended;
787
+ },
788
+ recordResolve: (encoder) => {
789
+ const pending = runtime.pendingFrame;
790
+ if (pending === void 0 || pending.endedCount !== pending.reservations.length) {
791
+ return err$1(runtimeError("the real geometry pass must end its occlusion query first"));
792
+ }
793
+ return recordOcclusionResolve(
794
+ encoder,
795
+ pending.resources,
796
+ pending.reservations[0]?.queryIndex ?? 0,
797
+ pending.reservations.length
798
+ );
799
+ },
800
+ commit: (submitted, submissionGeneration, profilePhase) => {
801
+ const pending = runtime.pendingFrame;
802
+ if (pending !== void 0)
803
+ runtime.commit(pending, submitted, submissionGeneration, profilePhase);
804
+ }
805
+ };
806
+ }
807
+ device;
808
+ facets;
809
+ shaderModuleFactory;
810
+ budget;
811
+ pool = new OcclusionQueryPool();
812
+ resources = /* @__PURE__ */ new Map();
813
+ activeCompletions = /* @__PURE__ */ new Map();
814
+ retiredResources = /* @__PURE__ */ new Set();
815
+ /** Candidate descriptors are rebuilt only when the extracted frame changes. */
816
+ candidateSource;
817
+ validCandidates = [];
818
+ candidateByKey = /* @__PURE__ */ new Map();
819
+ lastPrepareUnavailable = false;
820
+ pendingFrame;
821
+ disposed = false;
822
+ proxyBuffer;
823
+ proxyScratch;
824
+ proxyPipeline;
825
+ proxyPipelineSampleCount;
826
+ queryLatencySamplesUs = [];
827
+ lastQueryLatencyUs = 0;
828
+ projection;
829
+ prepare(candidate) {
830
+ return this.prepareBatch([candidate], 1, false);
831
+ }
832
+ prepareBatch(candidates, sampleCount = 1, useReadyQueue = true) {
833
+ this.lastPrepareUnavailable = false;
834
+ if (this.disposed || candidates.length === 0) return void 0;
835
+ if (this.pendingFrame !== void 0) {
836
+ this.lastPrepareUnavailable = true;
837
+ return void 0;
838
+ }
839
+ if (this.candidateSource !== candidates) {
840
+ this.candidateSource = candidates;
841
+ this.candidateByKey.clear();
842
+ const valid2 = [];
843
+ for (const candidate of candidates) {
844
+ this.candidateByKey.set(candidateKey(candidate.view, candidate.primitive), candidate);
845
+ if (finiteBounds(candidate.bounds)) valid2.push(candidate);
846
+ }
847
+ this.validCandidates = valid2;
848
+ }
849
+ const valid = this.validCandidates;
850
+ if (valid.length === 0) return void 0;
851
+ let selectedCandidates;
852
+ if (!useReadyQueue) {
853
+ selectedCandidates = valid.slice(0, this.budget.effectiveQueryBudget);
854
+ } else {
855
+ const firstView = valid[0]?.view;
856
+ if (firstView === void 0) return void 0;
857
+ selectedCandidates = [];
858
+ for (const entry of this.facets.dequeueQueryCandidates(
859
+ firstView,
860
+ this.budget.effectiveQueryBudget
861
+ )) {
862
+ const candidate = this.candidateByKey.get(candidateKey(entry.view, entry.primitive));
863
+ if (candidate === void 0 || !finiteBounds(candidate.bounds)) {
864
+ this.facets.requeueQueryCandidate(entry.view, entry.primitive);
865
+ continue;
866
+ }
867
+ selectedCandidates.push(candidate);
868
+ }
869
+ if (selectedCandidates.length === 0) return void 0;
870
+ }
871
+ const reservations = [];
872
+ let selectedCount = 0;
873
+ while (selectedCount < selectedCandidates.length) {
874
+ const candidate = selectedCandidates[selectedCount];
875
+ if (candidate === void 0) break;
876
+ const reservation = this.pool.reserve({
877
+ viewKey: viewKeyId(candidate.view),
878
+ attachmentId: candidate.view.attachmentId,
879
+ deviceGeneration: candidate.deviceGeneration,
880
+ worldGeneration: candidate.primitive.worldGeneration,
881
+ primitiveSlot: candidate.primitive.primitiveSlot,
882
+ slotGeneration: candidate.primitive.slotGeneration
883
+ });
884
+ if (reservation === void 0) {
885
+ this.lastPrepareUnavailable = true;
886
+ break;
887
+ }
888
+ if (reservations.length > 0 && reservation.pageIndex !== reservations[0]?.pageIndex) {
889
+ this.pool.cancel(reservation);
890
+ this.lastPrepareUnavailable = true;
891
+ break;
892
+ }
893
+ reservations.push(reservation);
894
+ selectedCount += 1;
895
+ if (reservations.length >= this.budget.effectiveQueryBudget) break;
896
+ }
897
+ if (reservations.length === 0) {
898
+ for (const candidate of selectedCandidates) {
899
+ if (useReadyQueue) this.facets.requeueQueryCandidate(candidate.view, candidate.primitive);
900
+ }
901
+ return void 0;
902
+ }
903
+ if (reservations.length < selectedCandidates.length) {
904
+ for (const reservation of reservations) this.pool.cancel(reservation);
905
+ for (const candidate of selectedCandidates) {
906
+ if (useReadyQueue) this.facets.requeueQueryCandidate(candidate.view, candidate.primitive);
907
+ }
908
+ return void 0;
909
+ }
910
+ const resources = this.ensureResources(reservations[0]?.pageIndex ?? 0);
911
+ if (resources === void 0) {
912
+ for (const reservation of reservations) this.pool.cancel(reservation);
913
+ this.lastPrepareUnavailable = true;
914
+ for (const candidate of selectedCandidates) {
915
+ if (useReadyQueue) this.facets.requeueQueryCandidate(candidate.view, candidate.primitive);
916
+ }
917
+ return void 0;
918
+ }
919
+ const selected = selectedCandidates;
920
+ let proxyFloatCount = 0;
921
+ for (const candidate of selected) {
922
+ const vertices = candidate.proxyVertices;
923
+ if (vertices === void 0) continue;
924
+ if (proxyFloatCount + vertices.length > this.proxyScratch.length) {
925
+ for (const reservation of reservations) this.pool.cancel(reservation);
926
+ for (const queuedCandidate of selectedCandidates) {
927
+ if (useReadyQueue) {
928
+ this.facets.requeueQueryCandidate(queuedCandidate.view, queuedCandidate.primitive);
929
+ }
930
+ }
931
+ this.lastPrepareUnavailable = true;
932
+ return void 0;
933
+ }
934
+ this.proxyScratch.set(vertices, proxyFloatCount);
935
+ proxyFloatCount += vertices.length;
936
+ }
937
+ const pending = {
938
+ reservations,
939
+ resources,
940
+ candidates: selected,
941
+ sampleCount,
942
+ endedCount: 0,
943
+ proxyFloatCount
944
+ };
945
+ this.pendingFrame = pending;
946
+ return this.projection;
947
+ }
948
+ inspect() {
949
+ return this.pool.inspect();
950
+ }
951
+ get prepareUnavailable() {
952
+ return this.lastPrepareUnavailable;
953
+ }
954
+ /** Map-only latency from the submitted query page; queue wait is excluded. */
955
+ inspectQueryLatency() {
956
+ if (this.queryLatencySamplesUs.length === 0) {
957
+ return { median: 0, p95: 0, last: this.lastQueryLatencyUs };
958
+ }
959
+ const sorted = [...this.queryLatencySamplesUs].sort((left, right) => left - right);
960
+ const rank = (percentile) => sorted[Math.max(0, Math.ceil(sorted.length * percentile) - 1)] ?? 0;
961
+ return {
962
+ median: rank(0.5),
963
+ p95: rank(0.95),
964
+ last: this.lastQueryLatencyUs
965
+ };
966
+ }
967
+ /**
968
+ * Wait until query readbacks submitted before the current observation have
969
+ * settled. The renderer receipt only fences command submission; query
970
+ * completion also includes the asynchronous map/unmap transition. Keeping
971
+ * that fence here makes `renderer.observe()` publish the latest visibility
972
+ * result and measured map latency instead of racing the readback promise.
973
+ */
974
+ async waitForCompletions() {
975
+ for (; ; ) {
976
+ const completions = [...this.activeCompletions.values()];
977
+ if (completions.length === 0) return;
978
+ await Promise.all(completions);
979
+ }
980
+ }
981
+ /** Count a successful frame for hidden rows that were not queried this frame. */
982
+ advanceSuccessfulSubmit(submissionGeneration) {
983
+ this.facets.advanceSuccessfulSubmits(submissionGeneration);
984
+ }
985
+ dispose() {
986
+ if (this.disposed) return;
987
+ this.disposed = true;
988
+ this.pool.dispose();
989
+ for (const resource of this.resources.values()) {
990
+ const completion = this.activeCompletions.get(resource);
991
+ if (completion === void 0) {
992
+ this.destroyResources(resource);
993
+ } else {
994
+ void completion.then(() => this.destroyResources(resource));
995
+ }
996
+ }
997
+ this.resources.clear();
998
+ if (this.proxyBuffer !== void 0) this.device.destroyBuffer(this.proxyBuffer);
999
+ this.proxyBuffer = void 0;
1000
+ this.proxyPipeline = void 0;
1001
+ this.proxyPipelineSampleCount = void 0;
1002
+ this.candidateSource = void 0;
1003
+ this.validCandidates = [];
1004
+ this.candidateByKey.clear();
1005
+ this.lastPrepareUnavailable = false;
1006
+ this.pendingFrame = void 0;
1007
+ }
1008
+ ensureResources(pageIndex) {
1009
+ const existing = this.resources.get(pageIndex);
1010
+ if (existing !== void 0) return existing;
1011
+ const created = createOcclusionQueryResources(this.device, pageIndex);
1012
+ if (!created.ok) return void 0;
1013
+ this.resources.set(pageIndex, created.value);
1014
+ return created.value;
1015
+ }
1016
+ commit(pending, submitted, submissionGeneration, profilePhase) {
1017
+ if (this.pendingFrame !== pending) return;
1018
+ this.pendingFrame = void 0;
1019
+ const tickets = [];
1020
+ const accepted = profilePhase === void 0 ? this.publishAndApplySubmit(pending, submitted, submissionGeneration, tickets) : profilePhase(
1021
+ "record/occlusion-query-submit",
1022
+ () => this.publishAndApplySubmit(pending, submitted, submissionGeneration, tickets)
1023
+ );
1024
+ if (!accepted) {
1025
+ for (const reservation of pending.reservations) this.pool.cancel(reservation);
1026
+ for (const candidate of pending.candidates) {
1027
+ this.facets.applyConfidence(candidate.view, candidate.primitive, {
1028
+ type: "failure",
1029
+ submissionGeneration
1030
+ });
1031
+ }
1032
+ return;
1033
+ }
1034
+ if (profilePhase === void 0) {
1035
+ this.facets.advanceSuccessfulSubmits(submissionGeneration);
1036
+ } else {
1037
+ profilePhase(
1038
+ "record/occlusion-global-advance",
1039
+ () => this.facets.advanceSuccessfulSubmits(submissionGeneration)
1040
+ );
1041
+ }
1042
+ const completion = this.completeAfterSubmit(tickets, pending);
1043
+ this.activeCompletions.set(pending.resources, completion);
1044
+ void completion.then(() => {
1045
+ if (this.activeCompletions.get(pending.resources) === completion) {
1046
+ this.activeCompletions.delete(pending.resources);
1047
+ }
1048
+ });
1049
+ }
1050
+ publishAndApplySubmit(pending, submitted, submissionGeneration, tickets) {
1051
+ for (const reservation of pending.reservations) {
1052
+ const ticket = this.pool.publish(reservation, { submitted, submissionGeneration });
1053
+ if (ticket !== void 0) tickets.push(ticket);
1054
+ }
1055
+ if (!submitted || tickets.length !== pending.reservations.length) return false;
1056
+ const submitEvent = Object.freeze({ type: "submit", submissionGeneration });
1057
+ for (const candidate of pending.candidates) {
1058
+ this.facets.applyConfidence(candidate.view, candidate.primitive, submitEvent);
1059
+ }
1060
+ return true;
1061
+ }
1062
+ destroyResources(resource) {
1063
+ if (this.retiredResources.has(resource)) return;
1064
+ this.retiredResources.add(resource);
1065
+ this.device.destroyBuffer(resource.resolveBuffer);
1066
+ this.device.destroyBuffer(resource.stagingBuffer);
1067
+ this.device.destroyQuerySet(resource.querySet);
1068
+ }
1069
+ async completeAfterSubmit(tickets, pending) {
1070
+ try {
1071
+ await this.device.queue.onSubmittedWorkDone();
1072
+ const mapStart = performance.now();
1073
+ await runtimeTestHooks?.beforeMap?.();
1074
+ const mapped = await pending.resources.stagingBuffer.mapAsync(
1075
+ GPU_BUFFER_USAGE_MAP_READ,
1076
+ 0,
1077
+ pending.reservations.length * 8
1078
+ );
1079
+ if (!mapped.ok) throw mapped.error;
1080
+ this.lastQueryLatencyUs = Math.max(0, Math.round((performance.now() - mapStart) * 1e3));
1081
+ this.queryLatencySamplesUs.push(this.lastQueryLatencyUs);
1082
+ if (this.queryLatencySamplesUs.length > 64) this.queryLatencySamplesUs.shift();
1083
+ const bytes = mapped.value.getMappedRange(0, pending.reservations.length * 8);
1084
+ if (!bytes.ok) throw bytes.error;
1085
+ const data = new Uint8Array(bytes.value.slice(0));
1086
+ const dataView = new DataView(data.buffer);
1087
+ const firstQuery = pending.reservations[0]?.queryIndex ?? 0;
1088
+ mapped.value.unmap();
1089
+ for (let ticketIndex = tickets.length - 1; ticketIndex >= 0; ticketIndex -= 1) {
1090
+ const ticket = tickets[ticketIndex];
1091
+ if (ticket === void 0) continue;
1092
+ const candidateIndex = ticket.queryIndex - firstQuery;
1093
+ const samples = dataView.getUint32(candidateIndex * 8, true);
1094
+ const completion = this.pool.complete(ticket, samples);
1095
+ if (completion.status !== "accepted") continue;
1096
+ const candidate = pending.candidates[candidateIndex];
1097
+ if (candidate === void 0) continue;
1098
+ this.facets.applyConfidence(candidate.view, candidate.primitive, {
1099
+ type: "result",
1100
+ samples,
1101
+ submissionGeneration: ticket.submissionGeneration
1102
+ });
1103
+ this.facets.applyCompletion(
1104
+ candidate.view,
1105
+ candidate.primitive,
1106
+ candidate.epoch,
1107
+ candidate.candidate
1108
+ );
1109
+ }
1110
+ } catch {
1111
+ for (const ticket of tickets) {
1112
+ this.pool.complete(ticket, Number.NaN);
1113
+ const candidate = pending.candidates[ticket.queryIndex - (pending.reservations[0]?.queryIndex ?? 0)];
1114
+ if (candidate !== void 0) {
1115
+ this.facets.applyConfidence(candidate.view, candidate.primitive, {
1116
+ type: "failure",
1117
+ submissionGeneration: ticket.submissionGeneration
1118
+ });
1119
+ }
1120
+ }
1121
+ }
1122
+ }
1123
+ encodeProxyBounds(pass, pending) {
1124
+ if (pending.proxyFloatCount === 0) {
1125
+ return err$1(runtimeError("every occlusion candidate needs conservative proxy geometry"));
1126
+ }
1127
+ const pipeline = this.ensureProxyPipeline(pending.sampleCount);
1128
+ if (pipeline === void 0) {
1129
+ for (const reservation of pending.reservations) {
1130
+ const began = pass.beginOcclusionQuery(reservation.queryIndex);
1131
+ if (!began.ok) return began;
1132
+ const ended = pass.endOcclusionQuery();
1133
+ if (!ended.ok) return ended;
1134
+ }
1135
+ pending.endedCount = pending.reservations.length;
1136
+ return ok$1(void 0);
1137
+ }
1138
+ const buffer = this.ensureProxyBuffer(pending.proxyFloatCount * Float32Array.BYTES_PER_ELEMENT);
1139
+ if (buffer === void 0) {
1140
+ return err$1(runtimeError("the backend must allocate the bounded proxy geometry buffer"));
1141
+ }
1142
+ const uploaded = this.device.queue.writeBuffer(
1143
+ buffer,
1144
+ 0,
1145
+ this.proxyScratch.subarray(0, pending.proxyFloatCount)
1146
+ );
1147
+ if (!uploaded.ok) return uploaded;
1148
+ pass.setPipeline(pipeline);
1149
+ pass.setVertexBuffer(0, buffer);
1150
+ let vertexOffset = 0;
1151
+ for (let index = 0; index < pending.reservations.length; index += 1) {
1152
+ const vertices = pending.candidates[index]?.proxyVertices;
1153
+ if (vertices === void 0 || vertices.length === 0) continue;
1154
+ const began = pass.beginOcclusionQuery(pending.reservations[index]?.queryIndex ?? 0);
1155
+ if (!began.ok) return began;
1156
+ pass.draw(vertices.length / 4, 1, vertexOffset / 4, 0);
1157
+ const ended = pass.endOcclusionQuery();
1158
+ if (!ended.ok) return ended;
1159
+ vertexOffset += vertices.length;
1160
+ }
1161
+ pending.endedCount = pending.reservations.length;
1162
+ return ok$1(void 0);
1163
+ }
1164
+ ensureProxyBuffer(byteLength) {
1165
+ if (this.proxyBuffer !== void 0) return this.proxyBuffer;
1166
+ const created = this.device.createBuffer({
1167
+ label: "occlusion-bounds-proxy-geometry",
1168
+ size: Math.max(16, PROXY_GEOMETRY_BYTES, byteLength),
1169
+ usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST
1170
+ });
1171
+ if (!created.ok) return void 0;
1172
+ this.proxyBuffer = created.value;
1173
+ return created.value;
1174
+ }
1175
+ ensureProxyPipeline(sampleCount) {
1176
+ if (this.proxyPipelineSampleCount === sampleCount && this.proxyPipeline !== void 0) {
1177
+ return this.proxyPipeline;
1178
+ }
1179
+ if (this.shaderModuleFactory === void 0) return void 0;
1180
+ const module = this.shaderModuleFactory.createShaderModule({
1181
+ label: "occlusion-bounds-proxy",
1182
+ code: PROXY_BOUNDS_WGSL
1183
+ });
1184
+ if (!module.ok) return void 0;
1185
+ const created = this.device.createRenderPipeline({
1186
+ label: "occlusion-bounds-proxy",
1187
+ layout: "auto",
1188
+ vertex: {
1189
+ module: module.value,
1190
+ entryPoint: "vs_main",
1191
+ buffers: [
1192
+ {
1193
+ arrayStride: 16,
1194
+ stepMode: "vertex",
1195
+ attributes: [{ shaderLocation: 0, offset: 0, format: "float32x4" }]
1196
+ }
1197
+ ]
1198
+ },
1199
+ // The proxy is recorded in the typed main pass, whose color attachment
1200
+ // remains RGBA16Float. Keep the pass compatible while suppressing all
1201
+ // color writes; occlusion only consumes depth samples.
1202
+ fragment: {
1203
+ module: module.value,
1204
+ entryPoint: "fs_main",
1205
+ targets: [{ format: "rgba16float", writeMask: 0 }]
1206
+ },
1207
+ primitive: { topology: "triangle-list" },
1208
+ depthStencil: {
1209
+ format: "depth24plus-stencil8",
1210
+ depthWriteEnabled: false,
1211
+ depthCompare: "less-equal"
1212
+ },
1213
+ multisample: { count: sampleCount }
1214
+ });
1215
+ if (!created.ok) return void 0;
1216
+ this.proxyPipeline = created.value;
1217
+ this.proxyPipelineSampleCount = sampleCount;
1218
+ return created.value;
1219
+ }
1220
+ };
1221
+ var PROXY_BOUNDS_WGSL = `
1222
+ @vertex
1223
+ fn vs_main(@location(0) position: vec4f) -> @builtin(position) vec4f {
1224
+ return position;
1225
+ }
1226
+ @fragment
1227
+ fn fs_main() -> @location(0) vec4f {
1228
+ return vec4f(0.0);
1229
+ }
1230
+ `;
1231
+ function candidateKey(view, primitive) {
1232
+ return `${viewKeyId(view)}|${primitiveKeyId(primitive)}`;
1233
+ }
1234
+ function finiteBounds(bounds) {
1235
+ return [...bounds.min, ...bounds.max].every((value) => Number.isFinite(value));
1236
+ }
1237
+ function runtimeError(hint) {
1238
+ return new RhiError({
1239
+ code: "webgpu-runtime-error",
1240
+ expected: "RenderSystem owns one active occlusion candidate for the shared frame",
1241
+ hint
1242
+ });
1243
+ }
1244
+ var PROBE_R_MIN = R_MIN;
1245
+ var PROBE_MAX_CONTRIBUTORS = PROBE_BLEND_RECORD_CAPACITY;
1246
+ var SH_C0 = 0.28209479177387814;
1247
+ var SH_C1 = 0.4886025119029199;
1248
+ var SH_C2 = 1.0925484305920792;
1249
+ var SH_C20 = 0.31539156525252005;
1250
+ var SH_C22 = 0.5462742152960396;
1251
+ function finiteVector(values, length) {
1252
+ if (values.length !== length) return false;
1253
+ for (let index = 0; index < values.length; index += 1) {
1254
+ if (!Number.isFinite(values[index])) return false;
1255
+ }
1256
+ return true;
1257
+ }
1258
+ function contributorIsActive(probe, radiusMinimum) {
1259
+ return probe.admitted && Number.isFinite(probe.distance) && probe.distance >= 0 && Number.isFinite(probe.radius) && probe.radius >= radiusMinimum && probe.distance < probe.radius && finiteVector(probe.irradiance, 27);
1260
+ }
1261
+ function emptyReceipt(radiusMinimum) {
1262
+ return {
1263
+ activeIdentities: [],
1264
+ admittedIdentities: [],
1265
+ rejectedIdentities: [],
1266
+ stableOrder: [],
1267
+ capacity: PROBE_MAX_CONTRIBUTORS,
1268
+ scaleRadius: radiusMinimum,
1269
+ finite: true
1270
+ };
1271
+ }
1272
+ function admitProbeContributors(candidates, radiusMinimum = PROBE_R_MIN) {
1273
+ const active = candidates.filter((candidate) => contributorIsActive(candidate, radiusMinimum));
1274
+ const overflowed = active.length > PROBE_MAX_CONTRIBUTORS;
1275
+ const stableActive = active.slice().sort((left, right) => left.identity.localeCompare(right.identity));
1276
+ const admitted = stableActive.slice(0, PROBE_MAX_CONTRIBUTORS);
1277
+ const rejected = stableActive.slice(PROBE_MAX_CONTRIBUTORS);
1278
+ const scaleRadius = active.reduce(
1279
+ (minimum, candidate) => Math.min(minimum, candidate.radius),
1280
+ active[0]?.radius ?? radiusMinimum
1281
+ );
1282
+ const receipt = {
1283
+ activeIdentities: stableActive.map((candidate) => candidate.identity),
1284
+ admittedIdentities: admitted.map((candidate) => candidate.identity),
1285
+ rejectedIdentities: rejected.map((candidate) => candidate.identity),
1286
+ stableOrder: admitted.map((candidate) => candidate.identity),
1287
+ capacity: PROBE_MAX_CONTRIBUTORS,
1288
+ ...overflowed ? { overflowReason: "active-count-exceeds-capacity" } : {},
1289
+ scaleRadius,
1290
+ finite: stableActive.every((candidate) => Number.isFinite(candidate.radius))
1291
+ };
1292
+ return {
1293
+ active: stableActive,
1294
+ admitted,
1295
+ rejected,
1296
+ receipt,
1297
+ ...overflowed ? {
1298
+ error: {
1299
+ code: "capacity-exceeded",
1300
+ activeCount: active.length,
1301
+ capacity: PROBE_MAX_CONTRIBUTORS,
1302
+ receipt
1303
+ }
1304
+ } : {}
1305
+ };
1306
+ }
1307
+ function invalidInputResult(radiusMinimum, code) {
1308
+ const receipt = emptyReceipt(radiusMinimum);
1309
+ return {
1310
+ terms: [],
1311
+ rStar: radiusMinimum,
1312
+ Q: 0,
1313
+ C: 0,
1314
+ S: 1,
1315
+ SHPreblend: new Array(27).fill(0),
1316
+ skyIrradiance: [0, 0, 0],
1317
+ diffuse: [0, 0, 0],
1318
+ finite: false,
1319
+ receipt,
1320
+ scaledQLogOffset: 0,
1321
+ error: { code, activeCount: 0, capacity: PROBE_MAX_CONTRIBUTORS, receipt }
1322
+ };
1323
+ }
1324
+ function neumaierSum(values) {
1325
+ let sum = 0;
1326
+ let correction = 0;
1327
+ for (const value of values) {
1328
+ const next = sum + value;
1329
+ if (Math.abs(sum) >= Math.abs(value)) correction += sum - next + value;
1330
+ else correction += value - next + sum;
1331
+ sum = next;
1332
+ }
1333
+ return sum + correction;
1334
+ }
1335
+ function roundProbeToF32(value) {
1336
+ return Number.isFinite(value) ? new Float32Array([value])[0] : void 0;
1337
+ }
1338
+ function scaledProbeWeight(distance, radius, radiusMinimum = PROBE_R_MIN) {
1339
+ if (!Number.isFinite(distance) || distance < 0 || !Number.isFinite(radius) || radius < radiusMinimum || distance >= radius) {
1340
+ return { coverage: 0, q: 0 };
1341
+ }
1342
+ const ratio = distance / radius;
1343
+ const coverage = 1 - ratio * ratio;
1344
+ return { coverage, q: coverage / (radius * radius) };
1345
+ }
1346
+ function sh9Irradiance(coefficients, normal) {
1347
+ const x = normal[0];
1348
+ const y = normal[1];
1349
+ const z = normal[2];
1350
+ const basis = [
1351
+ SH_C0,
1352
+ SH_C1 * y,
1353
+ SH_C1 * z,
1354
+ SH_C1 * x,
1355
+ SH_C2 * x * y,
1356
+ SH_C2 * y * z,
1357
+ SH_C20 * (3 * z * z - 1),
1358
+ SH_C2 * x * z,
1359
+ SH_C22 * (x * x - y * y)
1360
+ ];
1361
+ const result = [0, 0, 0];
1362
+ for (let band = 0; band < basis.length; band += 1) {
1363
+ const factor = basis[band] ?? 0;
1364
+ const offset = band * 3;
1365
+ result[0] = (result[0] ?? 0) + (coefficients[offset] ?? 0) * factor;
1366
+ result[1] = (result[1] ?? 0) + (coefficients[offset + 1] ?? 0) * factor;
1367
+ result[2] = (result[2] ?? 0) + (coefficients[offset + 2] ?? 0) * factor;
1368
+ }
1369
+ return result;
1370
+ }
1371
+ function vectorNeumaierSum(values, width) {
1372
+ return Array.from(
1373
+ { length: width },
1374
+ (_, channel) => neumaierSum(values.map((value) => value[channel] ?? 0))
1375
+ );
1376
+ }
1377
+ function blendProbeCoefficients(contributors, radiusMinimum) {
1378
+ if (contributors.length > PROBE_MAX_CONTRIBUTORS || contributors.some((contributor) => !contributorIsActive(contributor, radiusMinimum))) {
1379
+ return void 0;
1380
+ }
1381
+ const rStar = contributors.reduce(
1382
+ (minimum, contributor) => Math.min(minimum, contributor.radius),
1383
+ contributors[0]?.radius ?? radiusMinimum
1384
+ );
1385
+ const rawTerms = contributors.map((contributor) => {
1386
+ const { coverage } = scaledProbeWeight(contributor.distance, contributor.radius, radiusMinimum);
1387
+ const scaledRatio = rStar / contributor.radius;
1388
+ return {
1389
+ contributor,
1390
+ coverage,
1391
+ // Diagnostic q is never used for Q, alpha, C, or SH accumulation.
1392
+ q: coverage / (contributor.radius * contributor.radius),
1393
+ scaledQ: coverage * scaledRatio * scaledRatio
1394
+ };
1395
+ });
1396
+ const scaledQLogOffset = 0;
1397
+ const finiteTerms = rawTerms;
1398
+ if (finiteTerms.some((term) => !Number.isFinite(term.scaledQ))) return void 0;
1399
+ const Q = neumaierSum(finiteTerms.map((term) => term.scaledQ));
1400
+ const terms = finiteTerms.map((term) => ({
1401
+ identity: term.contributor.identity,
1402
+ distance: term.contributor.distance,
1403
+ radius: term.contributor.radius,
1404
+ coverage: term.coverage,
1405
+ q: term.q,
1406
+ scaledQ: term.scaledQ,
1407
+ qHat: term.scaledQ,
1408
+ alpha: Q > 0 ? term.scaledQ / Q : 0
1409
+ }));
1410
+ let product = 1;
1411
+ for (const term of finiteTerms) product *= 1 - term.coverage;
1412
+ const C = 1 - product;
1413
+ const S = 1 - C;
1414
+ const weightedSh = finiteTerms.map(
1415
+ (term) => Array.from(term.contributor.irradiance, (value) => value * (Q > 0 ? term.scaledQ / Q : 0))
1416
+ );
1417
+ const SHPreblend = vectorNeumaierSum(weightedSh, 27).map((value) => value * C);
1418
+ const finite = Number.isFinite(Q) && Number.isFinite(C) && Number.isFinite(S) && SHPreblend.every(Number.isFinite) && finiteTerms.every((term) => Number.isFinite(term.scaledQ));
1419
+ const receipt = {
1420
+ activeIdentities: contributors.map((contributor) => contributor.identity),
1421
+ admittedIdentities: contributors.map((contributor) => contributor.identity),
1422
+ rejectedIdentities: [],
1423
+ stableOrder: contributors.map((contributor) => contributor.identity),
1424
+ capacity: PROBE_MAX_CONTRIBUTORS,
1425
+ scaleRadius: rStar,
1426
+ finite
1427
+ };
1428
+ return {
1429
+ terms,
1430
+ rStar,
1431
+ Q,
1432
+ C,
1433
+ S,
1434
+ SHPreblend,
1435
+ finite,
1436
+ receipt,
1437
+ scaledQLogOffset
1438
+ };
1439
+ }
1440
+ function blendLightProbes(input) {
1441
+ const radiusMinimum = input.radiusMinimum ?? PROBE_R_MIN;
1442
+ const normal = input.normal;
1443
+ const sky = input.skyIrradiance;
1444
+ if (!finiteVector(normal, 3) || !finiteVector(sky, 3)) {
1445
+ return invalidInputResult(radiusMinimum, "invalid-admitted-prefix");
1446
+ }
1447
+ const coefficients = blendProbeCoefficients(input.contributors, radiusMinimum);
1448
+ if (coefficients === void 0)
1449
+ return invalidInputResult(radiusMinimum, "invalid-admitted-prefix");
1450
+ const shIrradiance = sh9Irradiance(coefficients.SHPreblend, normal);
1451
+ const diffuse = [0, 1, 2].map(
1452
+ (channel) => Math.max(shIrradiance[channel] ?? 0, 0) + coefficients.S * (sky[channel] ?? 0)
1453
+ );
1454
+ return {
1455
+ ...coefficients,
1456
+ skyIrradiance: [sky[0], sky[1], sky[2]],
1457
+ diffuse,
1458
+ finite: coefficients.finite && diffuse.every(Number.isFinite)
1459
+ };
1460
+ }
1461
+ function vectorDistance(left, right) {
1462
+ const dx = left[0] - right[0];
1463
+ const dy = left[1] - right[1];
1464
+ const dz = left[2] - right[2];
1465
+ return Math.hypot(dx, dy, dz);
1466
+ }
1467
+ function encodeProbeRecord(objectKey, generation, localBlendFraction, shPreblend, candidate, accepted, lastKnownGood, sentinel) {
1468
+ const values = new Float32Array(PROBE_BLEND_RECORD_BYTE_SIZE / Float32Array.BYTES_PER_ELEMENT);
1469
+ values[0] = objectKey;
1470
+ values[1] = generation;
1471
+ values[2] = roundProbeToF32(localBlendFraction) ?? 0;
1472
+ values[3] = (1 ) | (accepted ? 2 : 0) | (lastKnownGood ? 4 : 0);
1473
+ for (let band = 0; band < 9; band += 1) {
1474
+ const lane = 4 + band * 4;
1475
+ values[lane] = roundProbeToF32(shPreblend[band * 3] ?? 0) ?? 0;
1476
+ values[lane + 1] = roundProbeToF32(shPreblend[band * 3 + 1] ?? 0) ?? 0;
1477
+ values[lane + 2] = roundProbeToF32(shPreblend[band * 3 + 2] ?? 0) ?? 0;
1478
+ values[lane + 3] = 0;
1479
+ }
1480
+ const packedSh = new Array(27);
1481
+ for (let band = 0; band < 9; band += 1) {
1482
+ packedSh[band * 3] = values[4 + band * 4] ?? 0;
1483
+ packedSh[band * 3 + 1] = values[5 + band * 4] ?? 0;
1484
+ packedSh[band * 3 + 2] = values[6 + band * 4] ?? 0;
1485
+ }
1486
+ return {
1487
+ objectKey,
1488
+ generation,
1489
+ localBlendFraction: values[2] ?? 0,
1490
+ shPreblend: packedSh,
1491
+ bytes: new Uint8Array(values.buffer),
1492
+ byteLength: values.byteLength,
1493
+ candidate,
1494
+ accepted,
1495
+ lastKnownGood,
1496
+ ...sentinel === void 0 ? {} : { sentinel }
1497
+ };
1498
+ }
1499
+ function sameList(left, right) {
1500
+ return left.length === right.length && left.every((value, index) => value === right[index]);
1501
+ }
1502
+ function sameVector3(left, right) {
1503
+ return left[0] === right[0] && left[1] === right[1] && left[2] === right[2];
1504
+ }
1505
+ function sameProbeFacts(left, right) {
1506
+ if (left.length !== right.length) return false;
1507
+ return left.every((probe, index) => {
1508
+ const other = right[index];
1509
+ if (other === void 0 || probe.identity !== other.identity || probe.radius !== other.radius || probe.admitted !== other.admitted || !sameVector3(probe.position, other.position) || probe.irradiance.length !== other.irradiance.length)
1510
+ return false;
1511
+ for (let value = 0; value < probe.irradiance.length; value += 1) {
1512
+ if (probe.irradiance[value] !== other.irradiance[value]) return false;
1513
+ }
1514
+ return true;
1515
+ });
1516
+ }
1517
+ function sameSkyFacts(left, right) {
1518
+ const leftSky = left.sky;
1519
+ const rightSky = right.sky;
1520
+ if (leftSky === void 0 !== (rightSky === void 0)) return false;
1521
+ if (leftSky !== void 0 && rightSky !== void 0 && (leftSky.available !== rightSky.available || leftSky.identity !== rightSky.identity || leftSky.sourceKey !== rightSky.sourceKey || leftSky.fallbackReason !== rightSky.fallbackReason || !sameVector3(leftSky.irradiance, rightSky.irradiance)))
1522
+ return false;
1523
+ if (left.skyIrradiance === void 0 !== (right.skyIrradiance === void 0)) return false;
1524
+ return left.skyIrradiance === void 0 || right.skyIrradiance !== void 0 && sameVector3(left.skyIrradiance, right.skyIrradiance);
1525
+ }
1526
+ var ProbeBlendSceneProjection = class {
1527
+ previous;
1528
+ records = /* @__PURE__ */ new Map();
1529
+ lastError;
1530
+ lastReceipt = emptyReceipt(PROBE_R_MIN);
1531
+ lastActiveContributorCount = 0;
1532
+ lastAdmittedProbeCount = 0;
1533
+ lastCoverage = 0;
1534
+ lastFinite = true;
1535
+ lastSky = {
1536
+ available: false,
1537
+ irradiance: [0, 0, 0],
1538
+ fallbackReason: "no-skylight"
1539
+ };
1540
+ lastDirtyVisitCount = 0;
1541
+ lastDirtyReasons = [];
1542
+ bufferProjectionSource = Object.freeze({});
1543
+ bufferProjection = Object.freeze({
1544
+ sourceIdentity: this.bufferProjectionSource,
1545
+ revision: 0,
1546
+ baseRevision: 0,
1547
+ capacity: 1,
1548
+ records: Object.freeze([]),
1549
+ dirtyRecords: Object.freeze([]),
1550
+ removedCacheKeys: Object.freeze([])
1551
+ });
1552
+ resultRecords = Object.freeze([]);
1553
+ recordFacts = /* @__PURE__ */ new Map();
1554
+ getRecord(objectKey) {
1555
+ return this.records.get(objectKey);
1556
+ }
1557
+ hasRecords() {
1558
+ return this.records.size > 0;
1559
+ }
1560
+ /** Stable upload projection; rebuilt only when probe/object facts change. */
1561
+ recordBufferProjection() {
1562
+ return this.bufferProjection;
1563
+ }
1564
+ inspect() {
1565
+ return {
1566
+ activeContributorCount: this.lastActiveContributorCount,
1567
+ admittedProbeCount: this.lastAdmittedProbeCount,
1568
+ coverage: this.lastCoverage,
1569
+ skyResidualFraction: 1 - this.lastCoverage,
1570
+ finite: this.lastFinite,
1571
+ errorCode: this.lastError?.code,
1572
+ records: [...this.records.values()].map((record) => {
1573
+ const facts = this.recordFacts.get(record.objectKey);
1574
+ return {
1575
+ objectKey: record.objectKey,
1576
+ generation: record.generation,
1577
+ localBlendFraction: record.localBlendFraction,
1578
+ shPreblend: [...record.shPreblend],
1579
+ byteLength: record.byteLength,
1580
+ candidate: record.candidate,
1581
+ accepted: record.accepted,
1582
+ lastKnownGood: record.lastKnownGood,
1583
+ sentinel: record.sentinel,
1584
+ probeBlendIndex: record.objectKey,
1585
+ contributors: (facts?.contributors ?? []).map((term) => ({
1586
+ identity: term.identity,
1587
+ distance: term.distance,
1588
+ radius: term.radius,
1589
+ coverage: term.coverage,
1590
+ q: term.q,
1591
+ qHat: term.qHat,
1592
+ alpha: term.alpha
1593
+ })),
1594
+ qHatSum: facts?.qHatSum ?? 0,
1595
+ rStar: facts?.rStar ?? PROBE_R_MIN,
1596
+ fallbackReason: facts?.fallbackReason
1597
+ };
1598
+ }),
1599
+ sky: {
1600
+ available: this.lastSky?.available ?? false,
1601
+ identity: this.lastSky?.identity,
1602
+ sourceKey: this.lastSky?.sourceKey,
1603
+ irradiance: [...this.lastSky?.irradiance ?? [0, 0, 0]],
1604
+ fallbackReason: this.lastSky?.fallbackReason
1605
+ },
1606
+ dirtyVisitCount: this.lastDirtyVisitCount,
1607
+ dirtyReasons: [...this.lastDirtyReasons],
1608
+ receipt: {
1609
+ activeIdentities: [...this.lastReceipt.activeIdentities],
1610
+ admittedIdentities: [...this.lastReceipt.admittedIdentities],
1611
+ rejectedIdentities: [...this.lastReceipt.rejectedIdentities],
1612
+ stableOrder: [...this.lastReceipt.stableOrder],
1613
+ capacity: this.lastReceipt.capacity,
1614
+ scaleRadius: this.lastReceipt.scaleRadius,
1615
+ finite: this.lastReceipt.finite,
1616
+ overflowReason: this.lastReceipt.overflowReason
1617
+ }
1618
+ };
1619
+ }
1620
+ apply(input) {
1621
+ const dirtyReasons = [];
1622
+ const affectedObjectKeys = /* @__PURE__ */ new Set();
1623
+ const previous = this.previous;
1624
+ if (previous !== void 0 && previous.objects === input.objects && input.worldRevision === previous.worldRevision && input.retainSkyResidualRecords === previous.retainSkyResidualRecords && sameProbeFacts(input.probes, previous.probes) && sameSkyFacts(input, previous)) {
1625
+ return {
1626
+ records: this.resultRecords,
1627
+ contributors: [],
1628
+ activeContributorCount: this.lastActiveContributorCount,
1629
+ admittedProbeCount: this.lastAdmittedProbeCount,
1630
+ coverage: this.lastCoverage,
1631
+ skyResidualFraction: 1 - this.lastCoverage,
1632
+ dirtyReasons: [],
1633
+ affectedObjectKeys: [],
1634
+ visits: 0,
1635
+ allocations: 0,
1636
+ uploads: 0,
1637
+ receipt: this.lastReceipt,
1638
+ ...this.lastError === void 0 ? {} : { error: this.lastError }
1639
+ };
1640
+ }
1641
+ const objectKeys = input.objects.map((object) => String(object.objectKey)).sort();
1642
+ const previousObjectKeys = previous?.objects.map((object) => String(object.objectKey)).sort() ?? [];
1643
+ if (previous === void 0 || !sameList(objectKeys, previousObjectKeys)) {
1644
+ dirtyReasons.push("object-boundary");
1645
+ for (const object of input.objects) affectedObjectKeys.add(object.objectKey);
1646
+ }
1647
+ if (previous !== void 0 && input.worldRevision !== previous.worldRevision)
1648
+ dirtyReasons.push("world");
1649
+ if (previous !== void 0 && (!sameProbeFacts(input.probes, previous.probes) || input.retainSkyResidualRecords !== previous.retainSkyResidualRecords))
1650
+ dirtyReasons.push("probe-fact");
1651
+ if (previous !== void 0 && !sameSkyFacts(input, previous)) dirtyReasons.push("sky");
1652
+ const previousObjects = new Map(previous?.objects.map((object) => [object.objectKey, object]));
1653
+ for (const object of input.objects) {
1654
+ const oldObject = previousObjects.get(object.objectKey);
1655
+ if (oldObject === void 0) {
1656
+ affectedObjectKeys.add(object.objectKey);
1657
+ continue;
1658
+ }
1659
+ if (!sameVector3(object.position, oldObject.position)) {
1660
+ dirtyReasons.push("object-position");
1661
+ affectedObjectKeys.add(object.objectKey);
1662
+ }
1663
+ if (object.generation !== oldObject.generation) {
1664
+ dirtyReasons.push("generation");
1665
+ affectedObjectKeys.add(object.objectKey);
1666
+ }
1667
+ }
1668
+ if (dirtyReasons.includes("world") || dirtyReasons.includes("probe-fact") || dirtyReasons.includes("sky"))
1669
+ for (const object of input.objects) affectedObjectKeys.add(object.objectKey);
1670
+ const uniqueDirtyReasons = [...new Set(dirtyReasons)];
1671
+ if (previous !== void 0 && uniqueDirtyReasons.length === 0) {
1672
+ return {
1673
+ records: [...this.records.values()],
1674
+ contributors: [],
1675
+ activeContributorCount: this.lastActiveContributorCount,
1676
+ admittedProbeCount: this.lastAdmittedProbeCount,
1677
+ coverage: this.lastCoverage,
1678
+ skyResidualFraction: 1 - this.lastCoverage,
1679
+ dirtyReasons: [],
1680
+ affectedObjectKeys: [],
1681
+ visits: 0,
1682
+ allocations: 0,
1683
+ uploads: 0,
1684
+ receipt: this.lastReceipt,
1685
+ ...this.lastError === void 0 ? {} : { error: this.lastError }
1686
+ };
1687
+ }
1688
+ const nextRecords = new Map(this.records);
1689
+ const currentObjectKeys = new Set(input.objects.map((object) => object.objectKey));
1690
+ for (const objectKey of nextRecords.keys()) {
1691
+ if (!currentObjectKeys.has(objectKey)) {
1692
+ nextRecords.delete(objectKey);
1693
+ this.recordFacts.delete(objectKey);
1694
+ }
1695
+ }
1696
+ let firstTerms = [];
1697
+ let firstReceipt = emptyReceipt(PROBE_R_MIN);
1698
+ let firstError;
1699
+ let firstActiveCount = 0;
1700
+ let firstAdmittedCount = 0;
1701
+ let firstCoverage = 0;
1702
+ let firstObjectSeen = false;
1703
+ let visited = 0;
1704
+ for (const object of input.objects) {
1705
+ if (!affectedObjectKeys.has(object.objectKey)) continue;
1706
+ visited += 1;
1707
+ const candidates = input.probes.map((probe) => ({
1708
+ identity: probe.identity,
1709
+ admitted: probe.admitted,
1710
+ distance: vectorDistance(object.position, probe.position),
1711
+ radius: probe.radius,
1712
+ irradiance: probe.irradiance
1713
+ }));
1714
+ const admission = admitProbeContributors(candidates);
1715
+ if (!firstObjectSeen) {
1716
+ firstObjectSeen = true;
1717
+ firstActiveCount = admission.active.length;
1718
+ firstAdmittedCount = admission.admitted.length;
1719
+ firstReceipt = admission.receipt;
1720
+ }
1721
+ const previousRecord = this.records.get(object.objectKey);
1722
+ const lastKnownGood = input.lastKnownGood ?? previousRecord?.lastKnownGood ?? false;
1723
+ let record;
1724
+ if (admission.error !== void 0) firstError ??= admission.error;
1725
+ if (admission.admitted.length === 0) {
1726
+ if (input.probes.length === 0 && input.retainSkyResidualRecords !== true) {
1727
+ nextRecords.delete(object.objectKey);
1728
+ this.recordFacts.delete(object.objectKey);
1729
+ continue;
1730
+ }
1731
+ const sentinel = lastKnownGood ? "no-active" : "no-lkg";
1732
+ record = encodeProbeRecord(
1733
+ object.objectKey,
1734
+ object.generation,
1735
+ 0,
1736
+ new Array(27).fill(0),
1737
+ true,
1738
+ false,
1739
+ lastKnownGood,
1740
+ sentinel
1741
+ );
1742
+ this.recordFacts.set(object.objectKey, {
1743
+ contributors: [],
1744
+ qHatSum: 0,
1745
+ rStar: PROBE_R_MIN,
1746
+ fallbackReason: admission.error?.code ?? sentinel
1747
+ });
1748
+ } else {
1749
+ const blend = blendProbeCoefficients(admission.admitted, PROBE_R_MIN);
1750
+ if (blend === void 0) {
1751
+ firstError ??= {
1752
+ code: "invalid-admitted-prefix",
1753
+ activeCount: admission.active.length,
1754
+ capacity: PROBE_MAX_CONTRIBUTORS,
1755
+ receipt: admission.receipt
1756
+ };
1757
+ firstReceipt = admission.receipt;
1758
+ continue;
1759
+ }
1760
+ record = encodeProbeRecord(
1761
+ object.objectKey,
1762
+ object.generation,
1763
+ blend.C,
1764
+ blend.SHPreblend,
1765
+ true,
1766
+ true,
1767
+ true,
1768
+ void 0
1769
+ );
1770
+ this.recordFacts.set(object.objectKey, {
1771
+ contributors: blend.terms,
1772
+ qHatSum: blend.Q,
1773
+ rStar: blend.rStar,
1774
+ fallbackReason: admission.error === void 0 ? void 0 : "capacity-exceeded-stable-prefix"
1775
+ });
1776
+ if (firstTerms.length === 0) firstTerms = blend.terms;
1777
+ firstCoverage = blend.C;
1778
+ }
1779
+ nextRecords.set(object.objectKey, record);
1780
+ firstError ??= admission.error;
1781
+ }
1782
+ const allocations = previous === void 0 ? nextRecords.size : 0;
1783
+ const resultRecords = Object.freeze([...nextRecords.values()]);
1784
+ const result = {
1785
+ records: resultRecords,
1786
+ contributors: firstTerms,
1787
+ activeContributorCount: firstActiveCount,
1788
+ admittedProbeCount: firstAdmittedCount,
1789
+ coverage: firstCoverage,
1790
+ skyResidualFraction: 1 - firstCoverage,
1791
+ dirtyReasons: uniqueDirtyReasons,
1792
+ affectedObjectKeys: [...affectedObjectKeys].sort((left, right) => left - right),
1793
+ visits: visited,
1794
+ allocations,
1795
+ uploads: nextRecords.size,
1796
+ receipt: firstReceipt,
1797
+ ...firstError === void 0 ? {} : { error: firstError }
1798
+ };
1799
+ const projectedRecords = Object.freeze(
1800
+ [...nextRecords].map(([objectKey, record]) => Object.freeze({ cacheKey: objectKey, record }))
1801
+ );
1802
+ const dirtyRecords = Object.freeze(
1803
+ projectedRecords.filter(({ record }) => affectedObjectKeys.has(record.objectKey))
1804
+ );
1805
+ const removedCacheKeys = Object.freeze(
1806
+ [...this.records.keys()].filter((objectKey) => !nextRecords.has(objectKey))
1807
+ );
1808
+ const capacity = projectedRecords.reduce(
1809
+ (value, { record }) => Math.max(value, record.objectKey + 2),
1810
+ 1
1811
+ );
1812
+ this.previous = input;
1813
+ this.records = nextRecords;
1814
+ this.resultRecords = resultRecords;
1815
+ const baseRevision = this.bufferProjection.revision;
1816
+ this.bufferProjection = Object.freeze({
1817
+ sourceIdentity: this.bufferProjectionSource,
1818
+ revision: baseRevision + 1,
1819
+ baseRevision,
1820
+ capacity,
1821
+ records: projectedRecords,
1822
+ dirtyRecords,
1823
+ removedCacheKeys
1824
+ });
1825
+ this.lastError = firstError;
1826
+ this.lastReceipt = firstReceipt;
1827
+ this.lastActiveContributorCount = firstActiveCount;
1828
+ this.lastAdmittedProbeCount = firstAdmittedCount;
1829
+ this.lastCoverage = firstCoverage;
1830
+ this.lastFinite = this.resultRecords.every(
1831
+ (record) => record.bytes.length === PROBE_BLEND_RECORD_BYTE_SIZE
1832
+ );
1833
+ this.lastSky = input.sky ?? (input.skyIrradiance === void 0 ? { available: false, irradiance: [0, 0, 0], fallbackReason: "no-skylight" } : { available: true, irradiance: input.skyIrradiance });
1834
+ this.lastDirtyVisitCount = visited;
1835
+ this.lastDirtyReasons = uniqueDirtyReasons;
1836
+ return result;
1837
+ }
1838
+ };
1839
+
1840
+ export { OcclusionRenderRuntime, PROBE_MAX_CONTRIBUTORS, ProbeBlendSceneProjection, SSR_HISTORY_FORMAT, SsrHistoryOwner, admitProbeContributors, blendLightProbes, createExtendedLightingState, createSsrHistoryOwner, createSsrSpatialResources, emptyBloomInspection, estimateSsrSpatialMemory, primitiveKey, primitiveKeyId, projectBarrelDistortionInspection, projectExtendedLightingInspection, promoteExtendedLightingCandidate, recordExtendedLightingFailure, scaledProbeWeight, setOcclusionRuntimeTestHooks, viewKey, viewKeyId };
1841
+ //# sourceMappingURL=chunk-3XUBWRY2.mjs.map
1842
+ //# sourceMappingURL=chunk-3XUBWRY2.mjs.map