@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.
- package/package.json +1 -1
- package/src/engine/graphics3/PickingSystem.d.ts +3 -3
- package/src/engine/graphics3/PickingSystem.d.ts.map +1 -1
- package/src/engine/graphics3/PickingSystem.js +52 -55
- package/src/engine/graphics3/shader_picking_gather.d.ts +40 -0
- package/src/engine/graphics3/shader_picking_gather.d.ts.map +1 -0
- package/src/engine/graphics3/shader_picking_gather.js +120 -0
- package/src/engine/physics/cloth/MEASUREMENTS.md +1050 -0
- package/src/engine/physics/cloth/PLAN.md +411 -49
- package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts +52 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSerializationAdapter.js +70 -0
- package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts +12 -3
- package/src/engine/physics/cloth/ecs/ClothDynamics.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothDynamics.js +340 -331
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +36 -22
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.js +38 -23
- package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts +84 -0
- package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothSerializationAdapter.js +141 -0
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +51 -82
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.js +81 -869
- package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts +48 -0
- package/src/engine/physics/cloth/ecs/ClothWorkerCore.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothWorkerCore.js +107 -0
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +286 -0
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothWorld.js +1104 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +182 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +562 -0
- package/src/engine/physics/cloth/ecs/cloth.worker.d.ts +2 -0
- package/src/engine/physics/cloth/ecs/cloth.worker.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth.worker.js +12 -0
- package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_build_rows.js +2 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +58 -0
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +341 -271
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +20 -2
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +39 -15
- package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts +29 -0
- package/src/engine/physics/cloth/ecs/cloth_worker_protocol.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_worker_protocol.js +78 -0
- package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts +17 -0
- package/src/engine/physics/cloth/ecs/makeClothWorker.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/makeClothWorker.js +18 -0
- package/src/engine/physics/cloth/playground/README.md +73 -2
- package/src/engine/physics/cloth/playground/wind.html +166 -0
- package/src/engine/physics/cloth/playground/wind_build.d.ts +81 -0
- package/src/engine/physics/cloth/playground/wind_build.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/wind_build.js +141 -0
- package/src/engine/physics/cloth/playground/wind_main.d.ts +2 -0
- package/src/engine/physics/cloth/playground/wind_main.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/wind_main.js +681 -0
- package/src/engine/physics/cloth/solver/ClothState.d.ts +134 -79
- package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/ClothState.js +273 -12
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +33 -0
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.js +34 -0
- package/src/engine/physics/cloth/solver/cloth_step.d.ts +21 -2
- package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_step.js +173 -4
- package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts +40 -0
- package/src/engine/physics/cloth/solver/cloth_step_instance.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_step_instance.js +284 -0
- package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts +107 -0
- package/src/engine/physics/cloth/solver/cloth_wind_accelerate.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_wind_accelerate.js +263 -0
- package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts +88 -0
- package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/AbstractClothWind.js +100 -0
- package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts +85 -0
- package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/ClothAmbientWind.js +182 -0
- package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts +79 -0
- package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/ClothFluidWind.js +210 -0
- package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +101 -0
- package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -0
- package/src/engine/physics/cloth/wind/cloth_sample_air.js +290 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/device/mock/texture_copy.d.ts.map +1 -1
- package/src/shade/device/mock/texture_copy.js +28 -0
- package/src/shade/playground/vgeo_runtime/README.md +94 -0
- package/src/shade/playground/vgeo_runtime/index.html +62 -0
- package/src/shade/playground/vgeo_runtime/main.d.ts +2 -0
- package/src/shade/playground/vgeo_runtime/main.d.ts.map +1 -0
- package/src/shade/playground/vgeo_runtime/main.js +399 -0
- package/src/shade/playground/vgeo_viewer/sample_asset.js +1 -1
- package/src/shade/renderer/camera/Camera.d.ts +14 -0
- package/src/shade/renderer/camera/Camera.d.ts.map +1 -1
- package/src/shade/renderer/camera/Camera.js +7 -1
- package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GEOMETRY_METADATA_STRUCT.js +16 -1
- package/src/shade/renderer/geometry/GPUGeometryManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GPUGeometryManager.js +29 -9
- package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts +10 -0
- package/src/shade/renderer/geometry/GPUGeometryMetadata.d.ts.map +1 -1
- package/src/shade/renderer/geometry/GPUGeometryMetadata.js +13 -0
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +46 -18
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +70 -22
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +97 -19
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +12 -2
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +14 -3
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts +2 -2
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +124 -6
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts +22 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/build/vgeo_page_node_count.js +53 -0
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts +8 -1
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +8 -1
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/header/VGEO_FEATURE_IGNORABLE_BOUNDED_PAGES.js +15 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts +54 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/page/VGEO_DEFAULT_PAGE_SIZE.js +53 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts +13 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerHeader.js +14 -2
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts +100 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoPage.js +113 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_header.js +5 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_read_page.js +108 -0
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +23 -0
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +40 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts +228 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryPool.js +932 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts +176 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/GPUVirtualGeometryRuntime.js +425 -0
- package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +241 -0
- package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +574 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts +120 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoFeedbackLoop.js +674 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts +287 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VGeoRuntimeTables.js +976 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts +42 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.js +96 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts +16 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_FLAG_CULL.js +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_PARAMS_STRUCT.js +83 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/VGEO_CUT_WORKGROUP_SIZE.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/chunk_vgeo_cut_access.js +160 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_collect.js +255 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_queue_prepare.js +80 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_seed.js +144 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts +3 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/shader_vgeo_cut_traverse.js +530 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts +39 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/cut/vgeo_cut_feedback_layout.js +54 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts +41 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_if_enabled.js +56 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts +49 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/graph_vgeo_expand_instances_to_meshlets.js +328 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_OFFSET.js +51 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CLUSTER_ROW_WORDS.js +12 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts +17 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT.js +99 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_CUT_STAT_COUNT.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_FEEDBACK_OFFSET.js +27 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_NONE.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_OFFSET.js +35 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GEOMETRY_ROW_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts +15 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_FLAG_SELECTABLE.js +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_OFFSET.js +36 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_GROUP_ROW_WORDS.js +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_OFFSET.js +54 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_INSTANCE_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_LEAF_BIT.js +10 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts +7 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_OFFSET.js +30 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NODE_ROW_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_NO_CHILD_PAGE.js +12 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts +14 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_NONE.js +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts +11 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_OFFSET.js +61 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_PAGE_ROW_WORDS.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts +13 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_ITEM_WORDS.js +12 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts +9 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_QUEUE_TARGET_GROUP_BIT.js +8 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/layout/VGEO_WANT_WORDS.js +19 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts +23 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +70 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts +18 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_block_words.js +25 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts +35 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_page_slot_words.js +41 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts +32 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_write_page_block.js +138 -0
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +9 -0
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +12 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +12 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +10 -0
- package/src/shade/renderer/scene/GPUSceneContext.d.ts +18 -0
- package/src/shade/renderer/scene/GPUSceneContext.d.ts.map +1 -1
- package/src/shade/renderer/scene/GPUSceneContext.js +647 -594
- package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
- package/src/shade/renderer/scene/rows/GPUSceneRows.js +22 -0
- package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts +35 -0
- package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/mat3/chunk_mat3_singular_values.js +91 -0
- package/src/engine/graphics3/instantiate_scene_bundle.d.ts +0 -54
- package/src/engine/graphics3/instantiate_scene_bundle.d.ts.map +0 -1
- package/src/engine/graphics3/shade_node_to_entity_composition.d.ts +0 -39
- package/src/engine/graphics3/shade_node_to_entity_composition.d.ts.map +0 -1
- package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts +0 -35
- package/src/shade/renderer/animation/compute_skin_world_bounds.d.ts.map +0 -1
- package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts +0 -96
- package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.d.ts.map +0 -1
- package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts +0 -20
- package/src/shade/renderer/animation/pose/pose_evaluate_world.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_bind_transform.d.ts +0 -25
- package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_frame_defect.d.ts +0 -34
- package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts +0 -24
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +0 -1
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +0 -70
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +0 -1
- package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts +0 -96
- package/src/shade/renderer/buffer/table/GPUReadbackStagingRing.d.ts.map +0 -1
- package/src/shade/renderer/scene/InstanceBatch.d.ts +0 -92
- package/src/shade/renderer/scene/InstanceBatch.d.ts.map +0 -1
- package/src/shade/renderer/scene/Mesh.d.ts +0 -55
- package/src/shade/renderer/scene/Mesh.d.ts.map +0 -1
- package/src/shade/renderer/scene/Node3D.d.ts +0 -159
- package/src/shade/renderer/scene/Node3D.d.ts.map +0 -1
- package/src/shade/renderer/scene/Node3DDirtyList.d.ts +0 -59
- package/src/shade/renderer/scene/Node3DDirtyList.d.ts.map +0 -1
- package/src/shade/renderer/scene/SceneNodeBinding.d.ts +0 -52
- package/src/shade/renderer/scene/SceneNodeBinding.d.ts.map +0 -1
- package/src/shade/renderer/scene/SkinnedMesh.d.ts +0 -27
- package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +0 -1
- package/src/shade/renderer/scene/TransformAuthority.d.ts +0 -19
- 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.
|