{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/body-3d",
  "slug": "packages/body-3d",
  "kind": "capabilities",
  "bucket": "package",
  "title": "@leumas/body-3d",
  "name": "@leumas/body-3d",
  "eyebrow": null,
  "chip": null,
  "summary": "Procedurally composed 3D bodies: the entity-kit silhouette vocabulary, blueprint part lists, and the resolver that lets a game draw either without owning a single GLB.",
  "keywords": [
    "body-3d",
    "procedurally",
    "entity-kit",
    "blueprint",
    "silhouette",
    "leumas body 3d",
    "composed",
    "bodies"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# @leumas/body-3d\n\nProcedurally composed 3D bodies and props. `three` and data — no React, no DOM, no router.\n\n```js\nimport { bodyParts, partsToObject3D, blueprintParts } from '@leumas/body-3d';\nimport { createProceduralResolver } from '@leumas/body-3d/resolver';\n\nconst { root, limbs } = partsToObject3D({ body: 'bot', color: '#7fd1c0' });\nscene.add(root);            // limbs: [{role: 'leg-l', hinge}, …] — what an animator drives\n```\n\n## Why it exists\n\nThe silhouette vocabulary lived in `@leumas/features/nav-views/world`, which is a React package:\nimporting it drags React, the router and a stylesheet. `@leumas/game-3d` needs the same bodies and\nhas exactly one runtime dependency. So the geometry moved down to a package both can import;\n`features/nav-views/world/bodies3d.js` is now a one-line re-export shim, and every existing call site\nkept working.\n\n## What it holds\n\n| Export | What it is |\n|---|---|\n| `bodyParts(id)` · `BODY3D_IDS` | the entity-kit silhouette vocabulary, as shared cached geometry |\n| `bodyMaterials(color, emissive)` | the four material roles — `body`, `light`, `dark`, `glow` |\n| `partsToObject3D({body \\| parts, color, scale})` | parts list → `Object3D`, **with limb hinges** |\n| `blueprintParts(blueprint, seed)` | a forge catalogue record → a stand-in prop of the right size |\n| `createProceduralResolver({manifest, lookup})` | the `resolveUrl` / `loadModel` pair for the asset registry |\n\n## The two things to know\n\n**Geometry is shared and you must not dispose it.** Every camera in a room points at one tripod. A\nteardown that walks the tree calling `geometry.dispose()` blanks every other instance of that\nspecies, with no error, and the next mount renders nothing. Assembled meshes are tagged\n`userData.sharedGeometry` so a teardown can tell what it owns; `disposeBodyCache()` and\n`disposeBlueprintCache()` are the only things allowed to free them, and both are process-wide.\n\n**The resolver is a fallback, and the order matters.** A source listed in the manifest resolves to\nits real URL and the registry's own GLTF loader fetches it, DRACO and KTX2 included. Only a source\nwith no file becomes `proc:<source>` and is composed here. That is what makes a generated-asset pass\nan upgrade — same source strings, same call, better mesh when a file lands — rather than a migration\nout of something that replaced it.\n\n## Limbs\n\nA part may name a role (`leg-l`, `leg-r`, `arm-l`, `arm-r`, `head`) and a `pivot`, the height its\njoint sits at. `partsToObject3D` hangs those parts off a hinge group at the pivot and offsets the\nmesh back to where it rests, so rotating the hinge swings the limb about the joint. Rotating the mesh\ninstead swings it about its own centre, which reads as a limb being stirred rather than a body\nwalking. Several parts may share a role and travel as one limb.\n\nBodies with no roles return `limbs: []`, and an animation driver over them is a no-op rather than an\nerror — most silhouettes (a crate, a rack, a camera) have no joints.\n\n`@leumas/game-3d/anim` drives these hinges from replicated velocity and grounded state.\n",
  "source": {
    "path": "shared/packages/body-3d/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 3253,
    "hash": "28b71ff20f40c13687c835edf21c8a119becc566"
  },
  "urls": {
    "html": "/p/packages/body-3d",
    "json": "/docs/packages/body-3d.json",
    "md": "/docs/packages/body-3d.md"
  },
  "links": {
    "composes": [],
    "usedBy": [
      "pkg:@leumas/features",
      "pkg:@leumas/game-3d",
      "pkg:@leumas/studio",
      "pkg:@leumas/web"
    ],
    "product": [
      "pkg:@leumas/admin",
      "pkg:@leumas/studio",
      "pkg:@leumas/web"
    ],
    "howTo": [],
    "skills": []
  },
  "exports": {
    "total": 12,
    "component": 4,
    "hook": 0,
    "helper": 8,
    "names": [
      {
        "n": "BLUEPRINT_SHAPES",
        "k": "component"
      },
      {
        "n": "BODY_REST",
        "k": "component"
      },
      {
        "n": "BODY3D_IDS",
        "k": "component"
      },
      {
        "n": "GLOW_REST",
        "k": "component"
      },
      {
        "n": "blueprintParts",
        "k": "helper"
      },
      {
        "n": "bodyMaterials",
        "k": "helper"
      },
      {
        "n": "bodyParts",
        "k": "helper"
      },
      {
        "n": "createProceduralResolver",
        "k": "helper"
      },
      {
        "n": "disposeBlueprintCache",
        "k": "helper"
      },
      {
        "n": "disposeBodyCache",
        "k": "helper"
      },
      {
        "n": "manifestFromPack",
        "k": "helper"
      },
      {
        "n": "partsToObject3D",
        "k": "helper"
      }
    ]
  }
}
