{"_id":"box3d.js","_rev":"5-b609237499e6671645fee20eba59410b","name":"box3d.js","dist-tags":{"latest":"0.1.1"},"versions":{"0.0.0-alpha.0":{"name":"box3d.js","version":"0.0.0-alpha.0","keywords":["box3d","physics","3d","wasm","emscripten","embind"],"author":"","license":"MIT","_id":"box3d.js@0.0.0-alpha.0","maintainers":[{"name":"isaacmason","email":"isaac@isaacmason.com"}],"dist":{"shasum":"c213744271603e6d96aaea68364a34e275498467","tarball":"https://registry.npmjs.org/box3d.js/-/box3d.js-0.0.0-alpha.0.tgz","fileCount":6,"integrity":"sha512-KDQ8NeaiCQBvm5aR0T4Zusf+qSPLxaA9gpuPJ1ffDa2bsBtoy4a6L/ka3cD38JL40qCdbGFMGPlasDj4N2jrSg==","signatures":[{"sig":"MEYCIQCqgLuKi13ebWBlMBefhA49L79HECfDkhp9Njh6KJhQSwIhAIc883OE2s5ipgQeQrQqinOqo1qNR9biFpJ3klI8uOon","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":944235},"type":"module","exports":{".":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mjs"},"./inline":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.single.mjs"}},"gitHead":"40391d916483479b61294ed6169056b1055f7e83","scripts":{"test":"node test/smoke.mjs","build":"node scripts/build.mjs","build:debug":"node scripts/build.mjs --debug"},"_npmUser":{"name":"isaacmason","email":"isaac@isaacmason.com"},"_npmVersion":"11.6.1","description":"WebAssembly bindings for box3d, Erin Catto's 3D physics engine","directories":{},"_nodeVersion":"24.10.0","_hasShrinkwrap":false,"packageManager":"pnpm@10.32.1","_npmOperationalInternal":{"tmp":"tmp/box3d.js_0.0.0-alpha.0_1782925991859_0.8006929442323607","host":"s3://npm-registry-packages-npm-production"}},"0.0.1":{"name":"box3d.js","version":"0.0.1","keywords":["box3d","physics","3d","wasm","emscripten","embind"],"author":"","license":"MIT","_id":"box3d.js@0.0.1","maintainers":[{"name":"isaacmason","email":"isaac@isaacmason.com"}],"dist":{"shasum":"293afcb03b87cbca2557acdd20e5b6a3bc0e030d","tarball":"https://registry.npmjs.org/box3d.js/-/box3d.js-0.0.1.tgz","fileCount":10,"integrity":"sha512-c9OayHPZP2t2Mh3ak6GF1lbj3bKEJxIrndwYoWbuQWeGqU351/npxLpbll7Rt5Qt+Czz1nUL0ewb9PpIFuL08A==","signatures":[{"sig":"MEUCIQDYy+F37vOTLvSfkxzYvEswzfrDtxObTD05AIANlFXt2wIgFDtuklWxtnQEKFtCqNR6PcEnBBVji5/IXqH8uaM+OZI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3928781},"type":"module","exports":{".":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mjs"},"./mt":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mt.mjs"},"./inline":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.inline.mjs"},"./mt-inline":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mt.inline.mjs"}},"gitHead":"3ac59af6ff1cca19b5bd47bd0a825c857894fad9","scripts":{"lint":"biome lint examples/src","test":"node test/smoke.mjs","build":"node scripts/build.mjs","check":"biome check examples/src","format":"biome format examples/src","docs:build":"node docs/build.js","docs:check":"pnpm --filter @box3d/examples exec tsc --noEmit -p ../docs/tsconfig.json","build:debug":"node scripts/build.mjs --debug"},"_npmUser":{"name":"isaacmason","email":"isaac@isaacmason.com"},"_npmVersion":"11.6.1","description":"WebAssembly bindings for box3d, Erin Catto's 3D physics engine","directories":{},"_nodeVersion":"24.10.0","_hasShrinkwrap":false,"packageManager":"pnpm@10.32.1","devDependencies":{"@biomejs/biome":"2.5.2"},"_npmOperationalInternal":{"tmp":"tmp/box3d.js_0.0.1_1782964290877_0.8879240254580805","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"box3d.js","version":"0.0.2","keywords":["box3d","physics","3d","wasm","emscripten","embind"],"author":{"name":"Isaac Mason"},"license":"MIT","_id":"box3d.js@0.0.2","maintainers":[{"name":"isaacmason","email":"isaac@isaacmason.com"}],"homepage":"https://github.com/isaac-mason/box3d.js","bugs":{"url":"https://github.com/isaac-mason/box3d.js/issues"},"dist":{"shasum":"e2162ffaa9fec9f908ac73873e78dfd5eb5120ac","tarball":"https://registry.npmjs.org/box3d.js/-/box3d.js-0.0.2.tgz","fileCount":10,"integrity":"sha512-ziC6IqMbMAYns1aJ7E1czhBEE2Kj+/QK9L16vMXOz7UaXKUj9gX7Za5ut+Dg3euHK6I/1brFSHOpmOwCI6FhYQ==","signatures":[{"sig":"MEUCIFR9frOLpssZWBgi4uaUBzj/jps7MOrFcfcMDGTggNRzAiEAiqpKUX9B5zSVI95FfFjj0H7Nb+p7sNeaLVgGaouNpH4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3928973},"type":"module","exports":{".":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mjs"},"./mt":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mt.mjs"},"./inline":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.inline.mjs"},"./mt-inline":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mt.inline.mjs"}},"gitHead":"2617a0ff763a60c9f17cee57c6ea72aab75a5077","scripts":{"lint":"biome lint examples/src","test":"node test/smoke.mjs","build":"node scripts/build.mjs","check":"biome check examples/src","format":"biome format examples/src","docs:build":"node docs/build.js","docs:check":"pnpm --filter @box3d/examples exec tsc --noEmit -p ../docs/tsconfig.json","build:debug":"node scripts/build.mjs --debug"},"_npmUser":{"name":"isaacmason","email":"isaac@isaacmason.com"},"repository":{"url":"git+https://github.com/isaac-mason/box3d.js.git","type":"git"},"_npmVersion":"11.6.1","description":"WebAssembly bindings for box3d, Erin Catto's 3D physics engine","directories":{},"_nodeVersion":"24.10.0","_hasShrinkwrap":false,"packageManager":"pnpm@10.32.1","devDependencies":{"@biomejs/biome":"2.5.2"},"_npmOperationalInternal":{"tmp":"tmp/box3d.js_0.0.2_1782965051092_0.11433344910514798","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"box3d.js","version":"0.1.0","keywords":["box3d","physics","3d","wasm","emscripten","embind"],"author":{"name":"Isaac Mason"},"license":"MIT","_id":"box3d.js@0.1.0","maintainers":[{"name":"isaacmason","email":"isaac@isaacmason.com"}],"homepage":"https://github.com/isaac-mason/box3d.js","bugs":{"url":"https://github.com/isaac-mason/box3d.js/issues"},"dist":{"shasum":"04f37cd9021a42195c5f9a3dc68de5b4e7410bae","tarball":"https://registry.npmjs.org/box3d.js/-/box3d.js-0.1.0.tgz","fileCount":10,"integrity":"sha512-lLBimUG34Z35OtXZE2ai8UhmBLaLINmS70s6DT5zU1evaA+2cIkFXTDeqtlOGym+aT0ljV5XlNY2A/ISdsUtsA==","signatures":[{"sig":"MEQCIBl3FPfE5KQsf8uRUZIjaQYww1U3sH02FK8ylh0KW+tiAiAGP4MAZ9ZsMlqelCedCPjgetHoSI5Ti7eBG7A5UpG2pw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":4002635},"type":"module","exports":{".":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mjs"},"./mt":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mt.mjs"},"./inline":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.inline.mjs"},"./mt-inline":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mt.inline.mjs"}},"gitHead":"002697e9ee4edf867050db39f86b5e2e4dca03ce","scripts":{"lint":"biome lint examples/src","test":"node test/smoke.mjs","build":"node scripts/build.mjs","check":"biome check examples/src","format":"biome format examples/src","docs:build":"node docs/build.js","docs:check":"pnpm --filter @box3d/examples exec tsc --noEmit -p ../docs/tsconfig.json","build:debug":"node scripts/build.mjs --debug"},"_npmUser":{"name":"isaacmason","email":"isaac@isaacmason.com"},"repository":{"url":"git+https://github.com/isaac-mason/box3d.js.git","type":"git"},"_npmVersion":"11.6.1","description":"WebAssembly bindings for box3d, Erin Catto's 3D physics engine","directories":{},"_nodeVersion":"24.10.0","_hasShrinkwrap":false,"packageManager":"pnpm@10.32.1","devDependencies":{"@biomejs/biome":"2.5.2"},"_npmOperationalInternal":{"tmp":"tmp/box3d.js_0.1.0_1786001116872_0.01046751716080041","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"box3d.js","version":"0.1.1","description":"WebAssembly bindings for box3d, Erin Catto's 3D physics engine","type":"module","exports":{".":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mjs"},"./inline":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.inline.mjs"},"./mt":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mt.mjs"},"./mt-inline":{"types":"./dist/box3d.d.ts","import":"./dist/box3d.mt.inline.mjs"}},"scripts":{"build":"node scripts/build.mjs","build:debug":"node scripts/build.mjs --debug","test":"node test/smoke.mjs","docs:build":"node docs/build.js","docs:check":"pnpm --filter @box3d/examples exec tsc --noEmit -p ../docs/tsconfig.json","lint":"biome lint examples/src","format":"biome format examples/src","check":"biome check examples/src"},"devDependencies":{"@biomejs/biome":"2.5.2"},"keywords":["box3d","physics","3d","wasm","emscripten","embind"],"repository":{"type":"git","url":"git+https://github.com/isaac-mason/box3d.js.git"},"homepage":"https://github.com/isaac-mason/box3d.js","bugs":{"url":"https://github.com/isaac-mason/box3d.js/issues"},"author":{"name":"Isaac Mason"},"license":"MIT","packageManager":"pnpm@10.32.1","gitHead":"5d5a3af049cccd9948b2b55bac4342414af0ef64","_id":"box3d.js@0.1.1","_nodeVersion":"24.10.0","_npmVersion":"11.6.1","dist":{"integrity":"sha512-N3jkFuCpj5WxW3gmJDj/ai9Ticq6bIMMRk/tIJBkeOw4VZviHv6pAQEDFQczRwQQEtIo32Iy6OyhyxPxhzdF6w==","shasum":"33a6c983dd97d769134285ad556f1fc8be0e9885","tarball":"https://registry.npmjs.org/box3d.js/-/box3d.js-0.1.1.tgz","fileCount":10,"unpackedSize":4003730,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDIRbiXJOKXiz/vLMVhpB3K07r2muUJaoRKCHLZ8UOTbAIgZRLJo++G2nsxCLop4kAGBzaooS2+JELQV9/VhlhWb+U="}]},"_npmUser":{"name":"isaacmason","email":"isaac@isaacmason.com"},"directories":{},"maintainers":[{"name":"isaacmason","email":"isaac@isaacmason.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/box3d.js_0.1.1_1786001887642_0.9056496302322465"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-01T17:13:11.788Z","modified":"2026-08-06T07:38:08.013Z","0.0.0-alpha.0":"2026-07-01T17:13:12.060Z","0.0.1":"2026-07-02T03:51:31.083Z","0.0.2":"2026-07-02T04:04:11.290Z","0.1.0":"2026-08-06T07:25:17.072Z","0.1.1":"2026-08-06T07:38:07.867Z"},"bugs":{"url":"https://github.com/isaac-mason/box3d.js/issues"},"author":{"name":"Isaac Mason"},"license":"MIT","homepage":"https://github.com/isaac-mason/box3d.js","keywords":["box3d","physics","3d","wasm","emscripten","embind"],"repository":{"type":"git","url":"git+https://github.com/isaac-mason/box3d.js.git"},"description":"WebAssembly bindings for box3d, Erin Catto's 3D physics engine","maintainers":[{"name":"isaacmason","email":"isaac@isaacmason.com"}],"readme":"# box3d.js\n\nWebAssembly bindings for [box3d](https://github.com/erincatto/box3d) - Erin Catto's 3D rigid body physics engine - compiled with Emscripten and exposed as an ES module with full TypeScript definitions.\n\nThe API mirrors the box3d C API 1:1 (`b3CreateWorld`, `b3World_Step`, …) so the upstream docs and samples translate relatively directly.\n\n```bash\nnpm install box3d.js\n```\n\n**Builds**\n\n| Import | Use case |\n|--------|----------|\n| `box3d.js/inline` | Browser - single-file, no separate `.wasm` to serve |\n| `box3d.js` | Node.js or bundlers that can serve `.wasm` |\n| `box3d.js/mt-inline` | Browser + multithreading (requires cross-origin isolation) |\n| `box3d.js/mt` | Node.js + multithreading |\n\n**Examples**\n\n<table>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-shapes\">\n        <img src=\"./examples/public/screenshots/example-shapes.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Shapes\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-cube-heap\">\n        <img src=\"./examples/public/screenshots/example-cube-heap.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Cube Heap\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-stacking\">\n        <img src=\"./examples/public/screenshots/example-stacking.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Stacking\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-robustness\">\n        <img src=\"./examples/public/screenshots/example-robustness.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Robustness\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-restitution\">\n        <img src=\"./examples/public/screenshots/example-restitution.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Restitution\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-friction\">\n        <img src=\"./examples/public/screenshots/example-friction.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Friction\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-conveyor-belt\">\n        <img src=\"./examples/public/screenshots/example-conveyor-belt.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Conveyor Belt\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-ccd\">\n        <img src=\"./examples/public/screenshots/example-ccd.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Continuous Collision Detection\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-gravity-factor\">\n        <img src=\"./examples/public/screenshots/example-gravity-factor.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Gravity Factor\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-linear-damping\">\n        <img src=\"./examples/public/screenshots/example-linear-damping.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Linear Damping\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-angular-damping\">\n        <img src=\"./examples/public/screenshots/example-angular-damping.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Angular Damping\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-add-impulse-at-position\">\n        <img src=\"./examples/public/screenshots/example-add-impulse-at-position.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Add Impulse at Position\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-collision-filtering\">\n        <img src=\"./examples/public/screenshots/example-collision-filtering.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Collision Filtering\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-joints\">\n        <img src=\"./examples/public/screenshots/example-joints.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Joints\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-ragdoll\">\n        <img src=\"./examples/public/screenshots/example-ragdoll.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Ragdoll\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-character\">\n        <img src=\"./examples/public/screenshots/example-character.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Character\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-static-compound\">\n        <img src=\"./examples/public/screenshots/example-static-compound.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Static Compound\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-triangle-mesh\">\n        <img src=\"./examples/public/screenshots/example-triangle-mesh.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Triangle Mesh\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-mesh\">\n        <img src=\"./examples/public/screenshots/example-mesh.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Mesh Generators\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-cast-ray\">\n        <img src=\"./examples/public/screenshots/example-cast-ray.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Cast Ray\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-cast-shape\">\n        <img src=\"./examples/public/screenshots/example-cast-shape.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Cast Shape\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-hinge-motor\">\n        <img src=\"./examples/public/screenshots/example-hinge-motor.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Hinge Motor\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-gjk\">\n        <img src=\"./examples/public/screenshots/example-gjk.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        GJK Distance\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-manifold\">\n        <img src=\"./examples/public/screenshots/example-manifold.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Manifold\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-tree\">\n        <img src=\"./examples/public/screenshots/example-tree.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Dynamic Tree\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-explosion\">\n        <img src=\"./examples/public/screenshots/example-explosion.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Explosion\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-geometry\">\n        <img src=\"./examples/public/screenshots/example-geometry.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Geometry\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-sensor\">\n        <img src=\"./examples/public/screenshots/example-sensor.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Sensor\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-events\">\n        <img src=\"./examples/public/screenshots/example-events.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Events\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-contacts\">\n        <img src=\"./examples/public/screenshots/example-contacts.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Contacts\n      </a>\n    </td>\n  </tr>\n  <tr>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-multithreading\">\n        <img src=\"./examples/public/screenshots/example-multithreading.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Multithreading\n      </a>\n    </td>\n    <td align=\"center\">\n      <a href=\"https://isaac-mason.github.io/box3d.js/#example-replay\">\n        <img src=\"./examples/public/screenshots/example-replay.png\" width=\"180\" height=\"120\" style=\"object-fit:cover;\"/><br/>\n        Replay\n      </a>\n    </td>\n  </tr>\n</table>\n\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [How Does This Compare to Jolt / Rapier / Others?](#how-does-this-compare-to-jolt-rapier-others)\n- [Physics World](#physics-world)\n- [Rigid Bodies](#rigid-bodies)\n- [Shapes](#shapes)\n- [Joints](#joints)\n- [Queries](#queries)\n- [Events](#events)\n- [Multithreading](#multithreading)\n- [Building from Source](#building-from-source)\n\n## Quick Start\n\nInitialize the WASM module once with `await Box3D()`, then call the physics API through the returned module object.\n\n```ts\nimport Box3D from 'box3d.js';\nimport type { Box3DModule, b3Vec3 } from 'box3d.js';\n\n// Initialize the WASM module. Use box3d.js/inline if your environment can't serve a separate .wasm file.\nconst b3: Box3DModule = await Box3D();\n\n// Create a world with downward gravity. Math types are plain arrays: b3Vec3 is [x, y, z].\nconst worldDef = b3.b3DefaultWorldDef();\nworldDef.gravity = [0, -10, 0];\nconst world = b3.b3CreateWorld(worldDef);\n\n// Static ground: a wide flat box\nconst groundDef = b3.b3DefaultBodyDef();\ngroundDef.position = [0, 0, 0];\nconst ground = b3.b3CreateBody(world, groundDef);\nb3.b3CreateBoxShape(ground, b3.b3DefaultShapeDef(), 25, 0.5, 25);\n\n// Dynamic sphere dropped from above\nconst bodyDef = b3.b3DefaultBodyDef();\nbodyDef.type = b3.b3BodyType.b3_dynamicBody;\nbodyDef.position = [0, 10, 0];\nconst body = b3.b3CreateBody(world, bodyDef);\nb3.b3CreateSphereShape(body, b3.b3DefaultShapeDef(), { center: [0, 0, 0], radius: 0.5 });\n\n// Step the simulation at 60 Hz for ~2.5 seconds\nfor (let i = 0; i < 150; i++) {\n    b3.b3World_Step(world, 1 / 60, 4);\n}\n\n// Getters are out-param-first: pass a scratch array to fill (zero-allocation).\nconst pos: b3Vec3 = [0, 0, 0];\nb3.b3Body_GetPosition(pos, body);\nconsole.log(`sphere landed at y = ${pos[1].toFixed(2)}`);\n\nb3.b3DestroyWorld(world);\n```\n\n## How Does This Compare to Jolt / Rapier / Others?\n\nbox3d is Erin Catto's 3D rigid body engine — the 3D sibling of Box2D, from the author of Box2D itself.\n\nFor comparisons across engines (box3d.js, Jolt, Rapier, and others), see the **[JS physics benchmarks](https://isaac-mason.github.io/js-physics-benchmarks/)**.\n\n## Physics World\n\n### Creating a World\n\n```ts\nconst worldDef = b3.b3DefaultWorldDef();\nworldDef.gravity = [0, -10, 0];\n\nconst world = b3.b3CreateWorld(worldDef);\n```\n\n`b3DefaultWorldDef()` returns a world definition with sensible defaults. Common fields to override:\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `gravity` | `b3Vec3` | `[0, 0, 0]` | World gravity vector |\n| `workerCount` | `number` | `0` | Thread count for the MT build (see [Multithreading](#multithreading)) |\n| `maximumLinearSpeed` | `number` | `500` | Speed cap - raise this for CCD bullet bodies |\n\n### Stepping the Simulation\n\n```ts\n// Advance the simulation by one fixed time step.\n// subStepCount controls accuracy vs. performance (4 is a good default).\nb3.b3World_Step(world, 1 / 60, 4);\n```\n\nCall `b3World_Step` in your game loop. `subStepCount` controls solver accuracy - 4 is a good default.\n\n```ts\n// Typical browser game loop\nfunction animate() {\n    b3.b3World_Step(world, 1 / 60, 4);\n    // ... update mesh transforms from body positions ...\n    requestAnimationFrame(animate);\n}\nrequestAnimationFrame(animate);\n```\n\n### Destroying a World\n\n```ts\n// Always destroy the world when you are done to free WASM memory.\nb3.b3DestroyWorld(world);\n```\n\n### Memory Management\n\nbox3d.js wraps a WASM module, so some objects are allocated on the WASM heap and must be freed explicitly.\n\n**Hull data is copied** into the world's internal database on shape creation, so `b3HullData` handles can be destroyed immediately after - or reused across multiple shapes before being destroyed.\n\n**Mesh, compound, and heightfield data are not copied** - the world stores a raw pointer. `b3MeshData`, `b3CompoundData`, and `b3HeightFieldData` must be kept alive for as long as the shape (or world) exists, and destroyed only after.\n\n```ts\n// Hull data IS copied into the world's internal database on shape creation,\n// so the handle can be destroyed immediately after — or reused to stamp out\n// multiple shapes and destroyed when no longer needed.\nconst hull = b3.b3CreateHull(positions)!;\nconst bodyA = b3.b3CreateBody(world, b3.b3DefaultBodyDef());\nb3.b3CreateHullShape(bodyA, b3.b3DefaultShapeDef(), hull);\nb3.b3DestroyHull(hull); // safe — world keeps its own copy\n\n// Mesh, compound, and heightfield data are NOT copied. The world stores a raw\n// pointer to the data, so it must be kept alive for as long as the shape (or\n// world) exists. Destroy it only after the shape or world has been destroyed.\nconst mesh = b3.b3CreateMesh(positions, indices)!;\nconst bodyB = b3.b3CreateBody(world, b3.b3DefaultBodyDef());\nb3.b3CreateMeshShape(bodyB, b3.b3DefaultShapeDef(), mesh, [1, 1, 1]);\n// b3.b3DestroyMesh(mesh) — NOT safe here; the shape still holds a pointer to it\nb3.b3DestroyWorld(world);\nb3.b3DestroyMesh(mesh); // safe now — world (and its shapes) are gone\n```\n\n**Destroying a world** frees all bodies, shapes, and joints inside it automatically - no need to clean them up individually first.\n\n```ts\n// b3DestroyWorld frees all bodies, shapes, and joints inside it automatically.\n// Call it once at teardown — no need to destroy individual objects first.\nb3.b3DestroyWorld(world2);\n```\n\n**Removing objects during simulation** - use the individual destroy functions. Destroying a body also removes all its shapes and any joints attached to it.\n\n```ts\n// Destroying a body also removes all its shapes and any joints attached to it.\nconst dynDef = b3.b3DefaultBodyDef();\ndynDef.type = b3.b3BodyType.b3_dynamicBody;\nconst dynBody = b3.b3CreateBody(world3, dynDef);\nb3.b3CreateBoxShape(dynBody, b3.b3DefaultShapeDef(), 0.5, 0.5, 0.5);\nb3.b3DestroyBody(dynBody); // body, its shapes, and attached joints all removed\n\n// To remove one shape from a multi-shape body without destroying the body,\n// use b3DestroyShape. The boolean controls whether body mass is recalculated.\nconst multiBody = b3.b3CreateBody(world3, b3.b3DefaultBodyDef());\nconst shapeA = b3.b3CreateBoxShape(multiBody, b3.b3DefaultShapeDef(), 0.5, 0.5, 0.5);\nb3.b3CreateSphereShape(multiBody, b3.b3DefaultShapeDef(), { center: [0, 1, 0], radius: 0.3 });\nb3.b3DestroyShape(shapeA, true); // removes shapeA and recalculates body mass\n\n// Joints can also be destroyed independently. The boolean controls whether\n// the connected bodies are woken up.\nconst bodyC = b3.b3CreateBody(world3, b3.b3DefaultBodyDef());\nconst bodyD = b3.b3CreateBody(world3, b3.b3DefaultBodyDef());\nconst jd = b3.b3DefaultDistanceJointDef();\njd.base.bodyIdA = bodyC;\njd.base.bodyIdB = bodyD;\nconst joint = b3.b3CreateDistanceJoint(world3, jd);\nb3.b3DestroyJoint(joint, true); // removes joint; true = wake the connected bodies\n```\n\n### Units and Scale\n\nbox3d uses SI units and a right-handed coordinate system (+Y up by default):\n\n- **Length**: metres (m)\n- **Mass**: kilograms (kg) - note: default shape density is **1000 kg/m³**\n- **Time**: seconds (s)\n- **Triangle winding**: counter-clockwise (CCW) is the front face\n\n## Rigid Bodies\n\n### Body Types\n\n```ts\n// Static: immovable; infinite mass; collides with dynamic bodies\nconst staticDef = b3.b3DefaultBodyDef();\nstaticDef.type = b3.b3BodyType.b3_staticBody;\nstaticDef.position = [0, 0, 0];\nconst staticBody = b3.b3CreateBody(world, staticDef);\nb3.b3CreateBoxShape(staticBody, b3.b3DefaultShapeDef(), 10, 0.5, 10);\n\n// Dynamic: fully simulated; affected by forces and gravity\nconst dynamicDef = b3.b3DefaultBodyDef();\ndynamicDef.type = b3.b3BodyType.b3_dynamicBody;\ndynamicDef.position = [0, 5, 0];\nconst dynamicBody = b3.b3CreateBody(world, dynamicDef);\nb3.b3CreateSphereShape(dynamicBody, b3.b3DefaultShapeDef(), { center: [0, 0, 0], radius: 0.5 });\n\n// Kinematic: user-controlled velocity; pushes dynamic bodies but is not pushed back\nconst kinematicDef = b3.b3DefaultBodyDef();\nkinematicDef.type = b3.b3BodyType.b3_kinematicBody;\nkinematicDef.position = [0, 2, 0];\nconst kinematicBody = b3.b3CreateBody(world, kinematicDef);\nb3.b3CreateBoxShape(kinematicBody, b3.b3DefaultShapeDef(), 2, 0.2, 2);\n```\n\n| Type | Moves | Affected by forces | Collides with |\n|------|-------|-------------------|---------------|\n| `b3_staticBody` | Never | No | Dynamic only |\n| `b3_dynamicBody` | Simulated | Yes | All types |\n| `b3_kinematicBody` | Scripted | No | Dynamic only |\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-shapes\">\n      <img src=\"./examples/public/screenshots/example-shapes.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Shapes</strong>\n    </a>\n  </td>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-cube-heap\">\n      <img src=\"./examples/public/screenshots/example-cube-heap.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Cube Heap</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Position and Rotation\n\nMath types are plain arrays: `b3Vec3` is `[x, y, z]` and `b3Quat` is `[x, y, z, w]` (the identity rotation is `[0, 0, 0, 1]`) — pass and receive them straight from gl-matrix/mathcat-style libraries. Value getters are out-param-first and zero-allocation: pass a scratch array to fill instead of receiving a freshly allocated object.\n\n```ts\n// Read position and rotation. Getters are out-param-first: pass a scratch array\n// to fill (zero-allocation) — reuse it across frames to avoid GC pressure.\nconst pos: b3Vec3 = [0, 0, 0];\nconst rot: b3Quat = [0, 0, 0, 1]; // quaternion [x, y, z, w]\nb3.b3Body_GetPosition(pos, dynamicBody);\nb3.b3Body_GetRotation(rot, dynamicBody);\n\n// Set position and rotation\nconst IDENTITY_QUAT: b3Quat = [0, 0, 0, 1];\nb3.b3Body_SetTransform(dynamicBody, [1, 5, 0], IDENTITY_QUAT);\n\nvoid pos; void rot;\n```\n\n### Velocity\n\n```ts\n// Read velocities into reusable scratch arrays (out-param-first, zero-allocation)\nconst linVel: b3Vec3 = [0, 0, 0];\nconst angVel: b3Vec3 = [0, 0, 0];\nb3.b3Body_GetLinearVelocity(linVel, dynamicBody);\nb3.b3Body_GetAngularVelocity(angVel, dynamicBody);\n\n// Set velocities\nb3.b3Body_SetLinearVelocity(dynamicBody, [5, 0, 0]);\nb3.b3Body_SetAngularVelocity(dynamicBody, [0, 1, 0]); // spin around Y\n\nvoid linVel; void angVel;\n```\n\n### Forces and Impulses\n\nForces accumulate until the next `b3World_Step` call, then clear. Impulses apply an instant velocity change.\n\n**Important:** box3d's default shape density is 1000 kg/m³, making bodies much heavier than in most engines. Scale your impulse magnitudes by the body's mass to get predictable results.\n\n```ts\n// Apply force at center of mass (accumulates until next step)\nb3.b3Body_ApplyForceToCenter(dynamicBody, [0, 100, 0], true);\n\n// Apply force at a world-space point (generates torque)\nb3.b3Body_ApplyForce(dynamicBody, [0, 100, 0], [1, 5, 0], true);\n\n// Apply instant impulse at center of mass.\n// box3d's default shape density is 1000 kg/m3 -- bodies are heavy.\n// Scale impulse by mass so the magnitude is predictable regardless of size.\nconst mass = b3.b3Body_GetMass(dynamicBody);\nb3.b3Body_ApplyLinearImpulseToCenter(dynamicBody, [0, mass * 5, 0], true);\n\n// Apply impulse at a world-space point (generates both linear and angular velocity change)\nb3.b3Body_ApplyLinearImpulse(dynamicBody, [0, mass * 5, 0], [0.3, 5, 0], true);\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-add-impulse-at-position\">\n      <img src=\"./examples/public/screenshots/example-add-impulse-at-position.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Add Impulse at Position</strong>\n    </a>\n  </td>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-explosion\">\n      <img src=\"./examples/public/screenshots/example-explosion.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Explosion</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Damping\n\n```ts\n// Linear damping slows translational velocity each step (0 = none, higher = more drag)\nb3.b3Body_SetLinearDamping(dynamicBody, 0.5);\n\n// Angular damping slows rotational velocity each step\nb3.b3Body_SetAngularDamping(dynamicBody, 0.5);\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-linear-damping\">\n      <img src=\"./examples/public/screenshots/example-linear-damping.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Linear Damping</strong>\n    </a>\n  </td>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-angular-damping\">\n      <img src=\"./examples/public/screenshots/example-angular-damping.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Angular Damping</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Gravity Scale\n\n```ts\n// Multiply world gravity for this body (0 = weightless, 2 = double gravity)\nb3.b3Body_SetGravityScale(dynamicBody, 0.5);\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-gravity-factor\">\n      <img src=\"./examples/public/screenshots/example-gravity-factor.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Gravity Factor</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Sleeping\n\nBodies at rest are put to sleep automatically to save CPU. You can also control sleep manually.\n\n```ts\n// Check whether a body is awake\nconst awake = b3.b3Body_IsAwake(dynamicBody);\n\n// Manually wake or sleep a body\nb3.b3Body_SetAwake(dynamicBody, true);\nb3.b3Body_SetAwake(dynamicBody, false);\n\n// Disable sleep entirely for a body (keeps it active even at rest)\nb3.b3Body_EnableSleep(dynamicBody, false);\n\nvoid awake;\n```\n\n### Continuous Collision Detection\n\nEnable CCD for fast-moving objects (bullets, projectiles) to prevent tunneling through thin walls.\n\n```ts\n// Enable CCD (bullet mode) for fast-moving objects to prevent tunneling.\n// Also raise worldDef.maximumLinearSpeed if the body exceeds the default cap.\nb3.b3Body_SetBullet(dynamicBody, true);\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-ccd\">\n      <img src=\"./examples/public/screenshots/example-ccd.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Continuous Collision Detection</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Changing Body Type\n\n```ts\n// Change body type at runtime\nb3.b3Body_SetType(dynamicBody, b3.b3BodyType.b3_staticBody);\nb3.b3Body_SetType(dynamicBody, b3.b3BodyType.b3_dynamicBody);\n```\n\n### Kinematic Bodies\n\nUse `b3Body_SetTargetTransform` to move kinematic bodies each frame. box3d computes the velocities needed to reach the target, so dynamic bodies are pushed physically rather than teleported through.\n\n```ts\n// Move a kinematic body towards a target transform each frame.\n// box3d computes the velocities needed to reach it in `dt` seconds,\n// so dynamic bodies are pushed physically rather than teleported through.\nconst target: b3Transform = { position: [2, 2, 0], quaternion: [0, 0, 0, 1] };\nconst dt = 1 / 60;\nb3.b3Body_SetTargetTransform(kinematicBody, target, dt, true);\n```\n\n### Material Properties\n\nFriction and restitution can be set per shape at creation, or updated at runtime via `b3Shape_SetSurfaceMaterial`.\n\n```ts\n// Set friction and restitution via the shape's baseMaterial at creation time\nconst shapeDef = b3.b3DefaultShapeDef();\nshapeDef.baseMaterial.friction = 0.6;     // 0 = frictionless, 1 = rough\nshapeDef.baseMaterial.restitution = 0.8;  // 0 = no bounce, 1 = perfectly elastic\nconst shape = b3.b3CreateBoxShape(dynamicBody, shapeDef, 0.5, 0.5, 0.5);\n\n// Or update a shape's surface material after creation\nconst mat = b3.b3DefaultSurfaceMaterial();\nmat.friction = 0.1;\nmat.restitution = 0.9;\nb3.b3Shape_SetSurfaceMaterial(shape, mat);\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-friction\">\n      <img src=\"./examples/public/screenshots/example-friction.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Friction</strong>\n    </a>\n  </td>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-restitution\">\n      <img src=\"./examples/public/screenshots/example-restitution.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Restitution</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Collision Filtering\n\nbox3d filters collisions with `bigint` category and mask bits. A contact fires when `(categoryA & maskB) != 0n AND (categoryB & maskA) != 0n`.\n\n```ts\n// b3Filter uses bigint category/mask bits.\n// A contact is generated when (categoryA & maskB) != 0n AND (categoryB & maskA) != 0n.\nconst GROUND  = 1n;\nconst GROUP_A = 2n;\nconst GROUP_B = 4n;\n\nconst filterDef = b3.b3DefaultShapeDef();\n\n// Belongs to GROUP_A, collides with GROUND and GROUP_A (not GROUP_B)\nfilterDef.filter = { categoryBits: GROUP_A, maskBits: GROUND | GROUP_A, groupIndex: 0 };\nb3.b3CreateBoxShape(dynamicBody, filterDef, 0.5, 0.5, 0.5);\n\n// Update filter at runtime (second arg: wake bodies immediately)\nconst aShape = b3.b3Body_GetShapes(dynamicBody).get(0)!;\nb3.b3Shape_SetFilter(aShape, { categoryBits: GROUP_B, maskBits: GROUND | GROUP_B, groupIndex: 0 }, true);\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-collision-filtering\">\n      <img src=\"./examples/public/screenshots/example-collision-filtering.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Collision Filtering</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Sensors\n\nSensor shapes detect overlaps without generating contact forces. Both the sensor and each visitor shape must opt in to sensor events.\n\n```ts\n// A sensor detects overlaps without applying contact forces.\n// Both the sensor shape and visitor shapes must opt in to sensor events.\nconst sensorBodyDef = b3.b3DefaultBodyDef();\nsensorBodyDef.type = b3.b3BodyType.b3_kinematicBody;\nconst sensorBody = b3.b3CreateBody(world, sensorBodyDef);\n\nconst sensorShapeDef = b3.b3DefaultShapeDef();\nsensorShapeDef.isSensor = true;\nsensorShapeDef.enableSensorEvents = true;\nb3.b3CreateBoxShape(sensorBody, sensorShapeDef, 2, 2, 2);\n\n// Each visitor shape must also enable sensor events\nconst visitorDef = b3.b3DefaultBodyDef();\nvisitorDef.type = b3.b3BodyType.b3_dynamicBody;\nvisitorDef.position = [0, 5, 0];\nconst visitor = b3.b3CreateBody(world, visitorDef);\nconst visitorShapeDef = b3.b3DefaultShapeDef();\nvisitorShapeDef.enableSensorEvents = true;\nb3.b3CreateBoxShape(visitor, visitorShapeDef, 0.5, 0.5, 0.5);\n\n// Read begin/end events each frame through a reusable, wasm-backed events buffer\n// (allocate the buffer + scratch once; refilling with getEvents allocates nothing).\nconst eventsBuffer = b3.createEventsBuffer();\nconst sensorTouch = b3.createSensorTouchEvent();\nb3.b3World_Step(world, 1 / 60, 4);\nb3.getEvents(eventsBuffer, world);\nfor (let i = 0, n = b3.getNumSensorBeginEvents(eventsBuffer); i < n; i++) {\n    b3.getSensorBeginEventAt(sensorTouch, eventsBuffer, i);\n    console.log('entered sensor:', sensorTouch.visitorShapeId);\n}\nfor (let i = 0, n = b3.getNumSensorEndEvents(eventsBuffer); i < n; i++) {\n    b3.getSensorEndEventAt(sensorTouch, eventsBuffer, i);\n    console.log('left sensor:', sensorTouch.visitorShapeId);\n}\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-sensor\">\n      <img src=\"./examples/public/screenshots/example-sensor.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Sensor</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n## Shapes\n\nbox3d shapes attach to a body and define its collision geometry. A body can have multiple shapes.\n\n### Box\n\n```ts\n// Box: defined by half-extents (hx, hy, hz) from the body origin\nb3.b3CreateBoxShape(body, sd, 0.5, 0.5, 0.5);  // 1x1x1 cube\nb3.b3CreateBoxShape(body, sd, 2.0, 0.1, 2.0);  // flat platform\n```\n\n### Sphere\n\n```ts\n// center is a b3Vec3: [x, y, z]\nb3.b3CreateSphereShape(body, sd, { center: [0, 0, 0], radius: 0.5 });\n```\n\n### Capsule\n\n```ts\n// Capsule: a cylinder with hemispherical caps, defined by two center points + radius\nb3.b3CreateCapsuleShape(body, sd, {\n    center1: [0, -0.5, 0],\n    center2: [0,  0.5, 0],\n    radius: 0.3,\n});\n```\n\n### Convex Hull\n\n```ts\n// Convex hull: the tightest convex shape enclosing a set of points.\n// Pass a flat [x,y,z, x,y,z, ...] array -- box3d computes the hull internally.\nconst positions = [\n    -0.5, 0, -0.5,\n     0.5, 0, -0.5,\n     0.5, 0,  0.5,\n    -0.5, 0,  0.5,\n     0,   1,  0,   // apex\n];\nconst hullData = b3.b3CreateHull(positions)!;\nb3.b3CreateHullShape(body, sd, hullData);\nhullData.delete(); // free the C++ handle when done\n```\n\n### Cylinder and Cone\n\n```ts\n// Cylinder and cone are built-in hull makers.\n// b3CreateCylinder(halfHeight, radius, convexRadius, segments)\nconst cylData = b3.b3CreateCylinder(0.5, 0.4, 0.0, 16)!;\nb3.b3CreateHullShape(body, sd, cylData);\ncylData.delete();\n\n// b3CreateCone(height, radius, convexRadius, segments)\nconst coneData = b3.b3CreateCone(1.0, 0.5, 0.0, 16)!;\nb3.b3CreateHullShape(body, sd, coneData);\nconeData.delete();\n```\n\n### Triangle Mesh\n\nTriangle meshes are best suited for static terrain and level geometry. box3d performs internal preprocessing (active edges, BVH) on mesh creation.\n\n```ts\n// Triangle mesh: for complex static terrain and level geometry.\n// Positions: flat Float32Array [x,y,z, ...]; indices: Uint32Array.\n// Winding: counter-clockwise (CCW) is the front face.\nconst meshPositions = new Float32Array([\n    -10, 0, -10,\n     10, 0, -10,\n     10, 0,  10,\n    -10, 0,  10,\n]);\nconst meshIndices = new Uint32Array([0, 1, 2, 0, 2, 3]);\n\nconst meshData = b3.b3CreateMesh(meshPositions, meshIndices)!;\n\nconst staticBodyDef = b3.b3DefaultBodyDef();\nconst staticBody = b3.b3CreateBody(world, staticBodyDef);\nconst scale: b3Vec3 = [1, 1, 1];\nb3.b3CreateMeshShape(staticBody, b3.b3DefaultShapeDef(), meshData, scale);\nmeshData.delete();\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-triangle-mesh\">\n      <img src=\"./examples/public/screenshots/example-triangle-mesh.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Triangle Mesh</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Compound Shapes (Static Only)\n\nCompound shapes combine multiple child shapes on a single body. **In box3d, compound shapes are static-only** - `b3_dynamicBody` and `b3_kinematicBody` will reject compound shapes.\n\n```ts\n// Compound shapes combine multiple child shapes on a single body.\n// In box3d, compounds are static-only -- the body type must be b3_staticBody.\nconst compoundBodyDef = b3.b3DefaultBodyDef();\ncompoundBodyDef.type = b3.b3BodyType.b3_staticBody;\nconst compoundBody = b3.b3CreateBody(world, compoundBodyDef);\n\nconst IDENTITY_QUAT: b3Quat = [0, 0, 0, 1];\n\n// Each child is a convex hull plus the transform placing it on the body.\n// (position is a b3Vec3, quaternion a b3Quat.) Build box hulls from their corners:\nfunction boxHull(hx: number, hy: number, hz: number) {\n    return b3.b3CreateHull([\n        -hx, -hy, -hz,  hx, -hy, -hz,  hx, -hy, hz,  -hx, -hy, hz,\n        -hx,  hy, -hz,  hx,  hy, -hz,  hx,  hy, hz,  -hx,  hy, hz,\n    ])!;\n}\n\nconst spec = {\n    hulls: [\n        { hull: boxHull(0.5, 2, 0.5), transform: { position: [-2, 0, 0], quaternion: IDENTITY_QUAT } }, // left wall\n        { hull: boxHull(0.5, 2, 0.5), transform: { position: [ 2, 0, 0], quaternion: IDENTITY_QUAT } }, // right wall\n        { hull: boxHull(2.5, 0.5, 0.5), transform: { position: [0, -1, 0], quaternion: IDENTITY_QUAT } }, // floor\n    ],\n};\n\n// Compound data is NOT copied into the world -- keep it (and its hulls) alive for\n// as long as the shape exists, then free it after the body/world is destroyed.\nconst compoundData = b3.b3CreateCompound(spec)!;\nb3.b3CreateCompoundShape(compoundBody, b3.b3DefaultShapeDef(), compoundData);\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-static-compound\">\n      <img src=\"./examples/public/screenshots/example-static-compound.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Static Compound</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n## Joints\n\nJoints constrain the relative motion between two bodies. All joint defs share a `bodyIdA` / `bodyIdB` pair. Joint frames (`frameA`, `frameB`) are local-space transforms that define where and how the joint attaches.\n\n**Axis conventions:**\n- Revolute: rotates about the joint frame's local **Z**-axis\n- Prismatic: slides along the joint frame's local **X**-axis\n- Spherical: cone centered on frame **Z**\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-hinge-motor\">\n      <img src=\"./examples/public/screenshots/example-hinge-motor.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Hinge Motor</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Revolute (Hinge)\n\n```ts\n// Revolute joint: rotates around the joint frame's local Z-axis.\n// To hinge around world Y, rotate the frame +90 deg about X (local Z -> world Y).\n// All joint bodies and frames live on `def.base`. A local frame is a b3Transform:\n// { position: b3Vec3, quaternion: b3Quat }.\nconst revoluteDef = b3.b3DefaultRevoluteJointDef();\nrevoluteDef.base.bodyIdA = anchor;\nrevoluteDef.base.bodyIdB = pendulum;\nrevoluteDef.base.localFrameA = { position: [0, -1, 0], quaternion: IDENTITY_QUAT };\nrevoluteDef.base.localFrameB = { position: [0,  1, 0], quaternion: IDENTITY_QUAT };\nb3.b3CreateRevoluteJoint(world, revoluteDef);\n```\n\n### Revolute Motor\n\n```ts\n// Motorized revolute joint: the motor drives the hinge at a target angular speed\nconst motorDef = b3.b3DefaultRevoluteJointDef();\nmotorDef.base.bodyIdA = anchor;\nmotorDef.base.bodyIdB = pendulum;\nmotorDef.base.localFrameA = { position: [0, -1, 0], quaternion: IDENTITY_QUAT };\nmotorDef.base.localFrameB = { position: [0,  1, 0], quaternion: IDENTITY_QUAT };\nmotorDef.enableMotor = true;\nmotorDef.motorSpeed = 2.0;          // rad/s\nmotorDef.maxMotorTorque = 10000;    // must be large enough to overcome inertia\nconst motorJoint = b3.b3CreateRevoluteJoint(world, motorDef);\n\n// Adjust speed at runtime\nb3.b3RevoluteJoint_SetMotorSpeed(motorJoint, 4.0);\n```\n\n### Weld (Fixed)\n\n```ts\n// Weld joint: locks two bodies together rigidly (no relative motion)\nconst weldDef = b3.b3DefaultWeldJointDef();\nweldDef.base.bodyIdA = anchor;\nweldDef.base.bodyIdB = pendulum;\nweldDef.base.localFrameA = { position: [0, -1, 0], quaternion: IDENTITY_QUAT };\nweldDef.base.localFrameB = { position: [0,  1, 0], quaternion: IDENTITY_QUAT };\nb3.b3CreateWeldJoint(world, weldDef);\n```\n\n### Distance\n\n```ts\n// Distance joint: maintains a target distance between two local anchor points\nconst distanceDef = b3.b3DefaultDistanceJointDef();\ndistanceDef.base.bodyIdA = anchor;\ndistanceDef.base.bodyIdB = pendulum;\ndistanceDef.base.localFrameA = { position: [0, -1, 0], quaternion: IDENTITY_QUAT };\ndistanceDef.base.localFrameB = { position: [0,  1, 0], quaternion: IDENTITY_QUAT };\ndistanceDef.length = 2.0; // desired distance in metres\nb3.b3CreateDistanceJoint(world, distanceDef);\n```\n\n### Spherical (Ball and Socket)\n\n```ts\n// Spherical (ball-and-socket) joint: allows free rotation in all directions.\n// Optionally constrain the cone angle to limit how far B can swing from A's Z-axis.\nconst sphericalDef = b3.b3DefaultSphericalJointDef();\nsphericalDef.base.bodyIdA = anchor;\nsphericalDef.base.bodyIdB = pendulum;\nsphericalDef.base.localFrameA = { position: [0, -1, 0], quaternion: IDENTITY_QUAT };\nsphericalDef.base.localFrameB = { position: [0,  1, 0], quaternion: IDENTITY_QUAT };\nb3.b3CreateSphericalJoint(world, sphericalDef);\n```\n\n### Prismatic (Slider)\n\n```ts\n// Prismatic joint: slides along the joint frame's local X-axis\nconst prismaticDef = b3.b3DefaultPrismaticJointDef();\nprismaticDef.base.bodyIdA = anchor;\nprismaticDef.base.bodyIdB = pendulum;\nprismaticDef.base.localFrameA = { position: [0, 0, 0], quaternion: IDENTITY_QUAT };\nprismaticDef.base.localFrameB = { position: [0, 0, 0], quaternion: IDENTITY_QUAT };\nprismaticDef.enableLimit = true;\nprismaticDef.lowerTranslation = -2.0;\nprismaticDef.upperTranslation =  2.0;\nb3.b3CreatePrismaticJoint(world, prismaticDef);\n```\n\n### Wheel (Suspension)\n\n```ts\n// Wheel joint: revolute + suspension spring; models a vehicle wheel\nconst wheelDef = b3.b3DefaultWheelJointDef();\nwheelDef.base.bodyIdA = anchor;\nwheelDef.base.bodyIdB = pendulum;\nwheelDef.base.localFrameA = { position: [0, -1, 0], quaternion: IDENTITY_QUAT };\nwheelDef.base.localFrameB = { position: [0,  0, 0], quaternion: IDENTITY_QUAT };\nwheelDef.enableSuspensionSpring = true;\nwheelDef.suspensionHertz = 4.0;         // spring frequency (Hz)\nwheelDef.suspensionDampingRatio = 0.7;  // 0 = undamped, 1 = critically damped\nb3.b3CreateWheelJoint(world, wheelDef);\n```\n\n## Queries\n\nQueries ask questions about the physics world without advancing the simulation.\n\n### Cast Ray (Closest)\n\n```ts\n// Cast a ray and return the closest hit. origin/translation are b3Vec3 arrays.\n// origin + translation defines the ray: it runs from origin to origin+translation.\nconst origin: b3Vec3 = [0, 10, 0];\nconst translation: b3Vec3 = [0, -20, 0]; // cast 20m downward\n\nconst rayResult = b3.b3World_CastRayClosest(world, origin, translation, filter);\n\nif (rayResult.hit) {\n    const fraction = rayResult.fraction;    // [0..1] how far along translation\n    const normal = rayResult.normal;        // surface normal at hit point (b3Vec3)\n    const hitX = origin[0] + translation[0] * fraction;\n    const hitY = origin[1] + translation[1] * fraction;\n    const hitZ = origin[2] + translation[2] * fraction;\n    console.log(`hit at (${hitX.toFixed(2)}, ${hitY.toFixed(2)}, ${hitZ.toFixed(2)})`);\n    console.log('normal:', normal);\n}\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-cast-ray\">\n      <img src=\"./examples/public/screenshots/example-cast-ray.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Cast Ray</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Cast Ray (All Hits)\n\n```ts\n// Collect every hit along a ray via callback (called once per shape, unordered).\n// Return false from the callback to stop traversal early.\nconst hits: b3ShapeId[] = [];\nb3.b3World_CastRay(world, origin, translation, filter, (shapeId: b3ShapeId, _fraction: number, _normal: unknown) => {\n    hits.push(shapeId);\n    return true;\n});\nconsole.log(`ray hit ${hits.length} shapes`);\n```\n\n### Cast Shape (Shapecast)\n\nSweep a sphere proxy through the world and find the closest hit.\n\n```ts\n// Sweep a convex proxy through the world and find the closest hit (shapecast).\n// The proxy is a flat [x,y,z, ...] array of points; convexRadius rounds the edges.\n// The callback returns the current best fraction -- return a smaller value to narrow\n// the search, or Infinity to collect all hits.\nconst halfE = 0.3;\nconst boxProxy = [\n    -halfE, -halfE, -halfE,  halfE, -halfE, -halfE,\n     halfE, -halfE,  halfE, -halfE, -halfE,  halfE,\n    -halfE,  halfE, -halfE,  halfE,  halfE, -halfE,\n     halfE,  halfE,  halfE, -halfE,  halfE,  halfE,\n];\n\nconst castOrigin: b3Vec3 = [0, 5, 0];\nconst castDispacement: b3Vec3 = [0, -10, 0];\nlet bestFraction = Infinity;\n\nb3.b3World_CastShape(\n    world,\n    castOrigin,\n    boxProxy, 0,          // points array + convex radius\n    castDispacement,\n    filter,\n    (_shapeId: b3ShapeId, _point: unknown, _normal: unknown, fraction: number) => {\n        if (fraction < bestFraction) bestFraction = fraction;\n        return fraction;  // return current best to narrow search\n    },\n);\n\nif (bestFraction < Infinity) {\n    console.log(`shape hit at fraction ${bestFraction.toFixed(3)}`);\n}\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-cast-shape\">\n      <img src=\"./examples/public/screenshots/example-cast-shape.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Cast Shape</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Overlap AABB\n\n```ts\n// Find all shapes whose AABBs overlap a given axis-aligned box.\n// b3AABB is a flat array: [minX, minY, minZ, maxX, maxY, maxZ].\nconst aabb: b3AABB = [-2, -2, -2, 2, 2, 2];\n\nconst overlapping: b3ShapeId[] = [];\nb3.b3World_OverlapAABB(world, aabb, filter, (shapeId: b3ShapeId) => {\n    overlapping.push(shapeId);\n    return true; // return false to stop early\n});\nconsole.log(`${overlapping.length} shapes in AABB`);\n```\n\n### Overlap Shape\n\n```ts\n// Test which shapes overlap a convex proxy (exact narrowphase, not just AABB).\n// Same proxy format as b3World_CastShape: flat points array + convex radius.\nconst overlapOrigin: b3Vec3 = [0, 0, 0];\nconst overlapHits: b3ShapeId[] = [];\n\nb3.b3World_OverlapShape(\n    world,\n    overlapOrigin,\n    boxProxy, 0,  // reuse proxy from above\n    filter,\n    (shapeId: b3ShapeId) => {\n        overlapHits.push(shapeId);\n        return true;\n    },\n);\nconsole.log(`${overlapHits.length} shapes overlapping proxy`);\n```\n\n### Query Filter\n\n```ts\n// b3QueryFilter controls what a query can hit using category/mask bits (bigint).\n// A shape is tested when (filterMaskBits & shapeCategoryBits) != 0n.\nconst customFilter = b3.b3DefaultQueryFilter();\ncustomFilter.maskBits = 1n; // only hit shapes in category 0x0001\n```\n\n## Events\n\nbox3d surfaces physics events (contacts, sensors, body moves, joints) each `b3World_Step`. Events are opt-in per shape. Rather than allocating a JS object per event every step — which does not scale — box3d.js reads them through a **reusable, wasm-backed events buffer**: allocate the buffer (and small reader scratch objects) once, refill it each step with `getEvents`, and read it back with zero allocation. Free it with `destroyEventsBuffer` when done.\n\n### Contact Events\n\n```ts\n// Contact events are opt-in per shape -- set enableContactEvents on the shape def.\nconst shapeDef = b3.b3DefaultShapeDef();\nshapeDef.enableContactEvents = true;\nconst shape = b3.b3CreateBoxShape(body, shapeDef, 0.5, 0.5, 0.5);\n\n// Or enable on an existing shape at runtime\nb3.b3Shape_EnableContactEvents(shape, true);\n\n// Read events through a reusable, wasm-backed buffer. Allocate the buffer and any\n// event scratch objects ONCE, then refill each step with getEvents -- reading is\n// a zero-allocation loop over the wasm heap, so it scales to thousands of events.\nconst eventsBuffer = b3.createEventsBuffer();\nconst touch = b3.createContactTouchEvent();\nconst hit = b3.createContactHitEvent();\n\nb3.b3World_Step(world, 1 / 60, 4);\nb3.getEvents(eventsBuffer, world);\n\nfor (let i = 0, n = b3.getNumContactBeginEvents(eventsBuffer); i < n; i++) {\n    b3.getContactBeginEventAt(touch, eventsBuffer, i); // fills `touch` in place\n    console.log('contact begin:', touch.shapeIdA, touch.shapeIdB);\n}\nfor (let i = 0, n = b3.getNumContactEndEvents(eventsBuffer); i < n; i++) {\n    b3.getContactEndEventAt(touch, eventsBuffer, i);\n    console.log('contact end:', touch.shapeIdA, touch.shapeIdB);\n}\nfor (let i = 0, n = b3.getNumContactHitEvents(eventsBuffer); i < n; i++) {\n    // Impact event -- shapes struck each other above the hit speed threshold\n    b3.getContactHitEventAt(hit, eventsBuffer, i);\n    console.log('hit:', hit.shapeIdA, hit.shapeIdB, 'speed:', hit.approachSpeed);\n}\n\n// The buffer owns wasm memory -- free it when you're done with it.\n// b3.destroyEventsBuffer(eventsBuffer);\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-events\">\n      <img src=\"./examples/public/screenshots/example-events.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Events</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Reading Contacts Every Frame\n\nEvents fire when contacts begin, end, or hit. To instead inspect **every current contact manifold** each frame — for debug drawing, gameplay logic, or custom response — use a reusable, wasm-backed contacts buffer. This is the recommended fast path: its storage lives in the wasm heap and grows on its own, so refilling it each frame copies nothing across the wasm/JS boundary and allocates no typed arrays. You allocate the buffer (and small reader scratch objects) once, fill it in place each frame, and free it when done.\n\n```ts\n// Events tell you when contacts begin/end/hit. To instead inspect *every current\n// contact manifold* each frame, use a reusable, wasm-backed contacts buffer. Its\n// storage lives in the wasm heap and grows on its own, so refilling it copies\n// nothing to JS and allocates no typed arrays -- the fast path for per-frame use.\n\n// Allocate the buffer and the reader scratch objects ONCE (never in the loop).\nconst contactsBuffer = b3.createContactsBuffer();\nconst contact = b3.createContact();\nconst manifold = b3.createManifold();\n\n// Each frame, after stepping, refill the buffer for a body (or getShapeContactData\n// for a single shape), then read it back with zero allocation:\nb3.b3World_Step(world, 1 / 60, 4);\nb3.getBodyContactData(contactsBuffer, body);\n\nfor (let i = 0, n = b3.getNumContacts(contactsBuffer); i < n; i++) {\n    b3.getContactAt(contact, contactsBuffer, i); // fills `contact` in place (out-arg first)\n    for (let m = 0; m < contact.manifoldCount; m++) {\n        b3.getManifoldAt(manifold, contact, m); // fills `manifold` and its points in place\n        for (let p = 0; p < manifold.pointCount; p++) {\n            const point = manifold.points[p];\n            // point.anchorA / anchorB: world-space offsets from each body's centre of mass\n            // point.separation: negative when penetrating   point.normalImpulse: contact force\n            console.log(point.separation, point.normalImpulse);\n        }\n    }\n}\n\n// The buffer owns wasm memory -- free it when you're done with it.\nb3.destroyContactsBuffer(contactsBuffer);\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-contacts\">\n      <img src=\"./examples/public/screenshots/example-contacts.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Contacts</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Sensor Events\n\n```ts\n// Sensor shapes generate begin/end overlap events instead of contact forces.\n// Both the sensor shape and each visitor shape must opt in to sensor events.\nconst sensorBodyDef = b3.b3DefaultBodyDef();\nsensorBodyDef.type = b3.b3BodyType.b3_kinematicBody;\nconst sensorBody = b3.b3CreateBody(world, sensorBodyDef);\n\nconst sensorShapeDef = b3.b3DefaultShapeDef();\nsensorShapeDef.isSensor = true;\nsensorShapeDef.enableSensorEvents = true;\nb3.b3CreateBoxShape(sensorBody, sensorShapeDef, 2, 2, 2);\n\nconst visitorBodyDef = b3.b3DefaultBodyDef();\nvisitorBodyDef.type = b3.b3BodyType.b3_dynamicBody;\nconst visitorBody = b3.b3CreateBody(world, visitorBodyDef);\nconst visitorShapeDef = b3.b3DefaultShapeDef();\nvisitorShapeDef.enableSensorEvents = true;\nb3.b3CreateBoxShape(visitorBody, visitorShapeDef, 0.5, 0.5, 0.5);\n\n// Read sensor events from the same reusable events buffer (created above).\nconst sensorTouch = b3.createSensorTouchEvent();\nb3.b3World_Step(world, 1 / 60, 4);\nb3.getEvents(eventsBuffer, world);\n\nfor (let i = 0, n = b3.getNumSensorBeginEvents(eventsBuffer); i < n; i++) {\n    b3.getSensorBeginEventAt(sensorTouch, eventsBuffer, i);\n    console.log('entered sensor:', sensorTouch.sensorShapeId, 'visitor:', sensorTouch.visitorShapeId);\n}\nfor (let i = 0, n = b3.getNumSensorEndEvents(eventsBuffer); i < n; i++) {\n    b3.getSensorEndEventAt(sensorTouch, eventsBuffer, i);\n    console.log('left sensor:', sensorTouch.sensorShapeId, 'visitor:', sensorTouch.visitorShapeId);\n}\n```\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-sensor\">\n      <img src=\"./examples/public/screenshots/example-sensor.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Sensor</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n### Pre-Solve Callback\n\nThe pre-solve callback fires for each contact before the constraint solver runs. Return `false` to suppress the contact response entirely (useful for one-way platforms).\n\n```ts\n// Pre-solve callback: fires for each contact before the constraint solver runs.\n// Return false to suppress the contact response (useful for one-way platforms).\n// Set once -- the callback is retained for the world's lifetime.\nb3.b3World_SetPreSolveCallback(world, (shapeIdA: b3ShapeId, _shapeIdB: b3ShapeId, _manifold: unknown) => {\n    // Example: one-way platform -- let bodies pass through from below\n    const bodyA = b3.b3Shape_GetBody(shapeIdA);\n    const velA: b3Vec3 = [0, 0, 0];\n    b3.b3Body_GetLinearVelocity(velA, bodyA); // out-param fills velA; velA[1] is y\n    if (velA[1] > 0) return false; // moving upward: skip contact\n    return true;\n});\n```\n\n## Multithreading\n\nbox3d's internal solver can spread work across OS threads via Emscripten pthreads. This requires `SharedArrayBuffer`, which in turn requires [cross-origin isolation](https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated).\n\n**Required HTTP headers:**\n```\nCross-Origin-Opener-Policy: same-origin\nCross-Origin-Embedder-Policy: require-corp\n```\n\n### Initialization\n\n```ts\n// The multithreaded build uses SharedArrayBuffer under the hood.\n// The page must be served with cross-origin isolation headers:\n//   Cross-Origin-Opener-Policy: same-origin\n//   Cross-Origin-Embedder-Policy: require-corp\nconst isolated = self.crossOriginIsolated === true;\n\n// Pick the right build at runtime; fall back gracefully when isolation is absent\nconst factory = isolated\n    ? (await import('box3d.js/mt-inline')).default\n    : (await import('box3d.js/inline')).default;\n\nconst b3: Box3DModule = await factory();\n```\n\n### World Setup\n\n```ts\nconst worldDef = b3.b3DefaultWorldDef();\nworldDef.gravity = [0, -10, 0];\n\n// Set workerCount to enable box3d's internal multi-threaded solver.\n// box3d clamps this to [1, 32] (B3_MAX_WORKERS). Leave at 0 for single-threaded.\nconst hwThreads = navigator.hardwareConcurrency ?? 4;\nworldDef.workerCount = isolated ? Math.max(2, hwThreads - 1) : 0;\n\nconst world = b3.b3CreateWorld(worldDef);\n```\n\n### Stepping\n\n```ts\n// The simulation API is identical to the single-threaded build;\n// only the import and workerCount differ.\nb3.b3World_Step(world, 1 / 60, 4);\n```\n\nThe simulation API is identical to the single-threaded build - only the import path and `workerCount` differ.\n\n<table>\n  <tr>\n  <td align=\"center\">\n    <a href=\"https://isaac-mason.github.io/box3d.js/#example-multithreading\">\n      <img src=\"./examples/public/screenshots/example-multithreading.png\" width=\"200\" height=\"133\" style=\"object-fit:cover;\"/><br/>\n      <strong>Multithreading</strong>\n    </a>\n  </td>\n  </tr>\n</table>\n\n## Building from Source\n\n### Prerequisites\n\n- [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html) - `emcmake` and `em++` must be on `PATH`. The published builds are produced with **emsdk 6.0.2** (emcc/clang 6.0.2); other recent versions should work.\n- [CMake](https://cmake.org/) ≥ 3.22\n- Node.js ≥ 18\n- pnpm\n\n```bash\n# Install the pinned Emscripten version and activate the environment\ncd /path/to/emsdk\n./emsdk install 6.0.2\n./emsdk activate 6.0.2\nsource ./emsdk_env.sh\n```\n\n### Build\n\n```bash\ngit clone --recurse-submodules <repo-url>\npnpm install\npnpm build\n```\n\nOutputs to `dist/`:\n\n| File | Description |\n|------|-------------|\n| `box3d.mjs` + `box3d.wasm` | Single-threaded, separate WASM |\n| `box3d.inline.mjs` | Single-threaded, inlined WASM |\n| `box3d.mt.mjs` + `box3d.mt.wasm` | Multithreaded, separate WASM |\n| `box3d.mt.inline.mjs` | Multithreaded, inlined WASM |\n| `box3d.d.ts` | TypeScript definitions (shared by all builds) |\n\n```bash\n# Debug build\npnpm build:debug\n\n# Smoke test (falling-box simulation on both ST and MT builds)\npnpm test\n```\n\n### Docs\n\n```bash\n# Regenerate README.md from docs/README.template.md\npnpm docs:build\n\n# Typecheck all code snippets in docs/\npnpm docs:check\n```\n","readmeFilename":"README.md"}