@forgeax/engine-render 0.1.21 → 0.1.23

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 (426) hide show
  1. package/README.md +150 -29
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  5. package/dist/assembly/factory.d.ts +14 -6
  6. package/dist/assembly/factory.d.ts.map +1 -1
  7. package/dist/assembly/host-contract.d.ts +10 -0
  8. package/dist/assembly/host-contract.d.ts.map +1 -1
  9. package/dist/authoring.mjs +1 -1
  10. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  11. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  12. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  13. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  14. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  15. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  16. package/dist/chunk-FBC4R44J.mjs +1003 -0
  17. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  18. package/dist/{chunk-FS2KNUQH.mjs → chunk-FF7K3FSO.mjs} +253 -489
  19. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  20. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  21. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  22. package/dist/{chunk-6OPXDQ2E.mjs → chunk-QODA5UKU.mjs} +33 -294
  23. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  24. package/dist/chunk-YALPS44A.mjs +296 -0
  25. package/dist/chunk-YALPS44A.mjs.map +1 -0
  26. package/dist/{chunk-5YADUCA7.mjs → chunk-ZUWKAZ2I.mjs} +585 -991
  27. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  28. package/dist/cluster-binner.d.ts +5 -0
  29. package/dist/cluster-binner.d.ts.map +1 -1
  30. package/dist/components/index.d.ts +3 -0
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/light-helpers.d.ts +4 -0
  33. package/dist/components/light-helpers.d.ts.map +1 -1
  34. package/dist/components/light-probe.d.ts +9 -0
  35. package/dist/components/light-probe.d.ts.map +1 -0
  36. package/dist/components/rect-area-light.d.ts +12 -0
  37. package/dist/components/rect-area-light.d.ts.map +1 -0
  38. package/dist/components/scene-instance.d.ts +4 -0
  39. package/dist/components/scene-instance.d.ts.map +1 -1
  40. package/dist/components/spot-light.d.ts +3 -0
  41. package/dist/components/spot-light.d.ts.map +1 -1
  42. package/dist/construct-renderer.mjs +10437 -10044
  43. package/dist/construct-renderer.mjs.map +1 -1
  44. package/dist/device/gpu-residency.d.ts +8 -1
  45. package/dist/device/gpu-residency.d.ts.map +1 -1
  46. package/dist/device/lifecycle-transaction.d.ts +2 -0
  47. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  48. package/dist/environment/lifecycle.d.ts.map +1 -1
  49. package/dist/errors/gpu-driven.d.ts +26 -0
  50. package/dist/errors/gpu-driven.d.ts.map +1 -0
  51. package/dist/errors/index.d.ts +1 -0
  52. package/dist/errors/index.d.ts.map +1 -1
  53. package/dist/errors/recover.d.ts +9 -1
  54. package/dist/errors/recover.d.ts.map +1 -1
  55. package/dist/errors/render.d.ts +26 -1
  56. package/dist/errors/render.d.ts.map +1 -1
  57. package/dist/extract/gpu-driven.d.ts +50 -0
  58. package/dist/extract/gpu-driven.d.ts.map +1 -0
  59. package/dist/extract/visibility.d.ts.map +1 -1
  60. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  61. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  62. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  63. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  64. package/dist/gpu-driven/production-raster.d.ts +98 -2
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  67. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  68. package/dist/gpu-scene-schema.d.ts +14 -0
  69. package/dist/gpu-scene-schema.d.ts.map +1 -1
  70. package/dist/gpu-scene.d.ts +8 -1
  71. package/dist/gpu-scene.d.ts.map +1 -1
  72. package/dist/gpu-usage.d.ts +0 -1
  73. package/dist/gpu-usage.d.ts.map +1 -1
  74. package/dist/hdrp-buffers.d.ts +3 -3
  75. package/dist/hdrp-buffers.d.ts.map +1 -1
  76. package/dist/index.d.ts +12 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.mjs +90 -5
  79. package/dist/index.mjs.map +1 -1
  80. package/dist/inspection-types.d.ts +163 -1
  81. package/dist/inspection-types.d.ts.map +1 -1
  82. package/dist/internal.d.ts +9 -0
  83. package/dist/internal.d.ts.map +1 -1
  84. package/dist/internal.mjs +23 -2
  85. package/dist/internal.mjs.map +1 -1
  86. package/dist/light-buffer-layout.d.ts +58 -149
  87. package/dist/light-buffer-layout.d.ts.map +1 -1
  88. package/dist/materials.d.ts.map +1 -1
  89. package/dist/pbr-pipeline.d.ts +26 -12
  90. package/dist/pbr-pipeline.d.ts.map +1 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
  93. package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
  95. package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
  96. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
  97. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
  98. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  99. package/dist/pipeline-builder.d.ts +2 -0
  100. package/dist/pipeline-builder.d.ts.map +1 -1
  101. package/dist/pipeline-spec.d.ts +1 -1
  102. package/dist/pipeline-spec.d.ts.map +1 -1
  103. package/dist/plugin.d.ts.map +1 -1
  104. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  105. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  106. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  107. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  108. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  109. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  110. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  111. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  112. package/dist/record/frame-lighting.d.ts +8 -6
  113. package/dist/record/frame-lighting.d.ts.map +1 -1
  114. package/dist/record/frame-snapshot.d.ts +50 -10
  115. package/dist/record/frame-snapshot.d.ts.map +1 -1
  116. package/dist/record/frame.d.ts +7 -3
  117. package/dist/record/frame.d.ts.map +1 -1
  118. package/dist/record/gpu-timing.d.ts +19 -7
  119. package/dist/record/gpu-timing.d.ts.map +1 -1
  120. package/dist/record/helpers.d.ts +0 -20
  121. package/dist/record/helpers.d.ts.map +1 -1
  122. package/dist/record/main-pass-geometry.d.ts +6 -1
  123. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  124. package/dist/record/main-pass-material.d.ts +3 -1
  125. package/dist/record/main-pass-material.d.ts.map +1 -1
  126. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  127. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  128. package/dist/record/render-context.d.ts +57 -6
  129. package/dist/record/render-context.d.ts.map +1 -1
  130. package/dist/record/shadow-pass.d.ts.map +1 -1
  131. package/dist/record/typed-frame-graph.d.ts +49 -2
  132. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  133. package/dist/record/view-ubo.d.ts +1 -2
  134. package/dist/record/view-ubo.d.ts.map +1 -1
  135. package/dist/reflection/inspection.d.ts +10 -0
  136. package/dist/reflection/inspection.d.ts.map +1 -0
  137. package/dist/reflection/projection.d.ts +49 -0
  138. package/dist/reflection/projection.d.ts.map +1 -1
  139. package/dist/reflection/record-owner.d.ts +60 -0
  140. package/dist/reflection/record-owner.d.ts.map +1 -0
  141. package/dist/render-contract.d.ts +14 -8
  142. package/dist/render-contract.d.ts.map +1 -1
  143. package/dist/render-pipeline.d.ts +7 -0
  144. package/dist/render-pipeline.d.ts.map +1 -1
  145. package/dist/render-system-extract.d.ts +104 -16
  146. package/dist/render-system-extract.d.ts.map +1 -1
  147. package/dist/render-system.d.ts +12 -1
  148. package/dist/render-system.d.ts.map +1 -1
  149. package/dist/scene/probe-blend-record.d.ts +22 -0
  150. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  151. package/dist/scene/probe-blend.d.ts +142 -0
  152. package/dist/scene/probe-blend.d.ts.map +1 -0
  153. package/dist/scene/render-scene-types.d.ts +0 -6
  154. package/dist/scene/render-scene-types.d.ts.map +1 -1
  155. package/dist/scene/render-scene.d.ts +103 -9
  156. package/dist/scene/render-scene.d.ts.map +1 -1
  157. package/dist/scene/visibility/bounds.d.ts +20 -0
  158. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  159. package/dist/scene/visibility/budget.d.ts +16 -0
  160. package/dist/scene/visibility/budget.d.ts.map +1 -0
  161. package/dist/scene/visibility/facet.d.ts +73 -0
  162. package/dist/scene/visibility/facet.d.ts.map +1 -0
  163. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  164. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  165. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  166. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  167. package/dist/scene/visibility/inspection.d.ts +132 -0
  168. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  169. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  170. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  171. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  172. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  173. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  174. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  175. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  176. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  177. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  178. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  179. package/dist/scene/visibility/types.d.ts +34 -0
  180. package/dist/scene/visibility/types.d.ts.map +1 -0
  181. package/dist/ssr/admission.d.ts +122 -0
  182. package/dist/ssr/admission.d.ts.map +1 -0
  183. package/dist/ssr/composition.d.ts +32 -0
  184. package/dist/ssr/composition.d.ts.map +1 -0
  185. package/dist/ssr/errors.d.ts +39 -0
  186. package/dist/ssr/errors.d.ts.map +1 -0
  187. package/dist/ssr/identity.d.ts +13 -0
  188. package/dist/ssr/identity.d.ts.map +1 -0
  189. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  190. package/dist/temporal/frame-coordinator.d.ts +13 -0
  191. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  192. package/dist/temporal/frame.d.ts +2 -0
  193. package/dist/temporal/frame.d.ts.map +1 -1
  194. package/dist/temporal/index.mjs +6 -5
  195. package/dist/temporal/view.d.ts +7 -0
  196. package/dist/temporal/view.d.ts.map +1 -1
  197. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  198. package/dist/typed-render-graph-primitives.d.ts +4 -0
  199. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  200. package/dist/volume/passes.d.ts +8 -4
  201. package/dist/volume/passes.d.ts.map +1 -1
  202. package/package.json +20 -20
  203. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
  204. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  205. package/src/__tests__/device-feature-admission.unit.test.ts +13 -1
  206. package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
  207. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  208. package/src/__tests__/evidence-identity.ts +13 -0
  209. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  210. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  211. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  212. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  213. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  214. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  215. package/src/__tests__/factory-contract.integration.test.ts +33 -0
  216. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  217. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  218. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
  219. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
  220. package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
  221. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  222. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  223. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
  224. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  225. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  226. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  227. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  228. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  229. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  230. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  231. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  232. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  233. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  234. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  235. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  236. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  237. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  238. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  239. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  240. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  241. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  242. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  243. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  244. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  245. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  246. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  247. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  248. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  249. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  250. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  251. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  252. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  253. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  254. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  255. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  256. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  257. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  258. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  259. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  260. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  261. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  262. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  263. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  264. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  265. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  266. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  267. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  268. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  269. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  270. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  271. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  272. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  273. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  274. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  275. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  276. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  277. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  278. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  279. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  280. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  281. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  282. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  283. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  284. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  285. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  286. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  287. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  288. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  289. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  290. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  291. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  292. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  293. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  294. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  295. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  296. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  297. package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
  298. package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
  299. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  300. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  301. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  302. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  303. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  304. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
  305. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
  306. package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
  307. package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
  308. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  309. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  310. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  311. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  312. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  313. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  315. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  316. package/src/assembly/backend-contract.ts +13 -0
  317. package/src/assembly/device-feature-admission.ts +16 -9
  318. package/src/assembly/factory.ts +547 -93
  319. package/src/assembly/host-contract.ts +10 -0
  320. package/src/cluster-binner.ts +31 -0
  321. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  322. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  323. package/src/components/index.ts +3 -0
  324. package/src/components/light-helpers.ts +52 -1
  325. package/src/components/light-probe.ts +13 -0
  326. package/src/components/rect-area-light.ts +15 -0
  327. package/src/components/scene-instance.ts +6 -0
  328. package/src/components/spot-light.ts +3 -0
  329. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  330. package/src/device/gpu-residency.ts +140 -9
  331. package/src/device/lifecycle-transaction.ts +12 -0
  332. package/src/environment/lifecycle.ts +8 -1
  333. package/src/errors/gpu-driven.ts +92 -0
  334. package/src/errors/index.ts +1 -0
  335. package/src/errors/recover.ts +32 -0
  336. package/src/errors/render.ts +55 -1
  337. package/src/extract/camera.ts +3 -3
  338. package/src/extract/gpu-driven.ts +108 -0
  339. package/src/extract/visibility.ts +0 -23
  340. package/src/gpu-driven/batch-topology.ts +206 -28
  341. package/src/gpu-driven/prepared-draw.ts +208 -0
  342. package/src/gpu-driven/production-raster.ts +953 -41
  343. package/src/gpu-driven/view-gpu.ts +411 -29
  344. package/src/gpu-scene-schema.ts +13 -0
  345. package/src/gpu-scene.ts +134 -65
  346. package/src/gpu-usage.ts +0 -1
  347. package/src/hdrp-buffers.ts +6 -6
  348. package/src/index.ts +74 -0
  349. package/src/inspection-types.ts +220 -1
  350. package/src/internal.ts +38 -0
  351. package/src/light-buffer-layout.ts +154 -324
  352. package/src/materials.ts +16 -2
  353. package/src/pbr-pipeline.ts +151 -45
  354. package/src/pipeline/standard-forward-lane.ts +47 -0
  355. package/src/pipeline/standard-lighting/graph.ts +2 -1
  356. package/src/pipeline/standard-lighting/light-frame.ts +3 -2
  357. package/src/pipeline/standard-lighting/topology.ts +1 -1
  358. package/src/pipeline/standard-lighting/transport.ts +2 -1
  359. package/src/pipeline/standard-pipeline.ts +2 -0
  360. package/src/pipeline-builder.ts +5 -2
  361. package/src/pipeline-spec.ts +12 -1
  362. package/src/plugin.ts +4 -0
  363. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  364. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  365. package/src/prepare/extended-lighting/resources.ts +389 -0
  366. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  367. package/src/prepare/extended-lighting/state.ts +92 -0
  368. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  369. package/src/record/__tests__/temporal-payload.unit.test.ts +23 -1
  370. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  371. package/src/record/frame-lighting.ts +252 -53
  372. package/src/record/frame-snapshot.ts +57 -12
  373. package/src/record/frame.ts +410 -93
  374. package/src/record/gpu-timing.ts +108 -44
  375. package/src/record/helpers.ts +0 -58
  376. package/src/record/main-pass-geometry.ts +204 -39
  377. package/src/record/main-pass-material.ts +8 -1
  378. package/src/record/main-pass-sprite-draws.ts +52 -85
  379. package/src/record/mesh-ssbo.ts +47 -36
  380. package/src/record/render-context.ts +65 -4
  381. package/src/record/shadow-pass.ts +66 -40
  382. package/src/record/typed-frame-graph.ts +225 -83
  383. package/src/record/view-ubo.ts +14 -24
  384. package/src/reflection/inspection.ts +67 -0
  385. package/src/reflection/projection.ts +196 -1
  386. package/src/reflection/record-owner.ts +1283 -0
  387. package/src/render-contract.ts +17 -5
  388. package/src/render-pipeline.ts +5 -0
  389. package/src/render-system-extract.ts +493 -134
  390. package/src/render-system.ts +811 -615
  391. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  392. package/src/scene/probe-blend-record.ts +54 -0
  393. package/src/scene/probe-blend.ts +813 -0
  394. package/src/scene/render-scene-types.ts +0 -7
  395. package/src/scene/render-scene.ts +848 -183
  396. package/src/scene/visibility/bounds.ts +57 -0
  397. package/src/scene/visibility/budget.ts +40 -0
  398. package/src/scene/visibility/facet.ts +400 -0
  399. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  400. package/src/scene/visibility/gpu-lod.ts +119 -0
  401. package/src/scene/visibility/inspection.ts +313 -0
  402. package/src/scene/visibility/lod-selector.ts +84 -0
  403. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  404. package/src/scene/visibility/occlusion-pass.ts +165 -0
  405. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  406. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  407. package/src/scene/visibility/types.ts +50 -0
  408. package/src/ssr/admission.ts +451 -0
  409. package/src/ssr/composition.ts +73 -0
  410. package/src/ssr/errors.ts +76 -0
  411. package/src/ssr/identity.ts +12 -0
  412. package/src/systems/skin-palette-allocator.ts +1 -1
  413. package/src/temporal/frame-coordinator.ts +26 -0
  414. package/src/temporal/frame.ts +14 -1
  415. package/src/temporal/view.ts +11 -0
  416. package/src/tilemap-chunk-extract-system.ts +4 -4
  417. package/src/typed-render-graph-primitives.ts +48 -0
  418. package/src/volume/passes.ts +46 -49
  419. package/dist/.tsbuildinfo +0 -1
  420. package/dist/chunk-5YADUCA7.mjs.map +0 -1
  421. package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
  422. package/dist/chunk-BXBQQKFQ.mjs +0 -67
  423. package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
  424. package/dist/chunk-FS2KNUQH.mjs.map +0 -1
  425. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  426. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -5,6 +5,7 @@ import type { RenderPipelineAsset } from '@forgeax/engine-types';
5
5
  import type { RecoverFailure } from '../errors/recover';
6
6
  import type { ObservationUnavailableError, RenderError } from '../errors/render';
7
7
  import type { RenderFeature, RenderFeatureDiagnostics } from '../features/types';
8
+ import type { GpuDrivenProductionInspection, LodOcclusionInspection } from '../inspection-types';
8
9
  import type { MeshMaterialBindingObservation } from '../mesh-material-bindings';
9
10
  import type { FrameObservation, FrameObservationOptions } from '../record/frame';
10
11
  import type { CurrentGraphTarget, GraphTargetCaptureRequest } from '../record/frame-snapshot';
@@ -69,6 +70,15 @@ export interface RendererHostImplementation {
69
70
  destroyRenderTarget(target: RenderTarget): RenderResult<void, RenderError>;
70
71
  setProfile(profile: RenderProfile): RenderResult<void, RenderError>;
71
72
  inspect(): RenderInspection;
73
+ /**
74
+ * Internal bounded inspection for high-cardinality producers. The public
75
+ * snapshot also carries the full persistent render-scene record table, which
76
+ * is intentionally not materialized for per-frame performance evidence.
77
+ */
78
+ inspectLodOcclusion(): {
79
+ readonly lodOcclusion: LodOcclusionInspection | undefined;
80
+ readonly gpuDriven: GpuDrivenProductionInspection;
81
+ };
72
82
  drawFrame(request: RenderFrameInput): RenderResult<FrameReceipt, RhiError | RenderError>;
73
83
  observe(
74
84
  receipt: FrameReceipt,
@@ -27,6 +27,7 @@
27
27
 
28
28
  import { type Mat4, type Vec3, vec3 } from '@forgeax/engine-math';
29
29
  import { err, ok, type Result } from '@forgeax/engine-rhi';
30
+ import type { RectAreaWorldFrame } from './render-system-extract';
30
31
 
31
32
  // ── types ──────────────────────────────────────────────────────────────────
32
33
 
@@ -174,6 +175,36 @@ export function deriveCullingRadius(range: number, intensity: number, threshold
174
175
  return Math.min(derived, 1000);
175
176
  }
176
177
 
178
+ /** Conservative bounding-sphere radius for a finite Rect and its light range. */
179
+ export function rectAreaClusterRadius(frame: RectAreaWorldFrame, range: number): number {
180
+ return Math.max(0, range) + Math.hypot(frame.halfWidth, frame.halfHeight);
181
+ }
182
+
183
+ /** Test the finite Rect's closest-point distance against a strict range window. */
184
+ export function rectAreaIsInRange(
185
+ frame: RectAreaWorldFrame,
186
+ point: ArrayLike<number>,
187
+ range: number,
188
+ ): boolean {
189
+ const toPoint = vec3.create(
190
+ (point[0] ?? 0) - (frame.center[0] ?? 0),
191
+ (point[1] ?? 0) - (frame.center[1] ?? 0),
192
+ (point[2] ?? 0) - (frame.center[2] ?? 0),
193
+ );
194
+ const localX = Math.min(
195
+ frame.halfWidth,
196
+ Math.max(-frame.halfWidth, vec3.dot(toPoint, frame.axisX)),
197
+ );
198
+ const localY = Math.min(
199
+ frame.halfHeight,
200
+ Math.max(-frame.halfHeight, vec3.dot(toPoint, frame.axisY)),
201
+ );
202
+ const dx = (toPoint[0] ?? 0) - (frame.axisX[0] ?? 0) * localX - (frame.axisY[0] ?? 0) * localY;
203
+ const dy = (toPoint[1] ?? 0) - (frame.axisX[1] ?? 0) * localX - (frame.axisY[1] ?? 0) * localY;
204
+ const dz = (toPoint[2] ?? 0) - (frame.axisX[2] ?? 0) * localX - (frame.axisY[2] ?? 0) * localY;
205
+ return dx * dx + dy * dy + dz * dz < range * range;
206
+ }
207
+
177
208
  // ── view_z_to_z_slice ──────────────────────────────────────────────────────
178
209
 
179
210
  /**
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ LightProbe,
4
+ RectAreaLight,
5
+ SpotLight,
6
+ validateLightProbeData,
7
+ validateRectAreaLightData,
8
+ } from '../index';
9
+
10
+ describe('lighting authoring contracts', () => {
11
+ it('exposes only the declared Rect, Spot modifier, and Probe fields', () => {
12
+ expect(Object.keys(RectAreaLight.fields)).toEqual([
13
+ 'color',
14
+ 'intensity',
15
+ 'width',
16
+ 'height',
17
+ 'range',
18
+ ]);
19
+ expect(Object.keys(LightProbe.fields)).toEqual(['irradiance', 'radius']);
20
+ expect(SpotLight.fields.iesProfile.type).toBe('shared<IesProfileAsset>');
21
+ expect(SpotLight.fields.cookie.type).toBe('shared<TextureAsset>');
22
+ expect(SpotLight.fields.rollDeg.default).toBe(0);
23
+ });
24
+
25
+ it('keeps defaults and rejects invalid Rect/Probe facts before rendering', () => {
26
+ expect(RectAreaLight.fields.width.default).toBe(1);
27
+ expect(RectAreaLight.fields.height.default).toBe(1);
28
+ expect(LightProbe.fields.irradiance.type).toBe('array<f32, 27>');
29
+ expect(LightProbe.fields.radius.default).toBe(1e-4);
30
+
31
+ expect(validateRectAreaLightData({}).ok).toBe(true);
32
+ expect(validateRectAreaLightData({ width: 0 }).ok).toBe(false);
33
+ expect(validateRectAreaLightData({ intensity: Number.NaN }).ok).toBe(false);
34
+ expect(validateLightProbeData({ irradiance: new Float32Array(26), radius: 1 }).ok).toBe(false);
35
+ expect(validateLightProbeData({ irradiance: new Float32Array(27), radius: 1e-5 }).ok).toBe(
36
+ false,
37
+ );
38
+ });
39
+ });
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildRectAreaWorldFrame, rectAreaFacesPoint } from '../../render-system-extract';
3
+
4
+ describe('RectAreaLight world frame', () => {
5
+ it('normalizes world axes while preserving authored half extents', () => {
6
+ const frame = buildRectAreaWorldFrame({
7
+ center: [1, 2, 3],
8
+ axisX: [0, 2, 0],
9
+ axisY: [-3, 0, 0],
10
+ width: 4,
11
+ height: 2,
12
+ });
13
+
14
+ expect(Array.from(frame.axisX)).toEqual([0, 1, 0]);
15
+ expect(Array.from(frame.axisY)).toEqual([-1, 0, 0]);
16
+ expect(Array.from(frame.normal)).toEqual([0, 0, 1]);
17
+ expect(frame.halfWidth).toBe(2);
18
+ expect(frame.halfHeight).toBe(1);
19
+ });
20
+
21
+ it('defines the front from the world-axis cross product', () => {
22
+ const frame = buildRectAreaWorldFrame({
23
+ center: [0, 0, 0],
24
+ axisX: [1, 0, 0],
25
+ axisY: [0, 1, 0],
26
+ width: 2,
27
+ height: 2,
28
+ });
29
+
30
+ expect(rectAreaFacesPoint(frame, [0, 0, 1])).toBe(true);
31
+ expect(rectAreaFacesPoint(frame, [0, 0, -1])).toBe(false);
32
+ });
33
+ });
@@ -5,6 +5,8 @@ export * from './directional-light';
5
5
  export * from './fog';
6
6
  export { Instances, type InstancesData } from './instances';
7
7
  export { Layer } from './layer';
8
+ export * from './light-helpers';
9
+ export { LightProbe } from './light-probe';
8
10
  export { Lines } from './lines';
9
11
  export * from './mesh-filter';
10
12
  export * from './mesh-renderer';
@@ -18,6 +20,7 @@ export {
18
20
  pointShapeFromU32,
19
21
  } from './points';
20
22
  export { PostProcessParams } from './post-process-params';
23
+ export { RectAreaLight } from './rect-area-light';
21
24
  export * from './reflection-probe';
22
25
  export {
23
26
  SceneInstance,
@@ -14,7 +14,7 @@
14
14
  // transparent to AI users) + D-S5 (Layer 1 host-side range = 0 ->
15
15
  // invRangeSquared = 1e8 protects the 0 * Infinity = NaN intermediate).
16
16
 
17
- import { err, ok, type Result } from '@forgeax/engine-types';
17
+ import { err, ok, R_MIN, type Result } from '@forgeax/engine-types';
18
18
  import { SpawnLightInvalidBoundsError } from '../errors/ecs-validation';
19
19
  import { ShadowInvalidConfigError } from '../errors/render';
20
20
  import { DirectionalShadowFilterValue } from './directional-shadow-filter';
@@ -264,3 +264,54 @@ export function validatePointLightShadowData(
264
264
  }
265
265
  return ok(undefined);
266
266
  }
267
+
268
+ /** Validate rectangular area-light authoring facts before renderer admission. */
269
+ export function validateRectAreaLightData(
270
+ data: Readonly<Record<string, unknown>>,
271
+ ): LightValidationResult {
272
+ const intensity = (data.intensity as number | undefined) ?? 1;
273
+ if (typeof intensity !== 'number' || !Number.isFinite(intensity) || intensity < 0) {
274
+ return err(new SpawnLightInvalidBoundsError('RectAreaLight', 'intensity', intensity));
275
+ }
276
+ const color = (data.color as ArrayLike<number> | undefined) ?? [1, 1, 1];
277
+ if (
278
+ color.length !== 3 ||
279
+ Array.from(color).some(
280
+ (value) => typeof value !== 'number' || !Number.isFinite(value) || value < 0,
281
+ )
282
+ ) {
283
+ return err(new SpawnLightInvalidBoundsError('RectAreaLight', 'color', Array.from(color)));
284
+ }
285
+ for (const field of ['width', 'height', 'range'] as const) {
286
+ const value = (data[field] as number | undefined) ?? (field === 'range' ? 10 : 1);
287
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
288
+ return err(new SpawnLightInvalidBoundsError('RectAreaLight', field, value));
289
+ }
290
+ }
291
+ return ok(undefined);
292
+ }
293
+
294
+ /** Validate the fixed 27-value SH payload and minimum finite radius. */
295
+ export function validateLightProbeData(
296
+ data: Readonly<Record<string, unknown>>,
297
+ ): LightValidationResult {
298
+ const irradiance = data.irradiance as ArrayLike<number> | undefined;
299
+ if (
300
+ irradiance === undefined ||
301
+ irradiance.length !== 27 ||
302
+ Array.from(irradiance).some((value) => typeof value !== 'number' || !Number.isFinite(value))
303
+ ) {
304
+ return err(
305
+ new SpawnLightInvalidBoundsError(
306
+ 'LightProbe',
307
+ 'irradiance',
308
+ irradiance === undefined ? [] : Array.from(irradiance),
309
+ ),
310
+ );
311
+ }
312
+ const radius = (data.radius as number | undefined) ?? R_MIN;
313
+ if (typeof radius !== 'number' || !Number.isFinite(radius) || radius < R_MIN) {
314
+ return err(new SpawnLightInvalidBoundsError('LightProbe', 'radius', radius));
315
+ }
316
+ return ok(undefined);
317
+ }
@@ -0,0 +1,13 @@
1
+ // @forgeax/engine-render - local diffuse LightProbe authoring facts.
2
+
3
+ import { defineComponent } from '@forgeax/engine-ecs';
4
+ import { R_MIN } from '@forgeax/engine-types';
5
+
6
+ /**
7
+ * Local diffuse irradiance probe. Transform supplies position; the 27 SH
8
+ * values are nine RGB groups in the fixed real-SH order.
9
+ */
10
+ export const LightProbe = defineComponent('LightProbe', {
11
+ irradiance: { type: 'array<f32, 27>' },
12
+ radius: { type: 'f32', default: R_MIN },
13
+ });
@@ -0,0 +1,15 @@
1
+ // @forgeax/engine-render - single-sided rectangular area-light authoring facts.
2
+
3
+ import { defineComponent } from '@forgeax/engine-ecs';
4
+
5
+ /**
6
+ * Single-sided rectangular area light. Transform supplies the center and
7
+ * world orientation; width and height remain authored emitter dimensions.
8
+ */
9
+ export const RectAreaLight = defineComponent('RectAreaLight', {
10
+ color: { type: 'array<f32, 3>', default: new Float32Array([1, 1, 1]) },
11
+ intensity: { type: 'f32', default: 1 },
12
+ width: { type: 'f32', default: 1 },
13
+ height: { type: 'f32', default: 1 },
14
+ range: { type: 'f32', default: 10 },
15
+ });
@@ -94,6 +94,12 @@ export interface SceneInstanceState {
94
94
  */
95
95
  readonly source: import('@forgeax/engine-types').Handle<'SceneAsset', 'shared'>;
96
96
 
97
+ /** Author scene identity used by instance-relative SceneEntityRef resolution. */
98
+ readonly sceneSourceKey?: string;
99
+
100
+ /** Stable author bindingKey to live Entity mapping for this concrete instance. */
101
+ readonly bindings: Map<string, EntityHandle>;
102
+
97
103
  /**
98
104
  * Reverse mapping (live `Entity` -> source `LocalEntityId`). Used by
99
105
  * `world.setSceneOverride` to validate the member entity belongs to this
@@ -124,6 +124,9 @@ export const SpotLight = defineComponent('SpotLight', {
124
124
  range: { type: 'f32', default: 10.0 },
125
125
  innerConeDeg: { type: 'f32', default: 0 },
126
126
  outerConeDeg: { type: 'f32', default: 45 },
127
+ iesProfile: { type: 'shared<IesProfileAsset>' },
128
+ cookie: { type: 'shared<TextureAsset>' },
129
+ rollDeg: { type: 'f32', default: 0 },
127
130
  // Shadow opt-out gate: defaults to true so zero-config spawns cast shadows.
128
131
  castShadow: { type: 'bool', default: true },
129
132
  // 7 shadow fields aligned with the shared light shadow vocabulary.
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ createExtendedLightingResourceCandidate,
4
+ inspectExtendedLightingResources,
5
+ } from '../../prepare/extended-lighting/resources';
6
+ import { DeviceScope } from '../device-scope';
7
+
8
+ describe('extended lighting device recovery', () => {
9
+ it('retires the old scope and rebuilds one accepted resource generation', async () => {
10
+ const oldScope = DeviceScope.create(8, 'renderer');
11
+ const oldResource = oldScope.ref('texture', { id: 'old' });
12
+ const candidate = await createExtendedLightingResourceCandidate({
13
+ device: undefined,
14
+ scope: oldScope,
15
+ iesSliceCount: 0,
16
+ cookieSliceCount: 0,
17
+ cookieMatrices: 0,
18
+ });
19
+
20
+ expect(candidate.ok).toBe(false);
21
+ expect(oldResource.isCurrent(oldScope)).toBe(true);
22
+ expect(inspectExtendedLightingResources(oldScope).generation).toBe(8);
23
+ });
24
+ });
@@ -324,6 +324,123 @@ interface CubemapGpuEntry {
324
324
  faceViews: readonly TextureView[];
325
325
  }
326
326
 
327
+ interface PackedMeshLodGeometry {
328
+ readonly vertices: Float32Array;
329
+ readonly indices: Uint16Array | Uint32Array | undefined;
330
+ readonly lodRanges: readonly (readonly MeshGpuRange[])[] | undefined;
331
+ }
332
+
333
+ function packMeshLodGeometry(
334
+ root: TypesMeshAsset,
335
+ rootData: MeshRenderData,
336
+ lowerMeshes: readonly TypesMeshAsset[],
337
+ ): Result<PackedMeshLodGeometry, RhiError> {
338
+ if (lowerMeshes.length === 0) {
339
+ return ok({ vertices: root.vertices, indices: root.indices, lodRanges: undefined });
340
+ }
341
+ const rootStride = rootData.layoutProjection.arrayStride;
342
+ const allMeshes = [root, ...lowerMeshes];
343
+ const lowerData: MeshRenderData[] = [];
344
+ for (const lower of lowerMeshes) {
345
+ const projected = deriveRenderDataMesh(lower);
346
+ if (!projected.ok) {
347
+ return err(
348
+ new RhiError({
349
+ code: 'asset-not-registered',
350
+ expected: 'every MeshAsset LOD to project to a compatible GPU layout',
351
+ hint: 'repair the lower-detail MeshAsset attributes before drawing the LOD chain',
352
+ }),
353
+ );
354
+ }
355
+ lowerData.push(projected.value);
356
+ }
357
+ if (
358
+ lowerData.some(
359
+ (data) =>
360
+ data.layoutProjection.arrayStride !== rootStride ||
361
+ data.submeshes.length !== rootData.submeshes.length ||
362
+ data.indexFormat !== rootData.indexFormat,
363
+ )
364
+ ) {
365
+ return err(
366
+ new RhiError({
367
+ code: 'asset-not-registered',
368
+ expected: 'LOD meshes to share vertex/index layout and material-slot topology',
369
+ hint: 'reimport LOD meshes with matching attributes, index format, and submesh slots',
370
+ }),
371
+ );
372
+ }
373
+ if (allMeshes.some((mesh) => (mesh.indices === undefined) !== (root.indices === undefined))) {
374
+ return err(
375
+ new RhiError({
376
+ code: 'asset-not-registered',
377
+ expected: 'all LOD meshes to use the same indexed or non-indexed draw mode',
378
+ hint: 'reimport the LOD chain with consistent index data',
379
+ }),
380
+ );
381
+ }
382
+ const vertices = new Float32Array(allMeshes.reduce((sum, mesh) => sum + mesh.vertices.length, 0));
383
+ const ranges: MeshGpuRange[][] = [];
384
+ let vertexCursor = 0;
385
+ let indexCursor = 0;
386
+ const indexed = root.indices !== undefined;
387
+ const sourceIndices: number[] = [];
388
+ let requiresUint32 = root.indices instanceof Uint32Array;
389
+ for (let level = 0; level < allMeshes.length; level += 1) {
390
+ const mesh = allMeshes[level];
391
+ const data = level === 0 ? rootData : lowerData[level - 1];
392
+ if (mesh === undefined || data === undefined) continue;
393
+ vertices.set(mesh.vertices, vertexCursor);
394
+ const levelRanges: MeshGpuRange[] = [];
395
+ for (let submeshIndex = 0; submeshIndex < data.submeshes.length; submeshIndex += 1) {
396
+ const submesh = data.submeshes[submeshIndex];
397
+ const rootSubmesh = rootData.submeshes[submeshIndex];
398
+ if (
399
+ submesh === undefined ||
400
+ rootSubmesh === undefined ||
401
+ submesh.materialSlot !== rootSubmesh.materialSlot ||
402
+ submesh.topology !== rootSubmesh.topology
403
+ ) {
404
+ return err(
405
+ new RhiError({
406
+ code: 'asset-not-registered',
407
+ expected: 'LOD submeshes to preserve root material-slot and topology identity',
408
+ hint: 'repair LOD material slots and primitive topology before import',
409
+ }),
410
+ );
411
+ }
412
+ levelRanges.push(
413
+ indexed
414
+ ? {
415
+ first: indexCursor + submesh.indexOffset,
416
+ count: submesh.indexCount,
417
+ baseVertex: vertexCursor / (rootStride / 4),
418
+ }
419
+ : { first: vertexCursor / (rootStride / 4), count: submesh.vertexCount, baseVertex: 0 },
420
+ );
421
+ }
422
+ ranges.push(levelRanges);
423
+ if (indexed) {
424
+ const indices = mesh.indices;
425
+ if (indices === undefined) continue;
426
+ for (const value of indices) {
427
+ // Each LOD range carries its vertex cursor as baseVertex, so keep
428
+ // indices local to that LOD and apply the offset exactly once at draw.
429
+ sourceIndices.push(value);
430
+ if (value > 0xffff || indices instanceof Uint32Array) requiresUint32 = true;
431
+ }
432
+ indexCursor += indices.length;
433
+ }
434
+ vertexCursor += mesh.vertices.length;
435
+ }
436
+ const indices = indexed
437
+ ? requiresUint32
438
+ ? Uint32Array.from(sourceIndices)
439
+ : Uint16Array.from(sourceIndices)
440
+ : undefined;
441
+ return ok({ vertices, indices, lodRanges: ranges });
442
+ }
443
+
327
444
  export interface MeshGpuHandles {
328
445
  readonly vertexBuffer: GpuBuffer;
329
446
  /**
@@ -374,6 +491,14 @@ export interface MeshGpuHandles {
374
491
  * For single-submesh meshes this is a 1-element array (byte-identical to pre-M4).
375
492
  */
376
493
  readonly submeshes: readonly import('@forgeax/engine-types').Submesh[];
494
+ /** Packed root + lower-detail geometry ranges in the shared mesh buffers. */
495
+ readonly lodRanges?: readonly (readonly MeshGpuRange[])[];
496
+ }
497
+
498
+ export interface MeshGpuRange {
499
+ readonly first: number;
500
+ readonly count: number;
501
+ readonly baseVertex: number;
377
502
  }
378
503
 
379
504
  /**
@@ -951,6 +1076,7 @@ export class GpuResidencyCache {
951
1076
  handle: Handle<'MeshAsset', 'shared'>,
952
1077
  pod: TypesMeshAsset,
953
1078
  worldId?: World | number,
1079
+ lodMeshes?: readonly TypesMeshAsset[],
954
1080
  ): Result<MeshGpuHandles, RhiError | AssetError | ImageError>;
955
1081
  ensureResident(
956
1082
  handle: Handle<'TextureAsset', 'shared'>,
@@ -961,6 +1087,7 @@ export class GpuResidencyCache {
961
1087
  handle: Handle<'MeshAsset', 'shared'> | Handle<'TextureAsset', 'shared'>,
962
1088
  pod: TypesMeshAsset | TextureAsset,
963
1089
  worldId: World | number = 0,
1090
+ lodMeshes: readonly TypesMeshAsset[] = [],
964
1091
  ): Result<TextureGpuEntry | MeshGpuHandles, RhiError | AssetError | ImageError> {
965
1092
  const id = handleSlot(handle);
966
1093
  // Hit: O(1) cache lookup, never re-projects (AC-09 -- deriveRenderData*
@@ -977,7 +1104,7 @@ export class GpuResidencyCache {
977
1104
  if (existing !== undefined) return ok(existing);
978
1105
  const projected = deriveRenderDataMesh(pod);
979
1106
  if (!projected.ok) return projected;
980
- return this.uploadMeshById(key, pod, projected.value);
1107
+ return this.uploadMeshById(key, pod, projected.value, lodMeshes);
981
1108
  }
982
1109
  case 'texture': {
983
1110
  const texKey = this.worldKey(id, worldId);
@@ -1702,6 +1829,7 @@ export class GpuResidencyCache {
1702
1829
  id: string | number,
1703
1830
  mesh: TypesMeshAsset,
1704
1831
  renderData: MeshRenderData,
1832
+ lodMeshes: readonly TypesMeshAsset[] = [],
1705
1833
  ): Result<MeshGpuHandles, RhiError> {
1706
1834
  const device = this.gpuDevice;
1707
1835
  if (device === undefined) {
@@ -1714,13 +1842,15 @@ export class GpuResidencyCache {
1714
1842
  );
1715
1843
  }
1716
1844
 
1717
- const indices = mesh.indices;
1845
+ const packed = packMeshLodGeometry(mesh, renderData, lodMeshes);
1846
+ if (!packed.ok) return packed;
1847
+ const { vertices, indices, lodRanges } = packed.value;
1718
1848
  const indexBytesUnpadded = indices === undefined ? 0 : indices.byteLength;
1719
- const indexBytes = renderData.indexByteLength;
1849
+ const indexBytes = ((indexBytesUnpadded + 3) >> 2) << 2;
1720
1850
 
1721
1851
  const vboResult = device.createBuffer({
1722
1852
  label: `mesh-${id}-vbo`,
1723
- size: renderData.vertexByteLength,
1853
+ size: vertices.byteLength,
1724
1854
  usage: renderData.vertexUsage,
1725
1855
  mappedAtCreation: false,
1726
1856
  });
@@ -1740,7 +1870,7 @@ export class GpuResidencyCache {
1740
1870
  ibo = iboResult.value;
1741
1871
  }
1742
1872
 
1743
- const vboWriteResult = device.queue.writeBuffer(vbo, 0, mesh.vertices);
1873
+ const vboWriteResult = device.queue.writeBuffer(vbo, 0, vertices);
1744
1874
  if (!vboWriteResult.ok) return err(vboWriteResult.error);
1745
1875
 
1746
1876
  if (indices !== undefined) {
@@ -1766,10 +1896,10 @@ export class GpuResidencyCache {
1766
1896
  const entry: MeshGpuHandles = {
1767
1897
  vertexBuffer: this.wrapBuf(vbo),
1768
1898
  indexBuffer: ibo === null ? null : this.wrapBuf(ibo),
1769
- vboBytes: renderData.vertexByteLength,
1899
+ vboBytes: vertices.byteLength,
1770
1900
  iboBytes: indices === undefined ? 0 : indexBytes,
1771
- indexCount: renderData.indexCount,
1772
- indexFormat: renderData.indexFormat,
1901
+ indexCount: mesh.indices === undefined ? 0 : mesh.indices.length,
1902
+ indexFormat: indices instanceof Uint32Array ? 'uint32' : 'uint16',
1773
1903
  layout: renderData.layoutProjection.attributes.some(
1774
1904
  (attribute) => attribute.key === 'skinIndex' || attribute.key === 'skinWeight',
1775
1905
  )
@@ -1778,9 +1908,10 @@ export class GpuResidencyCache {
1778
1908
  layoutProjection: renderData.layoutProjection,
1779
1909
  uvSetCount: 1 + extraUvSets,
1780
1910
  vertexCount: mesh.vertices.byteLength / renderData.layoutProjection.arrayStride,
1781
- indexed: indices !== undefined,
1911
+ indexed: mesh.indices !== undefined,
1782
1912
  topology: renderData.submeshes[0]?.topology ?? 'triangle-list',
1783
1913
  submeshes: renderData.submeshes,
1914
+ ...(lodRanges === undefined ? {} : { lodRanges }),
1784
1915
  };
1785
1916
  this.meshGpuHandles.set(id, entry);
1786
1917
  this.meshResidencyEpoch += 1;
@@ -0,0 +1,12 @@
1
+ export {
2
+ type DeviceResourceKind,
3
+ DeviceResourceRef,
4
+ DeviceScope,
5
+ type DeviceScopeReceipt,
6
+ type DeviceScopeState,
7
+ type LifecycleFailure,
8
+ type LifecycleResourceSpec,
9
+ type LifecycleResult,
10
+ LifecycleTransaction,
11
+ type ResourceRef,
12
+ } from './device-scope';
@@ -42,7 +42,11 @@ export class EnvironmentLifecycle {
42
42
  }
43
43
 
44
44
  isActive(generation: EnvironmentGeneration): boolean {
45
- return this.active === generation;
45
+ return (
46
+ this.active?.generation === generation.generation &&
47
+ this.active.signature === generation.signature &&
48
+ this.active.scope === generation.scope
49
+ );
46
50
  }
47
51
 
48
52
  discard(generation: EnvironmentGeneration): void {
@@ -106,6 +110,9 @@ export class EnvironmentLifecycle {
106
110
  ),
107
111
  );
108
112
  }
113
+ if (this.active?.signature === frame.signature && this.active.scope.isAlive()) {
114
+ return ok(this.active.lane === lane ? this.active : this.laneView(this.active, lane));
115
+ }
109
116
  const existing = this.candidates.get(frame.signature);
110
117
  if (existing?.scope.isAlive()) {
111
118
  return ok(existing.lane === lane ? existing : this.laneView(existing, lane));
@@ -0,0 +1,92 @@
1
+ import type { MaterialShaderArtifactReceipt } from '@forgeax/engine-shader';
2
+
3
+ export type GpuDrivenPreparationErrorCode =
4
+ | 'missing-material-receipt'
5
+ | 'missing-uv'
6
+ | 'reflection-mismatch'
7
+ | 'vertex-input-mismatch'
8
+ | 'alpha-mask-mismatch'
9
+ | 'resource-not-ready'
10
+ | 'stale-generation'
11
+ | 'skin-receipt-mismatch';
12
+
13
+ export type GpuDrivenPreparationReason =
14
+ | 'material-receipt-missing'
15
+ | 'uv-set-missing'
16
+ | 'reflection-receipt-mismatch'
17
+ | 'vertex-semantic-mismatch'
18
+ | 'alpha-mask-receipt-missing'
19
+ | 'material-resource-missing'
20
+ | 'generation-stale'
21
+ | 'skin-address-missing';
22
+
23
+ export interface GpuDrivenPreparationErrorDetail {
24
+ readonly reason: GpuDrivenPreparationReason;
25
+ readonly owner: 'material' | 'geometry' | 'skin' | 'generation';
26
+ readonly expected?: string;
27
+ readonly actual?: string;
28
+ readonly expectedGeneration?: number;
29
+ readonly actualGeneration?: number;
30
+ }
31
+
32
+ const ERROR_POLICY: Readonly<
33
+ Record<GpuDrivenPreparationErrorCode, { readonly expected: string; readonly hint: string }>
34
+ > = {
35
+ 'missing-material-receipt': {
36
+ expected: 'a producer-owned Standard PBR material receipt is present',
37
+ hint: 'cook and load the Standard PBR artifact, then retry preparation',
38
+ },
39
+ 'missing-uv': {
40
+ expected: 'the geometry exposes every UV set named by the material receipt',
41
+ hint: 'repair the geometry UV semantic or route the draw to the CPU semantic lane',
42
+ },
43
+ 'reflection-mismatch': {
44
+ expected: 'the loaded reflection receipt matches the artifact layout identity',
45
+ hint: 'rebuild the material artifact and reflection from one source, then retry',
46
+ },
47
+ 'vertex-input-mismatch': {
48
+ expected: 'geometry vertex semantics match the material reflection receipt',
49
+ hint: 'repair the geometry vertex semantic layout or route the draw to the CPU lane',
50
+ },
51
+ 'alpha-mask-mismatch': {
52
+ expected: 'the Alpha Mask cutoff is present in the material receipt',
53
+ hint: 'declare alphaCutoff in the Standard PBR material schema and recook it',
54
+ },
55
+ 'resource-not-ready': {
56
+ expected: 'all material resource slots have resolved handles',
57
+ hint: 'resolve the declared texture and sampler resources before recording',
58
+ },
59
+ 'stale-generation': {
60
+ expected: 'receipt and prepared inputs belong to the requested generation',
61
+ hint: 'discard stale GPU facts, rebuild the producer receipt, and retry the same draw',
62
+ },
63
+ 'skin-receipt-mismatch': {
64
+ expected: 'skinned geometry has a current palette address receipt',
65
+ hint: 'rebuild the Skin palette receipt for this generation before recording',
66
+ },
67
+ };
68
+
69
+ export class GpuDrivenPreparationError extends Error {
70
+ readonly code: GpuDrivenPreparationErrorCode;
71
+ readonly expected: string;
72
+ readonly hint: string;
73
+ readonly detail: GpuDrivenPreparationErrorDetail;
74
+
75
+ constructor(code: GpuDrivenPreparationErrorCode, detail: GpuDrivenPreparationErrorDetail) {
76
+ const policy = ERROR_POLICY[code];
77
+ super(`[GpuDrivenPreparationError ${code}] ${policy.expected}`);
78
+ this.name = 'GpuDrivenPreparationError';
79
+ this.code = code;
80
+ this.expected = policy.expected;
81
+ this.hint = policy.hint;
82
+ this.detail = detail;
83
+ }
84
+ }
85
+
86
+ export interface GpuDrivenPreparedMaterial {
87
+ readonly receipt: MaterialShaderArtifactReceipt;
88
+ readonly resourceSlots: MaterialShaderArtifactReceipt['resourceSlots'];
89
+ readonly uvSets: MaterialShaderArtifactReceipt['uvSets'];
90
+ readonly vertexInputs: MaterialShaderArtifactReceipt['vertexInputs'];
91
+ readonly alphaMask: MaterialShaderArtifactReceipt['alphaMask'];
92
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './ecs-validation';
2
+ export * from './gpu-driven';
2
3
  export * from './recover';
3
4
  export * from './render';