{"_id":"@antpb/xr-publisher","_rev":"2-8111e0022295be081c5f58449b8b98c5","name":"@antpb/xr-publisher","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@antpb/xr-publisher","version":"0.1.0","_id":"@antpb/xr-publisher@0.1.0","maintainers":[{"name":"antpb","email":"anthony@broken.place"}],"dist":{"shasum":"f7a081281113d4a310ada19f312da1b19768b53f","tarball":"https://registry.npmjs.org/@antpb/xr-publisher/-/xr-publisher-0.1.0.tgz","fileCount":41,"integrity":"sha512-XXQh443AnTMLaRlQxGMWRaSRBa5utsZQWKIpGpM2ztQzlM3R9XWOdM0fuU3FT945fjWdARRclVx2MhHEC4NMPw==","signatures":[{"sig":"MEUCIQD0iVPgjegWb71QaGhzzWUOYplE0VntOg1bumSYWvDv4wIgVwzTv3fQaX7qIvEmKm6w8BQ2eAwnszrHNLxaxjd1hIk=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":66564958},"main":"dist/xr-publisher.cjs.js","type":"module","types":"dist/index.d.ts","module":"dist/xr-publisher.esm.js","gitHead":"3697ba7d4e70279caf80f177faa7fb5104de5565","scripts":{"dev":"vite","lint":"eslint src --ext .ts,.tsx","build":"vite build && cp -f dist/xr-publisher.umd.js examples/index_files/","format":"prettier --write \"src/**/*.{ts,tsx}\"","preview":"vite preview","typecheck":"tsc --noEmit"},"_npmUser":{"name":"antpb","email":"anthony@broken.place"},"_npmVersion":"10.9.0","description":"XR Publisher is a powerful JavaScript library for creating immersive 3D virtual worlds with support for VR, networking, and AI-powered NPCs. Built on top of React and Three.js, it provides a declarative way to build interactive 3D environments.","directories":{},"_nodeVersion":"22.12.0","dependencies":{"ossos":"^0.0.3","ecctrl":"^1.0.92","lodash":"^4.17.21","three-omi":"^0.1.5","easystarjs":"^0.4.4","convert-hex":"^0.1.0","three-icosa":"^0.4.0","lucide-react":"^0.460.0","@react-three/xr":"5.7.1","camera-controls":"^2.7.0","@pixiv/three-vrm":"3.1.4","tiny-simple-peer":"^10.1.2","@react-three/drei":"^9.118.0","three-pathfinding":"^1.3.0","troika-three-text":"^0.49.0","@react-three/fiber":"^8.17.0","base64-arraybuffer":"^1.0.2","@react-three/rapier":"^1.5.0","array-buffer-to-hex":"^1.0.0","react-scrollable-feed":"^1.3.1","@pixiv/types-vrmc-vrm-1.0":"^3.1.4"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.4.11","eslint":"^8.57.0","terser":"^5.36.0","prettier":"^3.2.0","typescript":"^5.3.0","@types/react":"^18.2.0","@types/three":"0.159.0","@types/lodash":"^4.17.13","vite-plugin-dts":"^3.7.0","@types/react-dom":"^18.2.0","vite-plugin-glsl":"^1.2.1","@vitejs/plugin-react":"^4.2.0","@rollup/plugin-replace":"^6.0.1","@typescript-eslint/parser":"^7.1.0","rollup-plugin-polyfill-node":"^0.13.0","@typescript-eslint/eslint-plugin":"^7.1.0"},"peerDependencies":{"react":"^18.2.0","three":"^0.170.0","react-dom":"^18.2.0"},"_npmOperationalInternal":{"tmp":"tmp/xr-publisher_0.1.0_1736236935185_0.22011267328789597","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@antpb/xr-publisher","version":"0.2.0","type":"module","main":"dist/xr-publisher.cjs.js","module":"dist/xr-publisher.es.js","types":"./dist/types/src/index.d.ts","scripts":{"dev":"vite","dev:watch":"vite build --watch","dev:serve":"vite --open","build:wasm":"cd crates/xr-publisher-world && PATH=\"$HOME/.cargo/bin:$PATH\" wasm-pack build --target web --release && cd ../.. && node scripts/inline-wasm.mjs","build":"vite build && vite build --mode standalone && cp -f dist/xr-publisher.umd.js examples/index_files/ && rm -rf dist/assets/defaults && mkdir -p dist/assets/defaults && cp -R src/defaults/avatars src/defaults/assets src/defaults/fonts dist/assets/defaults/ && find dist/assets/defaults -name .DS_Store -delete && node scripts/generate-third-party-notices.mjs","preview":"vite preview","typecheck":"tsc --noEmit","lint":"eslint src --ext .ts,.tsx","format":"prettier --write \"src/**/*.{ts,tsx}\"","prepublishOnly":"npm run build"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0","three":">=0.170.0 <0.181.0"},"devDependencies":{"@happy-dom/global-registrator":"^20.10.6","@pixiv/three-vrm":"^3.4.5","@react-three/drei":"^9.122.0","@react-three/fiber":"^9.4.2","@react-three/rapier":"^2.0.0","@react-three/uikit":"^0.8.21","@react-three/uikit-default":"^1.0.60","@react-three/uikit-lucide":"^0.8.21","@react-three/xr":"^6.6.16","@rollup/plugin-replace":"^6.0.1","@types/lodash":"^4.17.13","@types/node":"^22.15.3","@types/rbush":"^4.0.0","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","@types/seedrandom":"^3.0.8","@types/three":"^0.180.0","@typescript-eslint/eslint-plugin":"^7.1.0","@typescript-eslint/parser":"^7.1.0","@vitejs/plugin-react":"^4.2.0","array-buffer-to-hex":"^1.0.0","base64-arraybuffer":"^1.0.2","convert-hex":"^0.1.0","ecctrl":"1.0.92","eslint":"^8.57.0","eslint-plugin-react-hooks":"^4.6.2","lodash":"^4.17.21","lucide-react":"^0.460.0","maath":"^0.10.8","mp4box":"^2.3.0","navcat":"^0.0.5","playwright-core":"^1.61.1","prettier":"^3.2.0","rbush":"^4.0.1","react":"^19.1.1","react-dom":"^19.1.1","react-scrollable-feed":"^1.3.1","rollup-plugin-polyfill-node":"^0.13.0","seedrandom":"^3.0.5","simplex-noise":"^4.0.3","terser":"^5.36.0","three":"0.180.0","three-icosa":"^0.4.0","three-omi":"^0.1.5","tiny-simple-peer":"^10.1.2","typescript":"^5.3.0","vite":"^5.4.11","vite-plugin-dts":"^3.7.0","vite-plugin-glsl":"^1.2.1","zustand":"^5.0.8"},"license":"GPL-3.0-only","description":"React Three Fiber runtime for publishing 3D/XR worlds from custom HTML elements — WebGPU rendering with WebGL fallback, WASM procedural terrain, P2P multiplayer, VRM avatars, AI NPCs, and a plugin system for custom blocks, decorations, and post-processing","author":{"name":"antpb"},"homepage":"https://github.com/antpb/XR-Publisher-pkg#readme","repository":{"type":"git","url":"git+https://github.com/antpb/XR-Publisher-pkg.git"},"bugs":{"url":"https://github.com/antpb/XR-Publisher-pkg/issues"},"keywords":["webxr","three","threejs","react-three-fiber","webgpu","vr","metaverse","3d","virtual-worlds","multiplayer","vrm","procedural-generation"],"exports":{".":{"types":"./dist/types/src/index.d.ts","import":"./dist/xr-publisher.es.js","require":"./dist/xr-publisher.cjs.js"},"./standalone":{"types":"./dist/types/src/index.d.ts","import":"./dist/xr-publisher.standalone.es.js"},"./package.json":"./package.json"},"unpkg":"dist/xr-publisher.umd.js","_id":"@antpb/xr-publisher@0.2.0","gitHead":"402f9b2e5652a4dfd16525cf1221bc82ae6cf79a","_nodeVersion":"22.12.0","_npmVersion":"10.9.0","dist":{"integrity":"sha512-f75aWQjw5Bs4VpFiV4DAUNwmjERHEn2aVI7sfCE7PDkbLquR8C4n1cFzbpvG/+xVV70kmBAqQVbUUuxh4YcPow==","shasum":"0b37c92809ea48f8e2ffeb1272c4ae469acc6962","tarball":"https://registry.npmjs.org/@antpb/xr-publisher/-/xr-publisher-0.2.0.tgz","fileCount":204,"unpackedSize":44457692,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCFN7vROMJwKBjI6DPzSq86aU6Y78Y1Hhe2G34PIAJb4wIhAO+oucl67pGT2wZTUROoXoQ62hqLwojJdKIoOCmzySxv"}]},"_npmUser":{"name":"antpb","email":"anthony@broken.place"},"directories":{},"maintainers":[{"name":"antpb","email":"anthony@broken.place"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/xr-publisher_0.2.0_1789106933114_0.19988649708601613"},"_hasShrinkwrap":false}},"time":{"created":"2025-01-07T08:02:15.047Z","modified":"2026-09-11T06:08:53.637Z","0.1.0":"2025-01-07T08:02:15.647Z","0.2.0":"2026-09-11T06:08:53.474Z"},"description":"React Three Fiber runtime for publishing 3D/XR worlds from custom HTML elements — WebGPU rendering with WebGL fallback, WASM procedural terrain, P2P multiplayer, VRM avatars, AI NPCs, and a plugin system for custom blocks, decorations, and post-processing","maintainers":[{"name":"antpb","email":"anthony@broken.place"}],"readme":"# XR Publisher\n\nXR Publisher is a powerful JavaScript library for creating immersive 3D virtual worlds with support for VR, networking, and AI-powered NPCs. Built on top of React and Three.js, it provides a declarative way to build interactive 3D environments.\n\n> Development happens on the `develop` branch; releases are tagged from `main`.\n\n## Features\n\n- **Virtual Reality Support**: Built-in VR/AR compatibility with WebXR\n- **Multiplayer Networking**: P2P multi-user environments with voice chat\n- **AI-Powered NPCs**: Interactive characters with conversation capabilities\n- **Physics Engine**: Rapier physics for realistic interactions\n- **Procedural Terrain**: Infinite terrain generation with customizable noise\n- **Component-Based**: Modular design using custom HTML elements\n- **Asset Support**: GLB/GLTF models, VRM avatars, spatial audio, video\n- **WebGPU Rendering**: Modern rendering with WebGPU (WebGL fallback for XR)\n- **Plugin System**: register custom editor-placeable block types, deterministic scatter decorations, in-world dialogs/HUD, and post-processing from a single script — see [Plugin & Extension APIs](#plugin--extension-apis)\n\n## Installation\n\n```bash\nnpm install @antpb/xr-publisher\n# or\npnpm add @antpb/xr-publisher\n```\n\n`react`, `react-dom` (>= 18), and `three` (>= 0.170) are peer dependencies.\n\n## Quick Start\n\nThe library auto-initializes on `window.load`: it scans the DOM for `<three-environment-block>` elements and renders each one. For most published worlds you only need the script on the page and the block markup in the body — no JavaScript of your own.\n\nTo construct it manually (custom hosting, deferred init):\n\n```javascript\nimport { XRPublisher } from '@antpb/xr-publisher';\n\nconst publisher = new XRPublisher({\n    threeObjectPlugin: '/assets/',            // base URL for runtime assets\n    defaultAvatarAnimation: '',\n    defaultAvatar: '/assets/default-avatar.vrm',\n    multiplayerWorker: '/assets/multiplayer-worker.js',\n    userData: {},                             // per-visitor data passed through to components\n    // optional:\n    // apiBase: 'https://your-worker.example.com', // publishing-API origin (see below)\n    // postSlug: 'my-world',                  // world identifier for networking rooms\n    // hmdIcon: '/assets/vr-icon.png',        // custom Enter-VR icon\n    // containedMode: false,                  // render inside a container element instead of fullscreen\n});\n\npublisher.init();\n```\n\nThe UMD build (`dist/xr-publisher.umd.js`, also served by unpkg) exposes the same API as `window.XRPublisher` for script-tag use.\n\n### Configuring the API endpoint\n\nServer-backed features — NPC/character chat, the in-world editor and its tokens, the asset library, persisted world state — talk to a publishing-API worker (see the companion World and Character Management System project). The package ships with **no baked-in host**; the origin resolves in this order:\n\n1. `apiBase` constructor option, or `XRPublisher.setApiBase(url)` at any time\n2. `window.XRP_API_BASE` — set it in a `<script>` before the bundle loads (the natural spot for serve templates and script-tag embeds)\n3. localStorage `xr_publisher_edge_api_url` (the desktop editor's stored-settings convention)\n4. **Same origin** — a published world is served *by* the API worker, so this is the zero-config case that covers normal deployments\n\nPurely client-side features (rendering, terrain, physics, portals, media blocks) never touch the network API. P2P multiplayer signaling is configured separately via the `signalingUrl` attribute on `<three-networking-block>`.\n\n### Default assets\n\nThe package is self-contained: the default avatar (VRM), player/NPC animation clips (FBX), default grid environment (GLB), UI font, and textures ship in `dist/assets/defaults/`. At runtime the engine references them by the fixed path **`/assets/defaults/…`**, so serve that folder at your web root:\n\n```\ncp -R node_modules/@antpb/xr-publisher/dist/assets/defaults public/assets/defaults\n```\n\nTo host runtime assets elsewhere, set the `threeObjectPlugin` option to your asset root (it must end with `/`; the engine appends e.g. `avatars/walking.fbx`). Individual defaults are also directly overridable (`defaultAvatar`, `userData.playerVRM`, `hmdIcon`). No external CDN is referenced by default.\n\n## Block Reference\n\nWorlds are constructed using custom HTML web components. Each block type has specific attributes that control its behavior. See the `schemas/` folder for complete JSON schema definitions.\n\n### Block Categories\n\n| Category | Blocks | Description |\n|----------|--------|-------------|\n| **Core** | `environment-block` | Main world container |\n| **Objects** | `model-block`, `text-block` | 3D content |\n| **Media** | `audio-block`, `video-block`, `image-block` | Multimedia |\n| **Interactive** | `npc-block`, `portal-block`, `rideable-block` | Player interaction |\n| **Environment** | `light-block`, `sky-block`, `terrain-block` | World atmosphere |\n| **System** | `networking-block`, `spawn-point-block` | Configuration |\n\n---\n\n## Core Blocks\n\n### `<three-environment-block>`\n\nThe main container for your 3D world. All other blocks must be nested within this element.\n\n```html\n<three-environment-block \n    devicetarget=\"vr\" \n    threeobjecturl=\"path/to/world.glb\"\n    scale=\"1\" \n    positiony=\"-1\" \n    rotationy=\"0\" \n    camcollisions=\"1\"\n    backgroundcolor=\"#1a1a2e\"\n    hdr=\"path/to/environment.hdr\">\n    <!-- Child blocks go here -->\n</three-environment-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `threeobjecturl` | string | - | URL to main world model (GLB/GLTF) |\n| `devicetarget` | `\"vr\"` \\| `\"ar\"` \\| `\"2d\"` | `\"vr\"` | Target platform |\n| `scale` | number | `1` | Uniform scale factor |\n| `positionx/y/z` | number | `0` | World position offset |\n| `rotationy` | number | `0` | Y-axis rotation (radians) |\n| `camcollisions` | `\"0\"` \\| `\"1\"` | `\"1\"` | Camera collision detection |\n| `backgroundcolor` | string | - | Background color (hex) |\n| `previewimage` | string | - | Loading screen image URL |\n| `hdr` | string | - | HDR environment map URL |\n| `animations` | string | - | Comma-separated animation names |\n\n---\n\n## Object Blocks\n\n### `<three-model-block>`\n\nAdd 3D models with full transform controls, physics, and instancing support.\n\n```html\n<three-model-block \n    threeobjecturl=\"path/to/model.glb\"\n    positionx=\"0\" positiony=\"0\" positionz=\"0\"\n    rotationx=\"0\" rotationy=\"0\" rotationz=\"0\"\n    scalex=\"1\" scaley=\"1\" scalez=\"1\"\n    animations=\"idle,walk\" \n    collidable=\"1\"\n    alt=\"A decorative tree\">\n</three-model-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `threeobjecturl` | string | **required** | Model URL (GLB/GLTF/VRM) |\n| `positionx/y/z` | number | `0` | World position |\n| `rotationx/y/z` | number | `0` | Rotation (radians) |\n| `scalex/y/z` | number | `1` | Scale per axis |\n| `animations` | string | - | Comma-separated animation names |\n| `alt` | string | - | Accessibility description |\n| `collidable` | `\"0\"` \\| `\"1\"` | `\"0\"` | Enable physics collisions |\n| `instanced` | `\"true\"` \\| `\"false\"` | `\"false\"` | Enable GPU instancing |\n| `instances` | string (JSON) | - | Instance transform data |\n| `proximityload` | `\"0\"` \\| `\"1\"` | `\"0\"` | Load only when player is near |\n| `triggerlocationx/y/z` | number | `0` | Proximity trigger zone size |\n| `triggerlocationposx/y/z` | number | `0` | Trigger zone offset |\n\n**Instanced Rendering Example:**\n```html\n<three-model-block \n    threeobjecturl=\"tree.glb\"\n    instanced=\"true\"\n    instances='[\n        {\"positionX\":0,\"positionY\":0,\"positionZ\":0,\"rotationX\":0,\"rotationY\":0,\"rotationZ\":0,\"scaleX\":1,\"scaleY\":1,\"scaleZ\":1},\n        {\"positionX\":5,\"positionY\":0,\"positionZ\":5,\"rotationX\":0,\"rotationY\":0.5,\"rotationZ\":0,\"scaleX\":1.2,\"scaleY\":1.2,\"scaleZ\":1.2}\n    ]'>\n</three-model-block>\n```\n\n### `<three-text-block>`\n\nAdd 3D text elements to the environment.\n\n```html\n<three-text-block\n    textcontent=\"Welcome!\"\n    textcolor=\"#ffffff\"\n    positionx=\"0\" positiony=\"2\" positionz=\"-5\"\n    scalex=\"1\" scaley=\"1\" scalez=\"1\">\n</three-text-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `textcontent` | string | **required** | Text to display |\n| `textcolor` | string | `\"#ffffff\"` | Text color (hex) |\n| `positionx/y/z` | number | `0` | World position |\n| `rotationx/y/z` | number | `0` | Rotation (radians) |\n| `scalex/y/z` | number | `1` | Scale per axis |\n\n---\n\n## Media Blocks\n\n### `<three-audio-block>`\n\nAdd spatial or ambient audio with proximity triggers.\n\n```html\n<three-audio-block\n    audiourl=\"path/to/audio.mp3\"\n    positional=\"1\"\n    loop=\"1\"\n    volume=\"0.8\"\n    autoplay=\"0\"\n    refdistance=\"1\"\n    maxdistance=\"50\"\n    rollofffactor=\"1\"\n    distancemodel=\"inverse\"\n    positionx=\"0\" positiony=\"1\" positionz=\"0\"\n    triggerlocationx=\"4\" triggerlocationy=\"4\" triggerlocationz=\"4\"\n    triggerlocationposx=\"0\" triggerlocationposy=\"1\" triggerlocationposz=\"0\">\n</three-audio-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `audiourl` | string | **required** | Audio file URL (WAV/MP3/OGG) |\n| `positional` | `\"0\"` \\| `\"1\"` | `\"1\"` | Spatial (1) or ambient (0) |\n| `loop` | `\"0\"` \\| `\"1\"` | `\"0\"` | Loop playback |\n| `volume` | number | `1` | Volume (0.0 - 1.0) |\n| `autoplay` | `\"0\"` \\| `\"1\"` | `\"0\"` | Auto-play on load |\n| `refdistance` | number | `1` | Reference distance for falloff |\n| `maxdistance` | number | `10000` | Maximum audible distance |\n| `rollofffactor` | number | `1` | Volume rolloff rate |\n| `distancemodel` | string | `\"inverse\"` | `\"linear\"`, `\"inverse\"`, `\"exponential\"` |\n| `coneinnerangle` | number | `360` | Inner cone angle (degrees) |\n| `coneouterangle` | number | `0` | Outer cone angle (degrees) |\n| `coneoutergain` | number | `0` | Volume outside cone |\n| `positionx/y/z` | number | `0` | Audio source position |\n| `rotationx/y/z` | number | `0` | Audio source rotation |\n| `triggerlocationx/y/z` | number | `0` | Trigger zone size (0 = disabled) |\n| `triggerlocationposx/y/z` | number | `0` | Trigger zone offset |\n\n**Proximity Trigger Behavior:**\n- `autoPlay=\"1\"`: Audio plays only on first trigger entry\n- `autoPlay=\"0\"`: Audio restarts on each entry\n- On exit: Loop disabled, audio plays to completion then stops\n- On re-entry: Original loop setting restored\n\n### `<three-video-block>`\n\nAdd video planes or video-textured models.\n\n```html\n<three-video-block\n    videourl=\"path/to/video.mp4\"\n    aspectwidth=\"16\" aspectheight=\"9\"\n    autoplay=\"0\"\n    videocontrolsenabled=\"1\"\n    positionx=\"0\" positiony=\"2\" positionz=\"-5\"\n    scalex=\"3\" scaley=\"3\" scalez=\"1\">\n</three-video-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `videourl` | string | **required** | Video file URL (MP4/WebM) |\n| `aspectwidth` | number | `16` | Aspect ratio width |\n| `aspectheight` | number | `9` | Aspect ratio height |\n| `autoplay` | `\"0\"` \\| `\"1\"` | `\"0\"` | Auto-play on load |\n| `custommodel` | string | - | Custom model URL for video texture |\n| `modelurl` | string | - | Alternative model URL |\n| `videocontrolsenabled` | `\"0\"` \\| `\"1\"` | `\"0\"` | Show playback controls |\n| `positionx/y/z` | number | `0` | World position |\n| `rotationx/y/z` | number | `0` | Rotation (radians) |\n| `scalex/y/z` | number | `1` | Scale per axis |\n\n### `<three-image-block>`\n\nAdd 2D image planes to the 3D environment.\n\n```html\n<three-image-block\n    imageurl=\"path/to/image.png\"\n    aspectwidth=\"4\" aspectheight=\"3\"\n    transparent=\"true\"\n    positionx=\"0\" positiony=\"2\" positionz=\"-3\"\n    scalex=\"2\" scaley=\"2\" scalez=\"1\">\n</three-image-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `imageurl` | string | **required** | Image URL (PNG/JPG/WebP) |\n| `aspectwidth` | number | `1` | Aspect ratio width |\n| `aspectheight` | number | `1` | Aspect ratio height |\n| `transparent` | `\"true\"` \\| `\"false\"` | `\"false\"` | Enable alpha transparency |\n| `positionx/y/z` | number | `0` | World position |\n| `rotationx/y/z` | number | `0` | Rotation (radians) |\n| `scalex/y/z` | number | `1` | Scale per axis |\n\n---\n\n## Interactive Blocks\n\n### `<three-npc-block>`\n\nAdd AI-powered interactive characters.\n\n```html\n<three-npc-block\n    threeobjecturl=\"path/to/avatar.vrm\"\n    name=\"Guide\"\n    defaultmessage=\"Hello, traveler! How can I help you?\"\n    personality=\"A friendly and knowledgeable guide who loves helping visitors explore the world.\"\n    objectawareness=\"1\"\n    positionx=\"0\" positiony=\"0\" positionz=\"-3\">\n</three-npc-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `threeobjecturl` | string | **required** | Avatar model URL (VRM) |\n| `name` | string | **required** | NPC display name |\n| `defaultmessage` | string | - | Initial greeting message |\n| `personality` | string | - | AI personality description |\n| `objectawareness` | `\"0\"` \\| `\"1\"` | `\"0\"` | Awareness of environment objects |\n| `positionx/y/z` | number | `0` | World position |\n| `rotationx/y/z` | number | `0` | Rotation (radians) |\n| `scalex/y/z` | number | `1` | Scale per axis |\n\n### `<three-rideable-block>`\n\nAdd a rideable vehicle. The player walks up and presses **E** to mount (the same proximity prompt NPCs use for chat), then drives it; pressing **E** again dismounts. Driving is networked — other players see the vehicle move and see you seated in it.\n\nOne block covers eight locomotion styles via `vehicletype`:\n\n| `vehicletype` | Behavior |\n|---------------|----------|\n| `car` | Four-wheel ground vehicle — steer with A/D, accelerate with W/S, follows terrain height |\n| `bike` | Two-wheel — like `car` with a tighter turn radius |\n| `animal` | Ground mount — like `car`, gentler speed |\n| `flying` | Free flight — steers along the third-person camera look direction (W throttles where you look; Space/Shift fine up/down) |\n| `hovering` | Floats at a fixed height above the terrain, planar steering |\n| `boat` | Floats on the water surface; steers like a car. Beaches onto terrain that rises above the water |\n| `train` | On-rails — follows `waypoints`; W/S advance/reverse along the track |\n| `coaster` | On-rails — auto-advances along `waypoints` at `speed` (a guided ride) |\n\n```html\n<!-- A driveable car -->\n<three-rideable-block\n    threeobjecturl=\"car.glb\"\n    vehicletype=\"car\"\n    name=\"Roadster\"\n    positionx=\"0\" positiony=\"0\" positionz=\"5\"\n    seatoffsety=\"1.1\"\n    speed=\"1\">\n</three-rideable-block>\n\n<!-- An on-rails roller coaster following an authored path -->\n<three-rideable-block\n    threeobjecturl=\"coaster-car.glb\"\n    vehicletype=\"coaster\"\n    name=\"Cyclone\"\n    waypoints='[[0,0,0],[20,4,10],[40,0,0],[20,8,-20],[0,0,0]]'\n    speed=\"1.5\">\n</three-rideable-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `threeobjecturl` | string | **required** | Vehicle model URL (GLB/GLTF) |\n| `vehicletype` | string | `\"car\"` | `car`, `bike`, `flying`, `hovering`, `animal`, `boat`, `train`, `coaster` |\n| `name` | string | - | Banner label **and** stable networking id — keep it stable per vehicle |\n| `waypoints` | string (JSON) | - | `[[x,y,z],...]` rail path for `train`/`coaster` |\n| `speed` | number | `1` | Speed multiplier on the type's base speed |\n| `seatoffsetx/y/z` | number | `0,0,0` | Rider seat offset in vehicle-local space (the engine adds a fixed 0.22 seat lift on top of Y) |\n| `positionx/y/z` | number | `0` | Spawn position |\n| `rotationy` | number | `0` | Initial heading (degrees) |\n| `scalex/y/z` | number | `1` | Scale per axis |\n\n> **Tip:** `name` doubles as the cross-client id. If you reorder rideable blocks in published HTML, set an explicit `name` on each so multiplayer keeps them in sync.\n\n**Real wheel physics (car/bike/animal):** ground vehicles drive on Rapier's raycast vehicle controller — a dynamic chassis with suspension wheels, rear engine force, and front steering. To get **physically-placed, spinning, steering wheels**, name the four wheel objects in your GLB:\n\n| Object name | Wheel |\n|-------------|-------|\n| `xrp_wheel_01` | front-left |\n| `xrp_wheel_02` | front-right |\n| `xrp_wheel_03` | rear-left |\n| `xrp_wheel_04` | rear-right |\n\n(left / right / left / right, fronts first). On export from Blender these names travel in the GLB. The engine reads each wheel object's position to place the physics wheel exactly there, sizes the wheel from the object's bounds, and then spins/steers the actual mesh with the simulation (front wheels steer, all wheels roll and ride the suspension). Wheels should be modeled with their **axle along the vehicle's left-right (X) axis**. Models without these names still drive — the engine derives wheel positions from the bounding box (5% end inset, one tire-width in from the sides), they just won't visibly rotate.\n\n### `<three-portal-block>`\n\nCreate teleportation points for navigation.\n\n```html\n<three-portal-block\n    threeobjecturl=\"path/to/portal.glb\"\n    destinationurl=\"https://example.com/world\"\n    label=\"Enter Gallery\"\n    labeltextcolor=\"#00ff88\"\n    positionx=\"5\" positiony=\"0\" positionz=\"0\"\n    scalex=\"1\" scaley=\"2\" scalez=\"1\">\n</three-portal-block>\n```\n\n**For in-world teleportation:**\n```html\n<three-portal-block\n    label=\"Go to Rooftop\"\n    useteleportdestination=\"true\"\n    teleportdestinationx=\"0\"\n    teleportdestinationy=\"20\"\n    teleportdestinationz=\"0\"\n    positionx=\"5\" positiony=\"0\" positionz=\"0\">\n</three-portal-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `threeobjecturl` | string | - | Custom portal model URL |\n| `destinationurl` | string | **required*** | URL to navigate to |\n| `label` | string | - | Label text above portal |\n| `labeloffsetx/y/z` | number | `0` | Label position offset |\n| `labeltextcolor` | string | `\"#ffffff\"` | Label color (hex) |\n| `useteleportdestination` | `\"true\"` \\| `\"false\"` | `\"false\"` | Use in-world teleport |\n| `teleportdestinationx/y/z` | number | - | Teleport coordinates |\n| `animations` | string | - | Animation names |\n| `positionx/y/z` | number | `0` | World position |\n| `rotationx/y/z` | number | `0` | Rotation (radians) |\n| `scalex/y/z` | number | `1` | Scale per axis |\n\n*Required unless `useteleportdestination=\"true\"`\n\n---\n\n## Environment Blocks\n\n### `<three-light-block>`\n\nAdd light sources to illuminate the environment.\n\n```html\n<!-- Directional light (sun-like) -->\n<three-light-block\n    type=\"directional\"\n    color=\"#ffffff\"\n    intensity=\"1.5\"\n    positionx=\"30\" positiony=\"40\" positionz=\"20\"\n    castshadow=\"true\"\n    shadowmapsize=\"2048\">\n</three-light-block>\n\n<!-- Point light -->\n<three-light-block\n    type=\"point\"\n    color=\"#ff6600\"\n    intensity=\"2\"\n    distance=\"10\"\n    decay=\"2\"\n    positionx=\"0\" positiony=\"3\" positionz=\"0\">\n</three-light-block>\n\n<!-- Spotlight -->\n<three-light-block\n    type=\"spot\"\n    color=\"#ffffff\"\n    intensity=\"2\"\n    angle=\"0.5\"\n    penumbra=\"0.5\"\n    positionx=\"0\" positiony=\"5\" positionz=\"0\"\n    rotationx=\"-90\">\n</three-light-block>\n<!-- NOTE: light-block rotations are in DEGREES (converted to radians at\n     runtime). Most other blocks take radians directly. -->\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `type` | string | **required** | `\"ambient\"`, `\"directional\"`, `\"point\"`, `\"spot\"` |\n| `color` | string | `\"#ffffff\"` | Light color (hex) |\n| `intensity` | number | `1` | Light brightness |\n| `distance` | number | `0` | Range (point/spot, 0 = infinite) |\n| `decay` | number | `2` | Falloff rate (point/spot) |\n| `angle` | number | `0.52` | Cone angle in radians (spot) |\n| `penumbra` | number | `0` | Edge softness 0-1 (spot) |\n| `castshadow` | `\"true\"` \\| `\"false\"` | `\"false\"` | Cast shadows |\n| `shadowmapsize` | number | `1024` | Shadow resolution |\n| `shadowradius` | number | `1` | Shadow blur |\n| `shadowbias` | number | `-0.0005` | Shadow bias |\n| `positionx/y/z` | number | `0` | Light position |\n| `rotationx/y/z` | number | `0` | Light rotation |\n\n### `<three-sky-block>`\n\nConfigure skybox and atmospheric effects.\n\n```html\n<three-sky-block \n    distance=\"170000\" \n    rayleigh=\"1\" \n    turbidity=\"10\"\n    sunpositionx=\"0\" sunpositiony=\"1\" sunpositionz=\"-10000\">\n</three-sky-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `distance` | number | `170000` | Sky sphere distance |\n| `rayleigh` | number | `1` | Atmospheric scattering |\n| `turbidity` | number | `10` | Atmospheric haziness |\n| `miecoefficient` | number | `0.005` | Mie scattering |\n| `miedirectionalg` | number | `0.7` | Mie directionality |\n| `sunpositionx/y/z` | number | - | Sun direction vector |\n\n### `<three-terrain-block>`\n\nGenerate procedural infinite terrain with atmosphere.\n\n```html\n<three-terrain-block\n    width=\"100\" height=\"100\" segments=\"100\"\n    scale=\"0.5\" heightscale=\"50\"\n    positiony=\"-10\"\n    color=\"#4a7c59\"\n    roughness=\"0.8\" metalness=\"0.2\"\n    seed=\"my-world-seed\"\n    noiseoctaves=\"6\" noisepersistence=\"0.5\" noiselacunarity=\"2\"\n    skycolor=\"#87CEEB\" horizoncolor=\"#E0F7FF\"\n    cloudcolor=\"#ffffff\" clouddensity=\"0.5\" cloudscale=\"1\"\n    cloudspeed=\"0.1\" cloudheight=\"100\" cloudlayers=\"3\"\n    timeofday=\"12\" timecycleduration=\"0\">\n</three-terrain-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `width` | number | `100` | Chunk width |\n| `height` | number | `100` | Chunk depth |\n| `segments` | number | `100` | Geometry detail |\n| `scale` | number | `0.5` | Noise scale |\n| `heightscale` | number | `50` | Height multiplier |\n| `seed` | string | random | Generation seed |\n| `positiony` | number | `-10` | Base height |\n| `color` | string | `\"#4a7c59\"` | Terrain color |\n| `roughness` | number | `0.8` | Material roughness |\n| `metalness` | number | `0.2` | Material metalness |\n| `noiseoctaves` | number | `6` | Noise detail layers |\n| `noisepersistence` | number | `0.5` | Amplitude per octave |\n| `noiselacunarity` | number | `2` | Frequency per octave |\n| `skycolor` | string | `\"#87CEEB\"` | Zenith sky color |\n| `horizoncolor` | string | `\"#E0F7FF\"` | Horizon sky color |\n| `cloudcolor` | string | `\"#ffffff\"` | Cloud color |\n| `clouddensity` | number | `0.5` | Cloud coverage (0-1) |\n| `cloudscale` | number | `1` | Cloud pattern size |\n| `cloudspeed` | number | `0.1` | Cloud movement |\n| `cloudheight` | number | `100` | Cloud layer height |\n| `cloudlayers` | number | `3` | Number of layers |\n| `timeofday` | number | `12` | Time (0-24) |\n| `timecycleduration` | number | `0` | Day/night cycle seconds (0 = off) |\n| `renderdistance` | number | `3` | Chunk render distance |\n\n---\n\n## System Blocks\n\n### `<three-networking-block>`\n\nEnable multiplayer functionality.\n\n```html\n<three-networking-block \n    participantlimit=\"10\" \n    customavatars=\"1\"\n    voicechatenabled=\"1\">\n</three-networking-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `participantlimit` | number | `10` | Maximum concurrent users |\n| `customavatars` | `\"0\"` \\| `\"1\"` | `\"0\"` | Allow custom avatar URLs |\n| `voicechatenabled` | `\"0\"` \\| `\"1\"` | `\"0\"` | Enable voice chat |\n\n### `<three-spawn-point-block>`\n\nDefine player spawn location.\n\n```html\n<three-spawn-point-block positionx=\"0\" positiony=\"1\" positionz=\"5\">\n</three-spawn-point-block>\n```\n\n| Attribute | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `positionx/y/z` | number | `0` | Spawn position |\n\n---\n\n## Complete World Example\n\n```html\n<three-environment-block \n    devicetarget=\"vr\" \n    threeobjecturl=\"world.glb\"\n    scale=\"1\" \n    positiony=\"0\" \n    camcollisions=\"1\"\n    hdr=\"sky.hdr\">\n    \n    <!-- Networking -->\n    <three-networking-block participantlimit=\"8\" customavatars=\"1\">\n    </three-networking-block>\n    \n    <!-- Spawn Point -->\n    <three-spawn-point-block positionx=\"0\" positiony=\"1\" positionz=\"5\">\n    </three-spawn-point-block>\n    \n    <!-- Lighting -->\n    <three-light-block type=\"ambient\" intensity=\"0.4\" color=\"#ffffff\">\n    </three-light-block>\n    <three-light-block \n        type=\"directional\" \n        intensity=\"1.2\" \n        positionx=\"30\" positiony=\"50\" positionz=\"20\"\n        castshadow=\"true\">\n    </three-light-block>\n    \n    <!-- Interactive NPC -->\n    <three-npc-block\n        threeobjecturl=\"guide.vrm\"\n        name=\"Guide\"\n        defaultmessage=\"Welcome! Ask me anything.\"\n        personality=\"Friendly and helpful\"\n        positionx=\"0\" positiony=\"0\" positionz=\"-5\">\n    </three-npc-block>\n    \n    <!-- Decorative Models -->\n    <three-model-block \n        threeobjecturl=\"fountain.glb\"\n        positionx=\"10\" positiony=\"0\" positionz=\"10\"\n        collidable=\"1\">\n    </three-model-block>\n    \n    <!-- Background Music -->\n    <three-audio-block\n        audiourl=\"ambient.mp3\"\n        positional=\"0\"\n        loop=\"1\"\n        volume=\"0.3\"\n        autoplay=\"1\">\n    </three-audio-block>\n    \n    <!-- Portal to Another World -->\n    <three-portal-block\n        destinationurl=\"https://example.com/gallery\"\n        label=\"Visit Gallery\"\n        positionx=\"-10\" positiony=\"0\" positionz=\"0\">\n    </three-portal-block>\n    \n</three-environment-block>\n```\n\n---\n\n## Schema Files\n\nJSON Schema definitions for all blocks are available in the `schemas/` folder:\n\n```\nschemas/\n├── index.json                    # Schema index\n├── environment-block.schema.json\n├── model-block.schema.json\n├── npc-block.schema.json\n├── portal-block.schema.json\n├── audio-block.schema.json\n├── video-block.schema.json\n├── image-block.schema.json\n├── light-block.schema.json\n├── text-block.schema.json\n├── sky-block.schema.json\n├── terrain-block.schema.json\n├── networking-block.schema.json\n└── spawn-point-block.schema.json\n```\n\nThese schemas can be used for validation, editor autocomplete, and documentation generation.\n\n---\n\n## Plugin & Extension APIs\n\nBeyond declarative `<three-*-block>` HTML, XR Publisher exposes a JavaScript API for building world content and editor/runtime extensions from a plugin script. A plugin is a self-contained UMD build loaded via a `<script>` tag; every method below is available as `XRPublisher.<method>` (static, instance, and `window.XRPublisher`).\n\n### Plugin bootstrap pattern\n\nA plugin's `<script>` tag can load before or after the runtime finishes its initial DOM scan, so shipped plugins wait for the runtime and register late — the registries below are all designed around late registration (blocks/decorations pop in the moment they're registered, even mid-session).\n\n```js\n(function () {\n  function waitForRuntime(cb) {\n    if (window.XRPublisher && typeof window.XRPublisher.registerDecoration === 'function') return cb();\n    setTimeout(() => waitForRuntime(cb), 100);\n  }\n  function init() { waitForRuntime(() => { /* register here */ }); }\n  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);\n  else setTimeout(init, 300);\n})();\n```\n\nFull reference plugins live in `examples/plugins/` and `examples/decorations/` — `block-lab-xr-publisher-plugin.umd.js` is the stress test for every custom-block field type and lifecycle hook described below.\n\n### Custom Blocks — `XRPublisher.registerBlockType(spec)`\n\nRegister an editor-placeable block type. One call wires the block into every seam: the in-world editor's **Add** menu, a generated inspector panel, gizmo/undo, save round-trip (as `<three-{plugin}-{name}-block>`), and normal page rendering.\n\n```js\nXRPublisher.registerBlockType({\n  plugin: 'block-lab',       // must match your build filename slug\n  name: 'beacon',\n  title: 'Beacon',\n  category: 'Plugins',\n  fields: [\n    { key: 'height', type: 'number', label: 'Height', default: 2, min: 0.5, max: 10, step: 0.1 },\n    { key: 'color', type: 'color', default: '#5cc8f2' },\n    { key: 'label', type: 'string', default: 'Beacon' },\n    { key: 'mode', type: 'select', default: 'pulse', options: [\n      { value: 'pulse', label: 'Pulse' }, { value: 'steady', label: 'Steady' }, { value: 'strobe', label: 'Strobe' },\n    ]},\n    { key: 'active', type: 'boolean', default: true },\n  ],\n  create(ctx) {\n    const { props, THREE } = ctx;\n    return new THREE.Mesh(\n      new THREE.CylinderGeometry(0.3, 0.3, props.height),\n      new THREE.MeshStandardMaterial({ color: props.color, emissive: props.color })\n    );\n  },\n  update(obj, props, prevProps) {\n    if (props.mode !== prevProps.mode) return false; // recreate on mode change\n    obj.scale.y = props.height / prevProps.height;\n    obj.material.color.set(props.color);\n    return true; // handled in place\n  },\n  tick(obj, delta, elapsed) {\n    obj.material.emissiveIntensity = 1 + Math.sin(elapsed * 3) * 0.5;\n  },\n});\n```\n\n- **Tag**: `three-{plugin}-{name}-block`. `plugin`/`name` must be lowercase, `/^[a-z0-9]+(-[a-z0-9]+)*$/`. `plugin` must equal your build's filename slug (`{plugin}-xr-publisher-plugin.umd.js`) — the API server allowlists saved blocks by that prefix on every world save, stripping blocks whose plugin isn't currently active rather than rejecting the save.\n- **Field types**: `number` (`min`/`max`/`step`), `string`, `longtext` (textarea), `url` (asset-drawer drop target in the editor), `select` (`options` as strings or `{value,label}` pairs), `color`, `boolean`. Field keys are lowercased, no dashes.\n- **Transform is engine-owned** — every custom block gets the standard 9 transform attributes and gizmo handling for free; `create()` builds at local origin.\n- **Lifecycle**: `create(ctx) → Object3D|null` (required). `ctx = { props, el, THREE, isWebGPU, rng, seed }` — `rng` is a seeded PRNG (stable per block instance; use it instead of `Math.random()` for deterministic variation). `update(obj, props, prevProps, ctx)` is optional — return `false` (or omit `update` entirely) to have the block disposed and recreated instead of patched in place. `tick(obj, delta, elapsed)` runs every frame if present. `dispose(obj)` replaces the default deep geometry/material dispose — provide one if the block shares resources (e.g. a cached loaded model).\n- A block whose plugin hasn't loaded yet (or was removed) renders nothing in a published world and shows a selectable wireframe placeholder in the in-world editor — it's never silently deleted from the save.\n- `XRPublisher.unregisterBlockType(tag)` removes a registration.\n\n### Decorations — `XRPublisher.registerDecoration(spec)`\n\nRegister a scatter object that the procedural terrain places deterministically (same world seed → same spots, every visit, every renderer). This is the right tool for ambient world dressing — trees, props, creatures — that shouldn't be individually hand-placed in the editor.\n\n```js\nXRPublisher.registerDecoration({\n  name: 'campfire',\n  density: 0.02,\n  surface: 'grass',\n  maxSlope: 0.3,\n  groundPatch: { radius: 3, clearGrass: true },\n  create(spawn) {\n    const group = new THREE.Group();\n    // build with spawn.rng for seed-stable variation\n    return group;\n  },\n});\n```\n\nPlacement filters (`minHeight`/`maxHeight`, `maxSlope`, `minWaterDistance`/`maxWaterDistance`, `surface`, `biomes`, `minSpacing`) cover common cases; `spawn` also carries `slope`, `waterDistance`, `surface`, `biome`, and terrain-grid spawns get `groundHeightAt(dx, dz)` for seating multi-point structures on real terrain. Other capabilities:\n\n- **`anchor`**: `'terrain'` (default), `'tree'` (hangs off real canopy geometry), `'tree-base'` (trunk/ground contact ring), or `'mountain'` (near the summit).\n- **`instanced`**: render every spawn as one `InstancedMesh` — cheap for dense repeated models (incompatible with `update`).\n- **`groundPatch`**: paint a dirt circle and clear engine grass around each spawn (campfire/plaza clearings).\n- **`collision`**: `true`/`'cuboid'`, `'hull'`, or `'trimesh'` for a solid decoration; or set `object.userData.xrColliders` on the `create()` result for hand-authored box colliders (the efficient path for walk-in structures).\n- **`waterPlacement`**: `'floor'` | `'submerged'` | `'surface'` for decorations spawned in water.\n- **`interactable: true` + `onInteract(ctx)`**: makes a spawn collectable/usable with a \"Press E\" prompt (see Interactables below).\n- **`update(object, delta, elapsed)`**: present ⇒ the decoration ticks every frame (absent ⇒ static, matrices frozen for performance).\n\nDemo plugins in `examples/plugins/` and `examples/decorations/` cover most of the above (campfires, farm-plots, lake-fish, town-center, at-tree-base, on-mountains, biome-surfaces, instanced-models).\n\n### Programmatic world building\n\nFor content that doesn't fit the scatter model — one-off structures, procedural surfaces, teleport logic — inject directly:\n\n- **`XRPublisher.addModel(spec) → id`** / **`removeModel(id)`**: inject a glTF/GLB rendered through the engine's own model pipeline (instancing, trimesh collision, WebGPU conversion included). Persists across chunk streaming, unlike decoration spawns.\n- **`XRPublisher.addTerrainRegion({ center, radius, surface, scale?, mortar?, tint?, clearGrass? }) → id`** / **`removeTerrainRegion(id)`**: replace the terrain surface inside a world-space circle with a registered procedural surface (e.g. a cobblestone plaza).\n- **`XRPublisher.registerTerrainSurface({ id, tsl, fallbackColor })`**: register a custom procedural terrain surface authored in TSL (`XRPublisher.tsl`), composited into the shared terrain material. `'cobblestone'` ships built in.\n- **`XRPublisher.getGroundHeight(x, z) → number|null`**: sample terrain surface height via the physics raycast (returns `null` until a nearby collider has loaded — poll/retry).\n- **`XRPublisher.teleportPlayer(x, y?, z)`**: teleport the local player, snapping to ground on arrival.\n- **`XRPublisher.loadModel(url) → Promise<Object3D>`**: cached glTF/GLB loader — one fetch/parse no matter how many callers request the same URL; clone the resolved scene per use (`.clone(true)`), and if used inside a decoration/block, provide a no-op-ish `dispose()` since the default cleanup would otherwise free the shared geometry.\n\n### Interactables — \"Press E to interact\"\n\nAdd `interactable: true` and `onInteract(ctx)` to a decoration spec (or call `XRPublisher.registerInteractable(entry)` / `unregisterInteractable(id)` directly for objects mounted outside the decoration system) to make an object collectable or usable. The engine runs proximity detection, shows a \"Press E\" prompt, and calls your handler on interact. `ctx.remove()` hides the object and unregisters it. `interactionPrompt` may be a function, re-evaluated at each proximity check, for contextual prompts (\"Water crops\" → \"Harvest\").\n\n### UI extension points\n\n- **`XRPublisher.registerHud(id, domElement, anchor?)`** / **`unregisterHud(id)`**: mount a plugin-owned DOM element into a fixed corner-anchored overlay. Flatscreen only — not visible in VR.\n- **`XRPublisher.openDialog({ title, content, buttons, width?, dismissDistance?, onClose? })`** / **`closeDialog()`**: open an in-world 3D modal panel, clickable by mouse and VR controller alike. Buttons can chain into further `openDialog()` calls for branching flows (quizzes, dialogue trees).\n- **`XRPublisher.registerMenuItem({ id, label, icon?, onSelect, order? })`** / **`unregisterMenuItem(id)`**: add a row to the hamburger \"More\" menu in the top-left HUD cluster.\n\n### Post-processing — `XRPublisher.setPostProcessing(config)` / `clearPostProcessing()`\n\nWebGPU-only **threshold** bloom pipeline: anything whose rendered color exceeds the luminance threshold blooms. In practice you drive it with `emissive`/`emissiveIntensity` pushed past the threshold; ordinary lit surfaces (terrain, grass, buildings) stay below it and are untouched. It is not a per-material selection mask — a sufficiently bright non-emissive surface will also bloom.\n\n```js\nXRPublisher.setPostProcessing({\n  bloom: { strength: 1.2, radius: 0.4, threshold: 0.8 },\n  anamorphic: true,\n  chromaticAberration: 0.002,\n  audioReactive: true,\n});\n```\n\nCosts nothing until called, and nothing at all on `?renderer=webgl`, which always uses plain rendering.\n\n### Time of day — `XRPublisher.setTimeOfDay(hour, pauseCycle?)` / `setDayCyclePaused(paused)` / `getTimeOfDay()`\n\nWorld time is one shared, room-synced timeline — the first participant to join becomes the time authority and heartbeats state to late joiners. Never gate a time change on player proximity; that produces a different time-of-day per client, which reads as a bug rather than a feature.\n\n### Networking helpers for plugins\n\nAvailable once a world has networking enabled: `XRPublisher.broadcast(channel, data)` / `onMessage(channel, handler)` (channels prefixed `__xrp:` are reserved for engine use), `getClientId()`, `getPeers() → [{id,x,y,z}]` (remote player positions, for proximity effects), `getPlayerState()` (local player). See `examples/plugins/resonance-stage-xr-publisher-plugin.umd.js` for a full multi-peer synced example (leader-elected shared-epoch audio transport).\n\n### Persisted world state — `XRPublisher.{configureWorldState, fetchWorldState, getWorldState, setWorldState, subscribeWorldState}`\n\nSmall JSON records that persist server-side per `(world, plugin, key)`, for deterministic decorations that need to remember state across visits (a watered crop, a harvested node). Reads are synchronous from an in-memory snapshot inside `create()` — call `fetchWorldState` there to warm it, and `subscribeWorldState` to react when fresher state arrives from the server or a peer broadcast. Newest-`updatedAt` wins across optimistic local writes, peer broadcasts, and the server round-trip. Backed by the `GET /api/world-state` / `POST /api/world-state/set` endpoints on the publishing API (see that project's README). Reference implementation: `examples/plugins/farm-plots-xr-publisher-plugin.umd.js` (growth/harvest/wither/regrow cycle).\n\n### Utilities\n\n- **`XRPublisher.utils = { alea, createNoise2D, createNoise3D }`**: the same seeded PRNG/noise primitives the engine's own terrain/decoration builders use — use these (never `Math.random()`) for anything that should be stable per visit.\n- **`XRPublisher.tsl`** (three/tsl nodes) and **`XRPublisher.webgpu`** (node material classes): author custom WebGPU shaders/materials using the engine's own Three.js instance, so node identity isn't broken by a duplicate Three copy. Gate on `!XRPublisher.webgpu` to detect `?renderer=webgl` and fall back to stock materials.\n- **`window.THREE`**: the bundled Three.js namespace, so plugin code can build `Object3D`s without shipping a second copy of Three.\n\n---\n\n## Physics System\n\nThe library uses Rapier for physics simulation:\n\n1. Set `collidable=\"1\"` on model blocks for physics bodies\n2. Enable `camcollisions=\"1\"` on the environment for player collision\n3. Physics initializes automatically when the world loads\n4. Supports trimesh, cuboid, and hull colliders via GLTF extensions (OMI_collider)\n\n---\n\n## Avatar System\n\nPlayers use VRM avatars with full animation support:\n\n- Default avatar configurable in XRPublisher settings\n- Custom avatar URLs supported when `customavatars=\"1\"`\n- Mixamo animation retargeting built-in\n- First-person and third-person camera modes\n\n---\n\n## Browser Support\n\n- **WebGPU**: Modern browsers (Chrome 113+, Edge 113+)\n- **WebGL**: Fallback for VR/XR sessions\n- **WebXR**: VR headset support (Quest, Vive, etc.)\n- **Mobile**: Touch controls with virtual joystick\n\n---\n\n## Building From Source\n\n```bash\nnpm install            # package uses legacy-peer-deps (see .npmrc)\nnpm run build:wasm     # one-time: generates src/workers/wasm/ (requires rustup + wasm32-unknown-unknown target)\nnpm run build          # ES + CJS + UMD bundles + TypeScript declarations → dist/\n```\n\n`npm run build:wasm` only needs re-running when the Rust sources under `crates/` change. Installing the published npm package requires no Rust toolchain — the WASM is inlined in the bundles.\n\n---\n\n## License\n\nGPL-3.0-only — see [LICENSE](./LICENSE).\n\n## Support\n\nFor questions and support, [file an issue on GitHub](https://github.com/antpb/XR-Publisher-pkg/issues).\n","readmeFilename":"README.md","homepage":"https://github.com/antpb/XR-Publisher-pkg#readme","keywords":["webxr","three","threejs","react-three-fiber","webgpu","vr","metaverse","3d","virtual-worlds","multiplayer","vrm","procedural-generation"],"repository":{"type":"git","url":"git+https://github.com/antpb/XR-Publisher-pkg.git"},"author":{"name":"antpb"},"bugs":{"url":"https://github.com/antpb/XR-Publisher-pkg/issues"},"license":"GPL-3.0-only"}