@woosh/meep-engine 3.39.0 → 3.40.0

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 (367) hide show
  1. package/build/bundle-worker-terrain.js +1 -1
  2. package/package.json +1 -1
  3. package/src/engine/EngineHarness.d.ts.map +1 -1
  4. package/src/engine/EngineHarness.js +17 -4
  5. package/src/engine/asset/loaders/image/avif/threaded_image_encoder.js +1 -1
  6. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  7. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +4 -0
  8. package/src/engine/graphics/ecs/water/Water.d.ts +14 -17
  9. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  10. package/src/engine/graphics/ecs/water/Water.js +100 -110
  11. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.d.ts.map +1 -1
  12. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.js +36 -38
  13. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_3_4.d.ts +6 -0
  14. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_3_4.d.ts.map +1 -0
  15. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_3_4.js +10 -0
  16. package/src/engine/graphics/ecs/water/WaterWaves.d.ts +3 -3
  17. package/src/engine/graphics/ecs/water/WaterWaves.js +46 -46
  18. package/src/engine/graphics/texture/sampler/sampler2d_to_avif.d.ts.map +1 -1
  19. package/src/engine/graphics/texture/sampler/sampler2d_to_avif.js +8 -5
  20. package/src/engine/graphics3/WaterSystem.d.ts +21 -74
  21. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  22. package/src/engine/graphics3/WaterSystem.js +174 -367
  23. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  24. package/src/format/image/avif/api/convert_to_rgba.d.ts +2 -2
  25. package/src/format/image/avif/api/convert_to_rgba.d.ts.map +1 -1
  26. package/src/format/image/avif/api/convert_to_rgba.js +10 -9
  27. package/src/format/image/avif/api/decode_avif.d.ts +2 -2
  28. package/src/format/image/avif/api/decode_avif.d.ts.map +1 -1
  29. package/src/format/image/avif/api/decode_avif.js +1 -1
  30. package/src/shade/playground/ssr_reprojection/README.md +111 -103
  31. package/src/shade/playground/ssr_reprojection/SpatialFilterProbe.js +68 -68
  32. package/src/shade/playground/ssr_reprojection/index.html +80 -79
  33. package/src/shade/playground/ssr_reprojection/main.js +256 -247
  34. package/src/shade/playground/ssr_variance/capture.js +96 -96
  35. package/src/shade/playground/ssr_variance/reference.js +78 -78
  36. package/src/shade/playground/vsm/README.md +96 -0
  37. package/src/shade/playground/vsm/VSMDebugOverlay.d.ts +49 -0
  38. package/src/shade/playground/vsm/VSMDebugOverlay.d.ts.map +1 -0
  39. package/src/shade/playground/vsm/VSMDebugOverlay.js +351 -0
  40. package/src/shade/playground/vsm/clipmap_panel.d.ts +8 -0
  41. package/src/shade/playground/vsm/clipmap_panel.d.ts.map +1 -0
  42. package/src/shade/playground/vsm/clipmap_panel.js +138 -0
  43. package/src/shade/playground/vsm/index.html +170 -0
  44. package/src/shade/playground/vsm/main.d.ts +2 -0
  45. package/src/shade/playground/vsm/main.d.ts.map +1 -0
  46. package/src/shade/playground/vsm/main.js +389 -0
  47. package/src/shade/playground/vsm/scenes.d.ts +52 -0
  48. package/src/shade/playground/vsm/scenes.d.ts.map +1 -0
  49. package/src/shade/playground/vsm/scenes.js +320 -0
  50. package/src/shade/playground/vsm/verify_vsm.d.ts +38 -0
  51. package/src/shade/playground/vsm/verify_vsm.d.ts.map +1 -0
  52. package/src/shade/playground/vsm/verify_vsm.js +305 -0
  53. package/src/shade/playground/water_refraction/README.md +57 -9
  54. package/src/shade/playground/water_refraction/main.js +12 -3
  55. package/src/shade/playground/water_refraction/world.d.ts +1 -0
  56. package/src/shade/playground/water_refraction/world.d.ts.map +1 -1
  57. package/src/shade/playground/water_refraction/world.js +15 -5
  58. package/src/shade/renderer/Renderer.d.ts +19 -0
  59. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  60. package/src/shade/renderer/Renderer.js +72 -1
  61. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +8 -0
  62. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  63. package/src/shade/renderer/animation/GPUAnimationManager.js +18 -0
  64. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts +7 -0
  65. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.d.ts.map +1 -1
  66. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +11 -0
  67. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.d.ts +3 -1
  68. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.d.ts.map +1 -1
  69. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +10 -1
  70. package/src/shade/renderer/buffer/table/serde/table_wgsl_gen_read_code.d.ts.map +1 -1
  71. package/src/shade/renderer/buffer/table/serde/table_wgsl_gen_read_code.js +28 -0
  72. package/src/shade/renderer/buffer/table/serde/table_wgsl_gen_write_code.d.ts.map +1 -1
  73. package/src/shade/renderer/buffer/table/serde/table_wgsl_gen_write_code.js +31 -1
  74. package/src/shade/renderer/geometry/MeshletGeometry.d.ts +3 -0
  75. package/src/shade/renderer/geometry/MeshletGeometry.d.ts.map +1 -1
  76. package/src/shade/renderer/geometry/MeshletGeometry.js +1 -0
  77. package/src/shade/renderer/light/LIGHT_DATABASE_SPEC.d.ts.map +1 -1
  78. package/src/shade/renderer/light/LIGHT_DATABASE_SPEC.js +62 -4
  79. package/src/shade/renderer/light/environment/rgbe/parse_rgbe.d.ts +1 -1
  80. package/src/shade/renderer/light/environment/rgbe/parse_rgbe.js +2 -2
  81. package/src/shade/renderer/light/environment/rgbe/rgbe_byte_to_rgb_half.d.ts +2 -2
  82. package/src/shade/renderer/light/environment/rgbe/rgbe_byte_to_rgb_half.d.ts.map +1 -1
  83. package/src/shade/renderer/light/environment/rgbe/rgbe_byte_to_rgb_half.js +6 -14
  84. package/src/shade/renderer/light/environment/sh_exponential/chunk_sh_exp_workgroup_sum.d.ts.map +1 -1
  85. package/src/shade/renderer/light/environment/sh_exponential/chunk_sh_exp_workgroup_sum.js +5 -1
  86. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  87. package/src/shade/renderer/medium/ParticipatingMedium.d.ts +3 -0
  88. package/src/shade/renderer/medium/ParticipatingMedium.d.ts.map +1 -1
  89. package/src/shade/renderer/medium/ParticipatingMedium.js +7 -0
  90. package/src/shade/renderer/postprocess/eye/shader_autoexposure_build_histogram.d.ts.map +1 -1
  91. package/src/shade/renderer/postprocess/eye/shader_autoexposure_build_histogram.js +5 -35
  92. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  93. package/src/shade/renderer/postprocess/ssr/SSR.d.ts +12 -4
  94. package/src/shade/renderer/postprocess/ssr/SSR.d.ts.map +1 -1
  95. package/src/shade/renderer/postprocess/ssr/SSR.js +36 -8
  96. package/src/shade/renderer/postprocess/ssr/SSR_DENOISE_TILE_SIZE.d.ts +3 -0
  97. package/src/shade/renderer/postprocess/ssr/SSR_DENOISE_TILE_SIZE.d.ts.map +1 -0
  98. package/src/shade/renderer/postprocess/ssr/SSR_DENOISE_TILE_SIZE.js +2 -0
  99. package/src/shade/renderer/postprocess/ssr/chunk_ssr_denoise_tile_active.d.ts +8 -0
  100. package/src/shade/renderer/postprocess/ssr/chunk_ssr_denoise_tile_active.d.ts.map +1 -0
  101. package/src/shade/renderer/postprocess/ssr/chunk_ssr_denoise_tile_active.js +40 -0
  102. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts.map +1 -1
  103. package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.js +150 -137
  104. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_classify.d.ts +3 -0
  105. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_classify.d.ts.map +1 -0
  106. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_classify.js +44 -0
  107. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_dilate.d.ts +5 -0
  108. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_dilate.d.ts.map +1 -0
  109. package/src/shade/renderer/postprocess/ssr/shader_ssr_denoise_tile_dilate.js +35 -0
  110. package/src/shade/renderer/postprocess/ssr/ssr_graph_denoise_tiles.d.ts +26 -0
  111. package/src/shade/renderer/postprocess/ssr/ssr_graph_denoise_tiles.d.ts.map +1 -0
  112. package/src/shade/renderer/postprocess/ssr/ssr_graph_denoise_tiles.js +42 -0
  113. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts +1 -0
  114. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts.map +1 -1
  115. package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.js +113 -105
  116. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.d.ts +2 -0
  117. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.d.ts.map +1 -1
  118. package/src/shade/renderer/postprocess/ssr/ssr_spatial_denoise_shader.js +177 -167
  119. package/src/shade/renderer/rasterize/DEPTH_STENCIL_STATE_SHADOW.d.ts.map +1 -1
  120. package/src/shade/renderer/rasterize/DEPTH_STENCIL_STATE_SHADOW.js +10 -0
  121. package/src/shade/renderer/rasterize/RasterizationJob.d.ts +1 -2
  122. package/src/shade/renderer/rasterize/RasterizationJob.d.ts.map +1 -1
  123. package/src/shade/renderer/rasterize/RasterizationJob.js +1 -2
  124. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.d.ts +5 -6
  125. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.d.ts.map +1 -1
  126. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +5 -6
  127. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +2 -2
  128. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
  129. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +1 -1
  130. package/src/shade/renderer/scene/SCENE_DATABASE_SPEC.d.ts.map +1 -1
  131. package/src/shade/renderer/scene/SCENE_DATABASE_SPEC.js +11 -0
  132. package/src/shade/renderer/shader/chunk/atmosphere/phase/jd/build_lut_fog_parameters_jendersie_deon.d.ts.map +1 -1
  133. package/src/shade/renderer/shader/chunk/atmosphere/phase/jd/build_lut_fog_parameters_jendersie_deon.js +4 -5
  134. package/src/shade/renderer/shader/chunk/brdf/dfg/generate_dfg_texture.d.ts.map +1 -1
  135. package/src/shade/renderer/shader/chunk/brdf/dfg/generate_dfg_texture.js +1 -5
  136. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_importance.d.ts +30 -0
  137. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_importance.d.ts.map +1 -0
  138. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_importance.js +54 -0
  139. package/src/shade/renderer/shader/type/serde/read_gpu_typed_buffer.d.ts.map +1 -1
  140. package/src/shade/renderer/shader/type/serde/read_gpu_typed_buffer.js +3 -2
  141. package/src/shade/renderer/shadow/map/DIRECTIONAL_SHADOWMAP_STRUCT.d.ts +19 -0
  142. package/src/shade/renderer/shadow/map/DIRECTIONAL_SHADOWMAP_STRUCT.d.ts.map +1 -0
  143. package/src/shade/renderer/shadow/map/DIRECTIONAL_SHADOWMAP_STRUCT.js +27 -0
  144. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.d.ts +1 -0
  145. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.d.ts.map +1 -1
  146. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +4 -0
  147. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts +54 -0
  148. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  149. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +502 -10
  150. package/src/shade/renderer/shadow/map/Shadowmap.d.ts +3 -2
  151. package/src/shade/renderer/shadow/map/Shadowmap.d.ts.map +1 -1
  152. package/src/shade/renderer/shadow/map/Shadowmap.js +3 -2
  153. package/src/shade/renderer/shadow/map/shader/chunk_sample_shadowmap_atlas_for_light.d.ts +8 -0
  154. package/src/shade/renderer/shadow/map/shader/chunk_sample_shadowmap_atlas_for_light.d.ts.map +1 -1
  155. package/src/shade/renderer/shadow/map/shader/chunk_sample_shadowmap_atlas_for_light.js +34 -3
  156. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional.d.ts +3 -2
  157. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional.d.ts.map +1 -1
  158. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_directional.js +17 -2
  159. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.d.ts.map +1 -1
  160. package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.js +137 -133
  161. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_FIRST_TEXEL_SIZE.d.ts +15 -0
  162. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_FIRST_TEXEL_SIZE.d.ts.map +1 -0
  163. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_FIRST_TEXEL_SIZE.js +14 -0
  164. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_PAGE_BUDGET.d.ts +16 -0
  165. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_PAGE_BUDGET.d.ts.map +1 -0
  166. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_PAGE_BUDGET.js +15 -0
  167. package/src/shade/renderer/shadow/vsm/PLAN.md +440 -0
  168. package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +43 -0
  169. package/src/shade/renderer/shadow/vsm/VSMMoverTracker.d.ts +42 -0
  170. package/src/shade/renderer/shadow/vsm/VSMMoverTracker.d.ts.map +1 -0
  171. package/src/shade/renderer/shadow/vsm/VSMMoverTracker.js +153 -0
  172. package/src/shade/renderer/shadow/vsm/VSMPageManager.d.ts +327 -0
  173. package/src/shade/renderer/shadow/vsm/VSMPageManager.d.ts.map +1 -0
  174. package/src/shade/renderer/shadow/vsm/VSMPageManager.js +1408 -0
  175. package/src/shade/renderer/shadow/vsm/VSM_COVER_LEVEL_STEP.d.ts +14 -0
  176. package/src/shade/renderer/shadow/vsm/VSM_COVER_LEVEL_STEP.d.ts.map +1 -0
  177. package/src/shade/renderer/shadow/vsm/VSM_COVER_LEVEL_STEP.js +13 -0
  178. package/src/shade/renderer/shadow/vsm/VSM_FEEDBACK_LAYOUT.d.ts +63 -0
  179. package/src/shade/renderer/shadow/vsm/VSM_FEEDBACK_LAYOUT.d.ts.map +1 -0
  180. package/src/shade/renderer/shadow/vsm/VSM_FEEDBACK_LAYOUT.js +70 -0
  181. package/src/shade/renderer/shadow/vsm/VSM_LEVEL_LIMIT.d.ts +11 -0
  182. package/src/shade/renderer/shadow/vsm/VSM_LEVEL_LIMIT.d.ts.map +1 -0
  183. package/src/shade/renderer/shadow/vsm/VSM_LEVEL_LIMIT.js +10 -0
  184. package/src/shade/renderer/shadow/vsm/VSM_MOVER_LAYOUT.d.ts +54 -0
  185. package/src/shade/renderer/shadow/vsm/VSM_MOVER_LAYOUT.d.ts.map +1 -0
  186. package/src/shade/renderer/shadow/vsm/VSM_MOVER_LAYOUT.js +75 -0
  187. package/src/shade/renderer/shadow/vsm/VSM_RECORD_STRUCT.d.ts +23 -0
  188. package/src/shade/renderer/shadow/vsm/VSM_RECORD_STRUCT.d.ts.map +1 -0
  189. package/src/shade/renderer/shadow/vsm/VSM_RECORD_STRUCT.js +85 -0
  190. package/src/shade/renderer/shadow/vsm/VSM_SLOT_STRUCT.d.ts +16 -0
  191. package/src/shade/renderer/shadow/vsm/VSM_SLOT_STRUCT.d.ts.map +1 -0
  192. package/src/shade/renderer/shadow/vsm/VSM_SLOT_STRUCT.js +29 -0
  193. package/src/shade/renderer/shadow/vsm/VirtualShadowMap.d.ts +136 -25
  194. package/src/shade/renderer/shadow/vsm/VirtualShadowMap.d.ts.map +1 -1
  195. package/src/shade/renderer/shadow/vsm/VirtualShadowMap.js +282 -116
  196. package/src/shade/renderer/shadow/vsm/graph_vsm_feedback.d.ts +39 -0
  197. package/src/shade/renderer/shadow/vsm/graph_vsm_feedback.d.ts.map +1 -0
  198. package/src/shade/renderer/shadow/vsm/graph_vsm_feedback.js +289 -0
  199. package/src/shade/renderer/shadow/vsm/shader/chunk_shadowmap_sample_virtual.d.ts +18 -0
  200. package/src/shade/renderer/shadow/vsm/shader/chunk_shadowmap_sample_virtual.d.ts.map +1 -0
  201. package/src/shade/renderer/shadow/vsm/shader/chunk_shadowmap_sample_virtual.js +65 -0
  202. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_addressing.d.ts +17 -0
  203. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_addressing.d.ts.map +1 -0
  204. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_addressing.js +75 -0
  205. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_append_mover.d.ts +14 -0
  206. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_append_mover.d.ts.map +1 -0
  207. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_append_mover.js +45 -0
  208. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_find_page.d.ts +17 -0
  209. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_find_page.d.ts.map +1 -0
  210. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_find_page.js +100 -0
  211. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_page_view_projection.d.ts +15 -0
  212. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_page_view_projection.d.ts.map +1 -0
  213. package/src/shade/renderer/shadow/vsm/shader/chunk_vsm_page_view_projection.js +47 -0
  214. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_demand.d.ts +26 -0
  215. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_demand.d.ts.map +1 -0
  216. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_demand.js +156 -0
  217. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_gather_counts.d.ts +13 -0
  218. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_gather_counts.d.ts.map +1 -0
  219. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_gather_counts.js +65 -0
  220. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_movers.d.ts +30 -0
  221. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_movers.d.ts.map +1 -0
  222. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_movers.js +184 -0
  223. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_skinned_movers.d.ts +23 -0
  224. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_skinned_movers.d.ts.map +1 -0
  225. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_skinned_movers.js +109 -0
  226. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_stale.d.ts +56 -0
  227. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_stale.d.ts.map +1 -0
  228. package/src/shade/renderer/shadow/vsm/shader/shader_vsm_stale.js +211 -0
  229. package/src/shade/renderer/texture/format/decode_typed_gpu_data.d.ts +1 -1
  230. package/src/shade/renderer/texture/format/decode_typed_gpu_data.d.ts.map +1 -1
  231. package/src/shade/renderer/texture/format/decode_typed_gpu_data.js +3 -13
  232. package/src/shade/renderer/texture/format/infer_format_from_parameters.d.ts.map +1 -1
  233. package/src/shade/renderer/texture/format/infer_format_from_parameters.js +2 -3
  234. package/src/shade/renderer/texture/texture_write_to_gpu.d.ts.map +1 -1
  235. package/src/shade/renderer/texture/texture_write_to_gpu.js +16 -16
  236. package/src/shade/renderer/texture/virtual/VTPhysicalCache.d.ts +4 -90
  237. package/src/shade/renderer/texture/virtual/VTPhysicalCache.d.ts.map +1 -1
  238. package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +6 -210
  239. package/src/shade/renderer/texture/virtual/VTPhysicalSlots.d.ts +119 -0
  240. package/src/shade/renderer/texture/virtual/VTPhysicalSlots.d.ts.map +1 -0
  241. package/src/shade/renderer/texture/virtual/VTPhysicalSlots.js +245 -0
  242. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.d.ts +1 -1
  243. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.d.ts.map +1 -1
  244. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +3 -19
  245. package/src/shade/renderer/texture/virtual/chunk_vt_page_table_entry.d.ts +13 -0
  246. package/src/shade/renderer/texture/virtual/chunk_vt_page_table_entry.d.ts.map +1 -0
  247. package/src/shade/renderer/texture/virtual/chunk_vt_page_table_entry.js +34 -0
  248. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +7 -2
  249. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  250. package/src/shade/renderer/water/GPUWaterRenderer.js +49 -49
  251. package/src/shade/renderer/water/chunk_water_transport.js +3 -3
  252. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +11 -25
  253. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +1 -1
  254. package/src/shade/renderer/water/medium/GPUWaterBodies.js +64 -116
  255. package/src/shade/renderer/water/medium/WATER_BODIES_STRUCT.d.ts +1 -20
  256. package/src/shade/renderer/water/medium/WATER_BODIES_STRUCT.d.ts.map +1 -1
  257. package/src/shade/renderer/water/medium/WATER_BODIES_STRUCT.js +10 -42
  258. package/src/shade/renderer/water/medium/chunk_water_bodies.d.ts +3 -0
  259. package/src/shade/renderer/water/medium/chunk_water_bodies.d.ts.map +1 -0
  260. package/src/shade/renderer/water/medium/chunk_water_bodies.js +3 -0
  261. package/src/shade/renderer/water/medium/shader_water_bodies_derive.d.ts +1 -5
  262. package/src/shade/renderer/water/medium/shader_water_bodies_derive.d.ts.map +1 -1
  263. package/src/shade/renderer/water/medium/shader_water_bodies_derive.js +32 -30
  264. package/src/shade/renderer/water/medium/shader_water_meshlets.d.ts +4 -0
  265. package/src/shade/renderer/water/medium/shader_water_meshlets.d.ts.map +1 -0
  266. package/src/shade/renderer/water/medium/shader_water_meshlets.js +69 -0
  267. package/src/shade/renderer/water/optics/chunk_water_inscatter.d.ts.map +1 -1
  268. package/src/shade/renderer/water/optics/chunk_water_inscatter.js +124 -123
  269. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.d.ts.map +1 -1
  270. package/src/shade/renderer/water/optics/chunk_water_receiver_downwelling.js +75 -74
  271. package/src/shade/renderer/water/shader_water.d.ts +1 -1
  272. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  273. package/src/shade/renderer/water/shader_water.js +490 -406
  274. package/src/shade/renderer/water/surface/GPUWaterWaveSimulation.d.ts +12 -85
  275. package/src/shade/renderer/water/surface/GPUWaterWaveSimulation.d.ts.map +1 -1
  276. package/src/shade/renderer/water/surface/GPUWaterWaveSimulation.js +128 -408
  277. package/src/shade/renderer/water/surface/WATER_WAVE_RECORD_STRUCT.d.ts +6 -0
  278. package/src/shade/renderer/water/surface/WATER_WAVE_RECORD_STRUCT.d.ts.map +1 -0
  279. package/src/shade/renderer/water/surface/WATER_WAVE_RECORD_STRUCT.js +23 -0
  280. package/src/shade/renderer/water/surface/chunk_water_tessellation.d.ts +9 -0
  281. package/src/shade/renderer/water/surface/chunk_water_tessellation.d.ts.map +1 -0
  282. package/src/shade/renderer/water/surface/chunk_water_tessellation.js +144 -0
  283. package/src/shade/renderer/water/surface/chunk_water_wave.d.ts +3 -5
  284. package/src/shade/renderer/water/surface/chunk_water_wave.d.ts.map +1 -1
  285. package/src/shade/renderer/water/surface/chunk_water_wave.js +40 -48
  286. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +14 -0
  287. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts.map +1 -0
  288. package/src/shade/renderer/water/surface/graph_water_tessellation.js +34 -0
  289. package/src/shade/renderer/water/surface/shader_water_tiles.d.ts +4 -0
  290. package/src/shade/renderer/water/surface/shader_water_tiles.d.ts.map +1 -0
  291. package/src/shade/renderer/water/surface/shader_water_tiles.js +123 -0
  292. package/src/shade/renderer/water/surface/shader_water_wave_batch.d.ts +6 -0
  293. package/src/shade/renderer/water/surface/shader_water_wave_batch.d.ts.map +1 -0
  294. package/src/shade/renderer/water/surface/shader_water_wave_batch.js +85 -0
  295. package/src/shade/renderer/water/surface/shader_water_wave_domain.d.ts +6 -0
  296. package/src/shade/renderer/water/surface/shader_water_wave_domain.d.ts.map +1 -0
  297. package/src/shade/renderer/water/surface/shader_water_wave_domain.js +83 -0
  298. package/src/shade/renderer/water/water_height_texture_from_sampler.d.ts.map +1 -1
  299. package/src/shade/renderer/water/water_height_texture_from_sampler.js +4 -3
  300. package/src/engine/graphics/ecs/water/WATER_SIZE.d.ts +0 -2
  301. package/src/engine/graphics/ecs/water/WATER_SIZE.d.ts.map +0 -1
  302. package/src/engine/graphics/ecs/water/WATER_SIZE.js +0 -1
  303. package/src/shade/renderer/light/GPUViewLightContext.d.ts +0 -22
  304. package/src/shade/renderer/light/GPUViewLightContext.d.ts.map +0 -1
  305. package/src/shade/renderer/light/GPUViewLightContext.js +0 -62
  306. package/src/shade/renderer/shadow/map/csm/CSM_SHADOWMAP_STRUCT.d.ts +0 -14
  307. package/src/shade/renderer/shadow/map/csm/CSM_SHADOWMAP_STRUCT.d.ts.map +0 -1
  308. package/src/shade/renderer/shadow/map/csm/CSM_SHADOWMAP_STRUCT.js +0 -20
  309. package/src/shade/renderer/shadow/vsm/GPUViewShadowMaps.d.ts +0 -27
  310. package/src/shade/renderer/shadow/vsm/GPUViewShadowMaps.d.ts.map +0 -1
  311. package/src/shade/renderer/shadow/vsm/GPUViewShadowMaps.js +0 -147
  312. package/src/shade/renderer/shadow/vsm/GPUVirtualShadowTextureResidence.d.ts +0 -7
  313. package/src/shade/renderer/shadow/vsm/GPUVirtualShadowTextureResidence.d.ts.map +0 -1
  314. package/src/shade/renderer/shadow/vsm/GPUVirtualShadowTextureResidence.js +0 -10
  315. package/src/shade/renderer/shadow/vsm/VSM_RESIDENCE_WORKGROUP_TILE_RESOLUTION.d.ts +0 -2
  316. package/src/shade/renderer/shadow/vsm/VSM_RESIDENCE_WORKGROUP_TILE_RESOLUTION.d.ts.map +0 -1
  317. package/src/shade/renderer/shadow/vsm/VSM_RESIDENCE_WORKGROUP_TILE_RESOLUTION.js +0 -1
  318. package/src/shade/renderer/shadow/vsm/VSM_UNIFORMS_STRUCT.d.ts +0 -3
  319. package/src/shade/renderer/shadow/vsm/VSM_UNIFORMS_STRUCT.d.ts.map +0 -1
  320. package/src/shade/renderer/shadow/vsm/VSM_UNIFORMS_STRUCT.js +0 -8
  321. package/src/shade/renderer/shadow/vsm/VirtualShadowmapRenderer.d.ts +0 -27
  322. package/src/shade/renderer/shadow/vsm/VirtualShadowmapRenderer.d.ts.map +0 -1
  323. package/src/shade/renderer/shadow/vsm/VirtualShadowmapRenderer.js +0 -102
  324. package/src/shade/renderer/shadow/vsm/debug_inspect_coarse_bounds_buffer.d.ts +0 -8
  325. package/src/shade/renderer/shadow/vsm/debug_inspect_coarse_bounds_buffer.d.ts.map +0 -1
  326. package/src/shade/renderer/shadow/vsm/debug_inspect_coarse_bounds_buffer.js +0 -34
  327. package/src/shade/renderer/shadow/vsm/debug_inspect_residence_buffer.d.ts +0 -10
  328. package/src/shade/renderer/shadow/vsm/debug_inspect_residence_buffer.d.ts.map +0 -1
  329. package/src/shade/renderer/shadow/vsm/debug_inspect_residence_buffer.js +0 -64
  330. package/src/shade/renderer/shadow/vsm/decode_tile_address.d.ts +0 -11
  331. package/src/shade/renderer/shadow/vsm/decode_tile_address.d.ts.map +0 -1
  332. package/src/shade/renderer/shadow/vsm/decode_tile_address.js +0 -26
  333. package/src/shade/renderer/shadow/vsm/render/vms_render_tiles.d.ts +0 -2
  334. package/src/shade/renderer/shadow/vsm/render/vms_render_tiles.d.ts.map +0 -1
  335. package/src/shade/renderer/shadow/vsm/render/vms_render_tiles.js +0 -11
  336. package/src/shade/renderer/shadow/vsm/vms_pipeline_compute_coarse_bounds.d.ts +0 -20
  337. package/src/shade/renderer/shadow/vsm/vms_pipeline_compute_coarse_bounds.d.ts.map +0 -1
  338. package/src/shade/renderer/shadow/vsm/vms_pipeline_compute_coarse_bounds.js +0 -184
  339. package/src/shade/renderer/shadow/vsm/vsm_residence_pipeline.d.ts +0 -25
  340. package/src/shade/renderer/shadow/vsm/vsm_residence_pipeline.d.ts.map +0 -1
  341. package/src/shade/renderer/shadow/vsm/vsm_residence_pipeline.js +0 -230
  342. package/src/shade/renderer/shadow/vsm/vsm_tile_allocation_pipeline.d.ts +0 -19
  343. package/src/shade/renderer/shadow/vsm/vsm_tile_allocation_pipeline.d.ts.map +0 -1
  344. package/src/shade/renderer/shadow/vsm/vsm_tile_allocation_pipeline.js +0 -127
  345. package/src/shade/renderer/texture/encode/ENCODE_NOTES_2026_09_02.md +0 -462
  346. package/src/shade/renderer/water/optics/chunk_water_light_transmittance.d.ts +0 -23
  347. package/src/shade/renderer/water/optics/chunk_water_light_transmittance.d.ts.map +0 -1
  348. package/src/shade/renderer/water/optics/chunk_water_light_transmittance_dry.d.ts +0 -18
  349. package/src/shade/renderer/water/optics/chunk_water_light_transmittance_dry.d.ts.map +0 -1
  350. package/src/shade/renderer/water/surface/WATER_SURFACE_VERTEX_STRUCT.d.ts +0 -12
  351. package/src/shade/renderer/water/surface/WATER_SURFACE_VERTEX_STRUCT.d.ts.map +0 -1
  352. package/src/shade/renderer/water/surface/WATER_SURFACE_VERTEX_STRUCT.js +0 -27
  353. package/src/shade/renderer/water/surface/WATER_WAVE_PARAMETERS_STRUCT.d.ts +0 -14
  354. package/src/shade/renderer/water/surface/WATER_WAVE_PARAMETERS_STRUCT.d.ts.map +0 -1
  355. package/src/shade/renderer/water/surface/WATER_WAVE_PARAMETERS_STRUCT.js +0 -49
  356. package/src/shade/renderer/water/surface/chunk_water_wave_grid.d.ts +0 -9
  357. package/src/shade/renderer/water/surface/chunk_water_wave_grid.d.ts.map +0 -1
  358. package/src/shade/renderer/water/surface/chunk_water_wave_grid.js +0 -47
  359. package/src/shade/renderer/water/surface/shader_water_surface_write.d.ts +0 -10
  360. package/src/shade/renderer/water/surface/shader_water_surface_write.d.ts.map +0 -1
  361. package/src/shade/renderer/water/surface/shader_water_surface_write.js +0 -51
  362. package/src/shade/renderer/water/surface/shader_water_wave_step.d.ts +0 -12
  363. package/src/shade/renderer/water/surface/shader_water_wave_step.d.ts.map +0 -1
  364. package/src/shade/renderer/water/surface/shader_water_wave_step.js +0 -54
  365. package/src/shade/renderer/water/surface/water_surface_grid_indices.d.ts +0 -13
  366. package/src/shade/renderer/water/surface/water_surface_grid_indices.d.ts.map +0 -1
  367. package/src/shade/renderer/water/surface/water_surface_grid_indices.js +0 -39
@@ -3,7 +3,6 @@ import { clamp_sample } from "../color/clamp_sample.js";
3
3
  import { colour_transform_init } from "../color/ColourTransform.js";
4
4
  import { LINEAR_REFERENCE_NITS } from "../color/transfer_to_linear.js";
5
5
  import { MC_BT_601 } from "../av1/tables/av1_symbols.js";
6
- import { to_half_float_uint16 } from "../../../../core/binary/to_half_float_uint16.js";
7
6
  import { transfer_row_to_linear } from "../color/transfer_to_linear.js";
8
7
  import { UPSAMPLE_BILINEAR, UPSAMPLE_NEAREST, upsample_chroma_row } from "../color/upsample_chroma_row.js";
9
8
  import { ycbcr_row_to_rgb } from "../color/ycbcr_row_to_rgb.js";
@@ -50,7 +49,7 @@ export const CHROMA_UPSAMPLING_BEST = 2;
50
49
  * for, in nits; defaults to {@link LINEAR_REFERENCE_NITS}. Only read for `COLOUR_SPACE_LINEAR`,
51
50
  * and the exact counterpart of {@link encode_avif}'s option of the same name
52
51
  * @param {number} [options.chroma_upsampling] one of the `CHROMA_UPSAMPLING_*` constants
53
- * @returns {{data: (Uint8ClampedArray|Uint16Array), width: number, height: number}}
52
+ * @returns {{data: (Uint8ClampedArray|Uint16Array|Float16Array), width: number, height: number}}
54
53
  *
55
54
  * @author Alex Goldring
56
55
  * @copyright Company Named Limited (c) 2026
@@ -98,9 +97,9 @@ export function convert_to_rgba(decoder, image, options = {}) {
98
97
 
99
98
  const samples = width * height * 4;
100
99
 
101
- const data = output_float16 || output_bit_depth > 8
102
- ? new Uint16Array(samples)
103
- : new Uint8ClampedArray(samples);
100
+ const data = output_float16
101
+ ? new Float16Array(samples)
102
+ : output_bit_depth > 8 ? new Uint16Array(samples) : new Uint8ClampedArray(samples);
104
103
 
105
104
  const maximum = (1 << output_bit_depth) - 1;
106
105
 
@@ -180,10 +179,12 @@ export function convert_to_rgba(decoder, image, options = {}) {
180
179
  : clamp01((image.alpha[alpha_row + x] - alpha_offset) * alpha_scale);
181
180
 
182
181
  if (output_float16) {
183
- data[out] = to_half_float_uint16(rgb_row[i]);
184
- data[out + 1] = to_half_float_uint16(rgb_row[i + 1]);
185
- data[out + 2] = to_half_float_uint16(rgb_row[i + 2]);
186
- data[out + 3] = to_half_float_uint16(alpha);
182
+ for (let c = 0; c < 3; c++) {
183
+ const value = rgb_row[i + c];
184
+ // Preserve finite HDR saturation, even with a very small reference white.
185
+ data[out + c] = Number.isFinite(value) ? Math.min(Math.max(value, -65504), 65504) : value;
186
+ }
187
+ data[out + 3] = alpha;
187
188
  } else {
188
189
  data[out] = quantise(rgb_row[i], maximum);
189
190
  data[out + 1] = quantise(rgb_row[i + 1], maximum);
@@ -10,13 +10,13 @@
10
10
  * file's declared orientation — off by default, because the decoder this replaces left it to its
11
11
  * caller and a caller that already handles orientation would otherwise apply it twice
12
12
  * @param {AvifDecoder} [decoder] pass one back in to decode many images without reallocating
13
- * @returns {{data: (Uint8ClampedArray|Uint16Array), width: number, height: number}}
13
+ * @returns {{data: (Uint8ClampedArray|Uint16Array|Float16Array), width: number, height: number}}
14
14
  *
15
15
  * @author Alex Goldring
16
16
  * @copyright Company Named Limited (c) 2026
17
17
  */
18
18
  export function decode_avif(buffer: ArrayBuffer | Uint8Array, options?: object, decoder?: AvifDecoder): {
19
- data: (Uint8ClampedArray | Uint16Array);
19
+ data: (Uint8ClampedArray | Uint16Array | Float16Array);
20
20
  width: number;
21
21
  height: number;
22
22
  };
@@ -1 +1 @@
1
- {"version":3,"file":"decode_avif.d.ts","sourceRoot":"","sources":["../../../../../../src/format/image/avif/api/decode_avif.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;GAgBG;AACH,oCAVW,WAAW,GAAC,UAAU,YACtB,MAAM,YAGN,WAAW,GACT;IAAC,IAAI,EAAE,CAAC,iBAAiB,GAAC,WAAW,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAmClF;4BA/D2B,kBAAkB"}
1
+ {"version":3,"file":"decode_avif.d.ts","sourceRoot":"","sources":["../../../../../../src/format/image/avif/api/decode_avif.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;GAgBG;AACH,oCAVW,WAAW,GAAC,UAAU,YACtB,MAAM,YAGN,WAAW,GACT;IAAC,IAAI,EAAE,CAAC,iBAAiB,GAAC,WAAW,eAAa,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAmC/F;4BA/D2B,kBAAkB"}
@@ -26,7 +26,7 @@ const ALPHA_AUX_TYPE = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha";
26
26
  * file's declared orientation — off by default, because the decoder this replaces left it to its
27
27
  * caller and a caller that already handles orientation would otherwise apply it twice
28
28
  * @param {AvifDecoder} [decoder] pass one back in to decode many images without reallocating
29
- * @returns {{data: (Uint8ClampedArray|Uint16Array), width: number, height: number}}
29
+ * @returns {{data: (Uint8ClampedArray|Uint16Array|Float16Array), width: number, height: number}}
30
30
  *
31
31
  * @author Alex Goldring
32
32
  * @copyright Company Named Limited (c) 2026
@@ -1,103 +1,111 @@
1
- # SSR reflection lab
2
-
3
- Run `npm run dev` from `packages/meep-engine`, then open
4
- <http://localhost:5173/src/shade/playground/ssr_reprojection/>.
5
- The page uses the actual WebGPU renderer and production SSR shaders. Geometry and
6
- environment lighting are procedural; only the engine's bundled lookup/noise textures
7
- are loaded. The browser must meet the renderer's WebGPU feature requirements.
8
-
9
- ## Five-minute check
10
-
11
- 1. Start with **Lateral camera**, **Parallax**, roughness **0.03**, and final TAA off.
12
- Watch the reflected coloured blocks move relative to the floor grid. Solid crosses
13
- mark ideal planar-mirror image centres. Dashed crosses are fixed floor positions,
14
- aligned at the start of the path. Their separation illustrates surface sticking.
15
- 2. Select **Surface only**, then **No temporal history**. Switching mode restarts
16
- the path and resets histories. Compare edge lag and noise at the same part of the
17
- repeatable path. Surface only uses the new validation/blending, not the old shader.
18
- 3. Try **Forward camera** and **Orbit camera**, then raise roughness through 0.2.
19
- The virtual candidate is deliberately restricted to smooth, coherent reflections.
20
- 4. Use **Moving object** and **Occluder** to expose residual trails and disocclusion.
21
- Moving reflected geometry is an acknowledged limitation. Missing off-screen or
22
- hidden source geometry is also an SSR limitation, not necessarily a reprojection bug.
23
- 5. Press **Camera cut**, **Reset reflection history**, and change resolution.
24
- Old reflections should not persist. **SSR off** is a useful control proving that
25
- the visible reflection comes from SSR rather than the flat environment.
26
-
27
- The guides reflect physical object centres across y=0 independently of the temporal
28
- ray-extension formula. They are geometry guides, not colour references: visible
29
- front faces, occlusion and roughness do not necessarily centre on the marker. The
30
- floor is planar and its top is exactly y=0. Grid strips slightly above it are visible
31
- surface landmarks. Guides do not perform occlusion testing.
32
-
33
- The page fixes exposure and disables bloom, sharpening, ambient occlusion and motion
34
- blur. Final-image TAA defaults off to expose SSR's own temporal behaviour. Pause
35
- motion leaves temporal sampling running; **Still camera / noise** does the same at
36
- the initial view. **Step frame** advances the motion by 1/60 second. For exact manual
37
- frame counts, append `?manual` to the URL: no animation loop is scheduled, and the
38
- step button submits one frame at a time. From the browser console,
39
- `ssr_lab.step(60)` advances 60 deterministic frames at 1/60 second.
40
-
41
- **Validate GPU shaders** compiles trace, both resolves, both spatial variants, and
42
- temporal reprojection on the live GPU. This checks native WGSL compilation, while
43
- running the playground also exercises IBL pipeline creation and frame-graph wiring.
44
- The page reports and stops on uncaptured GPU errors. LPV composition is checked but
45
- the procedural scene uses IBL, so this does not test LPV atlas content or lighting.
46
-
47
- Production integration is small: `renderer.ssr.reprojection_mode` selects
48
- `SSRReprojectionMode.Parallax`, `.Surface`, or `.CurrentFrame`. Changing it invalidates
49
- SSR history. Default rendering continues to use Parallax.
50
-
51
- Do not use frame rate on this page as an SSR-only GPU timing measurement. It includes
52
- the entire renderer, the browser, and the overlay. Profile the actual passes before
53
- making performance claims. HMR is disabled by the playground Vite config; reload
54
- the browser explicitly after source edits.
55
-
56
- ## Validation performed
57
-
58
- The page was exercised on the native NVIDIA adapter: IBL rendering, all four
59
- comparison modes, a moving target and occluder, camera cut, resolution change,
60
- and final TAA toggle. The SSR-off control visibly removes the reflections.
61
- All six SSR shader modules compile on that device, and the 14 SSR regression
62
- tests pass. Declaration emission and consumer API pins pass; the existing
63
- repository type-error budget is still exceeded (899 errors versus 897).
64
-
65
- ## Spatial denoising
66
-
67
- The **Spatial denoising** checkbox defaults on. It toggles one spatial pass before
68
- reprojection and two afterward; the chosen reprojection mode stays unchanged.
69
- Changing this setting resets SSR history so filtered and unfiltered inputs are not
70
- mixed across the transition. The runtime setting is
71
- `renderer.ssr.spatial_denoising_enabled`. With the box unchecked and **No temporal
72
- history** selected, the output comes directly from resolve. Resolve still borrows
73
- neighbouring ray hits and uses roughness-dependent radiance filtering; this switch
74
- does not turn the resolve stage into a raw one-ray-per-pixel view.
75
-
76
- ## Measuring actual filter effectiveness
77
-
78
- **Measure spatial filter** records the first spatial pass's input and output in
79
- one GPU frame. It reports the fraction of upward-facing mirror pixels whose maximum
80
- RGB-channel difference exceeds 0.001, mean/max difference in linear HDR radiance,
81
- and the fraction whose input variance is effectively zero. It is an effect check,
82
- not an image-quality score or an SSR performance benchmark. Readback runs only on
83
- request; its temporary texture is released afterward. Ordinary playback has no
84
- probe passes. SSR must be enabled. With spatial filtering off, the bypass is compared
85
- with itself and the measured difference is zero.
86
-
87
- This probe exposed a filter failure on 2026-09-08: roughly 98% of this scene's mirror
88
- pixels had resolve variance ≤1e-6. The old first pass changed radiance by just 0.00137
89
- on average; the sparse noisy samples were being rejected as colour edges. Resolve's
90
- within-pixel ray variance is not sufficient evidence of low spatial noise when only
91
- one hit supplies radiance or the hit confidence switches between SSR and fallback.
92
-
93
- The first pass now bootstraps uncertainty from a Gaussian-weighted 3×3 luminance
94
- neighbourhood, using the existing geometry weights, and carries that uncertainty
95
- through the subsequent spatial passes. It does not bootstrap again at larger radii.
96
- A representative corrected GPU measurement gave mean change 0.00552 with filtering
97
- on, and 0 with it off. These are separate frames, not a matched-noise quality metric.
98
- The native images visibly differ, and an emulator regression verifies reduced
99
- checker noise with zero input variance, unchanged constant colour (within float
100
- precision), and rejection across opposite normals. The full SSR suite has 16 tests.
101
-
102
- Spatial filtering trades sharpness for noise reduction, including at reflected
103
- colour edges; these guides and tests do not establish an optimal filter bandwidth.
1
+ # SSR reflection lab
2
+
3
+ Run `npm run dev` from `packages/meep-engine`, then open
4
+ <http://localhost:5173/src/shade/playground/ssr_reprojection/>.
5
+ The page uses the actual WebGPU renderer and production SSR shaders. Geometry and
6
+ environment lighting are procedural; only the engine's bundled lookup/noise textures
7
+ are loaded. The browser must meet the renderer's WebGPU feature requirements.
8
+
9
+ ## Five-minute check
10
+
11
+ 1. Start with **Lateral camera**, **Parallax**, roughness **0.03**, and final TAA off.
12
+ Watch the reflected coloured blocks move relative to the floor grid. Solid crosses
13
+ mark ideal planar-mirror image centres. Dashed crosses are fixed floor positions,
14
+ aligned at the start of the path. Their separation illustrates surface sticking.
15
+ 2. Select **Surface only**, then **No temporal history**. Switching mode restarts
16
+ the path and resets histories. Compare edge lag and noise at the same part of the
17
+ repeatable path. Surface only uses the new validation/blending, not the old shader.
18
+ 3. Try **Forward camera** and **Orbit camera**, then raise roughness through 0.2.
19
+ The virtual candidate is deliberately restricted to smooth, coherent reflections.
20
+ 4. Use **Moving object** and **Occluder** to expose residual trails and disocclusion.
21
+ Moving reflected geometry is an acknowledged limitation. Missing off-screen or
22
+ hidden source geometry is also an SSR limitation, not necessarily a reprojection bug.
23
+ 5. Press **Camera cut**, **Reset reflection history**, and change resolution.
24
+ Old reflections should not persist. **SSR off** is a useful control proving that
25
+ the visible reflection comes from SSR rather than the flat environment.
26
+
27
+ The guides reflect physical object centres across y=0 independently of the temporal
28
+ ray-extension formula. They are geometry guides, not colour references: visible
29
+ front faces, occlusion and roughness do not necessarily centre on the marker. The
30
+ floor is planar and its top is exactly y=0. Grid strips slightly above it are visible
31
+ surface landmarks. Guides do not perform occlusion testing.
32
+
33
+ The page fixes exposure and disables bloom, sharpening, ambient occlusion and motion
34
+ blur. Final-image TAA defaults off to expose SSR's own temporal behaviour. Pause
35
+ motion leaves temporal sampling running; **Still camera / noise** does the same at
36
+ the initial view. **Step frame** advances the motion by 1/60 second. For exact manual
37
+ frame counts, append `?manual` to the URL: no animation loop is scheduled, and the
38
+ step button submits one frame at a time. From the browser console,
39
+ `ssr_lab.step(60)` advances 60 deterministic frames at 1/60 second.
40
+
41
+ **Validate GPU shaders** compiles trace, both resolves, both spatial variants, and
42
+ temporal reprojection on the live GPU. This checks native WGSL compilation, while
43
+ running the playground also exercises IBL pipeline creation and frame-graph wiring.
44
+ The page reports and stops on uncaptured GPU errors. LPV composition is checked but
45
+ the procedural scene uses IBL, so this does not test LPV atlas content or lighting.
46
+
47
+ Production integration is small: `renderer.ssr.reprojection_mode` selects
48
+ `SSRReprojectionMode.Parallax`, `.Surface`, or `.CurrentFrame`. Changing it invalidates
49
+ SSR history. Default rendering continues to use Parallax.
50
+
51
+ Do not use frame rate on this page as an SSR-only GPU timing measurement. It includes
52
+ the entire renderer, the browser, and the overlay. Profile the actual passes before
53
+ making performance claims. HMR is disabled by the playground Vite config; reload
54
+ the browser explicitly after source edits.
55
+
56
+ ## Validation performed
57
+
58
+ The page was exercised on the native NVIDIA adapter: IBL rendering, all four
59
+ comparison modes, a moving target and occluder, camera cut, resolution change,
60
+ and final TAA toggle. The SSR-off control visibly removes the reflections.
61
+ All six SSR shader modules compile on that device, and the 14 SSR regression
62
+ tests pass. Declaration emission and consumer API pins pass; the existing
63
+ repository type-error budget is still exceeded (899 errors versus 897).
64
+
65
+ ## Spatial denoising
66
+
67
+ The **Spatial denoising** checkbox defaults on. It toggles one spatial pass before
68
+ reprojection and two afterward; the chosen reprojection mode stays unchanged.
69
+ Changing this setting resets SSR history so filtered and unfiltered inputs are not
70
+ mixed across the transition. The runtime setting is
71
+ `renderer.ssr.spatial_denoising_enabled`. With the box unchecked and **No temporal
72
+ history** selected, the output comes directly from resolve. Resolve still borrows
73
+ neighbouring ray hits and uses roughness-dependent radiance filtering; this switch
74
+ does not turn the resolve stage into a raw one-ray-per-pixel view.
75
+
76
+ **Coverage tile denoising** is opt-in through `renderer.ssr.tile_denoising_enabled`.
77
+ It classifies 8×8 internal-pixel tiles and protects the full spatial/temporal filter
78
+ reach with a two-tile halo. Only stationary, constant radiance with no SSR support,
79
+ uncertainty, or changing history can skip spatial work. Temporal geometry validation
80
+ and history age still run; only proven constant neighborhood statistics are bypassed.
81
+ Changing the setting resets history. Classification adds overhead, so profile
82
+ the intended scene and device before enabling it, especially at low resolutions.
83
+
84
+ ## Measuring actual filter effectiveness
85
+
86
+ **Measure spatial filter** records the first spatial pass's input and output in
87
+ one GPU frame. It reports the fraction of upward-facing mirror pixels whose maximum
88
+ RGB-channel difference exceeds 0.001, mean/max difference in linear HDR radiance,
89
+ and the fraction whose input variance is effectively zero. It is an effect check,
90
+ not an image-quality score or an SSR performance benchmark. Readback runs only on
91
+ request; its temporary texture is released afterward. Ordinary playback has no
92
+ probe passes. SSR must be enabled. With spatial filtering off, the bypass is compared
93
+ with itself and the measured difference is zero.
94
+
95
+ This probe exposed a filter failure on 2026-09-08: roughly 98% of this scene's mirror
96
+ pixels had resolve variance ≤1e-6. The old first pass changed radiance by just 0.00137
97
+ on average; the sparse noisy samples were being rejected as colour edges. Resolve's
98
+ within-pixel ray variance is not sufficient evidence of low spatial noise when only
99
+ one hit supplies radiance or the hit confidence switches between SSR and fallback.
100
+
101
+ The first pass now bootstraps uncertainty from a Gaussian-weighted 3×3 luminance
102
+ neighbourhood, using the existing geometry weights, and carries that uncertainty
103
+ through the subsequent spatial passes. It does not bootstrap again at larger radii.
104
+ A representative corrected GPU measurement gave mean change 0.00552 with filtering
105
+ on, and 0 with it off. These are separate frames, not a matched-noise quality metric.
106
+ The native images visibly differ, and an emulator regression verifies reduced
107
+ checker noise with zero input variance, unchanged constant colour (within float
108
+ precision), and rejection across opposite normals. The full SSR suite has 16 tests.
109
+
110
+ Spatial filtering trades sharpness for noise reduction, including at reflected
111
+ colour edges; these guides and tests do not establish an optimal filter bandwidth.
@@ -1,68 +1,68 @@
1
- import { RenderExtension } from "../../renderer/extension/RenderExtension.js";
2
- import { ImageShader } from "../../renderer/shader/ImageShader.js";
3
- import { CodeChunk } from "../../renderer/shader/compiler/CodeChunk.js";
4
- import { chunk_decode_g_buffer_normal } from "../../renderer/shader/chunk/geometry/normal/chunk_decode_g_buffer_normal.js";
5
- import { graph_image_pass } from "../../renderer/shader/graph/graph_image_pass.js";
6
- import { graph_import_texture } from "../../renderer/shader/graph/graph_import_texture.js";
7
- import { ShaderResourceSetDescriptor } from "../../renderer/shader/resource/ShaderResourceSetDescriptor.js";
8
- import { ShaderDescriptor } from "../../renderer/shader/ShaderDescriptor.js";
9
- import { GPUTextureContext } from "../../renderer/texture/GPUTextureContext.js";
10
- import { download_texture_data } from "../../renderer/texture/download_texture_data.js";
11
-
12
- const resources = new ShaderResourceSetDescriptor();
13
- resources.createGroup().addTexture('tBefore').addTexture('tAfter').addTexture('tNormal', 'uint');
14
- const shader = new ImageShader({
15
- descriptor: ShaderDescriptor.from({label:'SSR lab / spatial effect',resources,
16
- body:CodeChunk.from(/* language=WGSL */ `
17
- @fragment fn main(@builtin(position) coord:vec4f) -> @location(0) vec4f {
18
- let p = vec2i(coord.xy);
19
- if (decode_g_buffer_normal(textureLoad(tNormal,p,0).xy).y < 0.99) { return vec4f(-1.0); }
20
- let a = textureLoad(tBefore,p,0);
21
- let b = textureLoad(tAfter,p,0);
22
- let difference = abs(a.rgb-b.rgb);
23
- return vec4f(max(difference.r,max(difference.g,difference.b)),a.w,b.w,1.0);
24
- }`, [chunk_decode_g_buffer_normal])}),
25
- targets:[{format:'rgba32float'}],
26
- });
27
-
28
- /** Same-frame GPU input/output comparison. Opt-in; no cost during ordinary playback. */
29
- export class SpatialFilterProbe extends RenderExtension {
30
- name = 'SSR lab / measure spatial filter';
31
- enabled = false;
32
- texture;
33
- device;
34
- constructor(device) { super(); this.device=device; }
35
- record(frame) {
36
- if (!this.enabled) { return; }
37
- // This diagnostic intentionally inspects the playground's production graph.
38
- const pass=frame.graph.__pass_nodes.find(p=>p.name==='SSR / spatial denoise with metadata');
39
- const resolve=frame.graph.__pass_nodes.find(p=>p.name==='SSR resolve / IBL');
40
- if (!resolve) { throw new Error('Enable SSR to measure its spatial filter.'); }
41
- const before=pass ? pass.data.tInput : resolve.data['@output'][0];
42
- const after=pass ? pass.data['@output'][0] : before;
43
- const [width,height]=frame.resolution;
44
- if (!this.texture || this.texture.width!==width || this.texture.height!==height) {
45
- this.texture?.destroy();
46
- this.texture=GPUTextureContext.fromTexture(this.device.createTexture({
47
- size:[width,height,1],format:'rgba32float',
48
- usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC,
49
- }),this.device);
50
- }
51
- graph_image_pass({graph:frame.graph,shader,
52
- inputs:{tBefore:before,tAfter:after,tNormal:resolve.data.tNormal},
53
- output:[graph_import_texture(frame.graph,this.texture,'SSR spatial measurement')],
54
- });
55
- }
56
- async read() {
57
- const data=await download_texture_data({device:this.device,source:this.texture.gpu_texture});
58
- let pixels=0,changed=0,zero_variance=0,mean=0,max=0;
59
- for(let i=0;i<data.length;i+=4) {
60
- if(data[i]<0) { continue; }
61
- if(!Number.isFinite(data[i])) { throw new Error('Non-finite denoising output.'); }
62
- pixels++; changed+=data[i]>0.001 ? 1 : 0; zero_variance+=data[i+1]<=1e-6 ? 1 : 0;
63
- mean+=data[i]; max=Math.max(max,data[i]);
64
- }
65
- if(pixels===0) { throw new Error('No upward-facing mirror pixels in this view.'); }
66
- return {pixels,changed_percent:100*changed/pixels,zero_variance_percent:100*zero_variance/pixels,mean:mean/pixels,max};
67
- }
68
- }
1
+ import { RenderExtension } from "../../renderer/extension/RenderExtension.js";
2
+ import { ImageShader } from "../../renderer/shader/ImageShader.js";
3
+ import { CodeChunk } from "../../renderer/shader/compiler/CodeChunk.js";
4
+ import { chunk_decode_g_buffer_normal } from "../../renderer/shader/chunk/geometry/normal/chunk_decode_g_buffer_normal.js";
5
+ import { graph_image_pass } from "../../renderer/shader/graph/graph_image_pass.js";
6
+ import { graph_import_texture } from "../../renderer/shader/graph/graph_import_texture.js";
7
+ import { ShaderResourceSetDescriptor } from "../../renderer/shader/resource/ShaderResourceSetDescriptor.js";
8
+ import { ShaderDescriptor } from "../../renderer/shader/ShaderDescriptor.js";
9
+ import { GPUTextureContext } from "../../renderer/texture/GPUTextureContext.js";
10
+ import { download_texture_data } from "../../renderer/texture/download_texture_data.js";
11
+
12
+ const resources = new ShaderResourceSetDescriptor();
13
+ resources.createGroup().addTexture('tBefore').addTexture('tAfter').addTexture('tNormal', 'uint');
14
+ const shader = new ImageShader({
15
+ descriptor: ShaderDescriptor.from({label:'SSR lab / spatial effect',resources,
16
+ body:CodeChunk.from(/* language=WGSL */ `
17
+ @fragment fn main(@builtin(position) coord:vec4f) -> @location(0) vec4f {
18
+ let p = vec2i(coord.xy);
19
+ if (decode_g_buffer_normal(textureLoad(tNormal,p,0).xy).y < 0.99) { return vec4f(-1.0); }
20
+ let a = textureLoad(tBefore,p,0);
21
+ let b = textureLoad(tAfter,p,0);
22
+ let difference = abs(a.rgb-b.rgb);
23
+ return vec4f(max(difference.r,max(difference.g,difference.b)),a.w,b.w,1.0);
24
+ }`, [chunk_decode_g_buffer_normal])}),
25
+ targets:[{format:'rgba32float'}],
26
+ });
27
+
28
+ /** Same-frame GPU input/output comparison. Opt-in; no cost during ordinary playback. */
29
+ export class SpatialFilterProbe extends RenderExtension {
30
+ name = 'SSR lab / measure spatial filter';
31
+ enabled = false;
32
+ texture;
33
+ device;
34
+ constructor(device) { super(); this.device=device; }
35
+ record(frame) {
36
+ if (!this.enabled) { return; }
37
+ // This diagnostic intentionally inspects the playground's production graph.
38
+ const pass=frame.graph.__pass_nodes.find(p=>p.name.startsWith('SSR / spatial denoise with metadata'));
39
+ const resolve=frame.graph.__pass_nodes.find(p=>p.name==='SSR resolve / IBL');
40
+ if (!resolve) { throw new Error('Enable SSR to measure its spatial filter.'); }
41
+ const before=pass ? pass.data.tInput : resolve.data['@output'][0];
42
+ const after=pass ? pass.data['@output'][0] : before;
43
+ const [width,height]=frame.resolution;
44
+ if (!this.texture || this.texture.width!==width || this.texture.height!==height) {
45
+ this.texture?.destroy();
46
+ this.texture=GPUTextureContext.fromTexture(this.device.createTexture({
47
+ size:[width,height,1],format:'rgba32float',
48
+ usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC,
49
+ }),this.device);
50
+ }
51
+ graph_image_pass({graph:frame.graph,shader,
52
+ inputs:{tBefore:before,tAfter:after,tNormal:resolve.data.tNormal},
53
+ output:[graph_import_texture(frame.graph,this.texture,'SSR spatial measurement')],
54
+ });
55
+ }
56
+ async read() {
57
+ const data=await download_texture_data({device:this.device,source:this.texture.gpu_texture});
58
+ let pixels=0,changed=0,zero_variance=0,mean=0,max=0;
59
+ for(let i=0;i<data.length;i+=4) {
60
+ if(data[i]<0) { continue; }
61
+ if(!Number.isFinite(data[i])) { throw new Error('Non-finite denoising output.'); }
62
+ pixels++; changed+=data[i]>0.001 ? 1 : 0; zero_variance+=data[i+1]<=1e-6 ? 1 : 0;
63
+ mean+=data[i]; max=Math.max(max,data[i]);
64
+ }
65
+ if(pixels===0) { throw new Error('No upward-facing mirror pixels in this view.'); }
66
+ return {pixels,changed_percent:100*changed/pixels,zero_variance_percent:100*zero_variance/pixels,mean:mean/pixels,max};
67
+ }
68
+ }
@@ -1,79 +1,80 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Meep — SSR reflection lab</title>
7
- <style>
8
- :root { color-scheme: dark; font: 14px/1.5 system-ui, sans-serif; background: #11151c; color: #e4eaf2; }
9
- * { box-sizing: border-box; }
10
- body { margin: 0; padding: 24px; }
11
- header { display: flex; align-items: baseline; gap: 16px; margin-bottom: 18px; }
12
- h1 { font-size: 24px; margin: 0; font-weight: 600; }
13
- header span, .hint, small { color: #a2afc1; }
14
- main { display: grid; grid-template-columns: minmax(0, 1fr) 290px; gap: 20px; align-items: start; }
15
- .stage { position: relative; background: #090c11; border: 1px solid #344153; border-radius: 8px; overflow: hidden; }
16
- /* Renderer writes inline presentation dimensions; both layers must scale together. */
17
- canvas { display: block; width: 100% !important; height: auto !important; }
18
- #overlay { position: absolute; inset: 0; pointer-events: none; }
19
- aside { background: #1a212c; border: 1px solid #344153; border-radius: 8px; padding: 18px; }
20
- label { display: block; margin-bottom: 13px; }
21
- label > span { display: block; font-size: 12px; color: #a2afc1; margin-bottom: 4px; }
22
- select, button { width: 100%; background: #252f3e; border: 1px solid #45556a; color: inherit; border-radius: 5px; padding: 8px; font: inherit; }
23
- button { cursor: pointer; }
24
- button:hover { background: #344359; }
25
- button:disabled { opacity: .4; cursor: default; }
26
- .buttons { display: flex; gap: 8px; margin-bottom: 10px; }
27
- .check { display: flex; gap: 8px; align-items: center; }
28
- input[type=range] { width: 100%; accent-color: #74c8ff; }
29
- hr { border: 0; border-top: 1px solid #344153; margin: 18px 0; }
30
- #status { font: 12px/1.5 ui-monospace, Consolas, monospace; white-space: pre-wrap; color: #95d6b4; }
31
- #status.error { color: #ff9a9a; }
32
- #hint { min-height: 3em; margin: 14px 0 0; }
33
- .legend { display: flex; gap: 24px; flex-wrap: wrap; margin-top: 12px; color: #a2afc1; font-size: 12px; }
34
- .legend b { color: #dce9ff; }
35
- details { margin-top: 18px; max-width: 85ch; color: #a2afc1; }
36
- summary { color: #dce9ff; cursor: pointer; }
37
- @media (max-width: 850px) { main { grid-template-columns: 1fr; } aside { width: 100%; } header { display: block; } }
38
- </style>
39
- </head>
40
- <body tabindex="0">
41
- <header><h1>SSR reflection lab</h1><span>Camera parallax · temporal stability · rejection</span></header>
42
- <main>
43
- <section>
44
- <div class="stage"><canvas id="view" width="960" height="600" tabindex="0" aria-label="Interactive WebGPU reflection scene"></canvas><canvas id="overlay" width="960" height="600" aria-hidden="true"></canvas></div>
45
- <div class="legend"><span><b>Solid cross</b> — ideal reflected centre</span><span><b>Dashed cross</b> — point fixed to the floor</span></div>
46
- <p id="hint" class="hint"></p>
47
- <details open><summary>A quick visual check</summary>
48
- <ol>
49
- <li>Run <b>Lateral camera</b> with <b>Parallax</b>. Reflections should follow the solid crosses as the floor moves underneath.</li>
50
- <li>Restart the same motion with <b>Surface only</b>, then <b>No temporal history</b>. Look for trails, delayed edges, and changes in noise.</li>
51
- <li>Try <b>Moving object</b> and <b>Occluder</b>. Inspect the magenta object’s trail and newly revealed floor. Use <b>Camera cut</b> to check reset.</li>
52
- </ol>
53
- <p><b>Spatial denoising</b> smooths neighbouring reflection pixels in three passes. It is on by default and independent of reprojection. Turn it off and select <b>No temporal history</b> to inspect the resolve output; resolve still reuses neighbouring ray hits and filters radiance for roughness.</p>
54
- <p>The crosses are geometric guides for object centres, not a pixel-perfect lighting reference. SSR loses information when reflected objects are hidden or off-screen. Surface only retains the new validation and blending fixes; it is not the old shader.</p>
55
- </details>
56
- </section>
57
- <aside>
58
- <label><span>Reprojection</span><select id="mode"><option value="0">Parallax</option><option value="1">Surface only</option><option value="2">No temporal history</option><option value="off">SSR off</option></select></label>
59
- <label><span>Experiment</span><select id="experiment"><option value="lateral">Lateral camera</option><option value="forward">Forward camera</option><option value="orbit">Orbit camera</option><option value="object">Moving object</option><option value="occluder">Occluder</option><option value="still">Still camera / noise</option><option value="free">Free camera</option></select></label>
60
- <label><span>Motion speed <output id="speed-value">1.0×</output></span><input id="speed" aria-label="Motion speed" type="range" min="0.2" max="2" value="1" step="0.1"></label>
61
- <label><span>Mirror roughness <output id="roughness-value">0.03</output></span><input id="roughness" aria-label="Mirror roughness" type="range" min="0.01" max="0.7" value="0.03" step="0.01"></label>
62
- <label><span>Render resolution</span><select id="resolution"><option value="640">640 × 400</option><option value="960" selected>960 × 600</option><option value="1280">1280 × 800</option></select></label>
63
- <label class="check"><input id="guides" type="checkbox" checked>Show geometric guides</label>
64
- <label class="check"><input id="denoising" type="checkbox" checked>Spatial denoising</label>
65
- <label class="check"><input id="taa" type="checkbox">Final-image TAA</label>
66
- <hr>
67
- <div class="buttons"><button id="play">Pause motion</button><button id="step">Step frame</button></div>
68
- <div class="buttons"><button id="restart">Restart path</button><button id="cut">Camera cut</button></div>
69
- <button id="reset">Reset reflection history</button>
70
- <hr>
71
- <button id="measure">Measure spatial filter</button>
72
- <button id="compile">Validate GPU shaders</button>
73
- <p id="status" role="status">Starting WebGPU…</p>
74
- <small>Fixed exposure. Bloom, sharpening, and motion blur are off. Free camera: drag to orbit, right-drag to pan, wheel to zoom.</small>
75
- </aside>
76
- </main>
77
- <script type="module" src="./main.js"></script>
78
- </body>
79
- </html>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Meep — SSR reflection lab</title>
7
+ <style>
8
+ :root { color-scheme: dark; font: 14px/1.5 system-ui, sans-serif; background: #11151c; color: #e4eaf2; }
9
+ * { box-sizing: border-box; }
10
+ body { margin: 0; padding: 24px; }
11
+ header { display: flex; align-items: baseline; gap: 16px; margin-bottom: 18px; }
12
+ h1 { font-size: 24px; margin: 0; font-weight: 600; }
13
+ header span, .hint, small { color: #a2afc1; }
14
+ main { display: grid; grid-template-columns: minmax(0, 1fr) 290px; gap: 20px; align-items: start; }
15
+ .stage { position: relative; background: #090c11; border: 1px solid #344153; border-radius: 8px; overflow: hidden; }
16
+ /* Renderer writes inline presentation dimensions; both layers must scale together. */
17
+ canvas { display: block; width: 100% !important; height: auto !important; }
18
+ #overlay { position: absolute; inset: 0; pointer-events: none; }
19
+ aside { background: #1a212c; border: 1px solid #344153; border-radius: 8px; padding: 18px; }
20
+ label { display: block; margin-bottom: 13px; }
21
+ label > span { display: block; font-size: 12px; color: #a2afc1; margin-bottom: 4px; }
22
+ select, button { width: 100%; background: #252f3e; border: 1px solid #45556a; color: inherit; border-radius: 5px; padding: 8px; font: inherit; }
23
+ button { cursor: pointer; }
24
+ button:hover { background: #344359; }
25
+ button:disabled { opacity: .4; cursor: default; }
26
+ .buttons { display: flex; gap: 8px; margin-bottom: 10px; }
27
+ .check { display: flex; gap: 8px; align-items: center; }
28
+ input[type=range] { width: 100%; accent-color: #74c8ff; }
29
+ hr { border: 0; border-top: 1px solid #344153; margin: 18px 0; }
30
+ #status { font: 12px/1.5 ui-monospace, Consolas, monospace; white-space: pre-wrap; color: #95d6b4; }
31
+ #status.error { color: #ff9a9a; }
32
+ #hint { min-height: 3em; margin: 14px 0 0; }
33
+ .legend { display: flex; gap: 24px; flex-wrap: wrap; margin-top: 12px; color: #a2afc1; font-size: 12px; }
34
+ .legend b { color: #dce9ff; }
35
+ details { margin-top: 18px; max-width: 85ch; color: #a2afc1; }
36
+ summary { color: #dce9ff; cursor: pointer; }
37
+ @media (max-width: 850px) { main { grid-template-columns: 1fr; } aside { width: 100%; } header { display: block; } }
38
+ </style>
39
+ </head>
40
+ <body tabindex="0">
41
+ <header><h1>SSR reflection lab</h1><span>Camera parallax · temporal stability · rejection</span></header>
42
+ <main>
43
+ <section>
44
+ <div class="stage"><canvas id="view" width="960" height="600" tabindex="0" aria-label="Interactive WebGPU reflection scene"></canvas><canvas id="overlay" width="960" height="600" aria-hidden="true"></canvas></div>
45
+ <div class="legend"><span><b>Solid cross</b> — ideal reflected centre</span><span><b>Dashed cross</b> — point fixed to the floor</span></div>
46
+ <p id="hint" class="hint"></p>
47
+ <details open><summary>A quick visual check</summary>
48
+ <ol>
49
+ <li>Run <b>Lateral camera</b> with <b>Parallax</b>. Reflections should follow the solid crosses as the floor moves underneath.</li>
50
+ <li>Restart the same motion with <b>Surface only</b>, then <b>No temporal history</b>. Look for trails, delayed edges, and changes in noise.</li>
51
+ <li>Try <b>Moving object</b> and <b>Occluder</b>. Inspect the magenta object’s trail and newly revealed floor. Use <b>Camera cut</b> to check reset.</li>
52
+ </ol>
53
+ <p><b>Spatial denoising</b> smooths neighbouring reflection pixels in three passes. It is on by default and independent of reprojection. Turn it off and select <b>No temporal history</b> to inspect the resolve output; resolve still reuses neighbouring ray hits and filters radiance for roughness.</p>
54
+ <p>The crosses are geometric guides for object centres, not a pixel-perfect lighting reference. SSR loses information when reflected objects are hidden or off-screen. Surface only retains the new validation and blending fixes; it is not the old shader.</p>
55
+ </details>
56
+ </section>
57
+ <aside>
58
+ <label><span>Reprojection</span><select id="mode"><option value="0">Parallax</option><option value="1">Surface only</option><option value="2">No temporal history</option><option value="off">SSR off</option></select></label>
59
+ <label><span>Experiment</span><select id="experiment"><option value="lateral">Lateral camera</option><option value="forward">Forward camera</option><option value="orbit">Orbit camera</option><option value="object">Moving object</option><option value="occluder">Occluder</option><option value="still">Still camera / noise</option><option value="free">Free camera</option></select></label>
60
+ <label><span>Motion speed <output id="speed-value">1.0×</output></span><input id="speed" aria-label="Motion speed" type="range" min="0.2" max="2" value="1" step="0.1"></label>
61
+ <label><span>Mirror roughness <output id="roughness-value">0.03</output></span><input id="roughness" aria-label="Mirror roughness" type="range" min="0.01" max="0.7" value="0.03" step="0.01"></label>
62
+ <label><span>Render resolution</span><select id="resolution"><option value="640">640 × 400</option><option value="960" selected>960 × 600</option><option value="1280">1280 × 800</option></select></label>
63
+ <label class="check"><input id="guides" type="checkbox" checked>Show geometric guides</label>
64
+ <label class="check"><input id="denoising" type="checkbox" checked>Spatial denoising</label>
65
+ <label class="check"><input id="tile-denoising" type="checkbox">Coverage tile denoising</label>
66
+ <label class="check"><input id="taa" type="checkbox">Final-image TAA</label>
67
+ <hr>
68
+ <div class="buttons"><button id="play">Pause motion</button><button id="step">Step frame</button></div>
69
+ <div class="buttons"><button id="restart">Restart path</button><button id="cut">Camera cut</button></div>
70
+ <button id="reset">Reset reflection history</button>
71
+ <hr>
72
+ <button id="measure">Measure spatial filter</button>
73
+ <button id="compile">Validate GPU shaders</button>
74
+ <p id="status" role="status">Starting WebGPU…</p>
75
+ <small>Fixed exposure. Bloom, sharpening, and motion blur are off. Free camera: drag to orbit, right-drag to pan, wheel to zoom.</small>
76
+ </aside>
77
+ </main>
78
+ <script type="module" src="./main.js"></script>
79
+ </body>
80
+ </html>