@woosh/meep-engine 3.24.0 → 3.25.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 (322) hide show
  1. package/package.json +1 -1
  2. package/src/engine/graphics3/PickingSystem.d.ts +3 -3
  3. package/src/engine/graphics3/PickingSystem.d.ts.map +1 -1
  4. package/src/engine/graphics3/PickingSystem.js +52 -55
  5. package/src/engine/graphics3/shader_picking_gather.d.ts +40 -0
  6. package/src/engine/graphics3/shader_picking_gather.d.ts.map +1 -0
  7. package/src/engine/graphics3/shader_picking_gather.js +120 -0
  8. package/src/engine/physics/cloth/MEASUREMENTS.md +1050 -0
  9. package/src/engine/physics/cloth/PLAN.md +411 -49
  10. package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts +52 -0
  11. package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts.map +1 -0
  12. package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.js +70 -0
  13. package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts +12 -3
  14. package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts.map +1 -1
  15. package/src/engine/physics/cloth/ecs/ClothDynamics.js +340 -331
  16. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +36 -22
  17. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  18. package/src/engine/physics/cloth/ecs/ClothInstance.js +38 -23
  19. package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts +84 -0
  20. package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts.map +1 -0
  21. package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.js +141 -0
  22. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +51 -82
  23. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  24. package/src/engine/physics/cloth/ecs/ClothSystem.js +81 -869
  25. package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts +48 -0
  26. package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts.map +1 -0
  27. package/src/engine/physics/cloth/ecs/ClothWorkerCore.js +107 -0
  28. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +286 -0
  29. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -0
  30. package/src/engine/physics/cloth/ecs/ClothWorld.js +1104 -0
  31. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +182 -0
  32. package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -0
  33. package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +562 -0
  34. package/src/engine/physics/cloth/ecs/cloth.worker.d.ts +2 -0
  35. package/src/engine/physics/cloth/ecs/cloth.worker.d.ts.map +1 -0
  36. package/src/engine/physics/cloth/ecs/cloth.worker.js +12 -0
  37. package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
  38. package/src/engine/physics/cloth/ecs/cloth_build_rows.js +2 -1
  39. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +58 -0
  40. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
  41. package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +341 -271
  42. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +20 -2
  43. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
  44. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +39 -15
  45. package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts +29 -0
  46. package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts.map +1 -0
  47. package/src/engine/physics/cloth/ecs/cloth_worker_protocol.js +78 -0
  48. package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts +17 -0
  49. package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts.map +1 -0
  50. package/src/engine/physics/cloth/ecs/makeClothWorker.js +18 -0
  51. package/src/engine/physics/cloth/playground/README.md +73 -2
  52. package/src/engine/physics/cloth/playground/wind.html +166 -0
  53. package/src/engine/physics/cloth/playground/wind_build.d.ts +81 -0
  54. package/src/engine/physics/cloth/playground/wind_build.d.ts.map +1 -0
  55. package/src/engine/physics/cloth/playground/wind_build.js +141 -0
  56. package/src/engine/physics/cloth/playground/wind_main.d.ts +2 -0
  57. package/src/engine/physics/cloth/playground/wind_main.d.ts.map +1 -0
  58. package/src/engine/physics/cloth/playground/wind_main.js +681 -0
  59. package/src/engine/physics/cloth/solver/ClothState.d.ts +134 -79
  60. package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
  61. package/src/engine/physics/cloth/solver/ClothState.js +273 -12
  62. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +33 -0
  63. package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
  64. package/src/engine/physics/cloth/solver/cloth_solver_constants.js +34 -0
  65. package/src/engine/physics/cloth/solver/cloth_step.d.ts +21 -2
  66. package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
  67. package/src/engine/physics/cloth/solver/cloth_step.js +173 -4
  68. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts +40 -0
  69. package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -0
  70. package/src/engine/physics/cloth/solver/cloth_step_instance.js +284 -0
  71. package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts +107 -0
  72. package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts.map +1 -0
  73. package/src/engine/physics/cloth/solver/cloth_wind_accelerate.js +263 -0
  74. package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts +88 -0
  75. package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts.map +1 -0
  76. package/src/engine/physics/cloth/wind/AbstractClothWind.js +100 -0
  77. package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts +85 -0
  78. package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts.map +1 -0
  79. package/src/engine/physics/cloth/wind/ClothAmbientWind.js +182 -0
  80. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts +79 -0
  81. package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -0
  82. package/src/engine/physics/cloth/wind/ClothFluidWind.js +210 -0
  83. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +101 -0
  84. package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -0
  85. package/src/engine/physics/cloth/wind/cloth_sample_air.js +290 -0
  86. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  87. package/src/shade/device/mock/texture_copy.d.ts.map +1 -1
  88. package/src/shade/device/mock/texture_copy.js +28 -0
  89. package/src/shade/playground/vgeo_runtime/README.md +94 -0
  90. package/src/shade/playground/vgeo_runtime/index.html +62 -0
  91. package/src/shade/playground/vgeo_runtime/main.d.ts +2 -0
  92. package/src/shade/playground/vgeo_runtime/main.d.ts.map +1 -0
  93. package/src/shade/playground/vgeo_runtime/main.js +399 -0
  94. package/src/shade/playground/vgeo_viewer/sample_asset.js +1 -1
  95. package/src/shade/renderer/camera/Camera.d.ts +14 -0
  96. package/src/shade/renderer/camera/Camera.d.ts.map +1 -1
  97. package/src/shade/renderer/camera/Camera.js +7 -1
  98. package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.d.ts.map +1 -1
  99. package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.js +16 -1
  100. package/src/shade/renderer/geometry/GPUGeometryManager.d.ts.map +1 -1
  101. package/src/shade/renderer/geometry/GPUGeometryManager.js +29 -9
  102. package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts +10 -0
  103. package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts.map +1 -1
  104. package/src/shade/renderer/geometry/GPUGeometryMetadata.js +13 -0
  105. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +46 -18
  106. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +70 -22
  107. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +97 -19
  108. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +12 -2
  109. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  110. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +14 -3
  111. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts +2 -2
  112. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
  113. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +124 -6
  114. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts +22 -0
  115. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts.map +1 -0
  116. package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.js +53 -0
  117. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +8 -1
  118. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
  119. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +8 -1
  120. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts +16 -0
  121. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts.map +1 -0
  122. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.js +15 -0
  123. package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts +54 -0
  124. package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts.map +1 -0
  125. package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.js +53 -0
  126. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +13 -2
  127. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
  128. package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +14 -2
  129. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +100 -0
  130. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
  131. package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +113 -0
  132. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.d.ts.map +1 -1
  133. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.js +5 -0
  134. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
  135. package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +108 -0
  136. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
  137. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +23 -0
  138. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
  139. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +40 -0
  140. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +228 -0
  141. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -0
  142. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +932 -0
  143. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +176 -0
  144. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -0
  145. package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +425 -0
  146. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +241 -0
  147. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +574 -0
  148. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +120 -0
  149. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -0
  150. package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +674 -0
  151. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +287 -0
  152. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -0
  153. package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +976 -0
  154. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts +42 -0
  155. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts.map +1 -0
  156. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.js +96 -0
  157. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts +16 -0
  158. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts.map +1 -0
  159. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.js +15 -0
  160. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts +19 -0
  161. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -0
  162. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +83 -0
  163. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts +9 -0
  164. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts.map +1 -0
  165. package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.js +8 -0
  166. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts +19 -0
  167. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -0
  168. package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +160 -0
  169. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts +3 -0
  170. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -0
  171. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +255 -0
  172. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts +3 -0
  173. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -0
  174. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +80 -0
  175. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts +3 -0
  176. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -0
  177. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +144 -0
  178. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts +3 -0
  179. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -0
  180. package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +530 -0
  181. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +39 -0
  182. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -0
  183. package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +54 -0
  184. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts +41 -0
  185. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts.map +1 -0
  186. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.js +56 -0
  187. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts +49 -0
  188. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -0
  189. package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +328 -0
  190. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts +10 -0
  191. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts.map +1 -0
  192. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.js +51 -0
  193. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts +13 -0
  194. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts.map +1 -0
  195. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.js +12 -0
  196. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +17 -0
  197. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts.map +1 -0
  198. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +99 -0
  199. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts +9 -0
  200. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts.map +1 -0
  201. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +8 -0
  202. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +7 -0
  203. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts.map +1 -0
  204. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +27 -0
  205. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts +14 -0
  206. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts.map +1 -0
  207. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.js +13 -0
  208. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts +7 -0
  209. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts.map +1 -0
  210. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.js +35 -0
  211. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts +9 -0
  212. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts.map +1 -0
  213. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.js +8 -0
  214. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts +15 -0
  215. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts.map +1 -0
  216. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.js +14 -0
  217. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +7 -0
  218. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts.map +1 -0
  219. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +36 -0
  220. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +10 -0
  221. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -0
  222. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +9 -0
  223. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts +11 -0
  224. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts.map +1 -0
  225. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.js +54 -0
  226. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts +9 -0
  227. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts.map +1 -0
  228. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.js +8 -0
  229. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts +11 -0
  230. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts.map +1 -0
  231. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.js +10 -0
  232. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts +7 -0
  233. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts.map +1 -0
  234. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.js +30 -0
  235. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts +9 -0
  236. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts.map +1 -0
  237. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.js +8 -0
  238. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts +13 -0
  239. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts.map +1 -0
  240. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.js +12 -0
  241. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts +14 -0
  242. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts.map +1 -0
  243. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.js +13 -0
  244. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +11 -0
  245. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts.map +1 -0
  246. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +61 -0
  247. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts +9 -0
  248. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -0
  249. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +8 -0
  250. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts +13 -0
  251. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts.map +1 -0
  252. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.js +12 -0
  253. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts +9 -0
  254. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts.map +1 -0
  255. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.js +8 -0
  256. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts +20 -0
  257. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts.map +1 -0
  258. package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.js +19 -0
  259. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts +23 -0
  260. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -0
  261. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +70 -0
  262. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +18 -0
  263. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -0
  264. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +25 -0
  265. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts +35 -0
  266. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts.map +1 -0
  267. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.js +41 -0
  268. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +32 -0
  269. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -0
  270. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +138 -0
  271. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  272. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  273. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
  274. package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +9 -0
  275. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  276. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +12 -1
  277. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
  278. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +12 -1
  279. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
  280. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +10 -0
  281. package/src/shade/renderer/scene/GPUSceneContext.d.ts +18 -0
  282. package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
  283. package/src/shade/renderer/scene/GPUSceneContext.js +647 -594
  284. package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
  285. package/src/shade/renderer/scene/rows/GPUSceneRows.js +22 -0
  286. package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts +35 -0
  287. package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts.map +1 -0
  288. package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.js +91 -0
  289. package/src/engine/graphics3/instantiate_scene_bundle.d.ts +0 -54
  290. package/src/engine/graphics3/instantiate_scene_bundle.d.ts.map +0 -1
  291. package/src/engine/graphics3/shade_node_to_entity_composition.d.ts +0 -39
  292. package/src/engine/graphics3/shade_node_to_entity_composition.d.ts.map +0 -1
  293. package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts +0 -35
  294. package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts.map +0 -1
  295. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts +0 -96
  296. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts.map +0 -1
  297. package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts +0 -20
  298. package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts.map +0 -1
  299. package/src/shade/renderer/animation/skin_bind_transform.d.ts +0 -25
  300. package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +0 -1
  301. package/src/shade/renderer/animation/skin_frame_defect.d.ts +0 -34
  302. package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +0 -1
  303. package/src/shade/renderer/animation/skin_joint_matrix.d.ts +0 -24
  304. package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +0 -1
  305. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +0 -70
  306. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +0 -1
  307. package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts +0 -96
  308. package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts.map +0 -1
  309. package/src/shade/renderer/scene/InstanceBatch.d.ts +0 -92
  310. package/src/shade/renderer/scene/InstanceBatch.d.ts.map +0 -1
  311. package/src/shade/renderer/scene/Mesh.d.ts +0 -55
  312. package/src/shade/renderer/scene/Mesh.d.ts.map +0 -1
  313. package/src/shade/renderer/scene/Node3D.d.ts +0 -159
  314. package/src/shade/renderer/scene/Node3D.d.ts.map +0 -1
  315. package/src/shade/renderer/scene/Node3DDirtyList.d.ts +0 -59
  316. package/src/shade/renderer/scene/Node3DDirtyList.d.ts.map +0 -1
  317. package/src/shade/renderer/scene/SceneNodeBinding.d.ts +0 -52
  318. package/src/shade/renderer/scene/SceneNodeBinding.d.ts.map +0 -1
  319. package/src/shade/renderer/scene/SkinnedMesh.d.ts +0 -27
  320. package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +0 -1
  321. package/src/shade/renderer/scene/TransformAuthority.d.ts +0 -19
  322. package/src/shade/renderer/scene/TransformAuthority.d.ts.map +0 -1
@@ -2237,3 +2237,1053 @@ it too, at seed, alongside the subtree walk and the graph colouring that seeding
2237
2237
  already does. Nothing pays it per step.
2238
2238
 
2239
2239
  ---
2240
+
2241
+ ## 38. What M5 proved wrong in PLAN.md
2242
+
2243
+ Four things, and the first two are the same mistake read from two directions:
2244
+ §12 described the worker's memory as a property of the *world*, and it is a
2245
+ property of the *cloth*.
2246
+
2247
+ | # | The design said | The measurement said |
2248
+ |---|---|---|
2249
+ | 0 | §12 — "**One `SharedArrayBuffer` for the whole cloth world**, not one per instance", with "each instance gets a region inside the buffer" | It is one per cloth, and the reseed contract is why. A `ClothState` is sized at seed from that cloth's particle and row counts, and `ClothSystem#reseed` — a chain gaining a link, a `ClothDynamics` change the row tables depend on — builds a new one of a different size while other cloths are mid-flight. A world buffer would need an allocator with relocation, and a relocation invalidates every view of every cloth in it. One buffer per cloth costs one cold-plane message per seed and makes a re-seed a re-registration. §39 |
2250
+ | 1 | §12 — the collider table is a region of the per-cloth layout; `ClothInstance` (M2) — it lives on the instance instead, "because its size is a property of the *world* … §12 puts it in the shared buffer when M5 needs it there" | §12 was right about *where* and M2 was right about *why*, and the two are not in conflict: the size is a property of the world, so the region has a capacity rather than a growth policy. A growable array is memory the worker would have to be told about again mid-run. What the capacity has to be sized against is not a garment but a **scene** — a cloth's table holds every collider on every character standing inside its query box: ten for one body, twenty for two, thirty for three. §41 |
2251
+ | 2 | §12 — "`ClothDynamics` reaches the worker through the command region, written every step" | Every step, yes; `ClothDynamics` , no. By the time the step can use them a cloth's dynamics have been through `cloth_dynamics_map.js` — a retention, a velocity limit, a self-collision radius, gravity rotated into the local frame — and sending the raw component would mean running that mapping a second time on the far side with no way to prove the two agreed. What crosses is the **step's own parameter block**, and `ClothSystem` fills the identical block in a private scratch array. §40 |
2252
+ | 3 | §18 — "**Open — the write-back has no interpolation.** §12 gives cloth `PhysicsSystem`'s producer / `__interp_restore` contract … It lands with M5" | It does not, and the contract named is not the one cloth needs. `Interpolated` is an **entity-keyed marker** — `InterpolationSystem` files snapshots under a key it assigns from the entity id — and a rigged garment's simulated things are `Node3D`s inside a skinned model, which are not entities. Cloth also needs only half of the contract it was pointed at: `__interp_restore` exists to undo render-time writes before the sim reads them, and cloth never reads back what it writes. §43 |
2253
+
2254
+ **And one thing the implementation confirmed rather than corrected**, which is
2255
+ the milestone's own gate. §12: "`ClothSystem` and `WorkerClothSystem` must
2256
+ produce bit-identical results. Identical code over identical data in an
2257
+ identical order." They do, and the number is zero — not at the end of a run but
2258
+ at **every step of one**, over the whole `ClothState` buffer. §40.
2259
+
2260
+ ---
2261
+
2262
+ ## 39. One buffer for the whole cloth world is one buffer per cloth
2263
+
2264
+ §12's opening sentence is the one M5 could not implement as written, and the
2265
+ obstacle is not the worker — it is `ClothSystem#reseed`, which M1 built and M5
2266
+ inherited.
2267
+
2268
+ A `ClothState` is one allocation whose every region is sized from the cloth:
2269
+ particle count, stretch rows, bend rows, leash rows, backstops, colours. Seeding
2270
+ produces it. **Re-seeding produces a different one**, and re-seeding is an
2271
+ ordinary event rather than a teardown: `cloth_dynamics_change_needs_reseed`
2272
+ returns true whenever a field the row tables depend on moves, a chain that gains
2273
+ a link says so through `reseed(entity)`, and a rigged garment re-seeds when its
2274
+ model finishes loading. Each of those changes at least one region's length.
2275
+
2276
+ Putting every cloth in one buffer therefore means an allocator with relocation —
2277
+ and a relocation is not a local event. Every view of every region of the moved
2278
+ cloth has to be re-cut, on both threads, while other cloths in the same buffer
2279
+ have steps in flight against their own regions. The engine has `OffsetAllocator`
2280
+ and it could do this; what it cannot do is make the *re-cut* free, and the
2281
+ re-cut is the part that has to be coordinated across a thread boundary.
2282
+
2283
+ One buffer per cloth costs, instead, exactly one cold-plane message per seed:
2284
+
2285
+ ```
2286
+ add_cloth { id, state: {buffer, six counts}, self_exclusions }
2287
+ remove_cloth { id }
2288
+ ```
2289
+
2290
+ and makes a re-seed a re-registration, which is the same code path as a first
2291
+ seed. The registration is what the worker is waiting for, so a cloth that
2292
+ re-seeds is simply not stepped for the few ticks the message takes — the same
2293
+ window a newly spawned garment already sits in.
2294
+
2295
+ **The layout is not serialized anywhere.** The descriptor carries the buffer and
2296
+ the six counts, and the worker runs the *same* `allocate` the main thread ran, so
2297
+ there is no second description of the layout that could drift from the first.
2298
+ That is worth saying because the alternative — a table of byte offsets in the
2299
+ message — is the obvious implementation and it is the one that rots.
2300
+
2301
+ ### What it cost to move the collider table inside
2302
+
2303
+ M2 left `collider_table` on the instance, growing by doubling. M5 moved it into
2304
+ the state at a fixed `CLOTH_COLLIDER_SLOTS`. The layout change is a `Float64`
2305
+ result region at the head of the buffer (three words, 8-byte aligned where the
2306
+ buffer's own alignment is free) and the table plus its key array inserted before
2307
+ the contact slots.
2308
+
2309
+ Both are on the hot path's critical structure, so the question is whether the
2310
+ step got slower. It did not:
2311
+
2312
+ ```
2313
+ interleaved A/B in one process, per-frame minimum over 1,200 frames
2314
+
2315
+ case HEAD's layout M5's layout ratio states agree
2316
+ 400p 2x4 1.100 ms 1.082 ms 0.983 bit-identical
2317
+ 400p 2x4 + self 1.454 ms 1.399 ms 0.962 bit-identical
2318
+ 576p 2x2 0.847 ms 0.803 ms 0.949 bit-identical
2319
+ 1024p 2x4 4.582 ms 4.487 ms 0.979 bit-identical
2320
+ ```
2321
+
2322
+ 0.95 to 0.98 is not a speed-up; it is noise with a sign. The bit-identical
2323
+ column is the load-bearing one: the two states are built from the same rest pose
2324
+ and stepped with the same inputs, and they stay equal to the last bit for 1,600
2325
+ steps, so the layout change moved bytes and nothing else.
2326
+
2327
+ ### Two things about measuring it that cost an hour
2328
+
2329
+ **The machine was not idle and could not be made idle.** Nineteen node processes
2330
+ and 30–38% background CPU throughout — other work in the same checkout. M4's own
2331
+ lesson says a timing run needs a quiet machine; what M4 did not have to answer is
2332
+ what to do when the machine will not go quiet. The answer is to stop measuring
2333
+ absolutes and measure a **ratio from interleaved samples**: whatever the rest of
2334
+ the machine is doing, it is doing it to both variants inside the same frame.
2335
+
2336
+ **And an interleaved A/B has to alternate its order**, which the first version of
2337
+ this harness did not. Running A then B every frame charges A for everything the
2338
+ rest of the machine evicted between frames, and B reads the cache A just warmed.
2339
+ The bias is not small:
2340
+
2341
+ ```
2342
+ ratio, M5 against HEAD A always first alternating
2343
+ 400p 2x4 0.693 0.983
2344
+ 400p 2x4 + self 0.738 0.962
2345
+ 576p 2x2 0.662 0.949
2346
+ 1024p 2x4 0.882 0.979
2347
+ ```
2348
+
2349
+ A 30% "improvement" that is entirely an artifact of going second. Anyone
2350
+ re-running a paired measurement in this repo should alternate.
2351
+
2352
+ ---
2353
+
2354
+ ## 40. The parameter block is the protocol, and that is why the parity is structural
2355
+
2356
+ §12 asks for bit-identical results between the two systems and calls the
2357
+ mechanism "identical code over identical data in an identical order". M3 met the
2358
+ same bar between the entity and rig paths (§25) by making the row builder and
2359
+ the pose reconstruction **one function each** rather than two that agreed. M5
2360
+ does the same thing one level down, and the shape it takes is a block of words.
2361
+
2362
+ `cloth_step_instance(state, exclusions, params, offset)` reads every number it
2363
+ needs out of a `Float64Array`: `dt`, gravity already rotated into the cloth's
2364
+ local frame, the substep and iteration counts, the damping retention, the
2365
+ velocity clamp, the contact radius, the self-collision radius, the collider
2366
+ count, and whether self-collision is on at all. `ClothSystem` fills that block in
2367
+ a thirteen-word private scratch array and calls the function. `WorkerClothSystem`
2368
+ fills a thirteen-word slice of the shared command region and the worker calls the
2369
+ function. **Neither system computes a step parameter of its own**, so neither can
2370
+ compute one differently.
2371
+
2372
+ `Float64` throughout the block is load-bearing rather than incidental. Every
2373
+ number in it is produced in double on the main thread and consumed in double by
2374
+ the step, so the round trip through shared memory is exact. An `f32` command
2375
+ region would round the velocity clamp, and a clamp that rounds is a velocity that
2376
+ rounds.
2377
+
2378
+ The same argument decided where the *results* live. The step answers with three
2379
+ scalars — the largest speed left, which the sleep test reads, and two diagnostic
2380
+ counts — and a `ClothState` attached over shared memory in a worker is a
2381
+ different JavaScript object over the same bytes, so a plain field on it is
2382
+ invisible to the main thread. They go in the buffer, in `Float64`, at the head
2383
+ where the alignment is free. `Float32` would have been enough for a diagnostic
2384
+ count and not for the speed: it is compared against `CLOTH_SLEEP_SPEED_RATIO ·
2385
+ reach`, and an f32 round on one side of that comparison and not the other is a
2386
+ cloth that falls asleep one step earlier than its twin and diverges from there.
2387
+
2388
+ ### The number
2389
+
2390
+ ```
2391
+ 240 steps, a 10-link rope, anchor walking a sine with a turn in it
2392
+ 180 steps, three 10-link strands in cotton, two colliders, one of them moving
2393
+
2394
+ differing bytes in ClothState's buffer, per step: 0
2395
+ differing bytes at the end of the run: 0
2396
+ ```
2397
+
2398
+ **Compared every step, not at the end.** A divergence that appears at step 200
2399
+ and one that appears at step 2 read the same from the end of a run and they are
2400
+ very different bugs — the first is drift, the second is a wrong parameter. The
2401
+ comparison is the whole buffer rather than selected fields: particle positions
2402
+ and velocities, every dual and penalty, the collider table, the contact slots and
2403
+ their warm-started multipliers, the self-collision pair list, and the step's own
2404
+ result words.
2405
+
2406
+ The second run is there because the first never takes two of the branches:
2407
+ `cloth_contact_find` with records in the table, and `cloth_self_find` at all.
2408
+ Cotton is one of §33's three entries that set `ClothDynamicsFlags.SelfCollision`,
2409
+ and the run is self-checked — it asserts that the collider table filled, that
2410
+ contact rows were created, and that self-collision pairs were found — because a
2411
+ parity test over a branch that did not run is a parity test over nothing.
2412
+
2413
+ ### What the write-back does, which is not identical and is not meant to be
2414
+
2415
+ The scene's poses lag by one tick. At tick `T` the worker system joins step
2416
+ `T − 1` and writes it back, then dispatches step `T`. The spec pins that exactly:
2417
+ the local transforms the worker system writes at tick `T` equal the ones
2418
+ `ClothSystem` wrote at tick `T − 1`, every tick, for the whole run.
2419
+
2420
+ **The local transforms rather than the world ones**, and the reason is a sentence
2421
+ M1 already wrote. A node's world pose is a function of its local pose *and of the
2422
+ anchor's pose now*, so two scenes whose steps are one tick apart show the same
2423
+ shape in different places: "the cloth's *shape* is one fixed step stale; its
2424
+ *position* is not, because the attachment cascade carries the whole subtree
2425
+ rigidly with the anchor". §4.1 says that about running cloth before the poser.
2426
+ The worker's one-tick lag is the same statement with the same consequence, which
2427
+ is a reassuring place for it to land: the quality cost of the worker is a cost
2428
+ the design had already priced.
2429
+
2430
+ ---
2431
+
2432
+ ## 41. What the main thread stops paying for
2433
+
2434
+ §17 asks M5 for "a measured main-thread cost reduction". The loopback is what
2435
+ makes it measurable: in production the worker's step and the main thread's tick
2436
+ overlap, but under `SystemWorkerLoopback` they are two calls on one thread, so
2437
+ `em.update()` is *exactly* what the main thread pays and `runPendingStep()` is
2438
+ *exactly* what moved.
2439
+
2440
+ ```
2441
+ 400 warm-up ticks then 900 measured, 3 rounds, minimum of the minima
2442
+ a garment as an entity subtree, anchor walking; "+ body" is a ten-part
2443
+ character collider set in the scene
2444
+
2445
+ workload ClothSystem WorkerClothSystem the worker's main thread
2446
+ main thread main thread step saved
2447
+
2448
+ COTTON 1024p 3.623 ms 0.181 ms 4.084 ms 95.0%
2449
+ COTTON 1024p + body 5.222 ms 0.206 ms 4.845 ms 96.1%
2450
+ SILK 400p 1.666 ms 0.059 ms 1.488 ms 96.5%
2451
+ SILK 400p + body 1.853 ms 0.066 ms 1.739 ms 96.4%
2452
+ DENIM 576p 1.106 ms 0.081 ms 0.992 ms 92.7%
2453
+ ROPE 40 links 0.130 ms 0.006 ms 0.129 ms 95.5%
2454
+ ```
2455
+
2456
+ **92% to 96%**, and the figure is not a surprise — `MEASUREMENTS.md` §7 put 69%
2457
+ of a frame in the stretch rows and 18% in the bend rows, and those are what
2458
+ crossed. What is worth stating is what is *left* on the main thread: the anchor's
2459
+ pose, the local-frame carry, the leash centres, the broadphase query and its
2460
+ rewrite into the cloth's frame, the sleep test, and the write-back. All of it
2461
+ reads components, walks hierarchies or queries an index, and all of it together
2462
+ is a fifth of a millisecond for a thousand-particle garment.
2463
+
2464
+ The columns are minima taken from separate runs on a machine carrying other work,
2465
+ so they do not add up exactly — the 1024p inline minimum landed below the
2466
+ worker's own step minimum, which is impossible within one scene and ordinary
2467
+ across two. The ratio is the result; the absolute column is an upper bound.
2468
+
2469
+ **Note what this does not do**, and §17 already says it: the worker moves the
2470
+ cost, it does not reduce it. "2 ms on the worker" with several characters in
2471
+ flight is a scheduling question, and `WorkerSystem` answers it with one step in
2472
+ flight at a time and a fixed-step gate — so the budget §17 sets is now a budget
2473
+ for *all the cloth in the scene together*, on one thread, per tick. What changed
2474
+ is that it no longer competes with the renderer for the same thread.
2475
+
2476
+ ### How full the per-cloth collider table gets
2477
+
2478
+ This is the measurement that sized `CLOTH_COLLIDER_SLOTS`, and it moved the
2479
+ number, because it showed the table is bounded by the **scene** rather than by
2480
+ the garment:
2481
+
2482
+ ```
2483
+ 600 ticks, a garment walking through a scene of ten-part character bodies
2484
+
2485
+ garment colliders in the scene worst mean capacity
2486
+ SILK 10 10 10.0 64
2487
+ COTTON 10 10 10.0 64
2488
+ DENIM 10 8 7.7 64
2489
+ ROPE 10 10 9.0 64
2490
+ COTTON 20 20 19.1 64
2491
+ COTTON 30 30 24.9 64
2492
+ ```
2493
+
2494
+ A garment's query box is its own particle bounds fattened by a step's travel, and
2495
+ a garment is worn *on* a body — so what lands in the table is every collider that
2496
+ body carries, and every collider carried by anyone standing close enough to touch
2497
+ it. One character fills ten slots. Three fill thirty. There is no garment-shaped
2498
+ number here at all.
2499
+
2500
+ Thirty-two was the first choice, against M2's twenty-collider scene, and three
2501
+ characters would have been inside 88% of it with an **arbitrary** drop past that
2502
+ — `ClothColliderIndex#query` answers in its own order, not in order of how much a
2503
+ collider matters, so an overflow discards colliders the cloth may be resting on.
2504
+ Sixty-four is six characters, nine kilobytes a cloth, and 1.4% of the §17
2505
+ workload's state — the same order as the 16% §33 already spends on
2506
+ self-collision storage that four of seven entries never read. The drop still
2507
+ warns once, because the capacity is a bound and not a rule.
2508
+
2509
+ ---
2510
+
2511
+ ## 42. The library, re-measured, and why no entry is re-sized
2512
+
2513
+ §17 says to re-measure at M5 and re-size anything that drifted outside. The
2514
+ absolute table taken with `cloth_library_bench.mjs` reads 15% to 25% above M4's:
2515
+
2516
+ ```
2517
+ M4's §33 M5's run entry
2518
+ 1.427 → 1.802 1.641 → 2.069 SILK 2x4
2519
+ 1.478 → 1.802 1.547 → 2.002 COTTON 2x4
2520
+ 1.297 → 1.765 1.458 → 2.020 DENIM 2x2
2521
+ 1.315 → 1.828 1.435 → 2.039 LEATHER 2x2
2522
+ 0.116 → 0.129 0.126 → 0.133 ROPE 2x4
2523
+ 0.165 → 0.183 0.183 → 0.215 CHAIN 2x6
2524
+ 1.438 → 1.798 1.571 → 1.997 HAIR 2x4
2525
+ ```
2526
+
2527
+ **That is the machine, not the milestone**, and the paired measurement in §39 is
2528
+ what says so. `cloth_library_bench.mjs` does not go anywhere near
2529
+ `WorkerClothSystem` — it calls `cloth_self_find` and `cloth_step` directly over a
2530
+ `ClothState`, so the only thing M5 changed that it can see is the buffer layout,
2531
+ and that is 0.95 to 0.98 with the states staying bit-identical. Every one of
2532
+ these entries is 400 to 576 particles at 2×4 or 2×2, which are exactly the four
2533
+ shapes §39 paired.
2534
+
2535
+ The run was taken with nineteen other node processes on the machine and 30–38%
2536
+ background CPU, which M4's own lesson predicts will cost 9% to 29%. It cost 15%
2537
+ to 25%. **No entry is re-sized**: re-sizing a library on numbers taken from a
2538
+ busy machine would bake someone else's test run into a shipped garment.
2539
+
2540
+ What the measurement *is* good for is the shape, and the shape is unchanged: the
2541
+ three entries that set `ClothDynamicsFlags.SelfCollision` are still the tightest,
2542
+ the chains still find no pairs at all, and leather still pays the most for the
2543
+ fewest. §33's ordering stands entry for entry.
2544
+
2545
+ **The thing to re-measure on a quiet machine** is the absolute column, and the
2546
+ question it answers is no longer "does a garment fit in a frame" but "does all
2547
+ the cloth in a scene fit in a worker tick" — §41's last paragraph. Whoever does
2548
+ it should check the process count first; this section exists so they know to.
2549
+
2550
+ ---
2551
+
2552
+ ## 43. Interpolation: the named contract cannot express a rigged garment
2553
+
2554
+ §18 carried "the write-back has no interpolation" as open and said it lands with
2555
+ M5, "where the worker makes the fixed-step producer contract load-bearing for a
2556
+ second reason". The second reason is real and it points the other way: the worker
2557
+ is what makes the simple implementation impossible.
2558
+
2559
+ **Three findings, in the order they change the answer.**
2560
+
2561
+ **1. `Interpolated` is entity-keyed, and a garment's joints are not entities.**
2562
+ The contract §13's reuse ledger points at is `PhysicsSystem`'s: an `Interpolated`
2563
+ marker component per interpolated body, carrying a `key` the
2564
+ `InterpolationSystem` assigns from the entity id at link time, and a list of
2565
+ interpolands each naming a component class and its serialization adapter. That
2566
+ works for a rope, whose links are entities. It cannot express a **rigged**
2567
+ garment at all: M3's write-back drives `Node3D.transform_local` on the joints of
2568
+ a skinned model, and `shade_node_to_entity_composition` does not expand a skinned
2569
+ node into entities — "that is a boundary rather than an omission" (D47, quoted in
2570
+ §3). There is no entity to put the component on and no key to file the snapshot
2571
+ under. §13's ledger row is wrong for half the feature.
2572
+
2573
+ **2. Cloth needs half the contract, and it is the half with no name.**
2574
+ `__interp_restore` exists because the render-time blend writes into the very
2575
+ components the simulation reads, so the sim has to be handed its authoritative
2576
+ pose back before it integrates. Cloth's simulation state is `ClothState.position`
2577
+ in the cloth's local frame, and `cloth_write_back` writes nodes `1` upward —
2578
+ never the anchor, which is the one transform it reads. **Cloth never reads back
2579
+ what it writes**, so there is nothing to restore. It needs the record/publish
2580
+ half and none of the restore half, and it gets the restore it *does* depend on
2581
+ for free: an anchor on a physics-driven character is restored by
2582
+ `PhysicsSystem.__interp_restore` before cloth reads it.
2583
+
2584
+ **3. The worker takes the cheap implementation away.** Without it, a render-time
2585
+ pass could read `ClothState.position`, reconstruct the pose and blend. With it,
2586
+ that memory is owned by the worker from dispatch until the next tick's join —
2587
+ which is essentially all of the time outside the fixed pass. An interpolating
2588
+ cloth therefore needs a **published pose slot**: the last two steps' node poses,
2589
+ copied out on the main thread at join, blended at render time from the copy. That
2590
+ is the same double-buffered publish slot `WorkerFluidSystem` documents as the
2591
+ unbuilt prerequisite for its own render-time reads, and it is the honest cost of
2592
+ the feature rather than an implementation detail of it.
2593
+
2594
+ ### What it would cost, and what it would buy
2595
+
2596
+ The slot is nearly free to *fill*, because `cloth_write_back` already computes
2597
+ every node's local TRS and would only have to keep it. What costs is the
2598
+ per-frame pass, which is a pose per node blended and written — strictly less work
2599
+ than the write-back, because it reads two stored poses instead of reconstructing
2600
+ one from particles, so the write-back's own cost is an upper bound on it:
2601
+
2602
+ ```
2603
+ write-back, 401 nodes 0.0487 ms
2604
+ ```
2605
+
2606
+ The arithmetic, spelled out, because the first draft of this section guessed at
2607
+ it and was wrong by a factor of two. A blend pass runs **every frame**, where the
2608
+ write-back runs every tick, and the write-back keeps running either way. So on a
2609
+ 165 Hz display against a 60 Hz fixed step:
2610
+
2611
+ ```
2612
+ 400 nodes 1,024 nodes (§17)
2613
+ a blend pass, per frame 0.049 ms 0.124 ms
2614
+ ... at 165 frames a second 8.1 ms/s 20.5 ms/s
2615
+
2616
+ what the main thread pays today 3.5 ms/s 10.9 ms/s (§41, x60)
2617
+ what it paid before M5 100.0 ms/s 217.4 ms/s (§41, x60)
2618
+ ```
2619
+
2620
+ **Interpolating a cloth would roughly triple what the main thread pays for it**
2621
+ — and leave it at a seventh of what it paid before the worker. That is the shape
2622
+ of the trade: the feature is affordable *because* M5 happened, and it is not free.
2623
+
2624
+ What it buys is the size of the hold, measured on M3's cape geometry — five
2625
+ strips of 8 cm links, 8.5 cm apart, which is the garment §37 measured:
2626
+
2627
+ ```
2628
+ 600 steps past settling; the largest world-space distance a node covers in one
2629
+ fixed step
2630
+
2631
+ anchor worst mean worst, in link lengths
2632
+ still 46.74 mm 12.51 mm 0.58
2633
+ walk 37.90 mm 27.98 mm 0.47
2634
+ sprint 106.71 mm 67.42 mm 1.33
2635
+ ```
2636
+
2637
+ Half a link length a step at a walk, and a whole one at a sprint. That is
2638
+ visible, and it is the *size* of what a display running faster than the fixed
2639
+ step shows as a hold — 105 of every 165 frames repeating a pose in the general
2640
+ case.
2641
+
2642
+ **The decision: it does not land with M5.** §17's M5 is four artifacts and three
2643
+ proofs and interpolation is none of them; the feature it turns out to need is a
2644
+ publish slot, a second system, an ordering contract with `InterpolationSystem`,
2645
+ and a way to name a `Node3D` that the engine's interpolation layer does not have.
2646
+ Landing a worker and a half-built interpolator together would have been the thing
2647
+ §17 asks milestones not to do. What has changed is that the open question is no
2648
+ longer "wire the existing contract": it is "build the cloth-shaped half of a
2649
+ contract whose entity-shaped half does not fit", and §18 now says so with the
2650
+ three findings above beside it.
2651
+
2652
+ The one piece of good news is that the cloth-shaped version is *simpler* than the
2653
+ engine's, not harder: no restore pass, no serialization adapters, no log — two
2654
+ poses per node and a lerp, driven by the same `alpha` `EntityManager` already
2655
+ exposes for the purpose, and it covers the rigged path and the entity path
2656
+ identically because both of them end in a pose per node.
2657
+
2658
+ ---
2659
+
2660
+ ## 44. Two things M5 deliberately did not build
2661
+
2662
+ **A playground page.** M0 through M4 each added one and M5 does not, for a
2663
+ reason that is about the dev server rather than about the cloth: a
2664
+ `SharedArrayBuffer` needs a cross-origin isolated page, and
2665
+ `packages/meep-engine/vite.config.mjs` sends no `Cross-Origin-Opener-Policy` or
2666
+ `Cross-Origin-Embedder-Policy`. Adding them is four lines and it is four lines
2667
+ that change how **every** playground page in the package loads its subresources,
2668
+ including the Sponza path-tracer pages under `src/shade/playground/`. That is a
2669
+ change to shared infrastructure to demonstrate a bridge that
2670
+ `SystemWorkerLoopback` already tests more precisely than a page could — the
2671
+ loopback makes message delivery and step execution separate events a spec can
2672
+ order, and a real worker makes them a race nobody can observe.
2673
+
2674
+ **A second step in flight.** `WorkerSystem` allows exactly one, for reasons its
2675
+ own docblock gives, and cloth has no reason to want more: the fixed-step gate
2676
+ means a cloth's step must complete before the next tick begins regardless.
2677
+
2678
+ ---
2679
+
2680
+ # Cloth — M6 measurements
2681
+
2682
+ > Written 2026-09-12, against the M6 implementation in `wind/`, `solver/` and
2683
+ > `ecs/`. Everything above this line is M0's through M5's and is unchanged.
2684
+ >
2685
+ > Two scripts, both runnable directly under node — the M0 note applies unchanged:
2686
+ >
2687
+ > ```
2688
+ > node src/engine/physics/cloth/ecs/__fixtures__/cloth_m6_measure.mjs
2689
+ > node src/engine/physics/cloth/ecs/__fixtures__/cloth_library_bench.mjs
2690
+ > ```
2691
+ >
2692
+ > The second is M1's, extended here with a section that prices §10's aerodynamic
2693
+ > term against each entry's own size and budget beside the one M4 added for
2694
+ > self-collision.
2695
+ >
2696
+ > **The behavioural numbers come off the playground**, driven from its own
2697
+ > `globalThis.cloth_wind_playground` handle rather than clicked, because the
2698
+ > question is what a banner does over four hundred settled steps and a screenshot
2699
+ > cannot answer that.
2700
+
2701
+ ---
2702
+
2703
+ ## 45. What M6 proved wrong in PLAN.md
2704
+
2705
+ Five things. The first is the one the milestone is *about* — §10 names a
2706
+ quantity that half the system cannot have — and the second is the one that would
2707
+ have shipped a garment blowing at a speed that depended on how finely someone
2708
+ had voxelised the room.
2709
+
2710
+ | # | The design said | The measurement said |
2711
+ |---|---|---|
2712
+ | 0 | §10 — `a_drag = drag · (u_air − v) · \|n·û\|`, "the normal-facing factor making a sheet catch the wind broadside and slice through it edge-on — the difference between a flag and a rope" | The **rope has no `n`**. A chain's particles span a line and every direction perpendicular to it is equally a normal, so any one this code picked would be the history-free fabrication §7 P4(a) exists to keep out — and a rope wants the *opposite* dependence anyway, catching the flow across it and slicing along it. Both are one quantity: the part of the flow the particle's own incident rows cannot let past. Rank 2 gives §10's `\|û·n\|` exactly, rank 1 gives the cross-flow, rank 0 gives 1. §47 |
2713
+ | 1 | §10 — "a cloth overlapping a `FluidComponent` **samples its velocity field** per particle" | It samples it and then has to **convert it**. `FluidField#sampleVelocity` answers in *grid cells* per second and documents that it deliberately does not rescale, "on the assumption that callers (vegetation sway, dust drift) interpret the field as a direction × magnitude wind". Cloth's drag row is a relaxation toward a velocity, so without the multiply by `cell_size` the same wind voxelised at 0.25 m instead of 1 m blows a garment four times as hard. §46 |
2714
+ | 2 | §10 — the whole section, which never says **which thread** samples | It cannot be the worker's. A fluid field is a hundred thousand floats its own solver rewrites every step and may itself live on another worker; the *answer* is three floats a particle. So the sample runs on the main thread and lands in a region of the cloth's own `SharedArrayBuffer`, where the worker already reads — nothing is copied — and everything built from it runs in `cloth_step_instance` with the rest of the arithmetic, which is what keeps §40's parity argument intact. §46 |
2715
+ | 3 | §10 — `drag` multiplies the relative flow directly | What the step multiplies by cannot be a rate. One acceleration is held for the whole fixed step, so an explicit `rate·(u − v)` overshoots the air once `rate·dt > 1` and diverges past 2 — and §1's first commitment is "not at 5 fps", which is a `dt` of 0.2 s and a rate of 10. What crosses is the **per-step response** `(1 − e^(−rate·dt))/dt`, the exact solution of the same ODE, which closes a fraction of the gap that is strictly below one for every rate and every step. It is also the step `GlobalFluidEffector` takes for the air itself. §49 |
2716
+ | 4 | §12 — "thirteen words into memory the worker already shares"; §10 — "sleep wakes on a wind change" | Fourteen now, and the gust needs a clock that counts **steps rather than ticks**. A worker cloth is not advanced until its registration is acknowledged (§39), so the two systems reach a cloth's first step at different tick counts — and a clock that counted ticks would hand that cloth a different gust on the same step under the two systems. That is a divergence the parity gate reports, and it is the only thing in M6 that did. §50 |
2717
+
2718
+ **And one thing the implementation found that the design had no line for at
2719
+ all.** §10's normal-facing factor is what makes a flag a flag, and it does —
2720
+ measured on the playground's banner, turning a *uniform* wind ninety degrees
2721
+ takes the mean facing factor from 0.221 to **0.000** and the mean aerodynamic
2722
+ acceleration from 43.2 m/s² to 0.08. Turning a **wake** ninety degrees does
2723
+ nothing of the kind: 0.216 to 0.280. A wake has velocity in every direction, so
2724
+ there is no orientation of a banner that is edge-on to one. §10's argument for
2725
+ reading a fluid simulation is therefore stronger than §10 knew — the simulation
2726
+ knows something the factor's own intuition does not — and the factor's headline
2727
+ claim is a statement about *laminar* air. §51.
2728
+
2729
+ ---
2730
+
2731
+ ## 46. Which side of the worker bridge, and the units nobody had converted
2732
+
2733
+ §10 describes what cloth does with the air and never says where the reading
2734
+ happens, which was fine until M5 put the arithmetic on a thread. M6 has to
2735
+ answer it, and both answers cost something real: sampling on the main thread
2736
+ gives back part of what M5 bought, and sampling in the worker means the field
2737
+ has to get there.
2738
+
2739
+ **The field cannot get there.** A `FluidComponent` is not a parameter, it is a
2740
+ simulation:
2741
+
2742
+ ```
2743
+ a 32³ fluid field, the size a room-sized volume wants
2744
+
2745
+ velocity_x 33 x 32 x 32 33,792 floats
2746
+ velocity_y 32 x 33 x 32 33,792
2747
+ velocity_z 32 x 32 x 33 33,792
2748
+ 101,376 floats = 396 KB
2749
+
2750
+ rewritten, every one of them, every fixed step, by a solver that may itself
2751
+ already be on a different worker (`WorkerFluidSystem`)
2752
+ ```
2753
+
2754
+ Against a cloth step that is 1 to 4 ms and a main-thread share that M5 got down
2755
+ to 0.18 ms, shipping 396 KB a step to feed it is not a trade, it is the feature
2756
+ costing more than the thing it feeds. And there is a second objection that is
2757
+ structural rather than arithmetic: `ClothSystem` would have to sample the field
2758
+ too, so the field would have to be on **both** sides and the two copies would
2759
+ have to be proved equal — which is exactly the shape `MEASUREMENTS.md` §40 says
2760
+ breaks the parity gate.
2761
+
2762
+ **So the field stays where it is and the answer crosses.** `cloth_sample_air`
2763
+ runs on the main thread in both systems, writes one air velocity per particle
2764
+ into `ClothState#air_velocity` — a region of the cloth's own buffer — and the
2765
+ step reads it there. Nothing is copied and nothing is marshalled: §39's "one
2766
+ buffer per cloth" already made that memory shared, and this is the first thing
2767
+ to use it in the main-thread-writes direction.
2768
+
2769
+ The **drag arithmetic** is not on the main thread. It reads positions and
2770
+ velocities the state already holds, so it belongs with the rest of the
2771
+ arithmetic, and it is one more word of the parameter block rather than a second
2772
+ computation. §49 is what it costs; §48 is what the sample costs.
2773
+
2774
+ ### The field answers in cells, and cloth wants metres
2775
+
2776
+ `FluidField#sampleVelocity` is trilinear over a MAC grid and answers in **grid
2777
+ cells per second**. Its docblock says so and says why: it "does NOT rescale
2778
+ them, on the assumption that callers (vegetation sway, dust drift) interpret the
2779
+ field as a direction × magnitude wind and want consistent strength regardless of
2780
+ the underlying cell size".
2781
+
2782
+ Cloth is not such a caller. §10's row is a relaxation toward a *velocity*, and a
2783
+ velocity in cells is a garment that blows differently depending on how finely
2784
+ someone voxelised the room:
2785
+
2786
+ ```
2787
+ the same 3-cells-per-second flow, sampled by ClothFluidWind
2788
+
2789
+ cell size what the field says what the cloth is handed
2790
+ 2.00 m 3 cells/s 6.0 m/s
2791
+ 0.50 m 3 cells/s 1.5 m/s
2792
+ ```
2793
+
2794
+ A factor of four between two authorings of the same room. `ClothFluidWind`
2795
+ multiplies by `cell_size`, `cloth_wind.spec.js` gates on it, and it is the one
2796
+ line of M6 that would have been silently wrong rather than loudly wrong.
2797
+
2798
+ ---
2799
+
2800
+ ## 47. The factor §10 wrote as `|n·û|`, and the rope that has no `n`
2801
+
2802
+ §10 asks for `|n·û|`, and it is right about what it wants: a sheet should catch
2803
+ the wind broadside and slice it edge-on. What it is wrong about is that a normal
2804
+ exists to take the dot product with. Half of what this system simulates is
2805
+ chains — `CLOTH_ROPE`, `CLOTH_CHAIN`, `CLOTH_HAIR`, and every cape whose proxy
2806
+ is strips (§37) — and a chain's particles span a **line**. Every direction
2807
+ perpendicular to it is equally a normal. Picking one would be the same
2808
+ fabrication §7 P4(a) rules out for a contact normal, and it would be worse than
2809
+ arbitrary, because a rope's aerodynamics are the *complement* of a sheet's: it
2810
+ catches the flow across it and slices along it.
2811
+
2812
+ Both are one quantity. Take the span `T` of the directions to the particle's
2813
+ stretch-row neighbours **in the current configuration** and the factor is the
2814
+ part of the flow that span cannot let past, `‖û − proj_T(û)‖`:
2815
+
2816
+ ```
2817
+ rank of T what the particle is the factor what §10 wanted
2818
+ 2 a point on a surface |û·n|, n = b1 x b2 exactly this
2819
+ 1 a link of a chain sqrt(1 - (û·t)^2) the complement
2820
+ 0 an isolated particle 1 (no case)
2821
+ ```
2822
+
2823
+ One expression, three topologies, no stored normal, no orientation, no sign, and
2824
+ nothing carried between steps — recomputed from the configuration every step,
2825
+ which is what P4(a) asks of a normal and what a baked one could not offer.
2826
+
2827
+ Measured on the topologies the two paths actually build
2828
+ (`cloth_wind_accelerate.spec.js`):
2829
+
2830
+ ```
2831
+ a 5x5 triangulated sheet in the XZ plane, interior particle
2832
+
2833
+ flow direction facing
2834
+ along the normal 1.000
2835
+ along the surface 0.000
2836
+ 45 degrees 0.707 (the cosine, as a sheet should)
2837
+
2838
+ boundary and corner particles, which carry three rows and two
2839
+ along the normal 1.000
2840
+ along the surface 0.000
2841
+
2842
+ a 5-link chain along X, middle particle and end particle
2843
+
2844
+ along the chain 0.000
2845
+ across it 1.000
2846
+ 60 degrees off it 0.866 (the *sine*, which is the rope's answer
2847
+ and not the sheet's)
2848
+ ```
2849
+
2850
+ **And the rows that would have faked a plane out of a line do not.** §11.1's
2851
+ chain bends through a skip row to its grandparent, which runs parallel to the
2852
+ two links it spans; §6's strain-limit row is a second row on an edge that
2853
+ already has one. Either would give a straight rope a surface, and a straight
2854
+ rope with a surface slices through a wind blowing across it — the exact opposite
2855
+ of the right answer. A second direction has to survive projection out of the
2856
+ first by a quarter of its length (about fourteen degrees) before it counts, and
2857
+ a six-link chain carrying both kinds of row still measures 0.000 along itself
2858
+ and 1.000 across.
2859
+
2860
+ ### What it does on a whole garment, which is not 1
2861
+
2862
+ A flat sheet held rigid and broadside would read 1. A banner does not:
2863
+
2864
+ ```
2865
+ the playground's 16x16 silk banner, 500 settled steps in a uniform 9 m/s wind
2866
+
2867
+ mean facing over the free particles 0.221
2868
+ ```
2869
+
2870
+ That is the correct answer and it is worth stating because it looks like a
2871
+ failure. A free flag **streams**: at equilibrium its surface lies along the
2872
+ flow, which is edge-on, and what is left facing the wind is the part near the
2873
+ pinned edge that cannot turn. A flag that stayed broadside would be a flag
2874
+ nailed to a board. The factor is what produces that shape rather than a number
2875
+ the shape should make large.
2876
+
2877
+ ---
2878
+
2879
+ ## 48. What the sample costs the main thread
2880
+
2881
+ This is §17's question for M6 — "a measured cost showing the per-step sampling
2882
+ stays inside budget" — and the answer has two very different halves depending on
2883
+ whether a fluid field actually reaches the cloth.
2884
+
2885
+ ```
2886
+ cloth_m6_measure.mjs section 1, paired and alternated, per-frame minima
2887
+ a triangulated sheet at 2 cm, the §17 topology
2888
+
2889
+ particles ambient field ratio per particle (field)
2890
+ 144 0.50 us 9.00 us 18.0x 62 ns
2891
+ 400 0.90 us 23.40 us 26.0x 58 ns
2892
+ 576 1.30 us 34.00 us 26.2x 59 ns
2893
+ 1,024 2.20 us 59.70 us 27.1x 58 ns
2894
+ ```
2895
+
2896
+ **A trilinear sample costs 58 to 62 nanoseconds a particle**, flat in particle
2897
+ count, which is what a per-particle pass with no term in the neighbour count
2898
+ should look like. For the §17 workload that is 0.060 ms a step.
2899
+
2900
+ **A uniform source costs a twenty-fifth of that**, and the reason is §10's own
2901
+ cost-control sentence read one step further. §10 says "a cloth outside every
2902
+ fluid field's bounds skips it entirely"; what makes that worth having is that
2903
+ the *skip* is a property the source can answer for a whole cloth at once.
2904
+ `AbstractClothWind#varies` is that question, `ClothAmbientWind` answers `false`
2905
+ for every box, and the sampler then takes one sample and fills the region —
2906
+ which is a bounds scan and a write, both linear with a handful of operations
2907
+ each. Weather is nearly free; air is not.
2908
+
2909
+ ### The whole main thread, which is what M5's number was
2910
+
2911
+ §41 measured `em.update()` under a `SystemWorkerLoopback` and called it "exactly
2912
+ what the main thread pays, because the worker's step is a separate call". M6
2913
+ runs the same measurement with a wind source attached:
2914
+
2915
+ ```
2916
+ cloth_m6_measure.mjs section 4
2917
+ a garment as an entity subtree, anchor walking; the field reaches all of it
2918
+
2919
+ garment no wind ambient field ambient field
2920
+ COTTON 1,024p 0.269 ms 0.293 ms 0.334 ms +6% +24%
2921
+ SILK 400p 0.059 ms 0.063 ms 0.092 ms +6% +55%
2922
+ ROPE 40 links 0.006 ms 0.006 ms 0.009 ms +5% +54%
2923
+ ```
2924
+
2925
+ The absolute column is this machine's and this scene's — the garment is strands
2926
+ rather than §41's patch — so the ratios are what the pairing licenses. Read
2927
+ against §41's own figures, a thousand-particle garment standing in a simulated
2928
+ wake pays about **a third more main thread** than one that is not, and about a
2929
+ twentieth more if the air is only weather. Against the 3.62 ms the same garment
2930
+ cost before M5, the whole of M6's main-thread share is under two per cent of
2931
+ what the worker removed.
2932
+
2933
+ The ambient column's +5% to +6% is the bounds scan and the fill, and it is worth
2934
+ naming what it is *not*: a source that answers `varies() === false` is not
2935
+ approximated, it is exact, because `false` is a promise rather than a hint.
2936
+
2937
+ ---
2938
+
2939
+ ## 49. What the aerodynamic term costs the step, and the trap in measuring it
2940
+
2941
+ The first version of this section measured a cloth in a gale against a cloth
2942
+ hanging still and reported the difference as the cost of the code. It is not,
2943
+ and §7 already said so — "the variants sit in different physical states, and an
2944
+ isolated loop over one accumulate measures a converged mesh rather than a moving
2945
+ one". The size of that mistake here:
2946
+
2947
+ ```
2948
+ the same 40-link rope, 2x4, stepped 3,300 times
2949
+
2950
+ hanging dead straight 0.029 ms per step
2951
+ blown horizontal at 9 m/s 0.095 ms per step 3.24x
2952
+ ```
2953
+
2954
+ None of that 3.24× is arithmetic. A rope blown out to 1.79 m of its own 2 m is
2955
+ fighting its own hard rows at every iteration; a rope hanging straight down has
2956
+ converged and is barely working. The wind's own code, measured on the same rope
2957
+ with the physics held fixed, is **0.7%**.
2958
+
2959
+ ### Holding the physics fixed
2960
+
2961
+ Fill the external-acceleration array with exactly gravity and the two paths
2962
+ simulate the same cloth, so the only difference left is which predict and
2963
+ integrate run. (Exactly the same cloth: the region is `Float32`, so the uniform
2964
+ side is handed `Math.fround(-9.81)` too, and the harness asserts the two
2965
+ position arrays are bit-identical after every round. Without that the two
2966
+ accelerations differ by 4e-7 and a hanging sheet — which is chaotic — is
2967
+ somewhere else entirely three thousand steps later, which is the confound above
2968
+ arriving by the back door.)
2969
+
2970
+ ```
2971
+ cloth_m6_measure.mjs section 2, paired and alternated
2972
+
2973
+ entry budget uniform varying path + term total
2974
+ SILK 2x4 0.930 0.934 +0.4% 0.2% +0.6%
2975
+ COTTON 2x4 0.933 0.931 -0.2% 0.2% -0.1%
2976
+ DENIM 2x2 0.649 0.648 -0.2% 0.3% +0.2%
2977
+ LEATHER 2x2 0.645 0.647 +0.3% 0.3% +0.6%
2978
+ ROPE 2x4 0.029 0.029 +0.3% 0.7% +1.0%
2979
+ CHAIN 2x6 0.050 0.050 -0.2% 0.4% +0.2%
2980
+ HAIR 2x4 0.918 0.927 +1.0% 0.2% +1.2%
2981
+ COTTON §17 2x4 3.232 3.196 -1.1% 0.1% -1.0%
2982
+ ```
2983
+
2984
+ **The whole feature is one per cent of a step, and the sign of the path column
2985
+ is noise.** Two loops rather than a branch inside one is why the uniform path
2986
+ does not pay: a cloth with no wind runs the code M0 through M5 measured, byte
2987
+ for byte, and `cloth_wind.spec.js` holds a 120-step run to exactly that.
2988
+
2989
+ `cloth_wind_accelerate` itself — the Gram-Schmidt walk over incident rows plus
2990
+ the arithmetic — is 0.1% to 0.7% of a step, larger as a fraction on a chain
2991
+ because a chain's step is small rather than because its walk is long.
2992
+
2993
+ ### What `drag` does
2994
+
2995
+ ```
2996
+ a 20-link rope hanging under gravity in a steady 2 m/s cross-wind, 2x4
2997
+ "lean" is the tip's downwind travel once settled; "half" the step at which it
2998
+ first passes half of that; "settle" the step the fastest particle first falls
2999
+ under 1 mm/s
3000
+
3001
+ drag rate 1/s response lean m half settle peak m/s
3002
+ 0.00 0.0 0.00 0.000 - 67 0.11
3003
+ 0.05 3.0 2.93 0.468 21 111 1.02
3004
+ 0.08 4.8 4.61 0.602 20 115 1.28
3005
+ 0.12 7.2 6.78 0.704 19 116 1.48
3006
+ 0.20 12.0 10.88 0.802 18 119 1.67
3007
+ 0.30 18.0 15.55 0.857 17 134 1.77
3008
+ 0.45 27.0 21.74 0.897 17 143 1.85
3009
+ 0.70 42.0 30.20 0.926 16 149 1.90
3010
+ 1.00 60.0 37.93 0.942 16 152 1.95
3011
+ ```
3012
+
3013
+ Monotone in every column and exactly zero at zero, which is what makes it a dial
3014
+ rather than a switch. The library's own spread — leather at 0.05 to silk at
3015
+ 0.45 — is 0.47 m of lean against 0.90 m at the same wind, which is the
3016
+ difference between a skirt that moves and a banner that flies.
3017
+
3018
+ Two things in that table are worth naming. **Settling gets slower, not faster**,
3019
+ as drag rises: 111 steps to 152. A livelier cloth is a cloth with more to
3020
+ dissipate, and §7 P5's sleep still arrives for every value — a banner in a
3021
+ steady wind settles into a displaced equilibrium and sleeps there, which
3022
+ `cloth_wind.spec.js` gates on. And **peak speed approaches the air speed**,
3023
+ 1.02 m/s to 1.95 against 2 m/s of air, which is `drag = 1` meaning what
3024
+ `cloth_dynamics_map.js` says it means: a cloth with no aerodynamic memory.
3025
+
3026
+ ### The response cannot overshoot, at any step length
3027
+
3028
+ ```
3029
+ a free particle, no rows, no gravity, air at 10 m/s, drag = 1
3030
+
3031
+ dt rate*dt closed/step v after 1 step v after 20
3032
+ 0.00417 0.25 0.221199 2.2120 9.9326
3033
+ 0.01667 1.00 0.632121 6.3212 10.0000
3034
+ 0.03333 2.00 0.864665 8.6466 10.0000
3035
+ 0.10000 6.00 0.997521 9.9752 10.0000
3036
+ 0.20000 12.00 0.999994 9.9999 10.0000
3037
+ ```
3038
+
3039
+ An explicit `rate·(u − v)` at the fourth row would move the particle to 60 m/s
3040
+ in one step and to 590 in two. The exact form closes 0.9975 of the gap and
3041
+ stops. §1's first design commitment is "it does not explode… not at 5 fps", and
3042
+ this is that commitment discharged by construction rather than by a clamp.
3043
+
3044
+ ### Memory
3045
+
3046
+ ```
3047
+ particles state bytes the two regions share
3048
+ 144 97,400 3,456 3.5%
3049
+ 400 262,776 9,600 3.7%
3050
+ 576 377,528 13,824 3.7%
3051
+ 1,024 671,160 24,576 3.7%
3052
+ ```
3053
+
3054
+ Twenty-four bytes a particle, unconditionally — the same shape as M4's
3055
+ self-collision regions, and for the same reason: `ClothState` is one allocation
3056
+ sized at seed, and making `drag` a reseed trigger would be a change to the
3057
+ reseed contract rather than a tidy-up. Against §33's 16% for three regions four
3058
+ of seven entries never read, 3.7% for two regions every cloth in a windy scene
3059
+ does is not the thing to fix first.
3060
+
3061
+ ---
3062
+
3063
+ ## 50. The clock counts steps, not ticks, and the parity gate is why
3064
+
3065
+ §12 asks the two systems for bit-identical results and M5 made that structural:
3066
+ one `ClothWorld`, one `cloth_step_instance`, one parameter block. M6 adds the
3067
+ first thing in the system that is a function of **time**, and time is where the
3068
+ two systems genuinely differ.
3069
+
3070
+ A worker cloth is not stepped until its registration is acknowledged, and §39's
3071
+ own argument is that it is not *advanced* either — "not stepped, not written
3072
+ back" — so `ClothSystem` reaches a cloth's first step on tick 1 and
3073
+ `WorkerClothSystem` reaches it on tick 2 or later. A gust that is a function of
3074
+ ticks would therefore be a different gust on the same step:
3075
+
3076
+ ```
3077
+ step ClothSystem's clock WorkerClothSystem's clock, ticking
3078
+ 1 dt 2dt
3079
+ 2 2dt 3dt
3080
+ ...
3081
+ ```
3082
+
3083
+ Measured, by putting the clock back on ticks and running the gate: the two
3084
+ buffers differ at **step 1, byte 0**. Byte 0 is the first word of
3085
+ `ClothState#result` — the largest particle speed the step produced — so the very
3086
+ first step of the run is already a different step. Not drift, a wrong parameter,
3087
+ which is exactly the distinction §40 built the per-step comparison to make: from
3088
+ the end of a run the two would look like any other divergence.
3089
+
3090
+ **The fix is not a synchronisation.** It is that the clock advances at the first
3091
+ `advance` of a tick rather than at `begin`, so a tick in which no cloth is
3092
+ advanced does not move the weather. A cloth that has not started stepping has no
3093
+ weather yet, which is the right semantic on its own terms — a paused simulation
3094
+ should not have the wind move — and it makes the two systems agree by
3095
+ construction rather than by arrangement. It sits **above** the sleep tests
3096
+ deliberately: a world whose every cloth is asleep still has weather, and §10's
3097
+ "sleep wakes on a wind change" is the whole reason it needs to.
3098
+
3099
+ ### The number
3100
+
3101
+ ```
3102
+ 200 steps, two 9-link strands in silk, a real fluid volume with a sheared
3103
+ flow in it, an ambient gust of 1.5 m/s on a 0.7 s period, and the anchor
3104
+ walked out of the volume partway through
3105
+
3106
+ differing bytes in ClothState's buffer, per step: 0
3107
+ steps sampled per particle: 104
3108
+ steps sampled once for the cloth: 96
3109
+ ```
3110
+
3111
+ Both of the sampler's paths ran, and the run is self-checked on that, because
3112
+ §40's own lesson is that a parity test over a branch that did not run is a
3113
+ parity test over nothing. The run also asserts the banner actually moved — a
3114
+ rope that hangs straight down under a gale would make every byte agree for the
3115
+ wrong reason.
3116
+
3117
+ ---
3118
+
3119
+ ## 51. The banner in the wake, and what a wake turns out not to be
3120
+
3121
+ §17 asks M6 for "a playground with a banner in a simulated wake". `wind.html` is
3122
+ that, and it is simulated in the strong sense: a `FluidField`, a
3123
+ `FluidSimulator`, a `GlobalFluidEffector` in its prevailing-wind regime, and a
3124
+ solid post written into `FluidField#solid`. The banner reads it through the same
3125
+ `ClothFluidWind` and `cloth_sample_air` a scene would.
3126
+
3127
+ M5 skipped a playground for a reason about the dev server — a `SharedArrayBuffer`
3128
+ needs a cross-origin isolated page (§44) — and that reason does not reach M6:
3129
+ this page drives `cloth_step` in process, like the solver and collision pages,
3130
+ and needs no shared memory at all.
3131
+
3132
+ ### The post's deficit is a third of the free stream
3133
+
3134
+ ```
3135
+ driven at 9 m/s, 18 x 18 x 29 cells at 8 cm, measured at the banner
3136
+
3137
+ post present 6.00 m/s
3138
+ post absent 8.69 m/s
3139
+ ```
3140
+
3141
+ That 31% is the incompressible projection's answer to a solid standing in a
3142
+ flow, and there is no authored falloff anywhere in it. It is also the whole of
3143
+ §10's argument in one number: nothing cloth-specific was written to produce it.
3144
+
3145
+ ### Turning the flow: the factor works, and the wake does not care
3146
+
3147
+ ```
3148
+ the 16x16 silk banner, 500 settled steps, gust off
3149
+
3150
+ source flow mean relative flow mean drag accel mean facing
3151
+ ambient 0° 9.00 m/s 43.24 m/s² 0.221
3152
+ ambient 90° 9.00 m/s 0.08 m/s² 0.000
3153
+ field 0° 7.25 m/s 34.00 m/s² 0.216
3154
+ field 90° 5.56 m/s 33.83 m/s² 0.280
3155
+ ```
3156
+
3157
+ **In uniform air the factor is everything.** Turn the wind ninety degrees at the
3158
+ same speed and the banner is not merely less driven, it is *undriven* — 0.08 m/s²
3159
+ of aerodynamic acceleration against 43.24, a factor of five hundred. That is
3160
+ §10's flag-versus-rope claim, and it is the sharpest single result in M6.
3161
+
3162
+ **In a wake it does nothing.** The factor reads 0.216 broadside and 0.280
3163
+ edge-on — slightly *higher* edge-on, which is noise around "no dependence at
3164
+ all". The reason is not a failure of the factor: a wake has velocity in every
3165
+ direction, so no orientation of a surface is edge-on to it, and `‖û − proj_T(û)‖`
3166
+ is correctly reporting that there is nothing to slice through.
3167
+
3168
+ This is a correction to §10's intuition rather than to its design. §10 offers
3169
+ the factor as the thing that makes reading the fluid worth doing; the
3170
+ measurement says the factor's headline behaviour is a statement about *laminar*
3171
+ air, and that what reading the fluid actually buys is the half §10 stated more
3172
+ quietly — a wake is a place where the air is slower and turned, and no
3173
+ orientation escapes it.
3174
+
3175
+ ### What the wake is not: a shedding street
3176
+
3177
+ ```
3178
+ the air a banner's width downstream of the post, over 4 settled seconds
3179
+ "wobble" is the largest deviation from its own mean, m/s
3180
+
3181
+ cells damping confinement speed wobble ms/step
3182
+ 18 0 0 3.11 0.002 5.0
3183
+ 18 0.2 0.6 3.07 0.001 5.2
3184
+ 26 0 0 7.29 0.005 15.5
3185
+ 26 0.2 0.6 6.29 0.232 16.0
3186
+ 34 0 0 6.77 0.031 34.7
3187
+ 34 0.2 0.6 5.79 0.000 37.0
3188
+ ```
3189
+
3190
+ At every resolution the page can afford, and with the simulator's damping and
3191
+ vorticity confinement at both ends of their useful range, the wake is a
3192
+ **steady** velocity deficit rather than a von Kármán street. The banner's own
3193
+ free corner moves 1.44 µm a step with the post present and 0.12 µm without — a
3194
+ factor of twelve, and both of them settled rather than fluttering.
3195
+
3196
+ That is the fluid solver's answer at this scale and not the cloth's: resolving a
3197
+ shedding shear layer needs a grid whose step is 15 ms at 26 cells and 35 ms at
3198
+ 34, against the 5 ms an 18-cell volume costs. It is recorded here rather than
3199
+ tuned away because the page says "a banner in a simulated wake" and a reader is
3200
+ entitled to know that the wake is steady.
3201
+
3202
+ ---
3203
+
3204
+ ## 52. The library, re-measured, and why no entry is re-sized
3205
+
3206
+ §17 says to re-measure with `cloth_library_bench.mjs` and re-size anything that
3207
+ drifted outside. The bench gains a section that runs §33's own scenario — the
3208
+ reversing gust that folds the sheet, each entry's own self-collision flag — with
3209
+ and without §10's aerodynamic term.
3210
+
3211
+ **And the first thing it measured is §42's warning, three times.** Three runs of
3212
+ the same bench on the same machine inside an hour, same code, no wind in any of
3213
+ these columns:
3214
+
3215
+ ```
3216
+ the §17 workload's baseline row, "M0 rows only", 1,024p at 2x4
3217
+
3218
+ run A 3.490 ms 12 node processes, nothing else of this session running
3219
+ run B 3.826 ms 8 node processes, this session driving a browser
3220
+ run C 4.257 ms 9 node processes, this session running the test suite
3221
+ ```
3222
+
3223
+ Run B had *fewer* processes than run A and was slower anyway, because the four
3224
+ that had gone were idle and what run B was actually sharing the machine with was
3225
+ a Vite dev server and a browser pane. §42 said "check the process count first";
3226
+ M6's amendment is that a process count is a proxy for load and not a measure of
3227
+ it, and that the only quantity worth reading off this bench is the **paired
3228
+ ratio** inside one run.
3229
+
3230
+ The size of what that does to the library, before wind is anywhere near it:
3231
+
3232
+ ```
3233
+ §33's scenario, self-collision on, no wind — the same measurement in four places
3234
+
3235
+ entry M4 §33 M5 §42 M6 run A M6 run C
3236
+ SILK 1.802 2.069 1.730 2.042
3237
+ COTTON 1.802 2.002 1.742 1.905
3238
+ DENIM 1.765 2.020 1.693 1.967
3239
+ LEATHER 1.828 2.039 1.676 1.814
3240
+ ROPE 0.129 0.133 0.123 0.134
3241
+ CHAIN 0.183 0.215 0.186 0.208
3242
+ HAIR 1.798 1.997 1.722 1.930
3243
+ ```
3244
+
3245
+ **Silk reads 2.042 ms in run C with no wind in it at all** — the same code M4
3246
+ measured at 1.802 and M6 measured at 1.730 forty minutes earlier. Anything a
3247
+ re-sizing decision took from that column would be a reading of the machine.
3248
+
3249
+ ### What the wind term costs, paired
3250
+
3251
+ ```
3252
+ cloth_library_bench.mjs, §33's scenario, with and without the wind term
3253
+ three runs, each paired inside itself
3254
+
3255
+ entry drag run A run B run C
3256
+ SILK 0.45 +4.5% +3.4% -2.3%
3257
+ COTTON 0.20 -0.5% -0.1% +0.5%
3258
+ DENIM 0.08 +6.2% +6.9% +5.5%
3259
+ LEATHER 0.05 +0.7% +2.1% +8.5%
3260
+ ROPE 0.12 +1.0% +9.3% -1.1%
3261
+ CHAIN 0.05 -2.7% -1.1% +0.3%
3262
+ HAIR 0.30 +6.1% +8.1% +4.4%
3263
+ ```
3264
+
3265
+ Between −2.7% and +9.3%, and **not monotone in `drag`** — the entry with the most
3266
+ of it comes back negative twice and the entry with the least comes back at
3267
+ +8.5%. That is what a column carrying both a code cost of one per cent (§49) and
3268
+ a *different cloth* should look like, and the second is the larger term: a
3269
+ garment blown out of its drape is not the garment beside it, which is §7's
3270
+ warning in its fourth setting.
3271
+
3272
+ **No entry is re-sized**, and M6 has a measured reason to add to M5's. M5's was
3273
+ that the absolute column came off a busy machine. M6's is §49: with the physics
3274
+ held fixed so both sides simulate the same cloth, the whole aerodynamic term is
3275
+ **one per cent of a step** — an order of magnitude below the run-to-run spread of
3276
+ the thing that would decide a re-sizing, and two below the levers §17 prices.
3277
+ Re-sizing on that would bake noise into a shipped garment.
3278
+
3279
+ §33's ordering stands entry for entry: the three entries that set
3280
+ `ClothDynamicsFlags.SelfCollision` are still the tightest, the chains still find
3281
+ no pairs at all, and leather still pays the most for the fewest.
3282
+
3283
+ **What is true, and is the thing to watch**, is that those three entries are now
3284
+ within a few per cent of 2 ms on this machine with wind live — and were already
3285
+ within a few per cent of it *without*. `MEASUREMENTS.md` §42's closing advice
3286
+ stands and M6 adds one clause to it: re-measure the absolute column on a quiet
3287
+ machine, take a paired reading even then, and if the answer is that the library
3288
+ is genuinely at its ceiling, the lever to spend is the one §17 prices rather than
3289
+ the feature that happened to be measured last.