@woosh/meep-engine 3.22.0 → 3.23.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/core/geom/3d/hash-grid/PointHashGrid3.d.ts +118 -0
- package/src/core/geom/3d/hash-grid/PointHashGrid3.d.ts.map +1 -0
- package/src/core/geom/3d/hash-grid/PointHashGrid3.js +283 -0
- package/src/core/geom/3d/shape/BoxShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/BoxShape3D.js +6 -17
- package/src/core/geom/3d/shape/CapsuleShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.js +3 -14
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/CylinderShape3D.js +3 -21
- package/src/core/geom/3d/shape/PlaneShape3D.d.ts +105 -0
- package/src/core/geom/3d/shape/PlaneShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/PlaneShape3D.js +251 -0
- package/src/core/geom/3d/shape/SphereShape3D.js +3 -3
- package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/shape_to_type.js +3 -0
- package/src/core/geom/3d/shape/json/type_adapters.d.ts +14 -0
- package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/type_adapters.js +15 -0
- package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts +51 -0
- package/src/core/geom/3d/shape/sdf/sdf3_box.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_box.js +113 -0
- package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts +46 -0
- package/src/core/geom/3d/shape/sdf/sdf3_capsule.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_capsule.js +82 -0
- package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts +60 -0
- package/src/core/geom/3d/shape/sdf/sdf3_cylinder.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_cylinder.js +126 -0
- package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts +50 -0
- package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_degenerate_normal.js +53 -0
- package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts +52 -0
- package/src/core/geom/3d/shape/sdf/sdf3_plane.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_plane.js +61 -0
- package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts +41 -0
- package/src/core/geom/3d/shape/sdf/sdf3_sphere.d.ts.map +1 -0
- package/src/core/geom/3d/shape/sdf/sdf3_sphere.js +71 -0
- package/src/core/process/ConcurrencyGate.d.ts +103 -0
- package/src/core/process/ConcurrencyGate.d.ts.map +1 -0
- package/src/core/process/ConcurrencyGate.js +207 -0
- package/src/engine/asset/AssetManager.d.ts +10 -0
- package/src/engine/asset/AssetManager.d.ts.map +1 -1
- package/src/engine/asset/AssetManager.js +10 -0
- package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +30 -13
- package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts +26 -0
- package/src/engine/graphics3/pose/collect_entity_playbacks.d.ts.map +1 -0
- package/src/engine/graphics3/pose/collect_entity_playbacks.js +40 -0
- package/src/engine/graphics3/pose/query_entity_node_world_pose.d.ts.map +1 -1
- package/src/engine/graphics3/pose/query_entity_node_world_pose.js +74 -101
- package/src/engine/physics/cloth/MEASUREMENTS.md +1287 -4
- package/src/engine/physics/cloth/PLAN.md +1967 -1581
- package/src/engine/physics/cloth/build/ClothProxy.d.ts +147 -0
- package/src/engine/physics/cloth/build/ClothProxy.d.ts.map +1 -0
- package/src/engine/physics/cloth/build/ClothProxy.js +207 -0
- package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts +70 -0
- package/src/engine/physics/cloth/build/cloth_proxy_from_joints.d.ts.map +1 -0
- package/src/engine/physics/cloth/build/cloth_proxy_from_joints.js +256 -0
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +176 -0
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/ClothColliderIndex.js +435 -0
- package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts +12 -0
- package/src/engine/physics/cloth/collider/ClothColliderKind.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/ClothColliderKind.js +38 -0
- package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts +120 -0
- package/src/engine/physics/cloth/collider/ClothColliderRecord.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/ClothColliderRecord.js +137 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts +25 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +190 -0
- package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts +44 -0
- package/src/engine/physics/cloth/collider/cloth_collider_pack.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/cloth_collider_pack.js +173 -0
- package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts +94 -0
- package/src/engine/physics/cloth/collider/cloth_collider_sdf.d.ts.map +1 -0
- package/src/engine/physics/cloth/collider/cloth_collider_sdf.js +275 -0
- package/src/engine/physics/cloth/ecs/ClothCollider.d.ts +126 -0
- package/src/engine/physics/cloth/ecs/ClothCollider.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothCollider.js +199 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts +9 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +32 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +114 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +243 -0
- package/src/engine/physics/cloth/ecs/ClothDynamicsFlags.js +66 -57
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +109 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.js +479 -363
- package/src/engine/physics/cloth/ecs/ClothRig.d.ts +95 -0
- package/src/engine/physics/cloth/ecs/ClothRig.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/ClothRig.js +140 -0
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +30 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.js +1021 -773
- package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts +90 -0
- package/src/engine/physics/cloth/ecs/cloth_build_rows.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_build_rows.js +374 -0
- package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts +32 -3
- package/src/engine/physics/cloth/ecs/cloth_dynamics_library.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_library.js +283 -254
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +59 -0
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +328 -0
- package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts +13 -0
- package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_reconstruct_pose.js +288 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +58 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +199 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +8 -63
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +317 -645
- package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +61 -0
- package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_write_back.js +212 -449
- package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts +49 -0
- package/src/engine/physics/cloth/ecs/cloth_write_back_rig.d.ts.map +1 -0
- package/src/engine/physics/cloth/ecs/cloth_write_back_rig.js +115 -0
- package/src/engine/physics/cloth/playground/README.md +155 -1
- package/src/engine/physics/cloth/playground/collide.html +178 -0
- package/src/engine/physics/cloth/playground/collide_build.d.ts +95 -0
- package/src/engine/physics/cloth/playground/collide_build.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/collide_build.js +315 -0
- package/src/engine/physics/cloth/playground/collide_main.d.ts +2 -0
- package/src/engine/physics/cloth/playground/collide_main.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/collide_main.js +720 -0
- package/src/engine/physics/cloth/playground/garment.html +182 -0
- package/src/engine/physics/cloth/playground/garment_build.d.ts +78 -0
- package/src/engine/physics/cloth/playground/garment_build.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/garment_build.js +359 -0
- package/src/engine/physics/cloth/playground/garment_main.d.ts +2 -0
- package/src/engine/physics/cloth/playground/garment_main.d.ts.map +1 -0
- package/src/engine/physics/cloth/playground/garment_main.js +633 -0
- package/src/engine/physics/cloth/solver/ClothState.d.ts +208 -5
- package/src/engine/physics/cloth/solver/ClothState.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/ClothState.js +302 -8
- package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +87 -0
- package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_contact_find.js +343 -0
- package/src/engine/physics/cloth/solver/cloth_self_find.d.ts +112 -0
- package/src/engine/physics/cloth/solver/cloth_self_find.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/cloth_self_find.js +448 -0
- package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts +4 -1
- package/src/engine/physics/cloth/solver/cloth_solve_vertex.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solve_vertex.js +16 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +112 -5
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.js +117 -6
- package/src/engine/physics/cloth/solver/cloth_step.d.ts +40 -9
- package/src/engine/physics/cloth/solver/cloth_step.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_step.js +122 -10
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +95 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +285 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts +129 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_contact_row.js +501 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts +75 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_self_row.d.ts.map +1 -0
- package/src/engine/physics/cloth/solver/constraint/cloth_self_row.js +202 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/playground/vgeo_viewer/README.md +18 -0
- package/src/shade/playground/vgeo_viewer/main.js +87 -50
- package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUDatabase.js +191 -62
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts +94 -2
- package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
- package/src/shade/renderer/buffer/table/GPUTypedTable.js +414 -2
- package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts +14 -0
- package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.d.ts.map +1 -0
- package/src/shade/renderer/buffer/table/GPU_TABLE_PARTIAL_UPLOAD_DESCRIPTOR_WORDS.js +13 -0
- package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts +17 -0
- package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.d.ts.map +1 -0
- package/src/shade/renderer/buffer/table/gpu_table_partial_upload_shader.js +116 -0
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts +9 -4
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/bvh/GPUGeometryBVHManager.js +843 -734
- package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts +12 -0
- package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.d.ts.map +1 -1
- package/src/shade/renderer/geometry/bvh/GPUGeometryMeshletsBVH.js +13 -0
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts +17 -0
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.d.ts.map +1 -1
- package/src/shade/renderer/geometry/meshlet/GPUMeshletManager.js +185 -30
- package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +17 -0
- package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +141 -24
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +204 -64
- package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +110 -10
- package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +19 -0
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts +19 -6
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoByteSource.js +26 -7
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts +173 -14
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoContainerReader.js +458 -51
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts +49 -13
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/VGeoReadOptions.js +52 -14
- package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts +20 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/VGEO_MAX_AUDIT_BYTES.js +19 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts +27 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_audit_container.js +48 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts +29 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/format/read/tool/vgeo_install_all_pages.js +60 -0
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts +8 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.d.ts.map +1 -1
- package/src/shade/renderer/geometry/virtual/format/read/vgeo_fetch_byte_source.js +32 -2
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts +258 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyManager.js +723 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts +49 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.d.ts.map +1 -0
- package/src/shade/renderer/geometry/virtual/residency/VGeoResidencyOptions.js +50 -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/texture/bindless/GPUBindlessTextureManager.d.ts +3 -0
- package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.d.ts.map +1 -1
- package/src/shade/renderer/texture/bindless/GPUBindlessTextureManager.js +121 -21
- package/src/shade/renderer/texture/virtual/README.md +6 -4
- package/src/shade/renderer/texture/virtual/VirtualTextureManager.d.ts.map +1 -1
- package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +7 -0
- package/src/shade/renderer/texture/virtual/source/VTSourceTiled.d.ts +3 -3
- package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +3 -3
- package/src/engine/ecs/system/SystemExecutionPhase.d.ts +0 -9
- package/src/engine/ecs/system/SystemExecutionPhase.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/NOTES.md +0 -128
- package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts +0 -6
- package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VT_DEFAULT_PAGE_RESOLUTION.js +0 -5
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts +0 -49
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureTileLoader.js +0 -264
- package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts +0 -44
- package/src/engine/graphics/texture/virtual/VirtualTextureUsage.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureUsage.js +0 -228
- package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts +0 -3
- package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/VirtualTextureUsageShader.js +0 -73
- package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts +0 -14
- package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/debug/ResidencyDebugView.js +0 -85
- package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts +0 -12
- package/src/engine/graphics/texture/virtual/debug/UsageDebugView.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/debug/UsageDebugView.js +0 -63
- package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts +0 -12
- package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/debug/UsagePyramidDebugView.js +0 -243
- package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts +0 -30
- package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/VirtualTextureTile.js +0 -39
- package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts +0 -9
- package/src/engine/graphics/texture/virtual/tile/compose_finger_print.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/compose_finger_print.js +0 -24
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts +0 -9
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.js +0 -26
- package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts +0 -11
- package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/decompose_finger_print.js +0 -12
- package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts +0 -7
- package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/finger_print_to_tile_address.js +0 -16
- package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts +0 -7
- package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.d.ts.map +0 -1
- package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.js +0 -35
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Cloth — measurements
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> are **M1**, the component and the system
|
|
5
|
-
>
|
|
6
|
-
>
|
|
3
|
+
> Four milestones, appended in order. §1–§8 are **M0**, the solver core;
|
|
4
|
+
> §9–§15 are **M1**, the component and the system; §16–§23 are **M2**,
|
|
5
|
+
> collision; §24–§29 are **M3**, skinned garments. Nothing above a milestone
|
|
6
|
+
> boundary is edited by the milestone below it — a measurement that turned out to
|
|
7
|
+
> be incomplete is corrected by a later section saying so, not by a rewrite.
|
|
7
8
|
|
|
8
9
|
---
|
|
9
10
|
|
|
@@ -954,3 +955,1285 @@ leash and the tether bound the reachable set.
|
|
|
954
955
|
It reads world `Transform64`s "the hierarchy has already composed" and writes
|
|
955
956
|
local ones back, which is the arrangement above, and is why its scoring being
|
|
956
957
|
ambiguous matters less than it looks.
|
|
958
|
+
|
|
959
|
+
---
|
|
960
|
+
|
|
961
|
+
# Cloth — M2 measurements
|
|
962
|
+
|
|
963
|
+
> Written 2026-09-10, against the M2 implementation in `collider/`, the contact
|
|
964
|
+
> and backstop rows in `solver/constraint/`, and `core/geom/3d/shape/sdf/`.
|
|
965
|
+
> Everything above this line is M0's and M1's and is unchanged.
|
|
966
|
+
>
|
|
967
|
+
> Two scripts, both runnable directly under node — the M0 note applies unchanged,
|
|
968
|
+
> and for the same reason:
|
|
969
|
+
>
|
|
970
|
+
> ```
|
|
971
|
+
> node src/engine/physics/cloth/solver/__fixtures__/cloth_m2_measure.mjs
|
|
972
|
+
> node src/engine/physics/cloth/solver/__fixtures__/cloth_m2_band.mjs
|
|
973
|
+
> ```
|
|
974
|
+
>
|
|
975
|
+
> And a third playground page, `playground/collide.html`, which is the packed
|
|
976
|
+
> collider table rather than the ECS: six scenes, a sweep toggle, and the two
|
|
977
|
+
> contact-band sliders live.
|
|
978
|
+
|
|
979
|
+
---
|
|
980
|
+
|
|
981
|
+
## 16. What M2 proved wrong in PLAN.md
|
|
982
|
+
|
|
983
|
+
Six things. The first is a sign error in the design that would have been silent,
|
|
984
|
+
and the second is one M2 made itself and then measured its way out of.
|
|
985
|
+
|
|
986
|
+
| # | The design said | The measurement said |
|
|
987
|
+
|---|---|---|
|
|
988
|
+
| 0 | §8.2 — swept collision interpolates "between the previous and current **matrices**" | A component-wise lerp of two rotation matrices is not a rotation. In the rotation plane it scales by `cos(θ/2)`, so the shape it poses **shrinks**: at the half-radian per step `CLOTH_ANCHOR_ROTATION_LIMIT` already calls a spin, a 25 cm collider loses 7.8 mm halfway through every step — *twice* a silk garment's thickness. A rigid pose interpolates as a translation and a quaternion. §19 |
|
|
989
|
+
| 1 | §8.2 — the sweep is what stops a fast collider being missed | Only if the contact **set** is swept too. Discovery evaluates at the end-of-step pose, so a capsule that crossed the cloth *within* the step is nowhere near it by then: at 20 m/s the pass created **zero rows** and the sheet was untouched — not tunnelled through so much as never noticed. Adding the collider's travel to the discovery reach costs one subtraction a record and makes 2 to 40 m/s behave alike. §20 |
|
|
990
|
+
| 2 | §6 — the collision row carries `k_contact` and friction carries `k_friction` | By M0's own rule a stiffness that is not `Infinity` is a **soft** row, and a soft row's whole force is `k·C` with `k` capped at `8·(m/h²)` — 4.6 N at one thickness of penetration, against the 11 to 376 N the hard row actually produced on a sheet arriving at 1 to 8 m/s. Both rows are hard. §18 |
|
|
991
|
+
| 3 | §6 — friction clamps at `±μ·f_n` | That is the *signed-slip* formulation, which needs a tangent basis chosen per contact — and any choice continuous frame to frame is state, which is what §7 P4(a) exists to keep out. Written as the magnitude of the slip the row is `[0, μ·f_n]`, the same physics with no basis. §18 |
|
|
992
|
+
| 4 | §5.4 — contact rows "never enter" `ClothState` and are applied Jacobi-style | A collider contact is **unary** — one particle against a surface fixed for the substep — so it couples no two particles and there is no Gauss-Seidel/Jacobi distinction to make. It accumulates in the vertex update like a leash row. What is true is that it comes and goes, which is a *storage* problem and not an ordering one. §18 |
|
|
993
|
+
| 5 | §5.2 — Chebyshev acceleration is "available and disabled" | Built, measured against the contacts it is gated on, and removed. It does not help at any spectral radius tried, costs 2–5%, and diverges past `ρ = 0.9`. §21 |
|
|
994
|
+
|
|
995
|
+
And one thing that is a property of a configuration rather than of the solver,
|
|
996
|
+
recorded because it looks like a solver failure: **an exactly axis-aligned rope
|
|
997
|
+
pushed by an exactly axis-aligned plane has no lateral degree of freedom to
|
|
998
|
+
buckle into**, and folds into an accordion that is an exact equilibrium. §22.
|
|
999
|
+
|
|
1000
|
+
---
|
|
1001
|
+
|
|
1002
|
+
## 17. The contact penalty band, and `β` at last
|
|
1003
|
+
|
|
1004
|
+
M0 chose `k_min = k_max` — no ramp — and said so honestly: the ramp exists for
|
|
1005
|
+
**transient** rows, a contact that should arrive soft rather than shock the mesh
|
|
1006
|
+
on its first frame, and M0 had none. M1 had none either. M2 has them, and this
|
|
1007
|
+
is that measurement.
|
|
1008
|
+
|
|
1009
|
+
A 400-particle sheet, 4 mm thick, dropped flat onto a ground plane at 2 substeps
|
|
1010
|
+
× 4 iterations. Cells are worst penetration in metres over the run / kinetic
|
|
1011
|
+
energy after 240 steps. **Every configuration in the grid settles to a resting
|
|
1012
|
+
penetration of 7.9e-8 m** — the differences below are entirely about the
|
|
1013
|
+
transient.
|
|
1014
|
+
|
|
1015
|
+
```
|
|
1016
|
+
arriving at 3 m/s
|
|
1017
|
+
|
|
1018
|
+
k_min/k_max β=0e+0 β=1e+2 β=1e+3 β=1e+4 β=1e+5
|
|
1019
|
+
× 0.00781 1.08e-2/7.65e-9 1.08e-2/2.83e-8 1.08e-2/1.54e-8 1.08e-2/6.91e-9 1.08e-2/4.06e-6
|
|
1020
|
+
× 0.03125 1.08e-2/2.04e-8 1.08e-2/1.56e-8 1.08e-2/3.20e-8 1.08e-2/4.85e-8 1.08e-2/6.42e-5
|
|
1021
|
+
× 0.12500 1.08e-2/2.89e-6 1.08e-2/2.92e-6 1.08e-2/3.19e-6 1.08e-2/7.42e-6 1.08e-2/4.85e-4
|
|
1022
|
+
× 0.50000 2.22e-2/9.77e-3 2.26e-2/1.38e-2 2.29e-2/3.36e-2 1.99e-2/2.08e-2 2.03e-2/5.71e-3
|
|
1023
|
+
× 1.00000 1.60e-2/1.11e-2 1.60e-2/1.11e-2 1.60e-2/1.11e-2 1.60e-2/1.11e-2 1.60e-2/1.11e-2
|
|
1024
|
+
|
|
1025
|
+
arriving at 8 m/s
|
|
1026
|
+
|
|
1027
|
+
k_min/k_max β=0e+0 β=1e+2 β=1e+3 β=1e+4 β=1e+5
|
|
1028
|
+
× 0.00781 1.12e-1/2.51e+0 9.51e-2/3.99e-2 8.11e-2/1.09e-1 1.16e-1/2.55e-1 1.14e-1/2.65e-2
|
|
1029
|
+
× 0.03125 1.06e-1/1.95e+0 1.11e-1/2.74e-1 8.55e-2/1.16e-1 8.99e-2/5.01e-1 8.05e-2/6.16e-1
|
|
1030
|
+
× 0.12500 1.22e-1/5.80e-1 1.18e-1/8.35e-2 9.92e-2/2.77e-1 1.28e-1/2.42e-1 1.25e-1/8.04e-2
|
|
1031
|
+
× 0.50000 1.21e-1/2.48e-1 1.19e-1/2.02e-1 1.25e-1/2.29e-1 1.05e-1/2.65e-1 8.47e-2/2.27e-1
|
|
1032
|
+
× 1.00000 9.34e-2/2.76e-1 9.34e-2/2.76e-1 9.34e-2/2.76e-1 9.34e-2/2.76e-1 9.34e-2/2.76e-1
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
The bottom row is flat across `β` because at `k_min = k_max` the penalty is
|
|
1036
|
+
already at the ceiling and `min(k + β|C|, k_max)` has nothing to do — which is
|
|
1037
|
+
exactly M0's configuration, reproduced here as the control.
|
|
1038
|
+
|
|
1039
|
+
**The floor is what matters, and lower is better.** At `×1` a 3 m/s landing peaks
|
|
1040
|
+
at 18.9 N of contact force and settles at 1.1e-2 J; at `×1/8` it peaks at 0.16 N
|
|
1041
|
+
and settles at 2.9e-6 J, and the *penetration improves* from 1.6 cm to 1.1 cm at
|
|
1042
|
+
the same time. Below about `×1/8` the penetration column stops moving entirely —
|
|
1043
|
+
1.08e-2 to three figures from `×1/8` down to `×1/128`.
|
|
1044
|
+
|
|
1045
|
+
That flatness is the augmented Lagrangian working as designed and is worth
|
|
1046
|
+
stating plainly: **the penalty only has to be large enough to generate a
|
|
1047
|
+
multiplier**, and the multiplier carries the load. A stiffer floor buys nothing
|
|
1048
|
+
except a harder first frame.
|
|
1049
|
+
|
|
1050
|
+
**`β` earns its place at the fast arrival and nowhere else.** At 3 m/s every
|
|
1051
|
+
value from 0 to 1e4 is indistinguishable. At 8 m/s, `β = 0` leaves the penalty at
|
|
1052
|
+
the floor for the whole impact and the residual energy is 1.95 to 2.51 J against
|
|
1053
|
+
0.11 to 0.12 J at `β = 1e3`; and `β = 1e5` reaches the ceiling on millimetre
|
|
1054
|
+
violations, giving back most of what the low floor bought — 6.4e-5 J against
|
|
1055
|
+
3.2e-8 J on the gentle landing.
|
|
1056
|
+
|
|
1057
|
+
**Chosen: `k_min = k_max/32`, `β = 1e3`.** The floor sits in the middle of the
|
|
1058
|
+
flat region with two decades of ramp above it; `β` sits in the middle of the
|
|
1059
|
+
range where the fast arrival is well behaved and the slow one is unaffected.
|
|
1060
|
+
|
|
1061
|
+
### The other direction a contact arrives from
|
|
1062
|
+
|
|
1063
|
+
The grid above is cloth arriving at a stationary collider. A garment's contacts
|
|
1064
|
+
are mostly the other way round — a bone driven into stationary cloth — and a
|
|
1065
|
+
floor low enough to make a landing gentle could plausibly be a floor a pushed
|
|
1066
|
+
collider gets further through. Measured, on a sphere driven 6 cm into a hanging
|
|
1067
|
+
cape at 1 m/s, beside the same landing:
|
|
1068
|
+
|
|
1069
|
+
```
|
|
1070
|
+
floor cape: sphere driven in at 1 m/s sheet: landing at 3 m/s
|
|
1071
|
+
worst resting kinetic worst force N kinetic
|
|
1072
|
+
×1.00000 3.02e-3 3.96e-4 3.59e-2 1.32e-2 1.963 2.11e-3
|
|
1073
|
+
×0.25000 2.60e-3 2.95e-4 2.44e-2 1.08e-2 0.805 1.41e-4
|
|
1074
|
+
×0.12500 2.56e-3 3.04e-4 2.56e-2 1.08e-2 0.800 2.67e-5
|
|
1075
|
+
×0.03125 2.66e-3 2.90e-4 2.25e-2 1.08e-2 0.803 9.99e-7
|
|
1076
|
+
×0.00781 2.71e-3 3.08e-4 2.36e-2 1.08e-2 0.805 5.35e-8
|
|
1077
|
+
|
|
1078
|
+
and the same cape with the sphere driven in faster:
|
|
1079
|
+
|
|
1080
|
+
floor 1 m/s 3 m/s 8 m/s (worst penetration, m)
|
|
1081
|
+
×1.00000 3.02e-3 6.54e-3 3.55e-2
|
|
1082
|
+
×0.12500 2.56e-3 5.53e-3 3.14e-2
|
|
1083
|
+
×0.03125 2.66e-3 5.69e-3 3.25e-2
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
They do not disagree. The cape's penetration stays inside its own 4 mm thickness
|
|
1087
|
+
at every floor and at every speed up to 3 m/s, and the lower floors are
|
|
1088
|
+
marginally *better* rather than worse. The choice above holds on both.
|
|
1089
|
+
|
|
1090
|
+
---
|
|
1091
|
+
|
|
1092
|
+
## 18. The contact row: hard, and what a soft one could have delivered
|
|
1093
|
+
|
|
1094
|
+
§6's table gives Collision a stiffness `k_contact` and Friction a `k_friction`,
|
|
1095
|
+
and M0's rule makes that literal: **a row is hard exactly when its stiffness is
|
|
1096
|
+
`Infinity`**, so a named stiffness is a statement that both rows are soft
|
|
1097
|
+
penalties.
|
|
1098
|
+
|
|
1099
|
+
A soft row's entire force is `k·C`, and M0 measured the largest `k` a soft row
|
|
1100
|
+
may carry and still dissipate: `8·(m/h²)`. So the most force a soft contact can
|
|
1101
|
+
produce before the cloth is a full thickness inside the collider is
|
|
1102
|
+
`8·(m/h²)·thickness`. Against what the hard row actually had to produce:
|
|
1103
|
+
|
|
1104
|
+
```
|
|
1105
|
+
a soft row's ceiling at one thickness of penetration: 4.608 N
|
|
1106
|
+
|
|
1107
|
+
arriving at 0.0 m/s: the hard row produced 0.582 N ( 0.13× the ceiling)
|
|
1108
|
+
arriving at 1.0 m/s: 11.063 N ( 2.40×)
|
|
1109
|
+
arriving at 3.0 m/s: 18.908 N ( 4.10×)
|
|
1110
|
+
arriving at 8.0 m/s: 375.655 N (81.52×)
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
A cloth that is merely *resting* is inside what a soft row could do — 13% of it.
|
|
1114
|
+
Anything that arrives is not. A soft contact row is therefore a contact that
|
|
1115
|
+
holds a draped garment and fails every time the garment is put down, and the
|
|
1116
|
+
failure is proportional to the impact.
|
|
1117
|
+
|
|
1118
|
+
Both rows are hard: `Infinity` stiffness, the penalty band of §17, and the
|
|
1119
|
+
multiplier carrying the load.
|
|
1120
|
+
|
|
1121
|
+
### Friction against the analytic answer
|
|
1122
|
+
|
|
1123
|
+
Coulomb says a body sticks when `μ > tan θ` and slides when it does not. A 20×20
|
|
1124
|
+
sheet released on an inclined plane, metres travelled downhill in four seconds:
|
|
1125
|
+
|
|
1126
|
+
```
|
|
1127
|
+
slope tan θ μ=0.0 μ=0.2 μ=0.4 μ=0.7 μ=1.2
|
|
1128
|
+
10° 0.1763 6.9139 0.0001 0.0000 0.0000 0.0000
|
|
1129
|
+
20° 0.3640 13.5772 5.6235 0.0001 0.0001 0.0000
|
|
1130
|
+
30° 0.5774 19.6705 11.7592 5.0431 0.0002 0.0001
|
|
1131
|
+
40° 0.8391 24.5862 16.9339 10.5951 2.8836 0.0002
|
|
1132
|
+
50° 1.1918 27.4699 20.5496 14.9029 8.1086 0.0007
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
The transition sits on `tan θ` in every row: 0.2 sticks at 10° and slides at 20°,
|
|
1136
|
+
0.4 sticks at 20° and slides at 30°, 0.7 sticks at 30° and slides at 40°, 1.2
|
|
1137
|
+
sticks at 50°. The sticking cells are tenths of a millimetre over four seconds,
|
|
1138
|
+
which is the row holding rather than creeping.
|
|
1139
|
+
|
|
1140
|
+
**The clamp is `[0, μ·f_n]`, not `±μ·f_n`.** §6's symmetric pair is the
|
|
1141
|
+
signed-slip formulation: a row whose `C` is a displacement along a chosen
|
|
1142
|
+
tangent. This row's `C` is the *magnitude* of the slip in the tangent plane, so
|
|
1143
|
+
`C ≥ 0` always and the negative half of the clamp would name a force pulling the
|
|
1144
|
+
particle further from where it stuck. Writing it as a magnitude is also what
|
|
1145
|
+
removes the tangent basis — a signed row needs two of them per contact, and any
|
|
1146
|
+
choice that is continuous frame to frame carries state, which is the thing §7
|
|
1147
|
+
P4(a) exists to keep out of this system.
|
|
1148
|
+
|
|
1149
|
+
**The cap reads the current iteration's normal force, not the previous
|
|
1150
|
+
iteration's multiplier.** VBD uses the multiplier, which is one iteration stale
|
|
1151
|
+
and therefore exactly zero on a contact's first iteration — so at the one
|
|
1152
|
+
iteration per substep §5.1 says the solver must be usable at, friction would
|
|
1153
|
+
never act at all.
|
|
1154
|
+
|
|
1155
|
+
### Contacts are unary, so §5.4's Jacobi treatment is not needed here
|
|
1156
|
+
|
|
1157
|
+
§5.4 says contact rows "cannot be pre-coloured", are "accumulated into a
|
|
1158
|
+
per-particle scratch buffer and applied Jacobi-style", and that this is what
|
|
1159
|
+
makes a step deterministic regardless of how many contacts a particle picked up.
|
|
1160
|
+
|
|
1161
|
+
Half of that is a solution to a problem a *collider* contact does not have. A
|
|
1162
|
+
contact against a collider touches **one** particle: the collider is fixed for
|
|
1163
|
+
the substep, so the row couples no two particles, so there is no Gauss-Seidel
|
|
1164
|
+
versus Jacobi distinction to make and nothing to keep out of the colouring. It
|
|
1165
|
+
accumulates in the vertex update exactly like a leash row, in colour order, and
|
|
1166
|
+
the step is deterministic because the contact *set* is built by one pass in a
|
|
1167
|
+
fixed order over a fixed table.
|
|
1168
|
+
|
|
1169
|
+
What is true is that a contact comes and goes, so it cannot be a CSR table sized
|
|
1170
|
+
at build time. M2's answer is a fixed **four slots per particle** inside
|
|
1171
|
+
`ClothState`'s own buffer, rebuilt each step, keyed on the collider's index
|
|
1172
|
+
handle so a multiplier survives the rebuild. §5.4's reasoning applies unchanged
|
|
1173
|
+
to M4's particle-particle rows, which really are pairs.
|
|
1174
|
+
|
|
1175
|
+
Four slots is generous. On the 20-collider budget scene, over 100 steps × 1,024
|
|
1176
|
+
particles: **zero particle-steps reached the slot limit**, and the mean occupancy
|
|
1177
|
+
was 0.10 slots per particle. The cost of the generosity is 36 words per particle
|
|
1178
|
+
— 147 KB at 1,024 particles against `ClothState`'s 370 KiB, a 40% increase in
|
|
1179
|
+
state for a structure that was never more than a quarter full. It is kept at four
|
|
1180
|
+
because the case it is sized for is an armpit rather than a sheet on a floor, and
|
|
1181
|
+
M3's garments are where that will be visible.
|
|
1182
|
+
|
|
1183
|
+
---
|
|
1184
|
+
|
|
1185
|
+
## 19. The interpolated pose is a rigid pose, not a lerp of matrices
|
|
1186
|
+
|
|
1187
|
+
§8.2 says swept behaviour "comes from interpolating the collider's pose between
|
|
1188
|
+
the previous and current **matrices** at the substep's fraction". That is the one
|
|
1189
|
+
part of §8 that does not work, and the reason is arithmetic rather than taste: a
|
|
1190
|
+
component-wise lerp of two rotation matrices is not a rotation matrix. Halfway
|
|
1191
|
+
through a turn of `θ` its columns are shorter by `cos(θ/2)` in the rotation
|
|
1192
|
+
plane, so the shape it poses is *shrunk*.
|
|
1193
|
+
|
|
1194
|
+
```
|
|
1195
|
+
θ (rad) |lerp(I,R)·x| shrink on a 25 cm collider vs a 4 mm cloth
|
|
1196
|
+
0.050 0.999688 0.078 mm 0.02×
|
|
1197
|
+
0.100 0.998750 0.312 mm 0.08×
|
|
1198
|
+
0.250 0.992198 1.951 mm 0.49×
|
|
1199
|
+
0.500 0.968912 7.772 mm 1.94×
|
|
1200
|
+
1.000 0.877583 30.604 mm 7.65×
|
|
1201
|
+
1.571 0.707107 73.223 mm 18.31×
|
|
1202
|
+
```
|
|
1203
|
+
|
|
1204
|
+
`CLOTH_ANCHOR_ROTATION_LIMIT` is half a radian per step, so 0.5 is the fastest
|
|
1205
|
+
rotation a cloth is allowed to see, and the shrink there is already twice a
|
|
1206
|
+
garment's thickness. A collider that shrinks by twice the cloth's thickness
|
|
1207
|
+
halfway through every step is a collider cloth passes through for reasons that
|
|
1208
|
+
have nothing to do with speed.
|
|
1209
|
+
|
|
1210
|
+
A translation and a quaternion interpolate rigidly by construction — the
|
|
1211
|
+
quaternion is renormalised, so its column reads 1.000000 at every angle — and
|
|
1212
|
+
that is what `ClothColliderRecord` stores. The `nlerp` is not constant angular
|
|
1213
|
+
velocity and over the fraction of a fixed step it does not need to be.
|
|
1214
|
+
|
|
1215
|
+
---
|
|
1216
|
+
|
|
1217
|
+
## 20. Sweeping the poses is not enough; the *set* has to be swept too
|
|
1218
|
+
|
|
1219
|
+
M2 built §8.2's pose interpolation, measured it against a capsule driven through
|
|
1220
|
+
a hanging sheet, and found it doing nothing at all above 5 m/s. The reason was
|
|
1221
|
+
not the interpolation.
|
|
1222
|
+
|
|
1223
|
+
`cloth_contact_find` runs once per step and evaluates the field at the
|
|
1224
|
+
**end-of-step** pose. A collider that crossed the cloth *within* the step is
|
|
1225
|
+
already past it by then, so the speculative margin — twice the contact offset,
|
|
1226
|
+
8 mm on a garment — finds nothing, no row is created, and the swept poses have
|
|
1227
|
+
nothing to act through.
|
|
1228
|
+
|
|
1229
|
+
```
|
|
1230
|
+
speed pose worst penetration deep rows/step
|
|
1231
|
+
2 m/s swept (§8.2) 1.85e-2 m 0 16.4
|
|
1232
|
+
2 m/s end-of-step 3.99e-2 m 4 7.2
|
|
1233
|
+
5 m/s swept (§8.2) 3.38e-2 m 1 14.5
|
|
1234
|
+
5 m/s end-of-step 0.00e+0 m 0 1.4
|
|
1235
|
+
10 m/s swept (§8.2) 3.08e-4 m 0 31.3
|
|
1236
|
+
10 m/s end-of-step 0.00e+0 m 0 1.4
|
|
1237
|
+
20 m/s swept (§8.2) 0.00e+0 m 0 94.8
|
|
1238
|
+
20 m/s end-of-step 0.00e+0 m 0 0.0
|
|
1239
|
+
40 m/s swept (§8.2) 0.00e+0 m 0 160.0
|
|
1240
|
+
40 m/s end-of-step 0.00e+0 m 0 0.0
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
Read the last column first. "end-of-step" — the previous pose set equal to the
|
|
1244
|
+
current one, which is what §8.2 would be without the sweep — creates **no rows at
|
|
1245
|
+
all** at 20 and 40 m/s. Its zero penetration is not a contact working; it is a
|
|
1246
|
+
capsule passing through a sheet that never knew it was there. "deep" counts
|
|
1247
|
+
particle-steps more than half a capsule radius inside, and the swept column holds
|
|
1248
|
+
it at zero or one across the whole range.
|
|
1249
|
+
|
|
1250
|
+
The fix is one subtraction per record per step: **the discovery reach includes
|
|
1251
|
+
how far the collider moved.** A particle within `offset·(1 + margin) + travel` of
|
|
1252
|
+
the end-of-step surface gets a row, which stays clamped out until a substep's
|
|
1253
|
+
interpolated pose actually brings the collider onto it.
|
|
1254
|
+
|
|
1255
|
+
That is also the honest boundary of P3. The *set* is swept by this margin and the
|
|
1256
|
+
*poses* by `cloth_collider_pose_at`, and both are translation-first: a collider
|
|
1257
|
+
spinning fast enough that a point on its surface travels more than its own radius
|
|
1258
|
+
within one substep is still missed, and more substeps is the answer to it.
|
|
1259
|
+
|
|
1260
|
+
---
|
|
1261
|
+
|
|
1262
|
+
## 21. Chebyshev acceleration: built, measured, removed
|
|
1263
|
+
|
|
1264
|
+
§5.2 carries Chebyshev acceleration as "available and disabled by default, turned
|
|
1265
|
+
on per-dynamics only where it measurably helps", gated on a particle not being in
|
|
1266
|
+
contact this step — which is why M0 and M1 could not evaluate it and M2 is the
|
|
1267
|
+
first milestone that can.
|
|
1268
|
+
|
|
1269
|
+
Implemented as the paper specifies: `ω₁ = 1`, `ω₂ = 2/(2−ρ²)`, `ωₙ = 4/(4−ρ²ωₙ₋₁)`,
|
|
1270
|
+
applied as `x ← ω(x − x^(k−1)) + x^(k−1)` after each colour sweep, skipping every
|
|
1271
|
+
particle with a contact slot.
|
|
1272
|
+
|
|
1273
|
+
```
|
|
1274
|
+
a 400-particle sheet landing at 3 m/s
|
|
1275
|
+
|
|
1276
|
+
ρ worst pen. resting pen. kinetic@240 ms/step
|
|
1277
|
+
0.00 1.60e-2 7.90e-8 1.11e-2 3.604
|
|
1278
|
+
0.50 1.42e-2 7.90e-8 1.67e-2 3.773
|
|
1279
|
+
0.80 1.70e-2 7.90e-8 1.50e-2 3.682
|
|
1280
|
+
0.90 7.72e-2 4.71e-2 7.60e+1 3.345
|
|
1281
|
+
0.95 4.05e+10 3.66e+10 3.87e+26 3.588
|
|
1282
|
+
0.99 diverged — a Hessian pivot reached Infinity
|
|
1283
|
+
|
|
1284
|
+
a hanging cape with a sphere pushed 6 cm into it, where most particles
|
|
1285
|
+
are free and the acceleration has room:
|
|
1286
|
+
|
|
1287
|
+
ρ=0.00 worst 5.78e-4 resting 3.00e-4 kinetic 4.70e-4
|
|
1288
|
+
ρ=0.80 worst 3.31e-4 resting 3.10e-4 kinetic 4.90e-3
|
|
1289
|
+
ρ=0.95 worst 3.06e-3 resting 0.00e+0 kinetic 1.68e+46
|
|
1290
|
+
```
|
|
1291
|
+
|
|
1292
|
+
The best cell in the table is the cape at `ρ = 0.8`, which takes a third off the
|
|
1293
|
+
penetration and pays ten times the residual kinetic energy for it — a livelier
|
|
1294
|
+
cloth, not a better-converged one. Everything else is inside the run-to-run
|
|
1295
|
+
spread or worse, the cost is 2–5%, and the top of the parameter range detonates.
|
|
1296
|
+
|
|
1297
|
+
Removed. The grid is here so that a future attempt starts from the numbers rather
|
|
1298
|
+
than from the idea, and `cloth_step.js` says where the branch was.
|
|
1299
|
+
|
|
1300
|
+
---
|
|
1301
|
+
|
|
1302
|
+
## 22. A rope with no lateral freedom stands on its own end
|
|
1303
|
+
|
|
1304
|
+
Not a solver defect, and recorded because it looks exactly like one.
|
|
1305
|
+
|
|
1306
|
+
A rope authored exactly along −Y, compressed by a floor whose normal is exactly
|
|
1307
|
+
+Y, has every force, every constraint gradient and every position on one axis.
|
|
1308
|
+
There is no lateral component anywhere in the system, so the accordion fold it
|
|
1309
|
+
lands in is an **exact equilibrium**: every link is at its rest length, the
|
|
1310
|
+
residual is zero, and nothing — including §7 P4(d)'s escape hatch, which watches
|
|
1311
|
+
the residual — has anything to act on.
|
|
1312
|
+
|
|
1313
|
+
An 8-link rope of 10 cm links, hanging from the origin onto a floor at −0.4:
|
|
1314
|
+
|
|
1315
|
+
```
|
|
1316
|
+
lean tip y after 200 steps what it did
|
|
1317
|
+
0 −0.201 folded, and stayed folded
|
|
1318
|
+
0.001 −0.385 lay down, one thickness clear of the floor
|
|
1319
|
+
0.01 −0.385 the same
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
`lean` is a lateral offset per link as a fraction of the link length, so 0.001 is
|
|
1323
|
+
a millimetre across a 10 cm link. **One part in a thousand is enough**, and no
|
|
1324
|
+
authored scene is closer to symmetric than that. The tip of the leaning rope
|
|
1325
|
+
lands at −0.385 against a floor at −0.4 and a `CLOTH_ROPE` thickness of 15 mm,
|
|
1326
|
+
which is the contact row holding it exactly one offset clear.
|
|
1327
|
+
|
|
1328
|
+
Recorded rather than fixed. The fix would be a lateral perturbation injected by
|
|
1329
|
+
the solver, which is noise added to a system whose whole design commitment is
|
|
1330
|
+
that it does not need any.
|
|
1331
|
+
|
|
1332
|
+
---
|
|
1333
|
+
|
|
1334
|
+
## 23. The frame budget with contacts in it
|
|
1335
|
+
|
|
1336
|
+
M1 measured the §17 workload — 1,024 particles at 2 substeps × 4 iterations on a
|
|
1337
|
+
32×32 patch — at 4.61 ms and re-sized the library around it. M2 adds a broadphase
|
|
1338
|
+
pass per particle per step, up to four contact rows per particle, and two field
|
|
1339
|
+
evaluations per contact per iteration.
|
|
1340
|
+
|
|
1341
|
+
```
|
|
1342
|
+
no collider system 4.426 ms 0 contact rows
|
|
1343
|
+
1 collider, out of range 4.473 ms + 1.1% 0 contact rows
|
|
1344
|
+
1 collider, touching 5.485 ms + 23.9% 4 contact rows
|
|
1345
|
+
4 colliders, touching 5.077 ms + 14.7% 41 contact rows
|
|
1346
|
+
8 colliders, touching 4.610 ms + 4.2% 72 contact rows
|
|
1347
|
+
20 colliders, touching 4.829 ms + 9.1% 107 contact rows
|
|
1348
|
+
```
|
|
1349
|
+
|
|
1350
|
+
**Read the range, not the rows.** The column is not monotone in the collider
|
|
1351
|
+
count — one collider costs more than eight — because the variants sit in
|
|
1352
|
+
different physical states, which is the same warning §7 gave about attributing
|
|
1353
|
+
cost by deletion. A cloth draped over a sphere is not the same workload as one
|
|
1354
|
+
hanging free, whatever the row count says.
|
|
1355
|
+
|
|
1356
|
+
What the table does license: **the collision layer costs between nothing and a
|
|
1357
|
+
quarter of a frame, and does not scale with the collider count over the range a
|
|
1358
|
+
character occupies.** The broadphase — a particle against a collider's swept AABB
|
|
1359
|
+
— is the part that does scale, and one collider out of range costs 1.1%, so
|
|
1360
|
+
twenty of them out of range would be about a fifth of a frame and worth a
|
|
1361
|
+
per-cloth spatial structure if a scene ever wants that many.
|
|
1362
|
+
|
|
1363
|
+
**The library is not re-sized.** M1's entries were chosen against 4.61 ms for the
|
|
1364
|
+
§17 workload and M2 measures 4.43 ms for the same thing — agreement to 4%, inside
|
|
1365
|
+
M1's own ±5% spread — and the worst contact case lands at 5.49 ms, which is 19%
|
|
1366
|
+
over M1's figure on a workload the library already spends two levers to avoid.
|
|
1367
|
+
Every entry's own budget is unchanged; §17's table stands. M4's self-collision is
|
|
1368
|
+
the next thing that could move it, and §17 already says so.
|
|
1369
|
+
|
|
1370
|
+
---
|
|
1371
|
+
|
|
1372
|
+
# Cloth — M3 measurements
|
|
1373
|
+
|
|
1374
|
+
> Written 2026-09-10, against the M3 implementation in `build/` and the rig path
|
|
1375
|
+
> in `ecs/`. Everything above this line is M0's, M1's and M2's and is unchanged.
|
|
1376
|
+
>
|
|
1377
|
+
> Two scripts, both runnable directly under node — the M0 note applies unchanged:
|
|
1378
|
+
>
|
|
1379
|
+
> ```
|
|
1380
|
+
> node src/engine/physics/cloth/ecs/__fixtures__/cloth_m3_measure.mjs
|
|
1381
|
+
> node src/engine/physics/cloth/build/__fixtures__/cloth_m3_joint_budget.mjs
|
|
1382
|
+
> ```
|
|
1383
|
+
>
|
|
1384
|
+
> And a fourth playground page, `playground/garment.html`, which is a `Node3D`
|
|
1385
|
+
> skeleton with a `Skin` over it and a garment built out of some of its joints.
|
|
1386
|
+
|
|
1387
|
+
---
|
|
1388
|
+
|
|
1389
|
+
## 24. What M3 proved wrong in PLAN.md
|
|
1390
|
+
|
|
1391
|
+
Four things, and the first is the one the whole rig path turns on.
|
|
1392
|
+
|
|
1393
|
+
| # | The design said | The measurement said |
|
|
1394
|
+
|---|---|---|
|
|
1395
|
+
| 0 | §11.2 — the writable handle is "the joint's `Node3D.transform_local` — **the same handle the animation systems write**" | The animation systems do not write it. `TransformAuthority` is explicit: a node a clip drives is `GPU`, and "the CPU allocates and seeds the row, then never writes to it again, so the node's own `transform_local` stops describing it and should not be read as if it did". Cloth writing it on such a node writes to something nothing reads. The seed **claims `CPU`** on every joint it drives, which takes those joints away from the animation — the right trade for a cape, and an authoring error for a garment whose joints are also keyed. §25 |
|
|
1396
|
+
| 1 | §11.1 — the rig path is "where the animated pose comes from the skeleton and **is live**" | Not for a rigged garment, and for the reason above: cloth owns those joints, so nothing else poses them, so their authored pose is the proxy's baked bind pose — constant in the anchor's frame, exactly as a rope's is. What moves is the anchor. The live case is the *unrigged* path, where the targets are mesh vertices skinned to the character's skeleton. §25 |
|
|
1397
|
+
| 2 | §11.3 step 5 — the fixed set comes from skin weights | That is the right rule for the unrigged path, where the particles are mesh vertices and there is no joint tree. For a rigged garment the **topology** is the answer: a joint whose parent is outside the named set is a joint the garment hangs from, and they all have to hang from the same bone. No weights are read. §25 |
|
|
1398
|
+
| 3 | §11.2's table — "output joints, order of magnitude, skirt: ~60" | For a 64 cm cape driven by a gust, 60 joints leaves a worst-case reconstruction error of about 5% of the garment's own size — five grid cells. RMS error goes as **1/joints** and the worst as **N^−0.83**; getting the worst inside a garment's own thickness needs about a thousand. §28 |
|
|
1399
|
+
|
|
1400
|
+
And one finding that is not a correction but is the thing an author will hit
|
|
1401
|
+
first: **a garment authored inside its own body collider reads as a tether
|
|
1402
|
+
failure.** §29.
|
|
1403
|
+
|
|
1404
|
+
---
|
|
1405
|
+
|
|
1406
|
+
## 25. The two paths are the same solver, and the number is zero
|
|
1407
|
+
|
|
1408
|
+
§11 claims the entity path and the rig path are "the solver, identical either
|
|
1409
|
+
way; only seeding and write-back differ". That is a claim about trajectories and
|
|
1410
|
+
it is checkable: build the same garment twice — five strips of seven links under
|
|
1411
|
+
one anchor, once as an entity subtree and once as the joints of a `Skin` —
|
|
1412
|
+
drive both the same way, and compare the particles in the cloth's own frame.
|
|
1413
|
+
|
|
1414
|
+
```
|
|
1415
|
+
fabric motion worst disagreement as a fraction of the shortest link
|
|
1416
|
+
SILK still 0.00e+0 m 0.00e+0
|
|
1417
|
+
SILK walk 0.00e+0 m 0.00e+0
|
|
1418
|
+
SILK sprint 0.00e+0 m 0.00e+0
|
|
1419
|
+
DENIM still 0.00e+0 m 0.00e+0
|
|
1420
|
+
DENIM walk 0.00e+0 m 0.00e+0
|
|
1421
|
+
DENIM sprint 0.00e+0 m 0.00e+0
|
|
1422
|
+
CHAIN still 0.00e+0 m 0.00e+0
|
|
1423
|
+
CHAIN walk 0.00e+0 m 0.00e+0
|
|
1424
|
+
CHAIN sprint 0.00e+0 m 0.00e+0
|
|
1425
|
+
```
|
|
1426
|
+
|
|
1427
|
+
**Bit-identical**, across three fabrics and three motion profiles, after 600
|
|
1428
|
+
steps. Not "agreeing to rounding" — the same bits.
|
|
1429
|
+
|
|
1430
|
+
That is not luck and it is worth saying why, because it is the shape the code was
|
|
1431
|
+
given to make it true. M3 moved the row builder out of `cloth_seed_subtree.js`
|
|
1432
|
+
into `cloth_build_rows.js`, and the two seeders' *entire* job is to produce a
|
|
1433
|
+
`node_parent` array and an `animated_position` array. Everything after that —
|
|
1434
|
+
geodesics, link rows, strain limits, skip rows, leashes, the colouring, the
|
|
1435
|
+
`ClothState` — is one function called by both. A second copy for the rig path
|
|
1436
|
+
would have made §11's claim true of the solver and false of everything above it,
|
|
1437
|
+
and this table is what that would have cost.
|
|
1438
|
+
|
|
1439
|
+
The write-back is the same story one level up: `cloth_reconstruct_pose.js` turns
|
|
1440
|
+
particles into a pose and stops, and the two writers put that pose onto an
|
|
1441
|
+
entity's `Transform64` + `TransformAttachment` or onto a `Node3D`.
|
|
1442
|
+
|
|
1443
|
+
### Cloth takes the joints, and that is the design rather than a side effect
|
|
1444
|
+
|
|
1445
|
+
§11.2 says the writable handle is `Node3D.transform_local`, "the same handle the
|
|
1446
|
+
animation systems write". They do not. `TransformAuthority`'s own docblock is
|
|
1447
|
+
unambiguous:
|
|
1448
|
+
|
|
1449
|
+
> The GPU owns the TRS — animation clips, effects, anything writing through the
|
|
1450
|
+
> pose accumulator. The CPU allocates and seeds the row, then never writes to it
|
|
1451
|
+
> again, so the node's own `transform_local` stops describing it and should not
|
|
1452
|
+
> be read as if it did.
|
|
1453
|
+
|
|
1454
|
+
And `instantiate_scene_bundle` stamps `TransformAuthority.GPU` on every node a
|
|
1455
|
+
clip channel targets. So a cape joint that an artist keyed — even with a
|
|
1456
|
+
placeholder curve — is a joint whose `transform_local` nothing reads, and cloth
|
|
1457
|
+
writing it would be writing into a field that stopped being the truth.
|
|
1458
|
+
|
|
1459
|
+
`cloth_seed_rig` therefore **claims `TransformAuthority.CPU`** on every joint it
|
|
1460
|
+
drives. The consequence is stated rather than hidden: those joints stop being
|
|
1461
|
+
animatable by a clip, because two writers of one row is the race the enum exists
|
|
1462
|
+
to prevent. For a cape that is the point of simulating it. For a garment whose
|
|
1463
|
+
joints are also keyed it is an authoring error, and the claim is where it
|
|
1464
|
+
surfaces.
|
|
1465
|
+
|
|
1466
|
+
### The anchor's pose is computed, never read back
|
|
1467
|
+
|
|
1468
|
+
A joint of an animated skeleton has no CPU-side `transform_global` — nothing
|
|
1469
|
+
maintains it — so the rig path evaluates the anchor's world pose with
|
|
1470
|
+
`pose_evaluate_world` over the clips the entity is playing. That is the engine's
|
|
1471
|
+
own sanctioned answer to "where is this bone", the one
|
|
1472
|
+
`query_entity_node_world_pose` gives gameplay, and using it means a cape and a
|
|
1473
|
+
scabbard on the same shoulder are placed by the same evaluator off the same
|
|
1474
|
+
clock. M3 lifted `collect_entity_playbacks` out of that query so both callers
|
|
1475
|
+
gather the same playbacks rather than two lists that could disagree.
|
|
1476
|
+
|
|
1477
|
+
---
|
|
1478
|
+
|
|
1479
|
+
## 26. The tether on a fanned garment
|
|
1480
|
+
|
|
1481
|
+
Worst `max(|x − anchor| / d_geo)` over 600 steps, both paths, five strips of
|
|
1482
|
+
seven.
|
|
1483
|
+
|
|
1484
|
+
```
|
|
1485
|
+
fabric motion entity path rig path
|
|
1486
|
+
SILK still 1.0005 1.0005
|
|
1487
|
+
SILK walk 1.0027 1.0027
|
|
1488
|
+
SILK sprint 1.1254 1.1254
|
|
1489
|
+
DENIM still 1.0026 1.0026
|
|
1490
|
+
DENIM walk 1.0019 1.0019
|
|
1491
|
+
DENIM sprint 1.4561 1.4561
|
|
1492
|
+
CHAIN still 1.0002 1.0002
|
|
1493
|
+
CHAIN walk 1.0029 1.0029
|
|
1494
|
+
CHAIN sprint 1.1738 1.1738
|
|
1495
|
+
```
|
|
1496
|
+
|
|
1497
|
+
Resting and walking are inside 0.3%, which is M1's own figure. **Sprinting is
|
|
1498
|
+
not**, and denim reaches 1.46.
|
|
1499
|
+
|
|
1500
|
+
The two columns being identical is the point: this is not the rig path failing,
|
|
1501
|
+
it is §7 P2's conditional bound showing on a shape M1 did not measure. M1's
|
|
1502
|
+
profile drove a **20-link rope of uniform 10 cm links with a 2 m reach**, and a
|
|
1503
|
+
±9 m/s square wave moves the anchor 15 cm a step — 7% of that reach. The garment
|
|
1504
|
+
here has a 0.68 m reach, so the same 15 cm is 22% of it, and it is a *fan*: five
|
|
1505
|
+
strips whose first links are nearly horizontal, so a sideways reversal loads them
|
|
1506
|
+
across rather than along.
|
|
1507
|
+
|
|
1508
|
+
Denim is worst because it is the entry that spends its levers on particles rather
|
|
1509
|
+
than on iterations: 2×2 against silk's 2×4, so half the correction authority for
|
|
1510
|
+
the same displacement. That is the library's own trade, priced.
|
|
1511
|
+
|
|
1512
|
+
The excursion is transient in every case and nothing teleports. What it says is
|
|
1513
|
+
that §7 P2's "no particle is ever further from the anchor than `d_geo`" needs its
|
|
1514
|
+
conditional read as a statement about **the anchor's displacement against the
|
|
1515
|
+
garment's reach**, not only against the shortest row: a short, wide garment on a
|
|
1516
|
+
fast anchor is the case that finds it.
|
|
1517
|
+
|
|
1518
|
+
---
|
|
1519
|
+
|
|
1520
|
+
## 27. What the rig path costs
|
|
1521
|
+
|
|
1522
|
+
The same garment, the same steps, the same fabric, best of three rounds.
|
|
1523
|
+
|
|
1524
|
+
```
|
|
1525
|
+
garment entity path rig path difference
|
|
1526
|
+
5 strips × 7 (35 joints) 0.0988 ms 0.1034 ms +4.6%
|
|
1527
|
+
3 strips × 9 (27 joints) 0.0861 ms 0.0883 ms +2.5%
|
|
1528
|
+
12 strips × 10 (120) 0.3082 ms 0.3183 ms +3.2%
|
|
1529
|
+
```
|
|
1530
|
+
|
|
1531
|
+
Two to five per cent, which is the CPU pose evaluation of the anchor joint plus a
|
|
1532
|
+
`Node3D` write-back in place of a `Transform64` one. The anchor evaluation is a
|
|
1533
|
+
walk up the joint chain per cloth per step and does not scale with the garment,
|
|
1534
|
+
which is why the largest garment shows the smallest relative difference.
|
|
1535
|
+
|
|
1536
|
+
For scale against §17's budget: 120 joints is 0.32 ms, and a garment's joint
|
|
1537
|
+
count is a *proxy* rather than a simulation resolution — the whole point of §11.2's
|
|
1538
|
+
three tiers is that a 5,000-vertex render mesh rides 120 joints. Nothing in the
|
|
1539
|
+
library needs re-sizing.
|
|
1540
|
+
|
|
1541
|
+
---
|
|
1542
|
+
|
|
1543
|
+
## 28. How many joints a drape takes — §18's open question
|
|
1544
|
+
|
|
1545
|
+
§18 carries the unrigged path's joint budget as open, with the mitigation that
|
|
1546
|
+
**M3 measures the fidelity curve before fixing a budget**. The unrigged runtime
|
|
1547
|
+
path is not in M3; this is the experiment that says what it would cost.
|
|
1548
|
+
|
|
1549
|
+
A 33×33 cape at 2 cm — 1,089 particles, 64 cm square, pinned along its top edge
|
|
1550
|
+
and driven by a gust — is simulated at full resolution and is the reference. A
|
|
1551
|
+
`k × k` grid of those particles is nominated as joints; every particle is bound
|
|
1552
|
+
to the four nearest of them in the mesh's own parameter space with
|
|
1553
|
+
inverse-distance weights, which is exactly what a four-weight palette does; and
|
|
1554
|
+
the garment is reconstructed each frame by linear blend skinning from the joints'
|
|
1555
|
+
fitted frames.
|
|
1556
|
+
|
|
1557
|
+
```
|
|
1558
|
+
joints RMS error worst as % of the garment degenerate frames
|
|
1559
|
+
4 6.13e-2 m 1.92e-1 m 29.93% 0
|
|
1560
|
+
9 2.14e-2 m 1.22e-1 m 19.13% 0
|
|
1561
|
+
16 1.19e-2 m 7.51e-2 m 11.73% 0
|
|
1562
|
+
25 8.01e-3 m 6.81e-2 m 10.65% 0
|
|
1563
|
+
36 5.92e-3 m 5.10e-2 m 7.97% 0
|
|
1564
|
+
49 4.44e-3 m 3.97e-2 m 6.21% 0
|
|
1565
|
+
81 2.77e-3 m 2.36e-2 m 3.69% 0
|
|
1566
|
+
121 2.04e-3 m 1.90e-2 m 2.97% 0
|
|
1567
|
+
289 9.35e-4 m 5.60e-3 m 0.88% 0
|
|
1568
|
+
```
|
|
1569
|
+
|
|
1570
|
+
**RMS error is inversely proportional to the joint count** — `RMS × joints` is
|
|
1571
|
+
0.245 at 4 joints and 0.270 at 289, flat across a factor of 72 — and the worst
|
|
1572
|
+
case falls more slowly, as about `N^−0.83`.
|
|
1573
|
+
|
|
1574
|
+
That gives the budget its shape. §11.2's table guesses "~60 output joints" for a
|
|
1575
|
+
skirt; at 60 the worst-case error on this cape is about 5% of its own size, which
|
|
1576
|
+
is five grid cells and reads as faceting. Putting the worst inside **one grid
|
|
1577
|
+
cell** takes about 120 joints; inside the garment's own 4 mm thickness takes
|
|
1578
|
+
about a thousand, which is not a budget, it is the simulation resolution again.
|
|
1579
|
+
|
|
1580
|
+
So the honest statement for a future unrigged path is: **80 to 120 joints for a
|
|
1581
|
+
cape of this size, and the four-weight palette is what sets that** — the error at
|
|
1582
|
+
289 joints is still 0.88%, and it is not the frames that are losing it.
|
|
1583
|
+
|
|
1584
|
+
### The frames did not degenerate, and that is measured rather than argued
|
|
1585
|
+
|
|
1586
|
+
§18's second open risk is that "a fold that collapses a triangle fan has no
|
|
1587
|
+
well-defined frame". The run counts a frame as degenerate when either fitted
|
|
1588
|
+
tangent falls below 1e-4 m. **Across 800 frames × up to 121 joints, in the table
|
|
1589
|
+
above and in a deliberately crumpled variant — bending at 1e-7 and a gust four
|
|
1590
|
+
times as hard — the count is zero:**
|
|
1591
|
+
|
|
1592
|
+
```
|
|
1593
|
+
joints RMS error worst as % of the garment degenerate frames
|
|
1594
|
+
16 3.69e-2 m 1.52e-1 m 23.82% 0
|
|
1595
|
+
36 1.94e-2 m 8.97e-2 m 14.01% 0
|
|
1596
|
+
81 1.05e-2 m 5.12e-2 m 8.00% 0
|
|
1597
|
+
121 8.17e-3 m 4.86e-2 m 7.60% 0
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
The mechanism is worth stating, because it is why it did not happen rather than a
|
|
1601
|
+
promise that it cannot. A tangent is a difference across **two links**, and the
|
|
1602
|
+
stretch rows are hard, so each of those links holds its length; the only way the
|
|
1603
|
+
difference vanishes is a fold of exactly 180° at the joint's own vertex, which is
|
|
1604
|
+
a measure-zero configuration the bend rows are actively pushing away from. It is
|
|
1605
|
+
*possible* — hard rows do not forbid it — and a future unrigged path should keep
|
|
1606
|
+
the epsilon and the fallback rather than delete them on the strength of a zero.
|
|
1607
|
+
|
|
1608
|
+
What the crumpled variant does show is that the error roughly triples: a garment
|
|
1609
|
+
with no bending resistance needs about three times the joints for the same
|
|
1610
|
+
fidelity, which is the other half of the budget question and says that the number
|
|
1611
|
+
is per-fabric rather than per-garment.
|
|
1612
|
+
|
|
1613
|
+
---
|
|
1614
|
+
|
|
1615
|
+
## 29. A garment authored inside its own body reads as a tether failure
|
|
1616
|
+
|
|
1617
|
+
Not a correction, and the first thing an author will hit.
|
|
1618
|
+
|
|
1619
|
+
The M3 playground's first cape was authored 9 cm behind the chest, and the
|
|
1620
|
+
character's torso collider was a capsule of 16 cm radius on the spine — so the
|
|
1621
|
+
garment's first links started **inside the body**. What that looks like is not a
|
|
1622
|
+
garment stuck in a torso. It looks like this:
|
|
1623
|
+
|
|
1624
|
+
```
|
|
1625
|
+
worst |x − anchor| / d_geo, a cape at rest, no motion at all
|
|
1626
|
+
|
|
1627
|
+
with the body colliders 1.3117
|
|
1628
|
+
without them 1.0000
|
|
1629
|
+
```
|
|
1630
|
+
|
|
1631
|
+
The contact row pushes the first links out of the collider, the tether row pulls
|
|
1632
|
+
them back, the contact wins — measured at 363 N of multiplier on a link holding
|
|
1633
|
+
a 0.12 kg strip — and the readout says the tether is failing by 31%. It is not.
|
|
1634
|
+
Both rows are doing exactly what they are for, and the configuration is one
|
|
1635
|
+
neither of them should ever have been given.
|
|
1636
|
+
|
|
1637
|
+
§11.3's step 10 is the answer and it says so: the build validates "that the
|
|
1638
|
+
skinned bind pose is collision-free against the character's `ClothCollider` set",
|
|
1639
|
+
and fails loudly with the offending vertex and collider named. **M3 does not
|
|
1640
|
+
build that check** — `cloth_proxy_from_joints` has a joint tree and no collider
|
|
1641
|
+
set — and this measurement is the argument for it being the next thing the build
|
|
1642
|
+
gains rather than a nicety. Moving the garment 21 cm back and shrinking the torso
|
|
1643
|
+
capsule to 13 cm returns the resting figure to 1.0022.
|
|
1644
|
+
|
|
1645
|
+
---
|
|
1646
|
+
|
|
1647
|
+
# Cloth — M4 measurements
|
|
1648
|
+
|
|
1649
|
+
> Written 2026-09-11, against the M4 implementation in `solver/` and the grid in
|
|
1650
|
+
> `core/geom/3d/hash-grid/`. Everything above this line is M0's, M1's, M2's and
|
|
1651
|
+
> M3's and is unchanged.
|
|
1652
|
+
>
|
|
1653
|
+
> Two scripts, both runnable directly under node — the M0 note applies unchanged:
|
|
1654
|
+
>
|
|
1655
|
+
> ```
|
|
1656
|
+
> node src/engine/physics/cloth/solver/__fixtures__/cloth_m4_measure.mjs
|
|
1657
|
+
> node src/engine/physics/cloth/ecs/__fixtures__/cloth_library_bench.mjs
|
|
1658
|
+
> ```
|
|
1659
|
+
>
|
|
1660
|
+
> The second is M1's, extended here with the section that prices self-collision
|
|
1661
|
+
> against each entry's own size and budget.
|
|
1662
|
+
>
|
|
1663
|
+
> **A folding sheet is chaotic**, and one run of one gust says nothing about a
|
|
1664
|
+
> parameter. Every folding measurement below is the mean over **twelve gust
|
|
1665
|
+
> realisations** — the same sheet, the same everything, twelve phases and rates
|
|
1666
|
+
> of the driving gust. The first version of §32 ran one realisation per row, and
|
|
1667
|
+
> at one the noise was larger than the effect and pointed at a different answer.
|
|
1668
|
+
|
|
1669
|
+
---
|
|
1670
|
+
|
|
1671
|
+
## 30. What M4 proved wrong in PLAN.md
|
|
1672
|
+
|
|
1673
|
+
Four things, and the first is the one the whole tier turns on.
|
|
1674
|
+
|
|
1675
|
+
| # | The design said | The measurement said |
|
|
1676
|
+
|---|---|---|
|
|
1677
|
+
| 0 | §9 — pairs closer than `r_i + r_j` get the unilateral row from §6, whose `r` reads as the same `ClothDynamics#thickness` a collider contact uses | Not at a garment's real proportions. A fabric is 4 to 20 mm thick and a proxy's particles are 2 to 8 cm apart, so the two layers of a fold are almost never within a fabric thickness of each other. At the fabric's own 6 mm the row removes **21%** of a folding sheet's self-intersections; at half the shortest row — a diameter of one particle spacing — it removes **82%**. The radius is a property of *the proxy*, not of the cloth it stands for. §31 |
|
|
1678
|
+
| 1 | §14.2 — a 3D uniform hash grid "modelled on `core/geom/2d/hash-grid/SpatialHashGrid.js`" | The 2D grid is a **persistent, incremental** structure: a bounded array of cells, elements that are AABBs and carry identity, and insert/remove/move as the primitive operations. Cloth's grid is none of those. Its elements are points with no identity across steps, its domain is unbounded, and every particle moves every step — so the operation that matters is *rebuild*, and insert/remove never runs at all. `PointHashGrid3` is a counting sort into a power-of-two hash and shares no structure with the 2D grid. The rebuild is 10% to 12% of the step it sits in front of, and most of everything self-collision costs. §34 |
|
|
1679
|
+
| 2 | §9 — the flag is "set on the library entries that need it (skirts, long coats, hair) and clear on the ones that do not (capes, ponytails, banners)" | That list is drawn along the wrong axis. What decides is not what the garment *is* but how far the fabric bends: the floppier the fabric the more it passes through itself. Silk, hair and cotton tangle 8 to 11 times a sample and the row removes 71% to 81%; leather tangles 0.18 times and the row makes it *worse*. §33 |
|
|
1680
|
+
| 3 | §9 — "Both tiers derive their normals from the proxy's surface orientation, per P4(a)" | M4a's normal is the line between the two particles, because a particle pair has no surface in it. What P4(a) actually asks for — nothing history-dependent, no "which side was this on last frame" to latch onto — the pair normal satisfies completely and more simply: recomputed every iteration from the current positions, with no multiplier, no warm start and no identity across steps. The sentence is right about M4b, where a point-triangle stencil genuinely needs an orientation to choose a side. |
|
|
1681
|
+
|
|
1682
|
+
**And two findings with no line in PLAN.md to correct**, because §9 never wrote a
|
|
1683
|
+
radius down at all. `max(thickness, shortest_row/2)` has an upper end and leather
|
|
1684
|
+
is on it (§36) — and that upper end has to be *measured* rather than reasoned
|
|
1685
|
+
from a sheet's topology, because the geometry M3 ships is not a sheet (§37).
|
|
1686
|
+
|
|
1687
|
+
Two things the implementation **confirmed** rather than corrected, both of them
|
|
1688
|
+
places where an earlier milestone wrote down what M4 would find:
|
|
1689
|
+
|
|
1690
|
+
- **§5.4's Jacobi paragraph.** M2 measured that a *collider* contact is unary
|
|
1691
|
+
and needs none of it, and left the paragraph standing "for M4's rows, which
|
|
1692
|
+
really are pairs". They are, and it does. `cloth_self_row.js` accumulates the
|
|
1693
|
+
pair list into a per-particle `(f, H)` scratch once per iteration and every
|
|
1694
|
+
vertex update adds its own entry, which is what keeps the step independent of
|
|
1695
|
+
the order the colours were swept in.
|
|
1696
|
+
- **§9's "it prevents the great majority of visible interpenetration".** 82% of
|
|
1697
|
+
it, at the radius §31 settles on. The tier's stated limit — cloth passing
|
|
1698
|
+
*between* particles — is real and is what the other 18% is: §35 went looking
|
|
1699
|
+
for a cheaper explanation, a pair search running one step behind the solver,
|
|
1700
|
+
and ruled it out.
|
|
1701
|
+
|
|
1702
|
+
---
|
|
1703
|
+
|
|
1704
|
+
## 31. The radius is the proxy's resolution, not the fabric's thickness
|
|
1705
|
+
|
|
1706
|
+
§6 writes the row as `‖x_i − x_j‖ − (r_i + r_j)` and §9 says the pairs that get
|
|
1707
|
+
one are those "closer than `r_i + r_j`". Both read naturally as the same `r` a
|
|
1708
|
+
collider contact uses, which is `ClothDynamics#thickness` — how thick the fabric
|
|
1709
|
+
is. M4 was built that way and it barely worked: on a folding garment the pair
|
|
1710
|
+
list held **1.6 to 3.7 pairs a step across the whole cloth**, and no stiffness
|
|
1711
|
+
made the sheet stop passing through itself.
|
|
1712
|
+
|
|
1713
|
+
The arithmetic is not subtle once it is written down. A fabric is 4 to 20 mm
|
|
1714
|
+
thick. A proxy's particles are 2 to 8 cm apart. Two layers of a fold are within
|
|
1715
|
+
a *fabric thickness* of each other only when their particles happen to line up,
|
|
1716
|
+
and nothing makes them line up.
|
|
1717
|
+
|
|
1718
|
+
A 12×12 sheet at 2 cm spacing, 6 mm thick, driven into a fold. Only the radius
|
|
1719
|
+
moves; the stiffness, the gust and the mesh are held fixed. "Crossings" counts
|
|
1720
|
+
edges passing through triangles they share no vertex with — §17's own metric —
|
|
1721
|
+
sampled every twentieth step and averaged over twelve gust realisations. The
|
|
1722
|
+
same sheet with no row at all sits at **11.39**.
|
|
1723
|
+
|
|
1724
|
+
```
|
|
1725
|
+
radius diameter pairs/step crossings: mean / worst overlap mean
|
|
1726
|
+
|
|
1727
|
+
3 mm 6 mm 0.5 10.60 / 156 5.2 mm
|
|
1728
|
+
6 mm 12 mm 2.6 9.00 / 190 10.7 mm <- the fabric thickness
|
|
1729
|
+
8 mm 16 mm 3.9 5.33 / 86 14.4 mm
|
|
1730
|
+
10 mm 20 mm 4.8 2.03 / 71 18.4 mm <- half the shortest row
|
|
1731
|
+
12 mm 24 mm 5.5 0.91 / 64 20.1 mm
|
|
1732
|
+
16 mm 32 mm 8.2 0.02 / 5 24.8 mm
|
|
1733
|
+
```
|
|
1734
|
+
|
|
1735
|
+
At the fabric's own thickness the row removes 21% of the crossings. At half the
|
|
1736
|
+
shortest row — a diameter of exactly one particle spacing — it removes 82%. The
|
|
1737
|
+
radius is a property of **the proxy**, and `ClothState#self_radius` is a separate
|
|
1738
|
+
field from `contact_radius` for that reason: a collider contact really is about
|
|
1739
|
+
how thick the cloth is, and this is not.
|
|
1740
|
+
|
|
1741
|
+
`ClothSystem` writes `max(thickness, shortest_row / 2)`, under a ceiling §36 and
|
|
1742
|
+
§37 derive. The `max` is not decoration: a heavy fabric on a coarse proxy — 2 cm
|
|
1743
|
+
of leather on 3 cm particles — is the one case where the fabric is the longer of
|
|
1744
|
+
the two lengths, and there the thickness is right.
|
|
1745
|
+
|
|
1746
|
+
### The column does not turn over, and the reason to stop is not in it
|
|
1747
|
+
|
|
1748
|
+
12 mm is better than 10, and 16 mm — a diameter of 1.6 particle spacings — very
|
|
1749
|
+
nearly eliminates crossings. §36 carries the sweep further and it keeps
|
|
1750
|
+
improving right up to a cliff at two spacings that has nothing to do with
|
|
1751
|
+
crossings at all.
|
|
1752
|
+
|
|
1753
|
+
**So the shipped value is not the minimum of this table, and it would be
|
|
1754
|
+
dishonest to present it as one.** Half the shortest row is a *fidelity* choice:
|
|
1755
|
+
a self-collision radius is the cloth's apparent half-thickness, and at a
|
|
1756
|
+
diameter of 1.6 spacings a silk cape on 8 cm particles would hold the two layers
|
|
1757
|
+
of a fold nearly 13 cm apart. For 4 mm of silk that is absurd, and no crossing
|
|
1758
|
+
count can see it — the metric being optimised cannot measure the thing that
|
|
1759
|
+
stops the optimisation. A diameter of one particle spacing is the largest
|
|
1760
|
+
separation the proxy can be said to *mean*, since it is the mesh's own finest
|
|
1761
|
+
feature, and it delivers §9's "great majority" at 82%.
|
|
1762
|
+
|
|
1763
|
+
What would settle it properly is a number M4 does not have: what the search
|
|
1764
|
+
costs as a function of cell size. §34 shows the search is nearly all of the cost
|
|
1765
|
+
and that it scales with particles, but a wider radius is a wider *cell*, and
|
|
1766
|
+
every walk then visits more candidates. Someone who wants a stricter garment can
|
|
1767
|
+
raise the radius today — the table above is the dial, and the ceiling in §36 is
|
|
1768
|
+
where it stops — but they should measure that first.
|
|
1769
|
+
|
|
1770
|
+
---
|
|
1771
|
+
|
|
1772
|
+
## 32. How stiff the row has to be, and what it is actually for
|
|
1773
|
+
|
|
1774
|
+
Same sheet, same gust, radius fixed at half the shortest row; the penalty moves.
|
|
1775
|
+
`CLOTH_SELF_RATIO` is a multiple of `m_eff/h²` exactly as the bend and leash
|
|
1776
|
+
bands are, and `m_eff` for a pair with two free ends is half a particle.
|
|
1777
|
+
|
|
1778
|
+
```
|
|
1779
|
+
k / (m_eff/h²) overlap: mean / worst crossings: mean / worst / rest pairs kinetic
|
|
1780
|
+
|
|
1781
|
+
0 19.4 / 19.7 mm 11.39 / 146 / 0.0 0.0 6.86e-3
|
|
1782
|
+
1 17.4 / 19.2 mm 3.42 / 51 / 0.0 6.9 6.63e-3
|
|
1783
|
+
2 16.6 / 19.3 mm 3.36 / 76 / 0.0 5.8 5.81e-3
|
|
1784
|
+
4 17.2 / 19.3 mm 2.75 / 83 / 0.0 4.8 6.28e-3
|
|
1785
|
+
8 18.4 / 19.8 mm 2.03 / 71 / 0.0 4.8 7.57e-3
|
|
1786
|
+
16 16.8 / 19.5 mm 2.03 / 96 / 0.0 4.6 7.00e-3
|
|
1787
|
+
32 16.8 / 19.3 mm 2.45 / 123 / 0.0 4.4 5.24e-3
|
|
1788
|
+
64 17.5 / 19.5 mm 3.87 / 125 / 0.0 4.3 6.81e-3
|
|
1789
|
+
```
|
|
1790
|
+
|
|
1791
|
+
Four readings, and the last two are the ones worth keeping.
|
|
1792
|
+
|
|
1793
|
+
**Most of the win is in having a row at all.** 11.39 crossings a sample without
|
|
1794
|
+
one, 3.42 with the softest row in the table. The remaining sweep moves it from
|
|
1795
|
+
3.42 to 2.03 — a third of what the first newton bought.
|
|
1796
|
+
|
|
1797
|
+
**The mean bottoms at 8 to 16 and the worst case gets steadily worse the whole
|
|
1798
|
+
way up.** 51 crossings at the worst realisation with `k = 1`, 125 with `k = 64`.
|
|
1799
|
+
A stiff penalty removes the average tangle and creates a rarer, larger one: it
|
|
1800
|
+
throws a particle hard enough to put the sheet through itself somewhere the row
|
|
1801
|
+
was not looking. `CLOTH_SELF_RATIO` ships at **8**, which is the joint minimum of
|
|
1802
|
+
the mean and is well down the worst-case curve.
|
|
1803
|
+
|
|
1804
|
+
**M0's dissipative band does not bind this row.** M0 measured that a soft row
|
|
1805
|
+
stiffer than about `8·(m/h²)` makes the step add energy rather than lose it, and
|
|
1806
|
+
`cloth_step` originally justified the ratio by that ceiling. The kinetic column
|
|
1807
|
+
says otherwise: across a sixty-four-fold range of stiffness it moves between
|
|
1808
|
+
5.2e-3 and 7.6e-3 with no trend at all. The band was measured on rows that are
|
|
1809
|
+
**on every step** — a bend row, a leash. A self-collision row is one-sided and
|
|
1810
|
+
vanishes the instant it is satisfied, so it does work only while it is
|
|
1811
|
+
compressing and has no standing spring to pump. Eight is the right number here
|
|
1812
|
+
and the band is not the reason; the comment in `cloth_step.js` no longer claims
|
|
1813
|
+
it is.
|
|
1814
|
+
|
|
1815
|
+
**The row never achieves its own separation, and it is not meant to.** The
|
|
1816
|
+
overlap column is `diameter − distance` at the worst governed pair, and it barely
|
|
1817
|
+
moves: 19.4 mm without the row and 16.6 to 18.4 mm with it, out of a 20 mm
|
|
1818
|
+
diameter. Inside a fold the hard stretch rows are holding the sheet's own
|
|
1819
|
+
geometry and a soft penalty cannot outvote them. What it can do is decide *which
|
|
1820
|
+
side* of the fold a particle ends up on, which is what a crossing count measures
|
|
1821
|
+
and what §9 asks the tier for.
|
|
1822
|
+
|
|
1823
|
+
### The settled sheet is clean either way, which is the whole flag question
|
|
1824
|
+
|
|
1825
|
+
The `rest` column is zero in every row of the table, the `k = 0` row included. A
|
|
1826
|
+
sheet with no bending resistance flattens itself once the gust stops and takes
|
|
1827
|
+
its tangles out with it. Every crossing counted above happened while the cloth
|
|
1828
|
+
was *moving*.
|
|
1829
|
+
|
|
1830
|
+
That is not a small caveat, it is the shape of the answer: if tangling were
|
|
1831
|
+
transient for every fabric, the flag would never be worth setting. It is not —
|
|
1832
|
+
what makes a tangle stay is bending. §33.
|
|
1833
|
+
|
|
1834
|
+
---
|
|
1835
|
+
|
|
1836
|
+
## 33. Which entries set the flag, and what it costs them
|
|
1837
|
+
|
|
1838
|
+
§9 says the flag is "set on the library entries that need it (skirts, long
|
|
1839
|
+
coats, hair) and clear on the ones that do not (capes, ponytails, banners)".
|
|
1840
|
+
That list is drawn along what the garment *is*. The measurement is drawn along
|
|
1841
|
+
how far the fabric bends, and it separates the entries far more sharply.
|
|
1842
|
+
|
|
1843
|
+
The same folding sheet, twelve realisations, with each library entry's own
|
|
1844
|
+
`bend` — a ratio of the bend row's inertial scale, mapped the way
|
|
1845
|
+
`cloth_build_rows` maps it — and nothing else changed.
|
|
1846
|
+
|
|
1847
|
+
```
|
|
1848
|
+
entry bend row? crossings: mean / worst pairs removed
|
|
1849
|
+
|
|
1850
|
+
SILK 0.04 none 8.08 / 167 0.0
|
|
1851
|
+
SILK 0.04 with 2.37 / 77 4.5 71%
|
|
1852
|
+
HAIR 0.20 none 10.62 / 139 0.0
|
|
1853
|
+
HAIR 0.20 with 1.98 / 71 4.4 81%
|
|
1854
|
+
COTTON 0.25 none 9.36 / 135 0.0
|
|
1855
|
+
COTTON 0.25 with 1.99 / 49 4.7 79%
|
|
1856
|
+
DENIM 0.62 none 3.59 / 103 0.0
|
|
1857
|
+
DENIM 0.62 with 1.37 / 70 2.2 62%
|
|
1858
|
+
LEATHER 1.00 none 0.18 / 39 0.0
|
|
1859
|
+
LEATHER 1.00 with 0.41 / 44 0.3 -137%
|
|
1860
|
+
```
|
|
1861
|
+
|
|
1862
|
+
**Leather does not tangle, and the row makes it worse.** 0.18 crossings a sample
|
|
1863
|
+
unaided — call it none — and 0.3 pairs a step, which is the row finding almost
|
|
1864
|
+
nothing and pushing on a sheet that had not asked. A fabric at the top of the
|
|
1865
|
+
bend scale will not fold tightly enough to pass through itself; there is nothing
|
|
1866
|
+
for the tier to prevent, and the handful of pairs it does find are noise it is
|
|
1867
|
+
adding rather than removing. The sign of that last column is the whole argument
|
|
1868
|
+
for the flag being off by default.
|
|
1869
|
+
|
|
1870
|
+
**Denim tangles a third as much as cotton** and the row removes 62% of it. It is
|
|
1871
|
+
the one genuinely marginal entry, and it is also the entry that has already spent
|
|
1872
|
+
both of its performance levers (§17): 576 particles at 2×2. It stays clear.
|
|
1873
|
+
|
|
1874
|
+
**Silk, cotton and hair are where the tier earns its keep** — 8 to 11 crossings a
|
|
1875
|
+
sample unaided, 71% to 81% of them gone. Cotton is "the default fabric, and the
|
|
1876
|
+
one every other entry is a deviation from", and a default that shows nine
|
|
1877
|
+
crossings in every sample taken while it moves is a bad default.
|
|
1878
|
+
|
|
1879
|
+
So the flag moves from one entry to three: `CLOTH_SILK` and `CLOTH_COTTON` join
|
|
1880
|
+
`CLOTH_HAIR`. `CLOTH_DENIM`, `CLOTH_LEATHER`, `CLOTH_ROPE` and `CLOTH_CHAIN`
|
|
1881
|
+
stay clear — the first two on the table above, the chains because a chain has no
|
|
1882
|
+
surface to pass through and a coiling rope is a case this measurement does not
|
|
1883
|
+
cover.
|
|
1884
|
+
|
|
1885
|
+
### What that costs
|
|
1886
|
+
|
|
1887
|
+
The solver-level figure first, on the folding sheet, so the shape of the cost is
|
|
1888
|
+
visible before the entries are priced:
|
|
1889
|
+
|
|
1890
|
+
```
|
|
1891
|
+
particles without with delta pairs/step slots full
|
|
1892
|
+
|
|
1893
|
+
144 0.445 ms 0.508 ms 14.3% 2.6 0
|
|
1894
|
+
256 0.813 ms 0.936 ms 15.1% 7.1 0
|
|
1895
|
+
484 1.541 ms 1.806 ms 17.2% 20.6 0
|
|
1896
|
+
1024 3.491 ms 4.100 ms 17.5% 36.0 0
|
|
1897
|
+
```
|
|
1898
|
+
|
|
1899
|
+
Flat at 14% to 18%, and the pair list never once hit its cap — 36 pairs a step
|
|
1900
|
+
across 1,024 particles at the widest, against a ceiling of eight per particle.
|
|
1901
|
+
The cost is not in the pairs; §34 says where it is.
|
|
1902
|
+
|
|
1903
|
+
And then every entry at its own size, its own budget and its own row set, from
|
|
1904
|
+
`cloth_library_bench.mjs` — the same harness M1 sized the library with, driven by
|
|
1905
|
+
a gust so the pair list is doing work:
|
|
1906
|
+
|
|
1907
|
+
```
|
|
1908
|
+
SILK 2x4 1.427 ms -> 1.802 ms 26.3% inside pairs 318.0 full 0
|
|
1909
|
+
COTTON 2x4 1.478 ms -> 1.802 ms 22.0% inside pairs 325.1 full 0
|
|
1910
|
+
DENIM 2x2 1.297 ms -> 1.765 ms 36.1% inside pairs 607.8 full 0
|
|
1911
|
+
LEATHER 2x2 1.315 ms -> 1.828 ms 39.0% inside pairs 740.2 full 0
|
|
1912
|
+
ROPE 2x4 0.116 ms -> 0.129 ms 11.5% inside pairs 0.0 full 0
|
|
1913
|
+
CHAIN 2x6 0.165 ms -> 0.183 ms 11.0% inside pairs 0.0 full 0
|
|
1914
|
+
HAIR 2x4 1.438 ms -> 1.798 ms 25.1% inside pairs 315.7 full 0
|
|
1915
|
+
```
|
|
1916
|
+
|
|
1917
|
+
**Every entry stays inside 2 ms, and the three that set the flag have about a
|
|
1918
|
+
tenth of a frame left.** 1.80 ms against 2.00 for silk, cotton and hair — which
|
|
1919
|
+
is the thinnest headroom in the library and is the thing to watch when M5 or M6
|
|
1920
|
+
adds anything to the step. The bare figures agree with M1's to within 4%
|
|
1921
|
+
(silk 1.427 against a claimed 1.49, denim 1.297 against 1.28), so **no entry is
|
|
1922
|
+
re-sized**: §17's table stands, and self-collision is spent out of headroom
|
|
1923
|
+
rather than out of a lever.
|
|
1924
|
+
|
|
1925
|
+
The delta here — 22% to 26% — is larger than the 14% to 17% above because this
|
|
1926
|
+
harness folds a 400-particle sheet hard enough to find 318 pairs a step against
|
|
1927
|
+
the folding sheet's 5. That is the honest worst case for a garment of this size,
|
|
1928
|
+
and it is the figure the entries were checked against.
|
|
1929
|
+
|
|
1930
|
+
**The chains find nothing**, which is the measurement behind leaving `CLOTH_ROPE`
|
|
1931
|
+
and `CLOTH_CHAIN` clear: 0.0 pairs a step, and the 11% they still pay is the
|
|
1932
|
+
grid being built and walked for a cloth that will never have a pair in it. A
|
|
1933
|
+
hanging chain's nearest ungoverned particles are three links apart and its links
|
|
1934
|
+
are 5 cm long; nothing about a rope brings them within one link of each other
|
|
1935
|
+
except coiling, which this harness does not do.
|
|
1936
|
+
|
|
1937
|
+
**Leather is the entry the §36 clamp is for.** Before it, this same row read
|
|
1938
|
+
1,398 pairs a step — the row had found the mesh rather than a fold. Clamped, its
|
|
1939
|
+
radius comes down from 20 mm to 16 mm and it reads 740, and those are folds. It
|
|
1940
|
+
still does not set the flag.
|
|
1941
|
+
|
|
1942
|
+
(§37 later replaced the ceiling's *derivation* without moving any number here:
|
|
1943
|
+
every entry's radius on this harness is identical under both rules, because on a
|
|
1944
|
+
sheet the closest ungoverned pair is exactly two hops. The bench takes the
|
|
1945
|
+
production path so that stays true rather than being assumed.)
|
|
1946
|
+
|
|
1947
|
+
### The other cost is memory, and four of seven entries pay it for nothing
|
|
1948
|
+
|
|
1949
|
+
`ClothState` is one `ArrayBuffer`, sized at seed, and M4's three regions —
|
|
1950
|
+
`self_pair`, `self_force`, `self_hessian` — are in it whether or not the flag
|
|
1951
|
+
is ever set. On the §17 workload:
|
|
1952
|
+
|
|
1953
|
+
```
|
|
1954
|
+
1,024 particles, 2,945 stretch rows, 2,821 bend rows
|
|
1955
|
+
|
|
1956
|
+
whole state buffer 637,344 bytes
|
|
1957
|
+
self-collision 102,400 bytes 16.1%
|
|
1958
|
+
```
|
|
1959
|
+
|
|
1960
|
+
Four of the seven library entries never read a byte of it. The reason it is
|
|
1961
|
+
unconditional is that the flag can be toggled without a reseed today —
|
|
1962
|
+
`cloth_dynamics_change_needs_reseed` returns false for it, because nothing in
|
|
1963
|
+
the row tables depends on it — so a lazily-sized region would have to become a
|
|
1964
|
+
reseed trigger. That is a change to the reseed contract for 100 KB a garment,
|
|
1965
|
+
and M4 leaves it: **recorded, not fixed**, and the number is here so whoever
|
|
1966
|
+
decides has it.
|
|
1967
|
+
|
|
1968
|
+
---
|
|
1969
|
+
|
|
1970
|
+
## 34. The grid is the cost, and it is not the 2D grid
|
|
1971
|
+
|
|
1972
|
+
§14.2 asks for "a 3D uniform hash grid, modelled on
|
|
1973
|
+
`core/geom/2d/hash-grid/SpatialHashGrid.js`". What landed shares the *idea* and
|
|
1974
|
+
none of the structure, and the reason is worth writing down because the next
|
|
1975
|
+
person to want a broadphase in `core` will read that sentence too.
|
|
1976
|
+
|
|
1977
|
+
The 2D grid is **persistent and incremental**. Its cells are a bounded array
|
|
1978
|
+
sized at construction, its elements are AABBs that carry identity and user data,
|
|
1979
|
+
and its primitives are `element_insert`, `element_remove` and the linked-list
|
|
1980
|
+
surgery that moves one between cells. That is the right shape for a world of
|
|
1981
|
+
things that mostly sit still and occasionally move — which is what it was built
|
|
1982
|
+
for.
|
|
1983
|
+
|
|
1984
|
+
A cloth's particles are the opposite of that in all four respects. They are
|
|
1985
|
+
**points**, not boxes. They carry **no identity across steps** — a pair list is
|
|
1986
|
+
rebuilt from scratch every step precisely because there is nothing on a pair
|
|
1987
|
+
worth keeping (`cloth_self_row.js`). Their domain is **unbounded**: a garment
|
|
1988
|
+
travels with its character and a fixed cell array would have to be re-based every
|
|
1989
|
+
frame. And **every particle moves every step**, so the incremental primitive
|
|
1990
|
+
never gets to be incremental; a full pass of remove-and-reinsert is a rebuild
|
|
1991
|
+
with extra bookkeeping.
|
|
1992
|
+
|
|
1993
|
+
`PointHashGrid3` is therefore a counting sort — count per bucket, prefix-sum,
|
|
1994
|
+
scatter — into a power-of-two hash of the integer cell, and it has no insert, no
|
|
1995
|
+
remove and no element identity at all. Two files, one word in common.
|
|
1996
|
+
|
|
1997
|
+
### The rebuild is most of what self-collision costs
|
|
1998
|
+
|
|
1999
|
+
Which makes it the thing to defend. The same folding sheet, timing the pair
|
|
2000
|
+
search separately from the step it feeds:
|
|
2001
|
+
|
|
2002
|
+
```
|
|
2003
|
+
particles step search rows search as % of the whole
|
|
2004
|
+
|
|
2005
|
+
144 0.442 ms 0.048 ms 0.010 ms 9.6%
|
|
2006
|
+
256 0.806 ms 0.113 ms 0.001 ms 12.3%
|
|
2007
|
+
484 1.615 ms 0.217 ms 0.011 ms 11.8%
|
|
2008
|
+
1024 3.547 ms 0.482 ms 0.164 ms 11.5%
|
|
2009
|
+
```
|
|
2010
|
+
|
|
2011
|
+
`step` is the solver with no pairs at all; `search` is `cloth_self_find`;
|
|
2012
|
+
`rows` is what the pairs then cost inside the solve, taken as the difference
|
|
2013
|
+
between two runs. That last column is a difference of two minima from separate
|
|
2014
|
+
runs and at 484 it comes out *negative*, which is the harness saying the quantity
|
|
2015
|
+
is below its own noise floor rather than saying the rows are free. Read it as
|
|
2016
|
+
"small": between 0.005 and 0.14 ms against a search of 0.05 to 0.50.
|
|
2017
|
+
|
|
2018
|
+
So the **search is most of it and the rows are the remainder** — two thirds to
|
|
2019
|
+
all of it across the four sizes, and the spread is the `rows` column's noise
|
|
2020
|
+
rather than anything about the sizes. And the search scales the way a counting
|
|
2021
|
+
sort does: linear in particles to within the noise, with no term in the pair
|
|
2022
|
+
count at all. §9's "O(n) with a small constant" is right about the shape and
|
|
2023
|
+
optimistic about the constant — half a microsecond a particle, which is 11% of
|
|
2024
|
+
a frame at the §17 workload.
|
|
2025
|
+
|
|
2026
|
+
The consequence for whoever takes M4b — point-triangle and edge-edge — is that
|
|
2027
|
+
the extra stencils are not what will cost; they ride on a grid that has already
|
|
2028
|
+
been built. The estimate in §9 that M4b "roughly triples the self-collision cost"
|
|
2029
|
+
is the thing to re-measure first, and this table is the reason to doubt it.
|
|
2030
|
+
|
|
2031
|
+
---
|
|
2032
|
+
|
|
2033
|
+
## 35. What is left is not the search being one step behind
|
|
2034
|
+
|
|
2035
|
+
`ClothSystem` calls `cloth_self_find` and then `cloth_step`. The pair set is
|
|
2036
|
+
therefore discovered from the positions the *previous* step left, and the row
|
|
2037
|
+
acts on positions `cloth_predict` has already moved. A pair that becomes a
|
|
2038
|
+
contact **during** the step is not in the list, and by the time the next step
|
|
2039
|
+
looks, the two particles have already crossed.
|
|
2040
|
+
|
|
2041
|
+
That is the shape of the gap §20 found in the contact set, where the answer was
|
|
2042
|
+
to widen the discovery reach by the collider's own travel —
|
|
2043
|
+
`cloth_contact_find` carries `CLOTH_CONTACT_MARGIN` and adds `TRAVEL[record]`
|
|
2044
|
+
for it. The self-collision search has no equivalent, and §32 leaves 2.03
|
|
2045
|
+
crossings a sample unaccounted for. The obvious suspicion is that they are the
|
|
2046
|
+
same thing.
|
|
2047
|
+
|
|
2048
|
+
They are not. Here the search runs at a multiple of the row's radius while the
|
|
2049
|
+
row is unchanged, so the extra pairs are found and then found to be out of
|
|
2050
|
+
contact — which is what a travel margin would do:
|
|
2051
|
+
|
|
2052
|
+
```
|
|
2053
|
+
search radius pairs/step crossings: mean / worst slots full
|
|
2054
|
+
|
|
2055
|
+
1.0x 4.8 2.03 / 71 0
|
|
2056
|
+
1.5x 32.3 2.13 / 69 0
|
|
2057
|
+
2.0x 187.8 1.77 / 110 0
|
|
2058
|
+
3.0x 738.9 4.76 / 89 0
|
|
2059
|
+
```
|
|
2060
|
+
|
|
2061
|
+
**The pair list grows a hundred and fifty times over and the crossing count does
|
|
2062
|
+
not move.** 2.03 at the shipped width, 2.13 at one and a half, 1.77 at two —
|
|
2063
|
+
inside the spread of twelve realisations — and then *worse* at three.
|
|
2064
|
+
|
|
2065
|
+
So the residual is not a discovery problem. It is the limit §9 states and does
|
|
2066
|
+
not hide: a particle-particle row can only act when two **particles** are close,
|
|
2067
|
+
and a sheet can pass through itself between them. No width of search reaches
|
|
2068
|
+
that, because the thing that needs a row is a point against a triangle and there
|
|
2069
|
+
is no point-triangle row here. This is M4b's, and M4b is deferred.
|
|
2070
|
+
|
|
2071
|
+
**Why three times over is worse rather than merely wasteful.** The list holds
|
|
2072
|
+
`CLOTH_SELF_PAIRS_PER_PARTICLE` — eight per particle — and a particle at the cap
|
|
2073
|
+
keeps the eight it found *first in grid order*. At the shipped radius every
|
|
2074
|
+
candidate inside it is in contact, so which eight is arbitrary among equals. At
|
|
2075
|
+
three times it, almost none of them are, and the budget goes on pairs that will
|
|
2076
|
+
be discarded a moment later by the distance test while the pairs that mattered
|
|
2077
|
+
never make the list. The cap is what turns a wider search from a cost into a
|
|
2078
|
+
regression, and it is the thing to redesign first if anyone does want a swept
|
|
2079
|
+
search: a second radius with its own budget, or the search moved inside the
|
|
2080
|
+
substep loop after `cloth_predict`.
|
|
2081
|
+
|
|
2082
|
+
---
|
|
2083
|
+
|
|
2084
|
+
## 36. `max(thickness, …)` has an upper end, and leather is over it
|
|
2085
|
+
|
|
2086
|
+
§31 settles the radius at half the shortest row and keeps a `max` against the
|
|
2087
|
+
fabric's thickness, for the case it named: a heavy fabric on a coarse proxy,
|
|
2088
|
+
where the fabric is the longer of the two lengths. Nobody looked at where that
|
|
2089
|
+
branch stops being safe.
|
|
2090
|
+
|
|
2091
|
+
**On this topology the nearest pair no row governs is two hops away.** One hop is
|
|
2092
|
+
a stretch row. The mesh diagonal is a stretch row too. The two vertices *across*
|
|
2093
|
+
a diagonal are the four particles of a bend stencil. Everything closer than two
|
|
2094
|
+
hops is excluded by construction — which is exactly what makes half a hop the
|
|
2095
|
+
right radius, and it also means a **diameter** that reaches two hops puts a flat,
|
|
2096
|
+
undisturbed sheet in contact with itself everywhere.
|
|
2097
|
+
|
|
2098
|
+
The same folding sheet, sweeping the diameter as a multiple of the particle
|
|
2099
|
+
spacing, with pairs counted both while it folds and after it settles:
|
|
2100
|
+
|
|
2101
|
+
```
|
|
2102
|
+
diameter / spacing pairs while folding pairs at rest crossings
|
|
2103
|
+
diameter / spacing pairs while folding pairs at rest crossings
|
|
2104
|
+
|
|
2105
|
+
0.5x 1.8 0.4 10.76
|
|
2106
|
+
0.8x 3.9 0.0 5.33
|
|
2107
|
+
1.0x 4.8 0.0 2.03
|
|
2108
|
+
1.4x 6.1 0.0 0.18
|
|
2109
|
+
1.8x 13.1 0.0 0.04
|
|
2110
|
+
1.9x 22.4 0.0 0.03
|
|
2111
|
+
2.0x 103.7 78.7 0.02
|
|
2112
|
+
2.2x 220.5 218.0 0.00
|
|
2113
|
+
```
|
|
2114
|
+
|
|
2115
|
+
And where the library lands on a 2 cm proxy, under `max(thickness, 10 mm)`:
|
|
2116
|
+
|
|
2117
|
+
```
|
|
2118
|
+
SILK thickness 4 mm -> radius 10 mm = 1.0x spacing
|
|
2119
|
+
HAIR thickness 6 mm -> radius 10 mm = 1.0x spacing
|
|
2120
|
+
COTTON thickness 8 mm -> radius 10 mm = 1.0x spacing
|
|
2121
|
+
DENIM thickness 14 mm -> radius 14 mm = 1.4x spacing
|
|
2122
|
+
LEATHER thickness 20 mm -> radius 20 mm = 2.0x spacing
|
|
2123
|
+
```
|
|
2124
|
+
|
|
2125
|
+
**The cliff is exactly where the topology says, and it is a cliff.** At 1.9
|
|
2126
|
+
spacings a settled sheet has no pairs at all; at 2.0 it has seventy-nine, and at
|
|
2127
|
+
2.2 it has two hundred and eighteen — on a cloth that is hanging still. Nothing
|
|
2128
|
+
about the fold changed. The row simply reached the first pair the mesh had not
|
|
2129
|
+
already claimed, and from there it is in contact with itself everywhere, for
|
|
2130
|
+
ever, pushing a flat sheet apart with a force that has nothing to resolve.
|
|
2131
|
+
|
|
2132
|
+
Note also that the crossing column is *still improving* across the cliff. A
|
|
2133
|
+
measurement that only counted crossings would walk straight over it, which is
|
|
2134
|
+
the argument for the "pairs at rest" column existing.
|
|
2135
|
+
|
|
2136
|
+
**Leather is on the number.** 20 mm of hide on a 20 mm proxy gives the row a
|
|
2137
|
+
diameter of exactly two spacings. Before the clamp, the library bench found it
|
|
2138
|
+
**1,398 pairs a step** on a 576-particle sheet — more than two per particle, on
|
|
2139
|
+
a cloth that was barely folded. Silk found 318 on the same harness.
|
|
2140
|
+
|
|
2141
|
+
The radius is now clamped at both ends, and the ceiling leaves two tenths of a
|
|
2142
|
+
spacing between itself and the cliff — the table says the sheet is clean well
|
|
2143
|
+
before that. **§37 then makes the ceiling a measured length rather than a
|
|
2144
|
+
multiple of the shortest row**, because the two-hop argument above is a fact
|
|
2145
|
+
about *sheets* and a rigged garment is not one. On a sheet the two agree
|
|
2146
|
+
exactly, so nothing in this section moves.
|
|
2147
|
+
|
|
2148
|
+
This is the one M4 finding with no line in PLAN.md to correct: §9 never wrote a
|
|
2149
|
+
radius down at all, which is how the whole question stayed open long enough to
|
|
2150
|
+
be got wrong twice — once too small, once too large.
|
|
2151
|
+
|
|
2152
|
+
---
|
|
2153
|
+
|
|
2154
|
+
## 37. What a rigged garment shows that a sheet cannot
|
|
2155
|
+
|
|
2156
|
+
Everything from §31 to §36 was measured on a triangulated sheet, because a sheet
|
|
2157
|
+
is the only thing in the system with a *surface* to pass through. The
|
|
2158
|
+
playground's cape is not a sheet. It is five strips of seven links, and **nothing
|
|
2159
|
+
joins one strip to the next** — §11.3's proxy is joint indices and a topology,
|
|
2160
|
+
and that topology is a set of chains.
|
|
2161
|
+
|
|
2162
|
+
Two things follow, and the second is a correction.
|
|
2163
|
+
|
|
2164
|
+
### Without the row a cape collapses to half the width it was drawn at
|
|
2165
|
+
|
|
2166
|
+
```
|
|
2167
|
+
the playground's cape — 5 strips of 7, silk, settled from the bind pose
|
|
2168
|
+
|
|
2169
|
+
bind pose 0.340 m wide
|
|
2170
|
+
settled, no self-collision 0.178 m −48%
|
|
2171
|
+
settled, with it 0.351 m +3%
|
|
2172
|
+
```
|
|
2173
|
+
|
|
2174
|
+
Nothing holds the strips apart but this row. Under gravity each one swings
|
|
2175
|
+
inward and they end up occupying the same space, so the cape settles at half the
|
|
2176
|
+
width the artist drew and its strips hang through one another. **This is the
|
|
2177
|
+
first thing in M4 that self-collision visibly does on the geometry M3 actually
|
|
2178
|
+
ships**, and no sheet measurement could have found it: a sheet's neighbouring
|
|
2179
|
+
columns are held apart by the stretch rows between them, and a cape's strips have
|
|
2180
|
+
nothing between them at all.
|
|
2181
|
+
|
|
2182
|
+
It is also the answer to a question §32 left open. There, every tangle was
|
|
2183
|
+
transient — the sheet untied itself whenever the gust stopped, which is why
|
|
2184
|
+
§17's escape hatch still has nothing to escape from. This one is not transient.
|
|
2185
|
+
It is what the garment settles *into*.
|
|
2186
|
+
|
|
2187
|
+
### And the ceiling has to be measured, not derived from the shortest row
|
|
2188
|
+
|
|
2189
|
+
§36 clamps the radius at four fifths of the shortest row, reasoning from a
|
|
2190
|
+
sheet: the nearest pair no row governs is two hops away, so four fifths of one
|
|
2191
|
+
hop leaves a margin. On a sheet that is exactly right —
|
|
2192
|
+
`cloth_self_closest_free_pair` returns 2.0000× the spacing at both 2 cm and
|
|
2193
|
+
5 cm, and the two ceilings agree to the precision Float32 positions carry.
|
|
2194
|
+
|
|
2195
|
+
On the cape they do not agree at all:
|
|
2196
|
+
|
|
2197
|
+
```
|
|
2198
|
+
shortest row — a link, along a strip 0.0801 m
|
|
2199
|
+
two hops, which a sheet would predict 0.1602 m
|
|
2200
|
+
the closest ungoverned pair, measured 0.0850 m
|
|
2201
|
+
```
|
|
2202
|
+
|
|
2203
|
+
The gap *between* strips is half what the link length implies, because it is a
|
|
2204
|
+
different length entirely: the artist put those joints where the garment needed
|
|
2205
|
+
them, and nothing relates that to how finely each strip is subdivided. Derived
|
|
2206
|
+
from the shortest row the ceiling came out at 0.064 m, the 0.040 m floor took
|
|
2207
|
+
precedence, and the settled cape carried **three pairs permanently in contact** —
|
|
2208
|
+
lateral ones, with nothing to do with folding. Measured, the ceiling is 0.034 m
|
|
2209
|
+
and the settled cape carries **none**.
|
|
2210
|
+
|
|
2211
|
+
So `CLOTH_SELF_RADIUS_MAX_RATIO` is two fifths of `ClothInstance#closest_free_pair`
|
|
2212
|
+
rather than four fifths of the shortest row, and `closest_free_pair` is measured
|
|
2213
|
+
once at seed, in the bind pose, by the same grid and the same exclusion set the
|
|
2214
|
+
pair search uses.
|
|
2215
|
+
|
|
2216
|
+
**§31 through §36 are unaffected.** Every one of them was taken on a sheet, where
|
|
2217
|
+
the new rule reproduces the old one to the precision the positions carry, and
|
|
2218
|
+
`cloth_self_radius.spec.js` pins exactly that so it cannot drift.
|
|
2219
|
+
|
|
2220
|
+
### What measuring it costs
|
|
2221
|
+
|
|
2222
|
+
Once per seed, and every cloth pays it whether or not the flag is set, because
|
|
2223
|
+
the flag can be turned on later without a re-seed:
|
|
2224
|
+
|
|
2225
|
+
```
|
|
2226
|
+
1,024 particles, 2,945 stretch rows, 2,821 bend rows
|
|
2227
|
+
|
|
2228
|
+
the exclusion set 1.792 ms
|
|
2229
|
+
the free-pair walk 0.664 ms
|
|
2230
|
+
2.455 ms, once
|
|
2231
|
+
```
|
|
2232
|
+
|
|
2233
|
+
The exclusion set is the larger half and it is not new work — it was built
|
|
2234
|
+
lazily on the first self-collision step before, so a cloth that sets the flag
|
|
2235
|
+
paid it anyway. What is new is that a cloth which never sets the flag now pays
|
|
2236
|
+
it too, at seed, alongside the subtree walk and the graph colouring that seeding
|
|
2237
|
+
already does. Nothing pays it per step.
|
|
2238
|
+
|
|
2239
|
+
---
|