@woosh/meep-engine 3.60.4 → 3.60.5

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 (379) hide show
  1. package/CHANGELOG.md +2602 -0
  2. package/CONTRIBUTING.md +7 -0
  3. package/MIGRATION_2_TO_3.md +136 -0
  4. package/SECURITY.md +4 -0
  5. package/editor/EditorCameraNavigation.d.ts +2 -1
  6. package/editor/EditorCameraNavigation.d.ts.map +1 -1
  7. package/editor/EditorCameraNavigation.js +51 -57
  8. package/package.json +6 -2
  9. package/src/engine/EngineHarness.d.ts +3 -9
  10. package/src/engine/EngineHarness.d.ts.map +1 -1
  11. package/src/engine/EngineHarness.js +6 -8
  12. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts +9 -6
  13. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts.map +1 -1
  14. package/src/engine/graphics/camera/makeOrbitalCameraController.js +44 -71
  15. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts +64 -0
  16. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts.map +1 -0
  17. package/src/engine/graphics/camera/orbital/MomentumAxis.js +187 -0
  18. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts +86 -0
  19. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts.map +1 -0
  20. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.js +319 -0
  21. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts +34 -10
  22. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts.map +1 -1
  23. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +116 -30
  24. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts +1 -7
  25. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  26. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +1 -37
  27. package/src/engine/graphics/ecs/water/Water.d.ts +8 -0
  28. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  29. package/src/engine/graphics/ecs/water/Water.js +8 -0
  30. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  31. package/src/engine/graphics3/WaterSystem.js +731 -734
  32. package/src/engine/graphics3/decal/DecalTileShader.d.ts +61 -0
  33. package/src/engine/graphics3/decal/DecalTileShader.d.ts.map +1 -0
  34. package/src/engine/graphics3/decal/DecalTileShader.js +138 -0
  35. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts +10 -4
  36. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
  37. package/src/engine/graphics3/decal/GPUDecalRenderer.js +149 -44
  38. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts +25 -0
  39. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts.map +1 -0
  40. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.js +55 -0
  41. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts +22 -0
  42. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts.map +1 -0
  43. package/src/engine/graphics3/decal/graph_gather_decal_tiles.js +70 -0
  44. package/src/engine/graphics3/decal/shader_decal_composite.d.ts +7 -5
  45. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  46. package/src/engine/graphics3/decal/shader_decal_composite.js +48 -44
  47. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts +12 -0
  48. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts.map +1 -0
  49. package/src/engine/graphics3/decal/shader_decal_tile_copy.js +78 -0
  50. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts +11 -0
  51. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts.map +1 -0
  52. package/src/engine/graphics3/decal/shader_gather_decal_tiles.js +80 -0
  53. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts +13 -0
  54. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts.map +1 -0
  55. package/src/engine/input/devices/mouse/MouseEventButtons.js +23 -0
  56. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts +4 -0
  57. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts.map +1 -1
  58. package/src/engine/input/ecs/controllers/KeyboardCameraController.js +9 -45
  59. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  60. package/src/shade/playground/water_refraction/README.md +5 -6
  61. package/src/shade/playground/water_refraction/index.html +1 -1
  62. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  63. package/src/shade/renderer/Renderer.js +7 -5
  64. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts +40 -80
  65. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts.map +1 -1
  66. package/src/shade/renderer/camera/orbital/OrbitalCameraController.js +197 -147
  67. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  68. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  69. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  70. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  71. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  72. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +2 -2
  73. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +2 -2
  74. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +2 -1
  75. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
  76. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +18 -1
  77. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts +31 -0
  78. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts.map +1 -0
  79. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.js +343 -0
  80. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts +17 -0
  81. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts.map +1 -0
  82. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.js +29 -0
  83. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -1
  84. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +7 -13
  85. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  86. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +1 -8
  87. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +5 -5
  88. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
  89. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +10 -10
  90. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +1 -4
  91. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  92. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +1 -10
  93. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts +5 -15
  94. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts.map +1 -1
  95. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.js +6 -34
  96. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
  97. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +1 -7
  98. package/src/shade/renderer/hiz/HZBFootprint.d.ts +14 -0
  99. package/src/shade/renderer/hiz/HZBFootprint.d.ts.map +1 -0
  100. package/src/shade/renderer/hiz/HZBFootprint.js +16 -0
  101. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts +10 -0
  102. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts.map +1 -0
  103. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.js +11 -0
  104. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts +10 -0
  105. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts.map +1 -0
  106. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.js +11 -0
  107. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +0 -7
  108. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  109. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +405 -350
  110. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts +12 -0
  111. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts.map +1 -0
  112. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.js +27 -0
  113. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts +12 -0
  114. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts.map +1 -0
  115. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.js +27 -0
  116. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts +14 -0
  117. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts.map +1 -0
  118. package/src/shade/renderer/hiz/hzb_footprint_texels.js +34 -0
  119. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +0 -5
  120. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  121. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +37 -57
  122. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts +11 -0
  123. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts.map +1 -0
  124. package/src/shade/renderer/hiz/{shader_hzb_reduce_mip0_clip.js → shader_hzb_reduce_mip0.js} +120 -111
  125. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  126. package/src/shade/renderer/material/GPUMaterialManager.d.ts +7 -0
  127. package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
  128. package/src/shade/renderer/material/GPUMaterialManager.js +11 -0
  129. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +12 -12
  130. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  131. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +17 -118
  132. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  133. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +7 -2
  134. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +0 -6
  135. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  136. package/src/shade/renderer/water/GPUWaterRenderer.js +12 -17
  137. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts +35 -0
  138. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts.map +1 -0
  139. package/src/shade/renderer/water/medium/graph_water_meshlets.js +89 -0
  140. package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
  141. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  142. package/src/shade/renderer/water/shader_water.js +22 -13
  143. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
  144. package/src/shade/renderer/water/shader_water_avboit.js +7 -7
  145. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +1 -1
  146. package/src/shade/renderer/water/surface/graph_water_tessellation.js +1 -1
  147. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +9 -1
  148. package/editor/REWORK_PLAN_2026_08.md +0 -692
  149. package/src/CODEC_LAYOUT_VERIFICATION.md +0 -419
  150. package/src/CORRECTNESS_HUNT_2026_07_24.md +0 -142
  151. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +0 -738
  152. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +0 -79
  153. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +0 -93
  154. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +0 -80
  155. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +0 -164
  156. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +0 -69
  157. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +0 -110
  158. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +0 -129
  159. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +0 -72
  160. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +0 -74
  161. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +0 -169
  162. package/src/DUPLICATION_AUDIT_2026_08_07.md +0 -254
  163. package/src/DUPLICATION_AUDIT_REMEDIATION.md +0 -250
  164. package/src/DUPLICATION_REVIEW_2026_10_24.md +0 -1072
  165. package/src/REVIEW_2026_08_06/adv-core.md +0 -511
  166. package/src/REVIEW_2026_08_06/adv-gfx.md +0 -480
  167. package/src/REVIEW_2026_08_06/adv-view.md +0 -446
  168. package/src/REVIEW_2026_08_06/api-core.md +0 -296
  169. package/src/REVIEW_2026_08_06/api-engine.md +0 -195
  170. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +0 -507
  171. package/src/REVIEW_2026_08_06/core-binary.md +0 -348
  172. package/src/REVIEW_2026_08_06/core-collection.md +0 -376
  173. package/src/REVIEW_2026_08_06/core-math.md +0 -221
  174. package/src/REVIEW_2026_08_06/core-process.md +0 -266
  175. package/src/REVIEW_2026_08_06/core-science.md +0 -223
  176. package/src/REVIEW_2026_08_06/engine-ai-nav.md +0 -279
  177. package/src/REVIEW_2026_08_06/engine-animation.md +0 -167
  178. package/src/REVIEW_2026_08_06/engine-asset.md +0 -166
  179. package/src/REVIEW_2026_08_06/engine-ecs.md +0 -920
  180. package/src/REVIEW_2026_08_06/engine-input.md +0 -197
  181. package/src/REVIEW_2026_08_06/engine-misc.md +0 -248
  182. package/src/REVIEW_2026_08_06/engine-terrain.md +0 -52
  183. package/src/REVIEW_2026_08_06/generation.md +0 -176
  184. package/src/REVIEW_2026_08_06/geom-2d.md +0 -434
  185. package/src/REVIEW_2026_08_06/geom-3d.md +0 -117
  186. package/src/REVIEW_2026_08_06/gfx-gi.md +0 -143
  187. package/src/REVIEW_2026_08_06/gfx-render.md +0 -740
  188. package/src/REVIEW_2026_08_06/gfx-texture.md +0 -690
  189. package/src/REVIEW_2026_08_06/prior-criticals.md +0 -49
  190. package/src/REVIEW_2026_08_06/prior-highs.md +0 -138
  191. package/src/REVIEW_2026_08_06/test-gaps.md +0 -364
  192. package/src/REVIEW_2026_08_06/view.md +0 -667
  193. package/src/REVIEW_2026_08_06.md +0 -610
  194. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +0 -542
  195. package/src/core/bvh2/bvh3/NOTES.md +0 -2
  196. package/src/core/bvh8/NOTES.md +0 -63
  197. package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +0 -243
  198. package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +0 -417
  199. package/src/core/color/COLOR_REVIEW_2026_08_28.md +0 -779
  200. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +0 -433
  201. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +0 -486
  202. package/src/core/geom/3d/atlas/BFF_PLAN.md +0 -174
  203. package/src/core/geom/3d/atlas/REVIEW_02.md +0 -392
  204. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +0 -146
  205. package/src/core/geom/3d/gjk/NOTES.md +0 -8
  206. package/src/core/geom/3d/topology/TOPO_MESH_RETIREMENT_PLAN.md +0 -135
  207. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +0 -381
  208. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +0 -684
  209. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +0 -554
  210. package/src/core/model/reactive/evaluation/PREDICATE_APPLICABILITY_PROPOSAL.md +0 -446
  211. package/src/engine/REVIEW_2026_07_03.md +0 -211
  212. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +0 -815
  213. package/src/engine/control/first-person/DESIGN.md +0 -658
  214. package/src/engine/control/first-person/DESIGN_COLLISION.md +0 -382
  215. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +0 -563
  216. package/src/engine/control/first-person/TODO.md +0 -160
  217. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +0 -531
  218. package/src/engine/ecs/HIERARCHY_REVIEW.md +0 -717
  219. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +0 -262
  220. package/src/engine/ecs/ik/PLAN.md +0 -179
  221. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +0 -272
  222. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +0 -199
  223. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +0 -521
  224. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +0 -463
  225. package/src/engine/graphics/ecs/camera/pp/NOTES.md +0 -48
  226. package/src/engine/graphics/render/frame_graph/CORRECTNESS_FIXES_2026_09_04.md +0 -659
  227. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +0 -235
  228. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +0 -490
  229. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +0 -466
  230. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +0 -717
  231. package/src/engine/input/ecs/ism/ISM_DESIGN.md +0 -1523
  232. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +0 -627
  233. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +0 -661
  234. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +0 -229
  235. package/src/engine/network/CONGESTION_CONTROL.md +0 -87
  236. package/src/engine/network/MESSAGE_TRANSPORT.md +0 -364
  237. package/src/engine/network/SMOOTHNESS_DESIGN.md +0 -400
  238. package/src/engine/physics/cloth/MEASUREMENTS.md +0 -5304
  239. package/src/engine/physics/cloth/PLAN.md +0 -3056
  240. package/src/engine/physics/fluid/GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md +0 -1382
  241. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +0 -243
  242. package/src/engine/physics/fluid/solver/NOTES.md +0 -46
  243. package/src/engine/sound/SOUND_EMITTER_RETIREMENT_PLAN.md +0 -536
  244. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +0 -432
  245. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +0 -250
  246. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +0 -322
  247. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +0 -261
  248. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +0 -121
  249. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +0 -999
  250. package/src/format/image/avif/DECISIONS.md +0 -545
  251. package/src/format/image/jpeg/NOTES.md +0 -3
  252. package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +0 -814
  253. package/src/generation/COORDINATES.md +0 -54
  254. package/src/generation/REVIEW_01_ACTION_PLAN.md +0 -628
  255. package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +0 -220
  256. package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +0 -172
  257. package/src/generation/foliage/CROWN_FULLNESS_BRIEF_2026_10_26.md +0 -86
  258. package/src/generation/foliage/FOLIAGE_DESIGN.md +0 -547
  259. package/src/generation/foliage/FOLIAGE_PLAN.md +0 -198
  260. package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +0 -158
  261. package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +0 -521
  262. package/src/generation/foliage/REAL_TIME_TREES_BRIEF_2026_10_27.md +0 -86
  263. package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +0 -347
  264. package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +0 -248
  265. package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +0 -694
  266. package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +0 -552
  267. package/src/shade/RENDERER_CONTRACT.md +0 -697
  268. package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +0 -263
  269. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +0 -569
  270. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +0 -755
  271. package/src/shade/device/mock/EXTENSION_PROPOSAL_2026_08_22.md +0 -458
  272. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +0 -255
  273. package/src/shade/device/mock/REVIEW_2026_08_22.md +0 -527
  274. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +0 -894
  275. package/src/shade/device/timing/profile/SGPT_FORMAT.md +0 -345
  276. package/src/shade/renderer/NOTES.md +0 -78
  277. package/src/shade/renderer/PLAN.md +0 -8
  278. package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +0 -145
  279. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +0 -357
  280. package/src/shade/renderer/atmosphere/NOTES.md +0 -6
  281. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts +0 -76
  282. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts.map +0 -1
  283. package/src/shade/renderer/camera/orbital/MomentumAxis.js +0 -164
  284. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts +0 -45
  285. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts.map +0 -1
  286. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.js +0 -66
  287. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts +0 -98
  288. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts.map +0 -1
  289. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.js +0 -184
  290. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts +0 -175
  291. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts.map +0 -1
  292. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.js +0 -411
  293. package/src/shade/renderer/camera/orbital/README.md +0 -81
  294. package/src/shade/renderer/camera/orbital/index.d.ts +0 -6
  295. package/src/shade/renderer/camera/orbital/index.d.ts.map +0 -1
  296. package/src/shade/renderer/camera/orbital/index.js +0 -9
  297. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +0 -964
  298. package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +0 -361
  299. package/src/shade/renderer/geometry/bvh/BLAS_REFIT_BATCH_PLAN.md +0 -619
  300. package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +0 -674
  301. package/src/shade/renderer/geometry/sdf/NOTES.md +0 -6
  302. package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +0 -540
  303. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +0 -305
  304. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +0 -913
  305. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +0 -787
  306. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +0 -481
  307. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +0 -301
  308. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +0 -448
  309. package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +0 -385
  310. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +0 -656
  311. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +0 -308
  312. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +0 -657
  313. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +0 -551
  314. package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +0 -529
  315. package/src/shade/renderer/global_illumination/lpv/NOTES.md +0 -3
  316. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +0 -4306
  317. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +0 -410
  318. package/src/shade/renderer/global_illumination/sharc/NOTES.md +0 -8
  319. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +0 -1369
  320. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +0 -202
  321. package/src/shade/renderer/gpu_primitive/indirect/NOTES.md +0 -94
  322. package/src/shade/renderer/hiz/NOTES.md +0 -3
  323. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +0 -8
  324. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +0 -1
  325. package/src/shade/renderer/light/cluster/NOTES.md +0 -11
  326. package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +0 -524
  327. package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +0 -1101
  328. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts +0 -24
  329. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts.map +0 -1
  330. package/src/shade/renderer/material/resident/shader_resident_texture_means.js +0 -120
  331. package/src/shade/renderer/particles/DESIGN.md +0 -1011
  332. package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +0 -280
  333. package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +0 -453
  334. package/src/shade/renderer/path_tracer/PATH_TRACED_MEDIA_PLAN_2026_10_02.md +0 -520
  335. package/src/shade/renderer/postprocess/bloom/NOTES.md +0 -6
  336. package/src/shade/renderer/postprocess/fsr/NOTES.md +0 -4
  337. package/src/shade/renderer/postprocess/gtao/NOTES.md +0 -6
  338. package/src/shade/renderer/postprocess/nss/NOTES.md +0 -332
  339. package/src/shade/renderer/postprocess/perspective/NOTES.md +0 -3
  340. package/src/shade/renderer/postprocess/sscs/NOTES.md +0 -5
  341. package/src/shade/renderer/postprocess/ssr/NOTES.md +0 -14
  342. package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +0 -286
  343. package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -39
  344. package/src/shade/renderer/postprocess/taa/NOTES.md +0 -11
  345. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +0 -78
  346. package/src/shade/renderer/postprocess/vrs/NOTES.md +0 -4
  347. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +0 -1218
  348. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +0 -600
  349. package/src/shade/renderer/restir/reservoir/NOTES.md +0 -6
  350. package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +0 -6
  351. package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +0 -391
  352. package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +0 -153
  353. package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +0 -322
  354. package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +0 -247
  355. package/src/shade/renderer/shader/chunk/fsr/NOTES.md +0 -3
  356. package/src/shade/renderer/shader/chunk/fsr/spd/NOTES.md +0 -4
  357. package/src/shade/renderer/shader/chunk/geometry/ray/NOTES.md +0 -4
  358. package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +0 -159
  359. package/src/shade/renderer/shader/chunk/mboit/NOTES.md +0 -38
  360. package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/NOTES.md +0 -3
  361. package/src/shade/renderer/shader/chunk/spherical_harmonics/sampling/NOTES.md +0 -8
  362. package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +0 -927
  363. package/src/shade/renderer/shadow/contact/FINDINGS.md +0 -413
  364. package/src/shade/renderer/shadow/map/csm/NOTES.md +0 -5
  365. package/src/shade/renderer/shadow/ray/NOTES.md +0 -31
  366. package/src/shade/renderer/shadow/sdf/NOTES.md +0 -9
  367. package/src/shade/renderer/shadow/sdf/brixelizer/NOTES.md +0 -5
  368. package/src/shade/renderer/shadow/sdf/cascade/NOTES.md +0 -9
  369. package/src/shade/renderer/shadow/vsm/NOTES.md +0 -12
  370. package/src/shade/renderer/shadow/vsm/PLAN.md +0 -442
  371. package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +0 -43
  372. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +0 -2444
  373. package/src/shade/renderer/texture/mipmaps/NOTES.md +0 -3
  374. package/src/shade/renderer/volumetrics/NOTES.md +0 -1329
  375. package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +0 -336
  376. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +0 -23
  377. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +0 -1
  378. package/src/shade/renderer/water/medium/GPUWaterBodies.js +0 -75
  379. package/src/shade/wgsl/NOTES.md +0 -4
@@ -1,466 +0,0 @@
1
- # FLIP on the CPU — implementation plan
2
-
3
- **Status:** LDR-FLIP implemented and conformance-tested against NVIDIA's `flip-evaluator` 1.7.
4
- HDR-FLIP and the Shade compute port remain unbuilt.
5
- **Scope:** CPU only, `Sampler2D` in / `Sampler2D` out.
6
-
7
- Sections below describe what was built; where the implementation departed from the original
8
- proposal, the departure is called out inline.
9
-
10
- FLIP is NVIDIA's perceptual image difference evaluator (Andersson et al., HPG 2020; HDR
11
- extension 2021). It answers "would a person flipping back and forth between these two images
12
- notice a difference, and where" — which is exactly the question a renderer regression gate
13
- wants answered, and exactly the question RMSE/PSNR answer badly.
14
-
15
- Reference: <https://github.com/NVlabs/flip> (BSD-3-Clause),
16
- <https://research.nvidia.com/publication/flip>.
17
-
18
- The output is a per-pixel error image in `[0, 1]` plus a scalar pooled score. It is symmetric,
19
- parameter-free apart from the viewing condition (pixels per degree), and it degrades
20
- gracefully: a uniform tint scores low, a displaced edge scores high.
21
-
22
- ---
23
-
24
- ## 1. What already exists, and what has to be written
25
-
26
- The bulk of the pipeline is colour-space plumbing and separable convolution. meep already owns
27
- most of the former.
28
-
29
- ### Reused verbatim
30
-
31
- | Need | Module |
32
- | --- | --- |
33
- | sRGB u8 RGBA -> linear f32 RGBA | `engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js` |
34
- | linear sRGB -> CIE XYZ (D65) | `core/color/xyz/rgb_to_xyz.js` |
35
- | CIE XYZ -> linear sRGB (D65) | `core/color/xyz/xyz_to_rgb.js` |
36
- | clamp to gamut | `core/math/clamp01.js` (`saturate`) |
37
- | `Y/116 + 16/116` channel remap | `sampler2d_channel_linear_transform.js` (gradient/intercept fit it exactly) |
38
- | replicate a channel into a wider image | `sampler2d_copy_channel_data.js` |
39
- | float image container | `Sampler2D.float32(...)`, `Sampler2D.uint8(...)` |
40
- | base64 for the conformance fixture and the colour map | `core/binary/base64/Base64.js` |
41
- | integer-backing check on the public entry point | `core/collection/array/typed/typed_array_is_integer.js` |
42
-
43
- The existing `rgb_to_xyz` / `xyz_to_rgb` matrices are the sRGB D65 primaries, which is what
44
- FLIP uses. `convert_channel_sRGB_to_linear` is the standard piecewise EOTF with the same
45
- `0.04045` breakpoint FLIP uses. Both drop in without modification.
46
-
47
- Downstream of the module, `sampler2d_to_data_url.js` and `downloadSamplerAsPNG.js` take the magma
48
- output straight to a browser harness. Nothing in the module calls them.
49
-
50
- ### Deliberately *not* reused
51
-
52
- - **`filter/sampler2d_blur_gaussian.js`** — cannot be used. It samples taps with
53
- `sampleChannelBilinear` at fractional offsets (an approximation of the filter, not the
54
- filter), applies one kernel to every channel, and only does Gaussians. FLIP needs a distinct
55
- kernel per channel, a two-Gaussian channel, and first/second derivative kernels.
56
- - **`filter/sampler2d_scale_down_generic.js`** — 2D radial weight function tied to a resampling
57
- ratio. Wrong shape.
58
- - **`sampler2d_combine.js`** — the proposal expected to use it for the two per-pixel difference
59
- passes, and the implementation does not. It marshals each texel into a plain array before handing
60
- it to the callback, whereas `lab_distance_hyab` already reads `(array, offset)` directly; going
61
- through it would add a copy per pixel and an indirection without shortening the call site.
62
- (Surveying it for this did turn up a defect, since fixed — see §7.)
63
- - **`computeStatisticalMean` / `computeStatisticalPartialMedian`** — the mean is accumulated in the
64
- same pass that already copies the map and finds its extremes, so routing it through a helper that
65
- takes `number[]` would add a pass and a conversion. The median helper is the wrong statistic
66
- outright; see §3.7.
67
- - **`PNGReader`** — the conformance fixture inlines raw RGB bytes rather than PNGs, which are
68
- smaller once base64'd and cannot be edited out of step with the expectation.
69
-
70
- ### New code
71
-
72
- | Module | Purpose |
73
- | --- | --- |
74
- | `core/color/ycxcz/xyz_to_ycxcz.js` | CIE XYZ -> YyCxCz opponent space |
75
- | `core/color/ycxcz/ycxcz_to_xyz.js` | inverse |
76
- | `core/color/lab/xyz_to_lab.js` | CIE XYZ -> CIELab (D65) |
77
- | `core/color/lab/lab_apply_hunt_adjustment.js` | FLIP's Hunt effect scaling of `a`,`b` by `0.01 * L` |
78
- | `core/color/lab/lab_distance_hyab.js` | HyAB colour distance |
79
- | `core/color/illuminant/D65_TRISTIMULUS_XYZ.js` | the white point the above three share |
80
- | `engine/graphics/texture/sampler/filter/SeparableKernel.js` | per-channel, per-axis tap storage |
81
- | `engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js` | **generic** separable convolution, per-channel kernels, clamp-to-edge |
82
- | `.../compare/flip/flip_constants.js` | the published constants |
83
- | `.../compare/flip/FlipParameters.js` | viewing condition |
84
- | `.../compare/flip/flip_compute_pixels_per_degree.js` | ppd from monitor geometry |
85
- | `.../compare/flip/flip_build_spatial_kernel.js` | CSF kernel, 4 channels |
86
- | `.../compare/flip/flip_build_feature_kernel.js` | point/edge kernels, 4 channels |
87
- | `.../compare/flip/flip_srgb_to_ycxcz.js` | decode and move into the opponent space |
88
- | `.../compare/flip/flip_filter_spatial.js` | CSF filter and the trip to Hunt-adjusted CIELab |
89
- | `.../compare/flip/flip_filter_feature.js` | point and edge detection |
90
- | `.../compare/flip/flip_compute_color_difference.js` | HyAB plus the redistribution curve |
91
- | `.../compare/flip/flip_compute_feature_difference.js` | the stronger of the two structural disagreements |
92
- | `.../compare/flip/sampler2d_flip_ldr.js` | the pipeline; returns the error map |
93
- | `.../compare/flip/FlipPooling.js` + `flip_pool.js` | mean / weighted median / min / max |
94
- | `engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js` | **generic** channel-through-a-lookup-table mapping |
95
- | `core/color/colormap/MAGMA_LUT.js` | the colour map the error visualization uses |
96
-
97
- The magma table is matplotlib's, which is public domain; nothing is copied from the FLIP sources.
98
-
99
- The last two started as a single `flip_error_to_magma` and were promoted out: neither the lookup
100
- nor the colour map has anything to do with FLIP. What remained FLIP-specific after the split was
101
- nothing at all, so no wrapper was kept.
102
-
103
- `Sampler2D` lives under `engine/`, so anything touching it must live under `engine/` too — the
104
- colour primitives are pure math and belong in `core/color/`, per the core/engine decoupling
105
- rule.
106
-
107
- ---
108
-
109
- ## 2. The one new generic primitive
110
-
111
- Everything convolutional in FLIP reduces to a single operation, and it is worth having in
112
- `filter/` on its own merits:
113
-
114
- ```js
115
- sampler2d_convolve_separable(output, input, kernel) // kernel: SeparableKernel
116
- ```
117
-
118
- The four loose parameters of the proposal became one `SeparableKernel` — taps, radius and channel
119
- count travel together, and callers stop being able to pair the wrong radius with the wrong taps.
120
-
121
- Notes on the shape:
122
-
123
- - **Per-channel kernels** are the whole reason this is new. FLIP's achromatic, red-green and
124
- blue-yellow channels have different spatial extents, and the feature stage wants a derivative
125
- along one axis and a smoothing kernel along the other *in the same pass*.
126
- - **Separate `kernel_x` / `kernel_y`** buys the feature stage for free: pack
127
- `[edge_x, edge_y, point_x, point_y]` as four channels with `kernel_x = [dg, g, ddg, g]` and
128
- `kernel_y = [g, dg, g, ddg]`, and one call produces all four responses.
129
- - **Clamp-to-edge** by clamping the tap index. At integer coordinates `sampleChannelBilinear`
130
- already degenerates to a clamped texel fetch, so this is the same operator written plainly;
131
- `readChannel` does *not* clamp, so it cannot be used directly.
132
- - Intermediate buffer: a module-static `Float64Array` grown on demand, following the pattern
133
- already in `sampler2d_blur_gaussian.js`. f64 accumulation, f32 storage.
134
- - **Loop order**: taps outside, channels inside. The border clamp is then evaluated once per tap
135
- rather than once per tap per channel, and a texel's channels accumulate in memory order. Measured
136
- at 2.3x on the four channel images this pipeline uses — see §8.
137
-
138
- ---
139
-
140
- ## 3. Pipeline
141
-
142
- All stages operate on full images; no tiling in v1.
143
-
144
- ### 3.0 Decode
145
-
146
- `sampler2d_srgb_to_linear(reference)` and the same for `test`. 4x u8 -> 4x f32. Alpha is carried
147
- but ignored — FLIP is defined on RGB.
148
-
149
- Precondition: both images share dimensions. Assert it; do not resample.
150
-
151
- ### 3.1 Linear RGB -> YyCxCz
152
-
153
- Per pixel: `rgb_to_xyz` then `xyz_to_ycxcz`, against the D65 reference white:
154
-
155
- ```
156
- Y = 116 * (y / Yn) - 16
157
- Cx = 500 * (x / Xn - y / Yn)
158
- Cz = 200 * (y / Yn - z / Zn)
159
- ```
160
-
161
- Output: 3x f32 `Sampler2D` per image. This image feeds *both* the spatial stage and the feature
162
- stage — the feature detectors read the **unfiltered** achromatic channel.
163
-
164
- **On the white point.** `D65_TRISTIMULUS_XYZ` is derived by summing the rows of `rgb_to_xyz`, not
165
- quoted from a table. The reference implementation quotes `(0.950428545, 1, 1.088900371)`, which
166
- differs from meep's matrix in the fifth digit — enough that its own reference white does not land
167
- exactly on `L* = 100, a* = 0, b* = 0`. Deriving the white from the matrix it is used with makes that
168
- round trip exact, which is a property `xyz_to_lab.spec.js` asserts, and the conformance spec shows
169
- the choice costs nothing in agreement: the residual is below half a quantization step regardless.
170
-
171
- ### 3.2 Spatial contrast sensitivity filter
172
-
173
- The CSF is fitted in the frequency domain as a sum of exponentials, giving a spatial-domain
174
- kernel that is a sum of Gaussians. Constants (Y, Cx, Cz):
175
-
176
- ```
177
- a1 = [ 1.0, 1.0, 34.1 ]
178
- b1 = [ 0.0047, 0.0053, 0.04 ]
179
- a2 = [ 0.0, 0.0, 13.5 ]
180
- b2 = [ 1.0e-5, 1.0e-5, 0.025 ]
181
- ```
182
-
183
- `a2 = 0` for Y and Cx, so those are single Gaussians and separable outright. Cz is a sum of two
184
- Gaussians, which is *not* separable as one kernel — but each term is, so the reference
185
- implementation runs both terms as separate channels and sums them after the vertical pass. That
186
- maps onto our primitive directly:
187
-
188
- 1. Build a **4-channel** input `[Y, Cx, Cz, Cz]` by copying channel 2 twice
189
- (`sampler2d_copy_channel_data`).
190
- 2. `flip_build_spatial_kernel(ppd)` returns a 4-channel kernel `[gY, gCx, gCz1, gCz2]` and a
191
- radius, with
192
-
193
- ```
194
- radius = ceil(3 * sqrt(max(b) / (2 * PI^2)) * ppd) // max(b) = 0.04
195
- delta_x = 1 / ppd
196
- ix = (i - radius) * delta_x
197
-
198
- gaussian(x2, a, b) = a * sqrt(PI / b) * exp(-PI^2 * x2 / b)
199
- gaussian_sqrt(x2, a, b) = sqrt(a * sqrt(PI / b)) * exp(-PI^2 * x2 / b)
200
-
201
- gY[i] = gaussian(ix*ix, a1.Y, b1.Y)
202
- gCx[i] = gaussian(ix*ix, a1.Cx, b1.Cx)
203
- gCz1[i] = gaussian_sqrt(ix*ix, a1.Cz, b1.Cz)
204
- gCz2[i] = gaussian_sqrt(ix*ix, a2.Cz, b2.Cz)
205
- ```
206
-
207
- `gaussian_sqrt` takes the square root of the *amplitude only*, so that the outer product
208
- `g(x) * g(y)` reproduces the intended 2D Gaussian.
209
-
210
- Normalization: `gY` and `gCx` are scaled to sum to 1 (which makes their 2D outer products sum
211
- to 1). The two Cz terms are scaled *jointly* by `1 / sqrt(S1^2 + S2^2)`, because the 2D mass
212
- of the summed filter is `S1^2 + S2^2`.
213
-
214
- 3. One `sampler2d_convolve_separable` call with the same kernel on both axes.
215
- 4. Fold: `Cz = ch2 + ch3`, producing a 3-channel filtered YCxCz.
216
-
217
- At the reference default (ppd 67.02) the radius is 10, so a 21-tap kernel; at a more typical
218
- 1080p desktop condition (ppd ~47) it is 7, so 15 taps.
219
-
220
- ### 3.3 Filtered YCxCz -> Hunt-adjusted CIELab
221
-
222
- Per pixel:
223
-
224
- ```
225
- ycxcz_to_xyz -> xyz_to_rgb -> saturate each channel -> rgb_to_xyz -> xyz_to_lab -> hunt
226
- ```
227
-
228
- The round trip through linear RGB is not redundant: the filtered signal can leave the sRGB
229
- gamut, and the clamp has to happen in **linear RGB** before Lab, exactly as the reference does.
230
-
231
- CIELab uses the same D65 white and the standard `delta = 6/29` piecewise nonlinearity.
232
-
233
- Hunt adjustment (this is FLIP's own lightness-dependent chroma scaling, unrelated to the
234
- existing `core/color/hunt/xyz_to_hpe.js` matrix):
235
-
236
- ```
237
- a' = 0.01 * L * a
238
- b' = 0.01 * L * b
239
- ```
240
-
241
- Output: 3x f32 per image.
242
-
243
- ### 3.4 Colour difference
244
-
245
- ```
246
- d = |L_r - L_t| + sqrt((a_r - a_t)^2 + (b_r - b_t)^2) // HyAB
247
- d = d ^ qc // qc = 0.7
248
-
249
- cmax = HyAB(hunt(lab(green)), hunt(lab(blue))) ^ qc // green/blue sRGB primaries
250
- pccmax = pc * cmax // pc = 0.4
251
-
252
- dc = d < pccmax
253
- ? (pt / pccmax) * d // pt = 0.95
254
- : pt + ((d - pccmax) / (cmax - pccmax)) * (1 - pt)
255
- ```
256
-
257
- `cmax` is computed once at module load from the primaries rather than hard-coded — it is a
258
- derived value, not a magic number.
259
-
260
- Output: 1x f32.
261
-
262
- ### 3.5 Feature difference
263
-
264
- 1. Normalized achromatic channel from the **unfiltered** YCxCz:
265
- `y_n = Y * (1/116) + (16/116)` — one `sampler2d_channel_linear_transform` call.
266
- 2. Broadcast into a 4-channel image (four `sampler2d_copy_channel_data` calls).
267
- 3. `flip_build_feature_kernel(ppd)`:
268
-
269
- ```
270
- std_dev = 0.5 * w * ppd // w = 0.082
271
- radius = ceil(3 * std_dev)
272
-
273
- g(x) = exp(-x^2 / (2 * std_dev^2))
274
- dg(x) = -x * g(x) // 1st derivative -> edge
275
- ddg(x) = (x^2 / std_dev^2 - 1) * g(x) // 2nd derivative -> point
276
- ```
277
-
278
- `g` is normalized to sum 1; `dg` and `ddg` have their positive and negative lobes normalized
279
- separately to `+1` and `-1`. Because `g` is non-negative and sums to 1, doing this in 1D
280
- gives the same lobe sums in the 2D outer product, so the separable build is equivalent to the
281
- reference's 2D normalization.
282
-
283
- `kernel_x = [dg, g, ddg, g]`, `kernel_y = [g, dg, g, ddg]`.
284
- 4. One `sampler2d_convolve_separable` call per image.
285
- 5. Per pixel:
286
-
287
- ```
288
- edge = hypot(ch0, ch1)
289
- point = hypot(ch2, ch3)
290
- df = ( (1 / sqrt(2)) * max(|edge_r - edge_t|, |point_r - point_t|) ) ^ qf // qf = 0.5
291
- ```
292
-
293
- Output: 1x f32.
294
-
295
- ### 3.6 Combine
296
-
297
- ```
298
- error = dc ^ (1 - df)
299
- ```
300
-
301
- 1x f32 `Sampler2D`, values in `[0, 1]`. This is the deliverable.
302
-
303
- ### 3.7 Pooling
304
-
305
- `flip_pool(error)` -> `FlipPooling { mean, weighted_median, minimum, maximum }`.
306
-
307
- **Correction to the proposal.** It claimed the reference tool derives its weighted median from a
308
- 100-bin histogram. It does not — the statistic is exact. Walk the error values in ascending order
309
- accumulating *error*, and return the value at which half the total has been reached. Checked
310
- against the tool: it reports `0.969462` for the fixture in §5, the exact walk gives
311
- `0.9694619774818420`, and a 100-bin histogram reading of the same data gives `0.965`. The spec pins
312
- this, because the number alone does not reveal which definition produced it.
313
-
314
- `computeStatisticalPartialMedian` is not reusable here — it is the median of the pixels, not of the
315
- error, and the two differ by a lot on any image whose disagreement is localized. Sorting is done on
316
- a `Float64Array` copy, which sorts numerically without a comparator and leaves the caller's map
317
- untouched.
318
-
319
- ### 3.8 Visualization
320
-
321
- `sampler2d_channel_apply_lut(error, 0, MAGMA_LUT, 3)` -> 3x u8 `Sampler2D`, then the existing
322
- `sampler2d_to_data_url` / `downloadSamplerAsPNG` for browser harnesses. This is what makes the
323
- metric usable — the error map is the point, the scalar is the summary.
324
-
325
- The lookup is a general sampler operation rather than a FLIP one. It maps a chosen channel onto
326
- table entries linearly across a range (defaulting to `[0, 1]`, which suits normalized data; pass
327
- `0, 255` to index a palette by a raw 8-bit value), and the result takes its channel count and its
328
- backing array type from the table. Magma is worth the table lookup over a greyscale ramp because it
329
- is monotonic in *perceptual lightness* — `MAGMA_LUT.spec.js` asserts exactly that, in CIELAB. Rec.709
330
- luma over the gamma-encoded bytes reverses at three of the 256 entries, which is a good illustration
331
- of why the property has to be stated in the right space to mean anything.
332
-
333
- ---
334
-
335
- ## 4. Public surface
336
-
337
- ```js
338
- const params = new FlipParameters();
339
- params.pixels_per_degree = flip_compute_pixels_per_degree(0.7, 0.7, 3840); // distance m, width m, px
340
-
341
- const error = sampler2d_flip_ldr(reference, test, params); // 1x f32 Sampler2D
342
- const pooled = flip_pool(error); // FlipPooling
343
-
344
- const preview = sampler2d_channel_apply_lut(error, 0, MAGMA_LUT, 3); // 3x u8 Sampler2D
345
- ```
346
-
347
- `FlipParameters` is a class with typed fields and a sensible default (ppd 67.0206, the reference
348
- tool's default viewing condition), not an options bag.
349
-
350
- ---
351
-
352
- ## 5. Verification
353
-
354
- `sampler2d_flip_ldr.spec.js` is the gate. Black-box only — observable behaviour of the error
355
- map, never internal call structure.
356
-
357
- **Structural, cheap, catches most breakage:**
358
-
359
- - `flip(A, A)` is zero at every pixel, for several images including hard edges and noise.
360
- - Symmetry: `flip(A, B)` equals `flip(B, A)` pixel for pixel. FLIP is symmetric by construction
361
- (HyAB and both feature differences are absolute), so any asymmetry is a bug.
362
- - Monotonicity: increasing a uniform luminance offset increases the mean, without exception.
363
- - A one-pixel shift of a high-contrast edge scores materially higher than a uniform tint of
364
- equal RMSE — the property that motivates using FLIP at all.
365
- - Kernel specs: spatial kernels sum to 1 per channel (Cz jointly), derivative lobes sum to
366
- `+/-1`, radius matches the published formulas at several ppd values.
367
-
368
- **Colour primitives** get their own specs against published values: D65 white -> `L*=100, a=0,
369
- b=0`; a YCxCz round trip returns the input; sRGB primaries land on their tabulated Lab
370
- coordinates.
371
-
372
- **Conformance** against the reference implementation is the one that actually pins the constants,
373
- and it is in `sampler2d_flip_ldr.conformance.spec.js`. A 24x24 quadrant fixture — one quadrant per
374
- path through the metric — is inlined as base64 along with the full error map and the pooled
375
- statistics that `flip-evaluator` 1.7 produces for it, at two viewing conditions. `PNGReader` turned
376
- out to be unnecessary: raw RGB bytes inline more compactly than a PNG and cannot drift from the
377
- expectation.
378
-
379
- Result: **the worst pixel in the map diverges by 0.498 of one 8-bit step**, which is below the
380
- half-step floor of what a quantized expectation can resolve — the two implementations agree to the
381
- last digit the fixture can express. The pooled mean differs by `4e-6` at the default condition and
382
- `1e-5` at the coarse one, consistent with the reference accumulating in float32 where this
383
- accumulates in float64.
384
-
385
- To regenerate the fixture — only needed if the fixture images themselves change — install the
386
- reference tool and read the values back out of it:
387
-
388
- ```bash
389
- pip install flip-evaluator
390
- ```
391
-
392
- `flip.evaluate(reference, test, "LDR", applyMagma=False)` returns the raw error map and the mean;
393
- the CLI (`flip -r ref.png -t test.png`) is the only source for the pooled median and quartiles.
394
- Neither is a dependency of the build or the test suite.
395
-
396
- ---
397
-
398
- ## 6. Phasing
399
-
400
- 1. ~~Colour primitives in `core/color/` + specs.~~ **Done.** Independently useful; CIELab and HyAB
401
- were missing from meep regardless of FLIP.
402
- 2. ~~`sampler2d_convolve_separable` + specs.~~ **Done.** Also independently useful.
403
- 3. ~~LDR-FLIP pipeline + the gate spec.~~ **Done.**
404
- 4. ~~Pooling and magma visualization.~~ **Done.** Wiring into the `probeShade*` harnesses so a
405
- device screenshot can be diffed against a golden is **not** done.
406
- 5. *Not built:* HDR-FLIP.
407
- 6. *Not built:* Shade compute port. The algorithm is embarrassingly parallel and the WGSL test
408
- infrastructure exists, so this is a natural follow-up — but the CPU path is what a vitest gate
409
- can call, so it comes first.
410
-
411
- Built: 23 implementation modules, 12 spec files, 67 tests.
412
-
413
- ---
414
-
415
- ## 7. Risks and open decisions
416
-
417
- 1. ~~**Conformance fixtures.**~~ **Resolved:** taken, and it was the right call — see §5. The tool
418
- was run once offline to generate the fixture; nothing from it is a runtime or test dependency.
419
-
420
- 2. ~~**Float precision.**~~ **Measured:** `4e-6` to `1e-5` on the pooled mean, so the spec asserts
421
- to 4 decimal places. Storage is f32 and accumulation f64, as proposed.
422
-
423
- 3. ~~**`sampler2d_combine` has a latent bug.**~~ Its read loop for `input1` was bounded by
424
- `input0.itemSize`, so it silently truncated when the second input was wider. Found while
425
- surveying it for reuse here, and **fixed separately** in `0bc24b72a`.
426
-
427
- 4. **HDR-FLIP must not reuse `tonemap_aces`.** The reference uses the Narkowicz rational fit with
428
- a 0.6 pre-exposure cancellation baked into the coefficients; meep's `tonemap_aces` is Stephen
429
- Hill's matrix fit. They are different operators and swapping them breaks parity. HDR-FLIP also
430
- needs `computeExposureParameters`, whose constants should be lifted verbatim from `FLIP.h`
431
- rather than re-derived.
432
-
433
- 5. **Provenance.** FLIP is BSD-3-Clause. Constants and the magma LUT are fine to port with the
434
- notice retained in the module headers; confirm before copying the LUT table verbatim.
435
-
436
- 6. ~~**Two formulas to re-check at implementation time.**~~ Both are confirmed correct by the
437
- conformance spec — a wrong amplitude on the two-lobe blue-yellow filter or a wrong lobe
438
- normalization would not agree to within half a quantization step.
439
-
440
- 7. **Redundant work in the feature pass.** The four detector channels are convolved from four
441
- copies of the same signal, and in the horizontal pass two of them (the two whose horizontal
442
- kernel is the plain gaussian) compute an identical result. Removing the duplicate would mean a
443
- convolution primitive whose second pass can read a different channel than its first, which is a
444
- worse primitive; the waste is roughly 12% of the feature filter. Left as is deliberately.
445
-
446
- ---
447
-
448
- ## 8. Cost
449
-
450
- Measured at 1920x1080, default viewing condition (spatial radius 10, feature radius 9):
451
-
452
- | Stage | Cost |
453
- | --- | --- |
454
- | decode + opponent space | ~0.3s per image |
455
- | spatial filter + trip to CIELab | ~1.7s per image |
456
- | feature filter | ~1.2s per image |
457
- | both difference passes + pooling | ~0.45s total |
458
-
459
- **Roughly 6 seconds for a 1080p pair warm, 10 cold.** The proposal guessed "low single-digit
460
- seconds" and was out by about 3x: the first working version measured 16s, and reordering the
461
- convolution loops (§2) brought it to 6. The two separable passes are ~90% of what remains.
462
-
463
- Fine for an offline gate and for a harness button; not fine for a per-frame in-engine overlay. The
464
- next lever, if one is wanted, is splitting the convolution into border and interior ranges so that
465
- the ~99% of pixels which cannot reach an edge skip the clamp entirely. After that the honest answer
466
- is the GPU, which is phase 6.