# NEOME World authoring ## One construction rule Create scenes only with executable JavaScript calling documented, pre-approved functions. The exported NEO preserves those scripts; the server validates every executable scene, including bundled and hosted dependencies. JavaScript variables, loops and pure helpers may produce ordinary parameters or object IDs. Pass engine context only in the documented context argument of an approved function, such as `NeomeScene.create({THREE,root,add})`. Use object IDs or names afterward; never read or mutate live engine objects. There is one submission path: approved function calls. Do not submit a serialized whole scene, call `NeomeScene.build/load/restore`, generate canvas textures, instantiate Three.js objects, or use raw generated mesh buffers as a fallback. Ordinary objects and arrays are only parameters to documented functions; they do not replace the construction script. Call approved functions directly, without aliasing individual methods. Do not use browser APIs, custom event callbacks, timers, fetches or render loops. Use approved geometry, material, UV, text, animation and interaction functions. If a feature is missing, propose a reusable approved function and wait for authorization to extend the engine. Keep the user's design, assets and requested scope. A request to explain or confirm does not authorize changes or publication. Create the original scene first. Optimize it afterward through the browser UI. Creation applies the image size and quality selected in that UI to every ordinary color image, including atlases. Optimization preserves existing image bytes and creates only the additional representations it needs. See the image-setting rules below for data maps and ground captures. ## Create → load → submit Keep the original connected client running. Edit local source and assets with normal file tools. Start with this approved-call script: ```javascript window.__studioReady=(async()=>{ const api=window.NeomeScene.create({THREE,root,add}); api.group('room'); api.add('floor','box',{parent:'room',size:[6,.2,6],position:[0,.1,0],color:'#8054fa'}); api.text('label','Room',{parent:'room',width:2,height:.4,position:[0,1,0]}); await api.ready(); })(); ``` Declare parents before children. IDs must be unique, short ASCII letters/digits/underscores/hyphens; use counters such as `wall_0`. Use `api.text` for text and `api.beam` for beams. The complete asynchronous initial construction belongs in `window.__studioReady`. Send commands to the existing client's stdin; source is read from disk: ```text world {"op":"load","path":"scene.txt"} world {"op":"capture"} world {"op":"submit","title":"My scene","description":"Optional description","category":"building"} ``` Run each command when its preceding stage is ready. File changes can also trigger a refresh: use the latest `WORLD_RENDER` revision, not an older success. An old scene may remain visible while the new one loads or fails. Avoid duplicate reloads. A render succeeds only with `WORLD_RENDER.ok:true`; check `submissionAllowed` and warnings. Capture when appearance needs checking, unless the user asks to test it themselves. Six views check appearance, not gameplay. Test actual controls before claiming interactions work. Submit only when authorized and the final render succeeds. `WORLD_SUBMIT_SENT` means requested; publication requires `WORLD_SUBMIT.ok:true` and `saved`. Report the stage actually completed. Use `saved.url` and `saved.preview`; the saved object UID is a package hash, separate from the connection UID. The connected browser performs export, preview capture and submission. Title: required, maximum 120 characters. Description: maximum 2000. Optional `path` must match the loaded scene. Optional Twitter handle: 1–15 letters/digits/underscores, with an optional leading `@`. Categories: person, animal, vehicle, building, interior, object, nature, infrastructure, world, game, others. One submission runs at a time. Read the exact error and correct that cause before retrying. On a timeout or disconnect, check whether publication succeeded before submitting again. Respect the user's wait limit; report a blocker promptly. Keep the original client alive through secure reconnects. ## Assets and textures Inspect a supplied package's manifest, catalog and README before selecting assets. Confirm entry paths, bounds, scale, origin and axes. A default entry may show an entire showroom; use a verified `sceneEntry` for one asset. Preserve existing geometry, UV mappings, textures and placement offsets. Metres, Y up and -Z front are defaults; catalog-specific axes take precedence. Place a published approved asset: ```javascript window.__studioReady=window.NeomeScene.asset( 'https://gpt.neome.com/validate/uploads/?file=PACK_HASH_optimized.neo', {sceneEntry:'files/assets/SELECTED_ASSET/scene.txt',position:[0,0,0],rotation:[0,0,0],scale:[1,1,1]} ); ``` Referencing a published pack does not require importing or repackaging it. Use its hosted URL directly. `CONNECT_WORLD_ASSETS` and `CONNECT_WORLD_PACKAGE` apply to local-file workflows. Use verified URLs and scene entries. When an optimized shared pack is supplied, reuse that optimized pack through `NeomeScene.asset` or `NeomeScene.assemble`; preserve its URL, selected entry, placements and interaction hierarchy. Do not rebuild or flatten it. The original-hash `_optimized.neo` URL follows later optimizations. A hash-shaped filename is a storage identifier, not a guarantee of immutable content: a package can be updated at the same URL. Hashing may deduplicate storage; loading does not require its bytes to match the filename. `NeomeScene.asset(url, options)` accepts `parent`, the ID of an already declared group or surface in the current scene. Its position, rotation and scale are local to that parent. Both asset placement and hosted assembly items support `sceneEntry` and `instances:[{position?,rotation?,scale?}]`; the runtime supplies their package context. Use one repeated import for identical modules rather than one asset import per tile. Keep asset namespaces and placement IDs distinct. Shared references remain approved calls after optimization. Visible exterior references contribute to the exterior bake; indoor references remain in the interior split. Declare local images before calling the approved texture function. Paths are relative to the workspace root, with no hidden, empty or `..` components: ```javascript // CONNECT_WORLD_ASSETS: [{"path":"atlas.webp"}] window.__studioReady=(async()=>{ await window.NeomeScene.texture('atlas','atlas.webp'); const api=window.NeomeScene.create({THREE,root,add}); api.add('floor','box',{size:[6,.2,6],texture:'atlas',textureRepeat:[1,1]}); await api.ready(); })(); ``` Replace the metadata comment to replace the complete asset list; an empty list clears it. Register images through `NeomeScene.texture`, then use their names and the documented atlas/UV parameters. Shared-image package declarations are described below. They are dependency metadata, not scene construction. For previewing a declared local NEO, use: ```javascript // CONNECT_WORLD_ASSETS: [{"path":"house.neo"}] window.__studioReady=window.NeomeScene.asset('house.neo'); ``` For an existing split package or approved asset library that must retain its manifest and dependencies, also declare `// CONNECT_WORLD_PACKAGE: {"path":"house.neo"}`. Use this marker only for eligible packages: a real `runtimeSplit:true` package, or a library with `purpose:"asset-library"`, `assetLibrary.version:1`, a matching `NEOME_ASSET_LIBRARY_CATALOG`, correct asset count and real scene entries. Every executable entry must use approved calls. The input must have no existing submission preview. An ordinary new asset should be the main approved-call scene with its declared assets. Loading an ordinary local NEO does not establish that its import can be exported: the current exporter can collide imported main-scene aliases. This is an exporter limitation, not permission to relabel the package as a library or split scene. Report it before attempting publication. ## Video surfaces Create a flat XY screen with an existing poster material. Use `api.youtube('screen','YOUR_VIDEO_ID')` for YouTube or `api.video('screen','vegas.mp4')` for a clip served from `/assets/videos/`. Attach it after placement and before `await api.ready()`. The MP4 loops, plays muted while in view within high-texture distance, and pauses outside that range. Click the video to pause or resume it. ## Limits and controls Source: 256 KiB including metadata. Maximum 32 declared assets; normally 16 MiB per asset, 128 MiB per NEO and 64 MiB combined assets. Browser imports: NEO ≤128 MiB, GLB ≤64 MiB. Captures: ≤16 MiB and ≤32 megapixels. Keep source and assets editable. Header controls: Upload, Export NEO, Submit on NEOME, Export GLB, Frame all, Start game, Fullscreen and workspace options. Orbit to inspect; select objects without triggering surface navigation. NEO file edits must be applied and loaded before submission. Exporting or downloading does not publish. Use existing engine systems. WebGL is required. Environment effects and game catalogs are host-managed; they are not automatically included in object NEOs. The references below describe approved authoring calls and supported host behavior without creating another authoring path. ## Live property registries Use the approved host loader to connect a world to published land records: ```js await window.NeomeScene.properties({id:'vegas_properties',url:'https://gpt.neome.com/biffvegas/land/?list=1',worldId:'vegas',refreshSeconds:60,labels:true}); ``` The host owns network requests, refresh timing and cleanup. Property IDs stay stable; property `version` updates metadata and building `version` updates a published pack. Coordinates are local to each property's placement; `rotationDegrees` is explicitly degrees. A failed registry request or building load retains the last working scene. The complete list must use schemaVersion 1 and nextCursor:null. Public GET ?list=1 returns records; ?id=A1&format=json returns one record. Public property uploads/edits are not authorized by this loader; publish records through the authenticated deployment workflow. Keep the approved call in the submitted source so future game loads fetch the current records. Do not embed network results or use custom fetch/timers in a scene. ## Detailed engine reference The following sections retain the supported geometry, modeling, gameplay and environment capabilities. Use only the sections relevant to the requested asset. ## Approved function reference The function names and reference below are read directly from the same shared definition used by the browser and server validators: https://gpt.neome.com/validate/authoring-policy.json . There is no second approval list in these instructions. Source-policy diagnostics are available at https://gpt.neome.com/validate/?health=1 ; they check authoring permission, not complete runtime behavior. Approval of a function name does not allow arbitrary arguments, callbacks or engine-object access. Pass finite plain values, parameter arrays/objects and IDs. Only `NeomeScene.create` may return the bound `api`; other engine returns must be ignored unless this reference explicitly identifies ordinary data or an ID. Pure data helpers, loops and the allowed JavaScript data operations remain available. Host-only geometry utilities are identified explicitly; use their ID-based construction alternatives for submitted objects. This reference describes supported signatures, not permission to pass live geometry between calls. Metres, Y up, -Z front; construction/modeling rotations are radians, character pose rotations are degrees. `context` is host-supplied inside packages; where an explicit context is shown, engine references belong only in that approved context argument. Do not add fetch/document/timer callbacks to it. IDs are local to one `api` instance; global modeling names must be unique in the scene. ### Scoped construction API | Function | Parameters, result and restrictions | | --- | --- | | `api.surfaceTexture(id, registeredName, options={})` | Create a local texture ID from an image first registered with NeomeScene.texture. Options: mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format, type, colorSpace, flipY, generateMipmaps, premultiplyAlpha, unpackAlignment, rotation, matrixAutoUpdate, offset/repeat/center (two numbers), matrix (nine numbers), atlasRect:[x,y,width,height] in source pixels. Returns the texture ID. Use generateMipmaps, not mipmaps. | | `api.surfaceMaterial(id, type, parameters={}, maps={})` | Declare a material ID. Supported types: MeshBasicMaterial, MeshStandardMaterial, MeshPhysicalMaterial, MeshPhongMaterial, MeshLambertMaterial, MeshToonMaterial, MeshNormalMaterial, MeshMatcapMaterial, LineBasicMaterial, LineDashedMaterial, PointsMaterial, ShadowMaterial. parameters are ordinary material values; maps maps map/normalMap/etc. to declared surfaceTexture IDs. Returns the material ID. | | `api.nodeState(id, options)` | Set name, position, quaternion, scale, 16-number matrix, matrixAutoUpdate, visible, shadows, renderOrder, frustumCulled, userData and morph state. A fixed matrix requires matrixAutoUpdate:false. userData replaces existing metadata; apply it before interaction declarations. Use api.transform for Euler rotation. | | `api.surface(id, definition, materialIds, options={})` | Create a surface with an approved geometry definition and a nonempty array of surfaceMaterial IDs. options: parent, type (Mesh by default; also InstancedMesh, Line, LineSegments, LineLoop, Points), instances. InstancedMesh needs count and exactly one 16-number transform per instance; optional colors are flat RGB triples. Set placement with api.transform or api.nodeState afterward. Returns its scoped object ID. | | `api.video(surfaceId, filename)` | Play an MP4 from /assets/videos/ on an existing flat XY screen mesh. Give the filename, such as vegas.mp4. Call after nodeState and before ready. The material serves as the poster. The clip loops, starts muted when visible within high-texture distance, and pauses out of view. Click to pause or resume. | | `api.youtube(surfaceId, videoId)` | Attach a YouTube player to an existing flat rectangular mesh in its local XY plane. Supply the 11-character YouTube ID. Call after nodeState and before ready. The existing material is the poster. Muted playback starts when the player looks at the screen within high-texture distance and pauses when it leaves view. | | `api.light(id, type, options)` | Create AmbientLight, DirectionalLight, PointLight, SpotLight or HemisphereLight. Options: parent, color (#rrggbb or RGB integer), intensity; Point/Spot distance and decay; Spot angle in radians and penumbra; Hemisphere groundColor. Returns the ID. Set placement, name and shadow flags through approved transform/nodeState calls. No engine constructors or returned-object edits. | | `api.interaction(id, options)` | Declare one interaction on an already constructed owner. options.type: vehicle, seat, door, garageDoor, drawer, wheel, steering, attachment, button, pickup, nozzle or elevator. Required fields and partner IDs depend on type; see the gameplay tables below. Declare all partners before await api.ready(). Stable IDs, hierarchy and moving pivots must agree. Returns the owner ID; no executable callbacks. Sectional garage doors: type:garageDoor, motion:sectional on a Group at the opening bottom center; panels is 2–32 direct Mesh child IDs ordered bottom to top; height (0.2–20), trackRadius (0.05–5), duration (0.2–30) are required; direction is 1 (local +Z, default) or -1. Panels start at [0,(i+0.5)*height/N,0], identity rotation/scale. Keep frames separate and attach decorations to individual panels. See /hello/world/ for the complete contract. Weapon pickups support pistol, rifle and paintball. Paintball neomeWeapon metadata: type:paintball, gripPoint:[x,y,z], paintColor:#rrggbb, optional paintSpeed:12–90, magSize and reserve. Paintball impacts are non-damaging and use bounded host-managed projectiles and splats. Paintball weapon metadata may also declare lod:{version:1,near:'near_owner_id',far:'far_owner_id',distancePolicy:'high-texture'}. Near and far must be sibling groups in the same attachment frame. Author a single combined vertex-color far mesh and initially hide its group. CombatSystem binds both to the renderer's native LOD traversal when the weapon is registered, including remote clones; it uses the existing lodTextureDistance setting with hysteresis. The far representation stays attached to the moving owner. Keep the near group's body/trigger/bolt/lid hierarchy intact. Saving or dropping the weapon preserves the LOD metadata and both representations. | | `api.group(id, options)` | Declare a parent group; options include parent, position, rotation, scale, userData and shadow flags. Returns the ID. | | `api.material(id, options={})` | Declare shared appearance parameters for api.add; reference them with materialId. Local material IDs are separate from object IDs. Returns the material ID. | | `api.add(id, type="box", options={})` | Create a documented shape and return its scoped object ID. Options include parent, materialId, appearance and transforms. Parents/materials must already exist. Supported shape parameters are listed below. Call await api.ready() after asynchronous shapes such as terrain. | | `api.geometry(id, definition, options={})` | Create one mesh from supported geometry-builder parameters and return its scoped object ID. Options match api.add. Use authored shape/point/face parameters; raw generated attribute buffers and serialized whole scenes are not submission inputs. | | `await api.ready()` | Wait for this API instance's asynchronous preparation and check interaction references. Resolves without a value. Call after imports/construction and after deformation; readiness does not test gameplay. | | `api.polygon(id, profile, holes=[], options={})` | Create a flat authored XY polygon from point rings with at least three [x,y] points. holes contains zero or more rings. options match api.add, including curveSegments (default 1), parent, appearance and transforms. Rotate X by -Math.PI/2 for a horizontal surface. Returns a scoped object ID; use boolean or extrude for solid openings. | | `api.combine(id, objectIds, options={})` | Combine 2–8192 distinct scoped IDs of compatible static leaf meshes. Consumes sources by default; consume:false retains them. options: parent, position, rotation, scale, consume. Materials, UV presence and render settings must match; positive invertible transforms, position/normal/optional UV only; no interactive, skinned, instanced or morphed sources. Preserves world placement and returns the new scoped ID. | | `api.curvePath(ownerId, {path, range, segments=32})` | Bend 1–256 static meshes beneath one declared owner, in a shared owner-local frame. Source is +Z length, X width, Y height. path: 2–64 finite [x,y,z] points; range:[startZ,endZ], increasing, defaults to owner Z bounds; segments: integer 1–128. Source vertices must be within the shared range. Maximum 524288 output vertices per call and 262144 per mesh. Repeat modules before bending to preserve texture spacing. Await imports and api.ready() before and after. No interactive, animated, skinned or instanced meshes. Both original and optimized packs are supported: import with NeomeScene.asset(...,{deformable:true}) before curving. Exterior geometry and near-flat texture bakes are curved together; distance bounds are refreshed after deformation. Other precomputed bake shapes remain unsupported. Keep intersections separate and avoid vertical tangents/inside-edge overlap. Returns the owner ID. | | `api.projectUV(id, {scale})` | Project UVs on an ordinary static leaf mesh. scale is finite and positive, in local metres per texture repeat. Set textureRepeat:[1,1] to enable repetition. Clones geometry, preserves other shared originals, and returns the object ID. | | `api.mesh(id, vertices, faces, options={})` | Create an authored mesh from [x,y,z] points and indexed polygon faces of at least three vertices. Indices and coordinates must be valid/finite; use consistent winding. Appearance, parent and transforms match api.add. Returns a scoped object ID; do not substitute generated BufferGeometry attributes. | | `api.beam(id, from, to, radius, options)` | Beam between two three-number points; positive radius. Returns the ID. | | `api.text(id, value, options)` | Text label; width/height in metres, widthPixels/heightPixels up to 4096, font, color, optional background, parent and transform. Returns the ID. | | `api.transform(id, options)` | Position, XYZ-radian rotation and/or scale. Returns the ID. | | `api.attach(id, parentId, preserveWorld=true)` | Reparent while preserving world placement by default; false retains the local transform. Returns the ID. | | `api.animate(id, definition)` | Animate this scoped mesh through the modeling animation system. definition: name, duration, loop, tracks; track property is position, rotation, scale or morph, with times and values and optional bone/name. Modeling rotations use radians. The engine advances playback; no scene render loop is needed. Returns the object ID. | | `api.remove(id)` | Remove an object owned by this API instance, its owned descendants and associated resources. Returns the removed ID. Use global remove only for global add IDs. | | `api.poolWater(id, options={})` | Portable pool water. profile is a simple XZ polygon (3 to 64 points), level defaults to .1m, depth to 1.5m, optional parent, groundOpening (default true), and water settings. Creates an ordinary metadata carrier retained by original and optimized NEOs. The host owns the live water shader, placement, visibility and cleanup; it cuts matching openings through thin flat support geometry and refreshes collision. Does not excavate tall/non-flat terrain volumes. Author the basin floor/walls/steps separately. Water settings: color/deepColor hex colors, waveAmplitude, waveLength, waveSpeed, opacity, roughness, clarity, reflection/refraction, foam, caustics and underwater. poolWater returns its object ID; do not inspect engine objects. | | `api.vfx(id, options={})` | Create a portable VFX descriptor. Required preset: firePortal, energyPortal, fire, smoke, explosion, sparks, electricArc, energyBeam, projectileTrail, impactBurst, shockwave, energyShield, magicAura, magicCircle, teleport, hologram, dissolve, waterSplash, dustCloud, confetti, blackHole, tornado, meteorStrike, lightningStorm, iceEruption, frostBreath, lavaFountain, poisonCloud, acidSpray, sandVortex, healingField, soulStream, gravityWell, timeDistortion, protectiveDome, scanningVolume, digitalDisintegration, crystalGrowth, fireworks, aurora, solarFlare, supernova, nebula, comet, galaxySpiral, rocketExhaust, thrusterIgnition, reactorCore, overloadDischarge, laserCutting, weldingArc, forcefieldImpact, waterfall, whirlpool, steamVent, bubbleColumn, fallingLeaves, fireflySwarm, butterflySwarm, batSwarm, muzzleFlash, bulletTracer, ricochet, metalImpact, stoneImpact, woodImpact, glassShatter, bloodHit, swordSlash, bladeRibbon, groundSlam, dashStreaks, footstepDust, spellCharging, attackWarningZone, homingProjectile, lootBeacon, pickupCollection, levelUp or targetPing. Options: parent, position/rotation/scale (three numbers; rotations radians), radius (.01–100), color/secondaryColor (#rrggbb), intensity (0–10), speed (0–10), duration (.05–120 seconds), loop/autoplay (booleans), seed (uint32), quality (low/medium/high), particleCount (1–1024), distance (1–500 metres), direction (nonzero vector), end (nonzero local beam/arc endpoint). Returns its scoped object ID. The host handles procedural rendering, playback, budgets and cleanup; scene scripts never access particles, shaders or render loops. Export preserves descriptors; live effects are excluded from static baking. | | `api.vfxControl(id, options={})` | Control an existing scoped VFX owner by ID. Options: playing:boolean and restart:boolean. restart:true resets particle playback; use playing:true to resume or replay a completed burst. Returns the ID. Declarative actions also support {type:"vfx",target:id,playing:true,restart:true}. | ### Scene, package and character calls | Function | Parameters, result and restrictions | | --- | --- | | `window.NeomeScene.create({THREE, root, add})` | Create a scoped construction API. This documented context argument is the exception allowing engine references. The returned API may be bound as const api; call its methods directly. Each API has its own IDs; create parents before children. | | `await window.NeomeScene.scene(path)` | Execute a bundled approved-call TXT entry in the current package context. path is a safe relative .txt path, with no absolute/hidden/parent components. All executable dependencies are checked. No standalone text fetch; requires package context. Maximum scene-chain depth 32 and assembly instance budget 100000. Ignore engine return values. | | `await window.NeomeScene.selectedScene({path, meshes})` | Load a bundled TXT scene, await its preparation, then select triangles using the same meshes rows as selectMeshes. Only path and meshes fields are accepted. Use stable unique mesh names. Resolves without a value. | | `window.NeomeScene.selectMeshes({meshes})` | Select triangles on already loaded meshes. meshes:[{name,triangles}], with one unique existing mesh name per row. triangles is a strictly increasing list of valid zero-based triangle indices; [] removes all faces, null keeps the mesh unchanged. Unlisted meshes are unchanged. Preserves the selected faces' material groups. Resolves without a value. | | `await window.NeomeScene.assemble({version:1, name?, items})` | Build a nonempty assembly. Each item needs unique id and either bundled path (.txt) or a verified hosted url. Optional position/rotation/scale, texturedBake/aerialBake/aerialOverlay. Hosted NEO items also accept bake:false: omit visual bakes and stream/show detailed geometry only within the live High texture distance; default baking behavior is unchanged. Hosted items also accept sceneEntry and instances:[{position?,rotation?,scale?}]; at most 100000 placements per hosted item and assembly instance budget applies. Do not mix path and url. Each bundled part has isolated object/material IDs. Promise resolves to an engine group: await and ignore it. | | `await window.NeomeScene.start({file, seat, background})` | Start the host game from declared bundled NEO packages. Both file and background are required safe relative .neo paths; seat is the driver-seat ID, 1–100 ASCII letters/digits/underscore/hyphen. Requires the host startup engine and asset bridge. Not a generic object import. Await completion and ignore the returned engine group. | | `await window.NeomeScene.hostedStart({avatar, aircraft, seat, background?, position?, rotation?, scale?})` | Start the host game using verified hosted NEO URLs for avatar, aircraft and optional background. seat is the aircraft driver-seat ID. Aircraft placement vectors have three finite numbers; scale components must be positive. Requires the host startup engine. Await and ignore engine return values. | | `await window.NeomeScene.character(spec, {THREE, root, window, add})` | Create a character with unique spec.id, either a bundled rigged GLB asset or procedural bones/parts (not both). Optional boneMap, parent, position/rotation/scale. Bones must exist and mappings be unambiguous. See character section for part/skin schemas. Returns the character ID; do not inspect engine skeletons. Package runtime supplies context when omitted. In submitted scripts await the call and use your supplied spec.id; the source checker conservatively treats this call's return as engine-owned. | | `window.NeomeScene.pose(action, {THREE, root, window})` | Play a registered character pose or embedded clip. action.target is the character ID. For a pose, use definition:{duration,loop?,tracks} or a previously registered pose name; rotation tracks contain bone,times,values in degrees, relative to rest. type:"clip" instead uses an exact unique clip name. Common options: loop,hold,speed,fade,repetitions. Returns the character ID. See the pose section; standalone pose playback does not load an arbitrary asset JSON. | | `window.NeomeScene.stopPose({target, reset=true}, {THREE, root, window})` | Stop playback for a registered character. Restores bone rest rotations by default; reset:false preserves current transforms and morph values. Returns the character ID. Package runtime supplies context when omitted. | | `window.NeomeScene.geometry(definition)` | Low-level geometry builder returning an engine BufferGeometry, not a scene object or ID. This approved name does not permit storing, reading, passing or mutating its engine return in submitted scripts. Use api.geometry(id,definition,options) or api.surface for placeable submitted geometry. Supported builder definitions are listed below. | | `window.NeomeScene.action(action, {THREE, root, window})` | Execute a predefined action by target ID. Types: pose, clip, stopPose (character fields above); animate (definition); visible (value); transform (position/rotation/scale); remove. cameraRange instead takes center, radius, minimumFar, near and needs host camera support. No custom callbacks. Return depends on action: character ID for pose/clip/stopPose, otherwise no value. Package runtime supplies context when omitted. | | `window.NeomeScene.event({event, target?, action}, {THREE, root, window})` | Register a predefined action without a callback. event:"load" runs immediately; "click" needs a target ID and host preview controls; "cameraChange" supports only a cameraRange action. action uses the action schema. The engine returns a cleanup function: submitted scripts must ignore it and never invoke/store it as an executable callback. Gameplay buttons should use api.interaction. | | `await window.NeomeScene.texture(name, path)` | Register a declared bundled image or declared shared-image virtual path by name. Optional third context argument is host-supplied. Returns the registered name; never generates canvas pixels. Declare local image dependencies before registration. | | `await window.NeomeScene.asset(ref, options={})` | Load a declared local NEO or a verified hosted NEO URL. options: parent (existing scoped owner ID), position, rotation, scale, sceneEntry, instances, aerialBake, texturedBake, deformable, bake. Set bake:false to omit visual bakes and stream/show detailed geometry only within the live High texture distance; default behavior is unchanged. Do not combine bake:false with deformable:true. instances is an array of {position?,rotation?,scale?}; the placement transform applies before each instance transform. This repeats assets; it does not require an InstancedMesh. Prefer one repeated import to one import per identical tile. Promise resolves to an engine group: await for completion and ignore the group; address declared IDs afterward. Optimized dependencies keep their original URL, entry, transforms and hierarchy. Set deformable:true before api.curvePath for original or optimized static modules. The optimized archive's exterior geometry and near-flat texture bakes are both loaded and curved, preserving materials and UVs; the curved bake remains available for normal distance selection. Geometry streaming, spatial LOD and interiors are disabled for this import. Other precomputed bake shapes are skipped. The optimized package URL is preserved with no original-URL fallback. | | `await window.NeomeScene.speak({target, text, voice="neo", volume=1, rate=1, autoplay=true, wait=false}, {THREE, root, window})` | Generate Wizape speech for an existing character/object ID and play it without a speech panel or Speak button. Text: 1–500 characters; voice: neo or elon; volume: 0–1; rate: 0.5–2. Mouth animation uses jawOpen/mouthOpen morphs when available. Default autoplay attempts playback on scene load; if the browser blocks audio, the next interaction inside the viewer starts it. No browser restriction is bypassed. autoplay:false waits for that interaction. wait:true resolves after speech ends; otherwise it resolves when audio is ready and playback starts or is queued. Repeated calls replace this target’s previous speech. Scene disposal stops audio and releases resources. Export/optimization records the call and never generates speech offline. Requires the NEOME viewer Wizape relay. Return is the target ID; it may be ignored. | | `await window.NeomeScene.cameraSequence({id="presentation", target?, keyframes:[{time:0, position:[x,y,z], lookAt:[x,y,z], fov?}, ...], loop=false, easing="smooth", stopOnInteraction=true}, {THREE, root, window})` | Start an automatic presentation camera move without custom render loops or controls. Provide 2–32 keyframes, with times in seconds starting at 0 and strictly increasing, up to 120 seconds. Positions/lookAt are world coordinates, or local to the existing target ID when supplied. Optional perspective fov: 10–120 degrees. easing: smooth or linear. Returns the sequence ID after starting, without waiting for completion. Scene disposal cancels the move; manual viewer interaction cancels it by default and restores orbit input. Automatic preview framing respects the chosen camera. Export/optimization preserves the data command without moving capture cameras. | | `await window.NeomeScene.npc({id, pack, behavior="idle", health=100, position=[0,0,0], speed?, range?, fightOnTouch?}, {THREE,root,window})` | Spawn one NPC from an existing host player pack. Requires initialized game NPC/Character/Combat systems; no engine objects or callbacks. id: unique 1–100 ASCII letters/digits/underscore/hyphen per scene; maximum 100 per scene. pack: exact registered player-pack ID. behavior: idle, wander, armed, hijackcar. health: 1–100000; speed: .01–50; range: 1–500; fightOnTouch: boolean. position: finite world [x,y,z]. Uses existing movement, combat, equipment and behavior configuration from that pack, with explicit field overrides. Cleanup removes owned NPCs. Returns your id. Game-host dependency: does not bundle the pack or initialize host services in standalone Connect. Offline export preserves the call. Alternatively supply asset:"safe/path/player.glb" instead of pack, declared as a bundled GLB dependency. This uses its skeleton, embedded textures and animation clips without requiring the host player catalog. Choose exactly one of pack or asset. | | `await window.NeomeScene.walkto({target, position?, destination?, speed?, stopDistance?, wait=false}, {THREE,root,window})` | NPC walking retains position/speed/stopDistance. A Starship request instead supplies target and destination IDs and optional wait; configure starshipTravel metadata on its root and starshipPad metadata on landing pads. Only Vegas–Moon routes are supported. wait:true resolves true after touchdown, false on reset/unload; setup failures reject. Offline conversion records the call without travelling. Shared travel requires the authoritative multiplayer service and currently rejects while connected. | | `await window.NeomeScene.properties({id, url, worldId, refreshSeconds:60, labels:true})` | Connect a persistent property registry to the owning scene. url must be the current NEOME host /biffvegas/land/?list=1 (gpt.neome.com and neome.com are supported) (optional worldId query), id/worldId are ASCII identifiers. Host validates schemaVersion:1, complete properties list (maximum 2000), unique IDs, statuses and published NEOME pack URLs. Polls every 15–3600 seconds, default 60; owns fetches, timers, cancellation and cleanup. Per-property position/scale and rotationDegrees use local metres and degrees. Loads each building.sceneEntry and swaps a changed building only after successful loading, retaining the previous building on network/validation/load failures. building:null clears that property building; removal from a valid full list removes that property. labels:true creates address/status markers from each record sign transform. Export and optimization preserve this command without baking live buildings. Await and ignore the returned ID. No scene fetch, callbacks or custom timers are permitted. Optional property.landscaping is an array of up to 64 direct published asset references with unique id, packUrl, sceneEntry, position/rotationDegrees/scale and optional instances using the same transforms. All are property-local; maximum 2000 placements per property. House and landscaping changes are staged before replacement; failed loads retain the previous working property. Landscaping-only changes reuse the existing house. No generated property pack or world resubmission is required. | ### Modeling calls and geometry helpers | Function | Parameters, result and restrictions | | --- | --- | | `window.ModelTools.boolean(id, a, b, options={})` | Apply operation:"subtract" (default), "union" or "intersect" to two uniquely named closed, consistently oriented two-manifold meshes. consume:true removes operands. Coplanar/degenerate cuts can fail. Returns an engine mesh; ignore it and address the result by its unique name. Topology changes precede rigging/shape keys. | | `window.ModelTools.bevel(meshName, {width=.05, segments=1})` | Chamfer the uniquely named mesh. Operates in mesh-local coordinates. Return is an engine mesh: ignore it; address meshName afterward. Modeling limits: 200000 triangles and 600000 attribute vertices. | | `window.ModelTools.subdivide(meshName, {iterations=1, split=true})` | Loop subdivision with integer iterations 0–3. Use before rigging/shape keys. Ignore the returned engine mesh; use the same name. | | `window.ModelTools.sculpt(meshName, options={})` | Sculpt local coordinates. mode: grab, inflate, pinch, smooth or flatten; center:[x,y,z], radius, strength, delta:[x,y,z], normal:[x,y,z]. Values must be finite. Ignore the returned engine mesh. | | `window.ModelTools.remesh(meshName, {resolution=24})` | Voxel-remesh a closed mesh with integer resolution 8–48. May lose detail and UVs. Use before rigging/shape keys; ignore the returned engine mesh. | | `window.ModelTools.uv(meshName, options={})` | Generate UVs. mode: charts, box, planar, cylindrical or spherical; axis defaults to y, chart angle defaults to 45 degrees and padding to .03. Ignore the returned engine mesh. | | `window.ModelTools.bake(lowName, highName, options={})` | CPU nearest-surface normal/albedo baking. type:"normal" or "albedo", size 16–512 (default 256), maxDistance default .25, apply default true. Low mesh requires UVs; one material per mesh. The engine applies the result when requested. Do not inspect its returned canvas/texture. | | `window.ModelTools.cleanup(meshName, {tolerance=1e-6})` | Weld vertices and remove duplicate/degenerate faces. Discards UVs, colors and groups. Ignore the returned engine mesh; use before rigging/shape keys. | | `window.ModelTools.decimate(meshName, {ratio=.5})` | Approximate simplification, ratio .05–1, input at most 10000 attribute vertices. Ignore the returned engine mesh; verify appearance after simplification. | | `window.ModelTools.rig(meshName, {bones, weights?})` | Create 1–128 bones, with name, optional parent, parent-local position and optional rotation. Optional per-vertex weights use 1–4 normalized influences. Uses a uniquely named existing mesh. Ignore engine return values and use the mesh name for pose/animation. | | `window.ModelTools.pose(rigName, bones)` | Set rig rest offsets using {boneName:{rotation:[rx,ry,rz],position:[x,y,z]}}. Rotations are radians, unlike NeomeScene character pose degrees. Target must have a ModelTools rig. Ignore engine return values. | | `window.ModelTools.shapeKey(meshName, name, {vertices})` | Add an absolute local shape target. vertices must keep the original count and order; each is [x,y,z]. Ignore the returned engine value. Topology changes after shape keys are unsupported. | | `window.ModelTools.morph(meshName, name, weight=1)` | Set an existing shape-key weight, finite and within -2 to 2. Ignore returned engine values. Use animate tracks for playback. | | `window.ModelTools.animate(meshName, {name?, duration, loop?, tracks})` | Play engine-driven tracks. Each has property rotation/position/scale/morph, matching times/values, optional bone or morph name. Rotation is XYZ radians relative to rest; position/scale are absolute local. duration must be positive. Ignore the returned engine playback record. No custom render loop. | | `window.ModelTools.inspect(meshName)` | Return ordinary diagnostic data: vertices, triangles, boundaryEdges, nonManifoldEdges, uv, skinned, shapeKeys. This data may be read. Uses a uniquely named ordinary mesh; zero boundary/non-manifold edges is required for a closed solid. | | `window.ModelTools.polygon(profile, holes=[], {curveSegments=1}={})` | Low-level polygon geometry helper. profile and holes are authored XY point rings with at least three points. Returns an engine BufferGeometry, not a placed object. Submitted scripts should use api.polygon(id,profile,holes,options) and must not retain/use this engine return. | | `window.ModelTools.loft({sections, cap=true})` | Low-level geometry helper. At least two consistently ordered 3D rings, each with the same point count of at least three. Returns BufferGeometry; submitted placeable geometry uses api.add(id,"loft",options) or api.geometry. | | `window.ModelTools.smoothLoft({sections, lengthSegments=64, radialSegments=48})` | Low-level centripetal loft geometry helper. At least two 3D rings with at least three points each. Sampling is bounded by the surface builder. Returns BufferGeometry; use api.add(id,"smoothLoft",options) for a submitted object. | | `window.ModelTools.bezierPatch({controlPoints, uSegments=32, vSegments=32})` | Low-level geometry helper with a 4×4 array of [x,y,z] control points. Sampling is bounded by the surface builder. Returns BufferGeometry; use api.add(id,"bezierPatch",options) for a submitted object. | | `window.ModelTools.body({stations, lengthSegments?, radialSegments?})` | Low-level body geometry helper. At least two stations:{x,width,height,y?,z?,power?}; finite x and positive width/height, power 1–6 (default 2). Returns BufferGeometry; use api.add(id,"body",options) for a submitted object. | | `window.ModelTools.wing({stations, lengthSegments?, radialSegments?})` | Low-level wing geometry helper. At least two stations:{span,chord,sweep?,rise?,thickness?,twist?}; finite span, positive chord, thickness strictly between 0 and 1 (default .12), twist radians. Returns BufferGeometry; use api.add(id,"wing",options) for a submitted object. | | `window.ModelTools.mesh(vertices, faces)` | Low-level geometry helper from authored [x,y,z] points and indexed polygon faces. Returns an engine BufferGeometry. For submitted placeable geometry use api.mesh; do not retain/read/pass the geometry return. | | `window.ModelTools.combine(geometries)` | Host-level geometry utility accepting engine geometries and returning a merged BufferGeometry. Its engine-object arguments/return cannot be supplied or inspected by submitted scripts. For plain ID-based construction use api.combine(id,objectIds,options); no engine objects are exposed. | | `window.ModelTools.modify(geometry, modifiers=[])` | Host-level geometry utility accepting and returning an engine geometry. Not a data-only authoring path. For submitted scripts pass modifiers as plain options.modifiers to api.add, or call approved named modeling operations. Never pass an engine geometry from a submitted script. | ### Global construction and host effects | Function | Parameters, result and restrictions | | --- | --- | | `add(id, type="box", options={})` | Global object builder using the shape/appearance parameters below. Reusing a global ID replaces that object. Returns an engine object: ignore it. Prefer api.add for scoped IDs and parent ownership. | | `await addSavedGLB(path)` | Load a declared bundled GLB using the host asset loader. path is a safe workspace/package-relative dependency. GLB must contain supported geometry/materials and bundled resources. Await completion and ignore its engine-object return. Requires a host GLB loader; do not replace it with a fetch or a Three.js constructor. | | `await loadNEO(ref)` | Compatibility import routed through the host NEO loader; in Connect it delegates to NeomeScene.asset. Use a declared local NEO or verified hosted NEO ref; await and ignore engine return values. Prefer NeomeScene.asset for explicit parent, entry and repeated placements. | | `remove(id)` | Remove the global add registry entry with this ID, associated children/resources and host registrations. Missing IDs are ignored. No return value. Scoped objects should use api.remove. | | `clear()` | Remove all global add registry entries by calling remove on their IDs. No return value. This does not mean every unrelated hosted import or host environment service is cleared. | | `addGrass(id, options={})` | Create/replace a host grass field. options include size:[width,depth], position:[x,y,z], density, count, maxBlades, bladeHeight, bladeWidth, color, tipColor, windStrength, castShadow. Decorative instanced geometry, random placement, no terrain conformance or collision. Ignore the returned engine mesh. Host effects are not automatically packaged into an object NEO. | | `removeGrass(id)` | Remove a host grass field and its resources; missing IDs are ignored. No return value. Does not remove unrelated authored meshes. | | `addWater(id, options={})` | Create/replace a host water surface. size:[width,depth], position:[x,y,z] or level, color/deepColor, quality, waveAmplitude, waveLength, waveSpeed, opacity, roughness, clarity, reflection/refraction, directionDegrees, flowSpeed, density, buoyancy, drag, foam, caustics, underwater and wake options. See environment section. Ignore its engine return; host behavior is not automatically embedded in a prop NEO. | | `removeWater(id)` | Remove a host water surface; missing IDs are ignored. No return value. Leave host global water IDs to the environment service. | | `addGun(id, options={})` | Register a host gun/pickup. type, position, acquired (defaults true), pickup, damage, fireRate, magSize, ammo, reserve, reloadTime, range, spread, automatic, recoil, muzzlePosition. Use acquired:false for a world pickup. options.object is a host engine-object hook and is not permitted in submissions. Ignore the returned engine record; use api.interaction for custom pickup geometry. type:paintball supports paintColor (#rrggbb), paintSpeed (12–90), zero damage, colored projectiles and matching splats. | | `setWind(options={})` | Merge host wind settings: enabled, directionDegrees (or direction:[x,y,z]), speed, gustStrength and related host-supported values. Do not inspect/mutate the returned host state. Affects existing registered wind objects; no second update loop. | | `setRain(options={})` | Merge and apply host rain settings, including enabled and intensity, and host-supported wetness/audio settings. Ignore returned host state; requires the initialized environment service. | | `setFog(options={})` | Merge and apply host fog settings, including enabled, color and density. Ignore returned host state. These effects are host-managed, not automatic object-package contents. | | `setSky(options={})` | Merge and apply host sky settings, including timeOfDay, cloudCover, cloudSpeed, sunIntensity, ambientIntensity and color. Ignore returned host state; no direct renderer/scene mutation. | | `setLightning(options={})` | Merge and apply host lightning settings, including enabled and frequency. Ignore returned host state. Storm audio requires a user gesture in the host. | | `setWeatherPreset(name)` | Apply a supported host weather preset name. Returns true on success, false for an unknown name; use the host UI to choose supported names. Does not create a new environment service or embed it into an object NEO. | | `setWaterPreset(name)` | Apply a supported host water preset and enable/update global water. Returns true on success, false for an unknown name. Use host UI preset names; no engine state inspection. | ## Static polygon, batching and UV calls Use approved construction calls and their documented parameters. Export creates and shares the checked geometry definitions automatically; do not construct exported geometry tables by hand. - `api.polygon(id, profile, holes=[], options={})` creates a flat XY polygon with optional holes. `profile` and each hole are arrays of `[x,y]` points. Rotate around X by `-Math.PI/2` for horizontal surfaces. Use modest outlines; no extrusion is needed for a flat roof or floor. - `api.combine(id, objectIds, options={})` combines at least two compatible static leaf meshes. It preserves world placement and materials, and consumes the source IDs by default. `consume:false` retains them. Options are `parent`, `position`, `rotation`, `scale`, `consume`. Materials, UV presence and render settings must match; only position/normal/UV attributes are supported. Skinned, instanced, morphed meshes and mirrored or singular transforms are rejected. Batch only parts that should share visibility and movement. - `api.projectUV(id,{scale:3})` projects UVs in object-local metres: 3 metres per texture repeat. It changes this object's geometry only. Declare `textureRepeat:[1,1]` on textured construction to enable repetition. Scale must be finite and positive. Call polygon, UV projection and combination functions directly: ```javascript const api=window.NeomeScene.create({THREE,root,add}); const outline=[[0,0],[4,0],[4,4],[0,4]]; api.polygon('roofA',outline,[],{color:'#d8c9a8',rotation:[-Math.PI/2,0,0]}); api.polygon('roofB',outline,[],{color:'#d8c9a8',position:[4,0,0],rotation:[-Math.PI/2,0,0]}); api.projectUV('roofA',{scale:3}); api.projectUV('roofB',{scale:3}); api.combine('roof',['roofA','roofB']); ``` ## Curving a reusable static module `api.curvePath(ownerId,{path,range,segments:32})` bends all static meshes beneath one declared owner group using a shared coordinate frame. Author the straight module along local **+Z**, with width along X and height along Y. `range:[startZ,endZ]` is its original length interval; omit it to use the whole owner's bounds. `path` contains 2–64 `[x,y,z]` points in the owner's local coordinates. Two points make a straight segment; additional points make a smooth centripetal curve. The source length maps across the full path; width and height remain offsets from its centerline. UV coordinates are interpolated, so stretching a module also stretches its existing texture and marking pattern. Repeat modules to preserve marking spacing on long roads. The function accepts 1–256 static meshes and at most 524288 output vertices per call (262144 per mesh). It subdivides triangles along the source length (`segments`: 1–128), keeps materials and UVs, and rebuilds normals and bounds. It operates once during construction. Await imported assets and `api.ready()` before calling it, then finish with `api.ready()` before publishing. Original shared geometry remains unchanged. Use static modules. Import original or optimized packages through NeomeScene.asset with deformable:true before api.curvePath. Exterior geometry and near-flat texture bakes are curved together with their materials and UVs; the curved bake is used at the normal distance threshold. Distance bounds are refreshed after curving. Other precomputed bake shapes are skipped, and geometry streaming, spatial LOD and interiors are disabled for this import. Keep the optimized package URL. Deform before collision registration or animation. Non-flat baked distance representations and interactive, skinned or instanced objects are unsupported. Intersections need separate modules. Avoid tight curves that make the inside edge overlap itself and paths with vertical tangents. ```javascript const api=window.NeomeScene.create({THREE,root,add}); api.group('road'); // Declare the road surfaces beneath road, or await a hosted original module // using NeomeScene.asset(url,{parent:'road',sceneEntry:'scene.txt'}). await api.ready(); api.curvePath('road',{range:[0,20],path:[[0,0,0],[0,0,10],[5,0,20]],segments:32}); await api.ready(); ``` ## Coordinates, geometry and materials Metres; X lateral, Y up, -Z front. Positions are mesh centers: height h on y=0 has position y=h/2. Construction rotations are XYZ radians; doorAngle and seatYaw are degrees. State any different asset-local axes explicitly. Prefer `api.add`, which returns an object ID; `api.remove` removes an authored item. Direct approved `add`, `remove` and `clear` calls remain available, but do not edit returned engine objects. Use explicit dimensions and modest mesh/texture budgets. Common options: position:[x,y,z], rotation:[rx,ry,rz], scale:[sx,sy,sz], color, roughness, metalness, opacity, transparent, visible, userData, castShadow, receiveShadow, wireframe, flatShading, doubleSide, side:'front'|'back'|'double', depthWrite, depthTest, alphaTest, vertexColors. material:'standard'|'physical'|'basic'|'lambert'|'phong'|'toon'|'normal'|'matcap'; physical adds transmission, thickness, ior, clearcoat, sheen and iridescence. texture names and atlas crops are declared above. Geometry type / options: - box: size:[w,h,d]. sphere: radius, segments, rings. - cylinder: radiusTop, radiusBottom, height, segments, openEnded. cone: radius, height, segments. - polygon: profile:[[x,y],...],holes:[[[x,y],...],...],curveSegments (default 1); flat XY geometry. - plane: width,height; XY plane, thickness:0. Rotate -PI/2 around X for a flat XZ surface. **Textured planes:** Always specify `thickness: 0` for flat foliage, flowers, decals, and billboards. Omitting thickness can default to a thick shape; with a texture, the engine may generate dense image-silhouette geometry. Use positive thickness only when that geometry is intentional. **Verify triangle counts in the live preview:** Local geometry checks without loaded textures do not reproduce image-silhouette generation. Check the actual `WORLD_RENDER` triangle count before calling the scene ready. - torus: radius,tube,radialSegments,tubularSegments,arc. torusknot: radius,tube,tubularSegments,radialSegments,p,q. - circle/ring: radius or innerRadius/outerRadius,segments. capsule: radius,length,capSegments,radialSegments. - tetrahedron/octahedron/dodecahedron/icosahedron: radius,detail. - lathe: points:[[radius,y],...],segments. tube: points:[[x,y,z],...],tube,tubularSegments,radialSegments,closed. - mesh: vertices:[[x,y,z],...],faces:[[indices...],...]; valid winding and indices. - extrude: profile:[[x,y],...],holes:[[[x,y],...],...],depth,bevel; Z-centered unless center:false. - sweep: profile/holes,path:[[x,y,z],...],steps,closed. - loft/smoothLoft: sections of consistently ordered 3D rings with equal vertex counts; loft cap, smoothLoft lengthSegments/radialSegments. - bezierPatch: 4x4 controlPoints of [x,y,z],uSegments,vSegments. - body: stations:[{x,width,height,y?,z?,power?},...],length/radialSegments. - wing: stations:[{span,chord,sweep?,rise?,thickness?,twist?},...]. - heightmap and image3d: below. terrain: the combined heightmap/ground/road generator described under Terrain, roads and cities; heightmap is the height-only geometry type. Real openings use holes or boolean geometry: ```js add('wall_opening','extrude',{profile:[[-2,0],[2,0],[2,3],[-2,3]], holes:[[[-.6,.1],[-.6,2.3],[.6,2.3],[.6,.1]]],depth:.2,color:'#fac9a8',doubleSide:true}); ``` image3d uses texture, atlas, width/height or size, thickness, alphaThreshold and traceResolution. Modes include silhouette, smooth_silhouette, wrapped and contour_wrap. backTexture/backAtlas selects the back; topTexture/topAtlas and wrapTexture/wrapAtlas supply top/wrap surfaces for the modes that use them. Transparent empty areas become geometry openings; painted dark areas do not. A textured plane with nonzero thickness makes an alpha slab. Test front, back and edges; do not assume every THREE constructor option is forwarded by add. options.modifiers runs in order: transform (position/rotation/scale), array (count 1..256,offset), mirror (axis,merge), twist (axis,angle), taper (axis,factor), bend (axis,angle), plus the modeling modifiers below. Axes x/y/z. Use approved geometry functions for custom geometry. ## Modeling and animation Call `window.ModelTools.METHOD(...)` directly; storing `window.ModelTools` in an alias is rejected. Pass names unique within the scene to modeling functions; use scoped `api` methods for objects identified only within one construction API. Apply changes through these approved functions. Topology changes precede rigging/shape keys. Sculpt/UV/bone/shape coordinates are mesh-local; boolean operands bake to root coordinates. Bounds: 200,000 triangles / 600,000 attribute vertices. - window.ModelTools.boolean(id,a,b,{operation:'subtract'|'union'|'intersect',consume:true}): closed, consistently oriented two-manifold solids; degenerate/coplanar cuts may fail. - window.ModelTools.bevel(mesh,{width:.05,segments:1}): single chamfer. window.ModelTools.subdivide(mesh,{iterations:1,split:true}): Loop, iterations 0..3. - window.ModelTools.sculpt(mesh,{mode:'grab'|'inflate'|'pinch'|'smooth'|'flatten',center:[0,0,0],radius:1,strength:.1,delta:[.1,0,0],normal:[0,1,0]}). - window.ModelTools.remesh(mesh,{resolution:24}): closed-mesh voxel remesh, integer 8..48; detail/UV loss. window.ModelTools.uv(mesh,{mode:'charts'|'box'|'planar'|'cylindrical'|'spherical',axis:'y',angle:45,padding:.03}). - window.ModelTools.bake(low,high,{type:'normal'|'albedo',size:256,maxDistance:.25,apply:true}): CPU nearest-surface, size 16..512, low UVs, one material per mesh; applies its result when apply:true. Do not inspect the returned canvas or texture from a submitted script; inspect coverage through host tools. - window.ModelTools.cleanup(mesh,{tolerance:1e-6}): weld/remove duplicate/degenerate faces; discards UVs/colors/groups. window.ModelTools.decimate(mesh,{ratio:.5}): ratio .05..1, <=10,000 attribute vertices; approximate. - window.ModelTools.rig(mesh,{bones:[{name,parent?,position:[...],rotation:[...]}],weights?}): creates the rig through the approved call; use its unique name in subsequent calls, with 1..128 bones, parent-local transforms; optional normalized per-vertex weights, 1..4 influences. - window.ModelTools.pose(rig,{boneName:{rotation:[...],position:[...]}}): rest offsets. window.ModelTools.shapeKey(mesh,'name',{vertices:[[x,y,z],...]}) uses absolute local targets with unchanged count/order. window.ModelTools.morph(mesh,'name',weight). - window.ModelTools.animate(mesh,{name:'motion',duration:2,loop:true,tracks:[...]}) advances automatically. Track properties rotation/position/scale/morph, times,values, optional bone/morph name. Rotation XYZ radians relative to rest; position/scale absolute local. Match loop endpoints. - window.ModelTools.inspect(mesh): vertices,triangles,boundaryEdges,nonManifoldEdges,uv,skinned,shapeKeys. Closed solids need zero boundary/non-manifold edges. bevel/subdivide/sculpt/remesh/uv/cleanup/decimate also work as modifiers. Boolean, baking, rigging and animation are separate calls. The approved low-level polygon/loft/smoothLoft/bezierPatch/body/wing/mesh helpers return engine geometries; their engine returns are not a submission interface. Use api.add/api.geometry/api.mesh/api.polygon to place equivalent geometry with plain parameters. ModelTools.combine/modify are host geometry utilities; use api.combine and approved modifiers in submitted scripts. See the generated reference for exact signatures. ## Automatic speech and presentation cameras Scenes may speak through Wizape and move the camera with approved data calls. No speech panel, Speak button, custom callback, audio element or render loop is needed in authoring source. Declare/import the target first. This example assumes its existing ID is `neo`: ```javascript window.__studioReady=(async()=>{ await window.NeomeScene.speak({target:'neo', text:'Why not give it a try?', voice:'neo'}); await window.NeomeScene.cameraSequence({target:'neo', keyframes:[ {time:0, position:[0,1.65,-1.1], lookAt:[0,1.6,0]}, {time:4, position:[0,1.1,-3.5], lookAt:[0,.95,0]} ]}); })(); ``` Speech attempts autoplay on load. A browser that blocks sound requires one interaction inside the viewer; playback then resumes without a Speak button. Mouth animation uses available jawOpen/mouthOpen morphs. `wait:true` waits for the speech to finish; the default only waits until audio is ready. Camera keyframe times are seconds; positions are local to `target`, or world coordinates when no target is supplied. The camera call starts its sequence and returns immediately. Manual interaction cancels the camera move by default. Both calls survive validation, optimization and publication; offline conversion never requests paid speech or moves capture cameras. Reload NEOME after an engine update. ## Authored game NPCs and walking Use the initialized game host NPC system through approved calls. These calls depend on registered host player packs and the game update loop; they do not bundle a player pack or initialize gameplay inside a standalone object preview. Use the exact player-pack ID from the game NPC selector. Alternatively use `asset:"people/player.glb"` instead of `pack` and declare that local GLB in CONNECT_WORLD_ASSETS. Bundled NPCs retain their embedded skeleton, textures and clips and do not require the player catalog. ```javascript window.__studioReady=(async()=>{ await window.NeomeScene.npc({id:'guard01',pack:'brady',behavior:'idle',health:150,position:[0,0,0],speed:2.2,range:30,fightOnTouch:false}); await window.NeomeScene.walkto({target:'guard01',position:[10,0,5],speed:2.2}); })(); ``` Behaviors: idle, wander, armed, hijackcar. Existing pack equipment/configuration remains available. Use idle for scripted movement; autonomous behaviors may issue their own movement orders. Health and other supported bounds are in the generated function reference below. `wait:false` is the default so scene loading can finish; `wait:true` requires a running game. Calls survive export as commands and owned NPCs are removed with their scene. Test movement, damage, death and behavior in Play. ## Characters, poses and NPCs Portable characters can be procedural bones/parts or a bundled rigged GLB. Use approved character and action calls: ```javascript window.__studioReady=(async()=>{ await window.NeomeScene.character({id:'person',asset:'assets/person.glb',boneMap:{head:'Head'}},{THREE,root,window,add}); await window.NeomeScene.action({type:'pose',target:'person',definition:{duration:2,loop:true, tracks:[{bone:'head',property:'rotation',times:[0,1,2],values:[[0,0,0],[0,15,0],[0,0,0]]}]}},{THREE,root,window}); })(); ``` Bundle GLB textures, skeleton and skin weights. boneMap maps pose names to imported bone names; missing/ambiguous bones fail. Optional character parent,position,rotation,scale. Supply pose keyframes as plain definition data. Direct pose/action calls do not load an asset JSON field. Static poses may use bones:{head:[0,15,0]}. For procedural characters, pass `bones: [{id:'hips',position:[0,1,0]},{id:'head',parent:'hips',position:[0,.65,0]}]` instead of asset to `NeomeScene.character`, with `parts: [{id:'body',bone:'hips',geometry:{type:'BoxGeometry',args:[.5,.7,.25]},material:{color:'#8b5cf6'}}]`. Declare bones parent-before-child with unique IDs. Parts use approved geometry function parameters/material data and attach rigidly to bone. Optional skin:{bones:['hips','head'],weights:[[[0,1]],[[0,.5],[1,.5]],[[1,1]]]} replaces bone for a three-vertex deformable mesh; one row per vertex, <=4 normalized influences. Preserve vertex order; do not toNonIndexed after weights. Construction rotations are radians. Character pose rotation tracks are XYZ degrees relative to each bone's rest quaternion: rest * Euler(x,y,z,'XYZ'). Calibrate a new rig one bone/axis at a time before authoring poses. Actions pose, clip (exact embedded animation name), stopPose support loop,hold,speed,fade seconds and repetitions with loop:true. stopPose resets to rest unless reset:false. Use load/click events for portable playback; no callbacks. The full game manages NPC locomotion, routes, equipment, combat and population/traffic catalogs. These services require the host game and loaded assets; do not initialize or register them from a construction script. ## Gameplay controls and doors Start game loads the preview player and enables gameplay. Click the viewport for pointer lock; Escape returns to orbit/editor. Interaction requires a nearby aimed object, not merely a visible object. | Context | Controls | |---|---| | On foot | WASD/arrows move; Shift run; Space jump; mouse look; V first/back view | | Interaction | E activates focused doors, drawers, elevator buttons, subway controls, pickups and seats | | Car entry outside | Aim at the exact door or seat; E or F runs Enter Car, opens its paired door, seats the player and closes the door | | Car exit inside | F runs Exit Car through the occupied seat's paired door, opening and closing it; E with no focused control sounds the car horn | | Orbit object preview | Click a car door to toggle its articulation; no player-entry sequence | | Car / boat | W/S or up/down throttle/reverse; A/D or left/right steer; car Space handbrake; V driver/back view | | Plane | W/S throttle; A/D roll; left/right arrows yaw; up/down arrows pitch; Space brake; R reset; V driver/back view | | Inventory | I toggle; Escape close; choose items/weapons in the inventory panel | | Combat | Left mouse fire/punch; right mouse aim; R reload; Tab cycle weapons; 1..9 select; 0 holster; P punch; K kick | | View | F11 fullscreen; Escape releases gameplay/editor mode | ## Explicit types and stable IDs Declare every gameplay behavior with `api.interaction(id, options)` after constructing its owner, then call `await api.ready()`. Set `type` and all required fields from the table below. Object names are display labels; use stable IDs for behavior references. A geometric `plane` remains a flat surface unless its owner is explicitly declared as an aircraft. IDs are stable references within a scene scope. Use unique IDs of 1–100 letters, digits, underscores or hyphens. `owner`, `pairedDoor`, `pairedSeat` and `target` refer to IDs, never display names or prefixes. Keep each moving part's visual children under that owner. Do not combine interactive owners into static geometry. | Behavior | Explicit declaration | |---|---| | Vehicle root | `{type:'vehicle', kind:'car'}`; kinds: `car`, `aircraft`, `helicopter`, `boat` | | Driver seat | `{type:'seat', owner:'vehicleId', role:'driver', pairedDoor:'doorId'}` | | Passenger seat | `{type:'seat', owner:'vehicleId', role:'passenger', pairedDoor:'doorId'}` | | Vehicle door | `{type:'door', owner:'vehicleId', motion:'swivel_left', side:'left', pairedSeat:'seatId'}` | | Building door | `{type:'door', motion:'swivel_left'}` | | Sectional garage door | `{type:'garageDoor', motion:'sectional', panels:['lower','upper'], height:2.6, trackRadius:.35, duration:3, direction:1}` on a Group | | Drawer | `{type:'drawer', motion:'front'}` | | Wheel | `{type:'wheel', owner:'vehicleId', axle:'front', side:'left'}` | | Steering | `{type:'steering', owner:'vehicleId'}` | | Other vehicle part | `{type:'attachment', owner:'vehicleId'}` | | Action button | `{type:'button', action:{type:'visible', target:'lampId', value:true}}` | | Dispenser outlet | `{type:'nozzle'}` | | Dispenser control | `{type:'button', action:'dispense', target:'nozzleId'}` | | Collectible | `{type:'pickup', itemType:'goldbar', quantity:1}` | | Weapon pickup | `{type:'pickup', weaponType:'pistol'}`; weapons: `pistol`, `rifle`, `paintball` | `label` is optional. Geometry types and behavior types are separate: use `api.add(id,'plane',...)` for geometry and `api.interaction(id,{type:'vehicle',kind:'aircraft'})` for an aircraft root. Use the declaration table for all interaction types; do not rely on descriptive names to specify behavior. ## Doors, drawers and buttons Door motion is explicit: `swivel_left`/`swivel_right` rotates around local Y at the left/right X edge; `left`/`right` slides −/+X; `up`/`down` slides +/−Y; `front`/`back` slides +/−Z. Drawers use the six sliding directions. Doors and drawers must be real Mesh owners, authored closed. Frames remain separate. Attach handles and panels to the moving owner. `userData.doorAngle` sets swivel degrees; `userData.doorTravel` sets door slide distance. Sliding doors have hard stops at their authored closed and open positions; closing cannot travel past the closed position. Author paired leaves so their inner edges meet (or leave a small seal gap) in the initial closed pose. The runtime does not correct geometry already overlapping in that authored pose. Slide distances are in the owner's parent coordinate system; use positive `userData.doorTravel`. Re-registering a live door preserves its motion state. ### Sectional garage doors Use `api.interaction(id,{type:'garageDoor',motion:'sectional',panels:[...],height,trackRadius,duration,direction:1})`. This is an approved interaction declaration, not a custom animation callback. The owner must be an `api.group` positioned at the **bottom center** of the closed opening. Local X spans the opening, local Y points up, and `direction:1` curves into local +Z (`-1` curves into −Z). Rotate the owner group to orient the whole garage. Declare 2–32 separate Mesh panels as direct children, listed bottom to top. For N panels, pitch is `height/N`; panel i starts at `[0,(i+.5)*pitch,0]`, with identity rotation and unit scale. Use centered geometry and leave a small seam between sections. Parent each window, trim and handle to its section. Keep the fixed frame outside this group. Never merge the panels or their owner into static geometry, and do not declare separate interactions on panels. `height` is 0.2–20 m; `trackRadius` is 0.05–5 m; `duration` is 0.2–30 seconds (3 recommended). Reserve ceiling clearance above `height+trackRadius`, plus panel thickness, and sufficient inward track depth for the entire door. Panels rise, follow a quarter-circle track and finish horizontal. E or preview click toggles the entire door independently of neighboring bays. Idle doors do no motion work. Closing conservatively reopens if the opening's world bounding box intersects the player (in Play) or a registered vehicle; this is not a general-purpose obstacle sensor for arbitrary props. ```js const api=window.NeomeScene.create({THREE,root,add}); api.group('garage01',{position:[0,0,0]}); api.add('garageLower','box',{parent:'garage01',size:[3,1.29,.08],position:[0,.65,0]}); api.add('garageUpper','box',{parent:'garage01',size:[3,1.29,.08],position:[0,1.95,0]}); api.interaction('garage01',{type:'garageDoor',motion:'sectional',panels:['garageLower','garageUpper'],height:2.6,trackRadius:.5,duration:3,direction:1}); await api.ready(); ``` Prefer 6–8 shorter sections for a tighter curve and better visual clearance. Test open, close, reversal, player/vehicle obstruction and ceiling clearance in Play after export/reload. A screenshot alone does not verify gameplay. For a custom hinge, use hinge-local geometry, set `userData.hingeReady:true`, and place the Mesh origin at the hinge. Otherwise centered geometry receives the engine's automatic hinge conversion. Keep a clear doorway and swept path on both sides. Buttons use predefined `visible`, `transform` or `animate` actions. Their target IDs must be in the same parent container. Dispensers use an explicit nozzle ID, never a matching name prefix. `userData.liquidColor` sets liquid color. ```js const api=window.NeomeScene.create({THREE,root,add}); api.add('gold01','box',{size:[.25,.08,.12],color:'#d9ac35'}); api.interaction('gold01',{type:'pickup',itemType:'goldbar',label:'Gold Bar',quantity:1,valueEach:75000}); api.add('storage01','box',{size:[.6,.18,.4]}); api.interaction('storage01',{type:'drawer',motion:'front',label:'Drawer'}); await api.ready(); ``` ## Vehicles and seats Declare one vehicle root and put its visual body and parts beneath it. Declare every seat's `owner` and `role`; only `role:'driver'` grants control. Declare exactly one driver per vehicle. Passenger seats never become drivers because of a name or proximity to a steering wheel. Car wheels require explicit `axle:'front'|'rear'` and `side:'left'|'right'`; each wheel owner contains the complete tire/rim/hub assembly. Cars face local −Z; X is the side axis. Preserve part pivots and hierarchy. Pair a seat with `pairedDoor:'doorId'`; a door can declare the reciprocal `pairedSeat:'seatId'`. Both must belong to the same vehicle. Car entry/exit needs a paired door. Open aircraft/boat seats may omit a paired door. Hinge side and driver/passenger role are independent. `seatAnchor:[x,y,z]` is a vehicle-local hip point and `seatYaw` is degrees. Door opening direction and angle remain explicit metadata. ```js const api=window.NeomeScene.create({THREE,root,add}); api.group('car01'); api.interaction('car01',{type:'vehicle',kind:'car'}); api.group('driver01',{parent:'car01'}); api.interaction('driver01',{type:'seat',owner:'car01',role:'driver',pairedDoor:'entry01'}); api.add('entry01','box',{parent:'car01',size:[.08,1.1,1.2]}); api.interaction('entry01',{type:'door',owner:'car01',motion:'swivel_left',side:'left',pairedSeat:'driver01'}); ``` This illustrates declarations, not a complete drivable car: add the body geometry, four declared wheels, steering and appropriate transforms. Keep moving parts out of static combines and far bakes. Optimization must preserve behavior metadata, IDs, references and moving hierarchy. Vehicle roots, seats and controls require explicit metadata and stable ID references. Use the declarations above for every authored vehicle and part. Complete all required fields before saving or optimizing. Test driver and passenger entry, each paired door, F exit, controls and collision clearance after export/reload. Screenshots and a successful render do not verify gameplay. ## Elevators and subway Elevator source doors use `{type:'elevator',role:'landingDoor',owner:'liftGroupId',floor:0}` on Mesh owners, with an existing group ID for the system. Keep them axis-aligned in an unrotated, unit-scale host. Mark floor surfaces using `userData.surfaceRole:'floor'|'slab'|'ground'`; mark roofs/ceilings explicitly. The engine also uses geometry bounds to discover horizontal levels. Leave a real shaft and clear door pockets. `userData.elevatorSize:'compact'` requests a 2 m cage. Generated elevator cages, doors and controls carry system IDs, roles and floor indices. Preserve these fields and the hierarchy; never infer them from names or add a second animation loop. Outside controls call the cage; cabin controls select a floor. Subway markers use the explicit `subway` construction type: ```js add('station_a','subway',{position:[-40,0,0],stationName:'West',depth:8.5,trainSpeed:9}); add('station_b','subway',{position:[40,0,0],stationName:'East',depth:8.5,trainSpeed:9}); ``` Use 2–3 stations at least 24 m apart. Route order follows `userData.subwayOrder`, or construction order when no order is set; display names do not control it. The engine generates stations, entrances, tunnels, train and controls from marker metadata. Preserve generated system metadata and test travel in Play. ## Weapon grip attachment A pistol needs only two weapon metadata fields: `type` and `gripPoint`. ```js api.group('my_pistol', { position: [0,1,0], // World pickup only. userData: {neomeWeapon: {type:'pistol', gripPoint:[x,y,z]}} }); ``` Build the gun at its intended physical size in metres, muzzle along -Z and top along +Y. `gripPoint` is the upper back-of-grip point in the pickup owner's local coordinates, including all internal model transforms. Bake the gun's size into its internal construction. The equipped path uses the authored physical size directly. The shared attachment code handles hand placement, orientation, thumb contact, the approved equipped hand pose, and restoration on unequip. The existing arm aiming system remains responsible for arm movement; the grip helper does not rotate the arm or wrist. Creators do not provide `handPosition`, `handRotation`, `attachScale`, view transforms/scales, bone names, arm rotations or finger rotations. Player world rotation is inherited from the hand. World pickup position/rotation/scale controls only the pickup; it does not control the equipped gun. Pickup declaration, cloning, export and submission must preserve `type` and `gripPoint`. Export standard pistol metadata with only those two fields; shared defaults provide the remaining behavior. Do not guess or auto-detect a grip from arbitrary geometry. The default Neo hand uses the approved corrections only while equipped; its normal hand is restored afterwards. Validate the actual equipped player and NPC paths before submitting. ## Inventory, collectibles and weapons Prefer api.interaction for pickups. Existing explicit inventory:true options also declare collectible behavior; no name suffix is required: ```js add('gold_01_pickup','box',{size:[.25,.08,.12],position:[0,1,0],color:'#d9ac35', inventory:true,itemType:'goldbar',label:'Gold Bar',quantity:1,valueEach:75000}); add('cash_01_pickup','box',{size:[.15,.02,.07],position:[.4,1,0],color:'#2fbd68', inventory:true,itemType:'money',amount:100}); ``` Options: itemType,label,quantity,amount,valueEach/value,icon,stackable,metadata,pickupCollision. Use unique stable IDs; collected IDs hide their world object. E/click collects. Stackable items accumulate quantity; money increases cash; wealth = cash + item values. The full game saves inventory in browser storage; Connect's inventory is frame-local. add(id,'money',options) and add(id,'goldbar',options) request the game's default GLB models; those external model paths are not available in Connect unless the host supplies them. Prefer the explicit visible mesh examples for self-contained scenes. Inventory registration, persistence and state changes are host-managed. Declare pickups through approved construction and interaction parameters. Weapons: pistol, rifle or paintball. An authored add mesh with weaponType:'pistol'|'rifle' on an owner named PREFIX_pickup explicitly registers an independent weapon pickup; the engine creates its hidden equip copy. Preserve weapon metadata when exporting and verify host registration on reload. weapon metadata/registration needs a host with CombatSystem initialized. A full-game procedural weapon can use: ```js addGun('pistol_01_pickup',{type:'pistol',position:[0,1,0],acquired:false,pickup:true}); ``` Call addGun directly; do not inspect or edit its returned engine record. Its host-only options.object accepts a live object and is not a submission parameter. For custom pickup geometry, use api.add and api.interaction with a weaponType instead. Options include damage,fireRate,magSize,ammo,reserve,reloadTime,range,spread,automatic,recoil,muzzlePosition. addGun's default acquired is true; explicitly set false for a world pickup. E acquires; 1..9/Tab selects; 0 holsters; left fire/right aim/R reload. Full game includes ammunition, hit/damage feedback, health, death, recoil and remote shots. Target registration and combat state are host-managed; authored targets can declare damageable/health metadata. ## Terrain, roads and cities Declare named images: height (grayscale elevations), ground (color terrain texture), roads (city road mask), cityRoad (road surface atlas), raceRoute (race route mask) and raceBanner (banner image). One call creates a complete terrain group using the same road tracing, grading, excavation and junction geometry as the game city generator: ```js const api=window.NeomeScene.create({THREE,root,add}); api.add('world', 'terrain', { heightmap: 'height', texture: 'ground', roads: 'roads', roadTexture: 'cityRoad', size: [200, 20, 200], resolution: 128, whiteHeight: 0, blackHeight: 20, roadWidth: 8, sidewalkWidth: 2, lights: true, race: { roads: 'raceRoute', banner: 'raceBanner', start: [0, 0, 30], finish: [0, 0, -30] }, vertexColors: false, color: '#ffffff' }); await api.ready(); ``` height/ground/roads are declared asset names, not remote URLs. The road image is a MASK: white or other non-black roads on pure black; transparent background is also supported. It is traced into real roads, curves, branches, junctions and sidewalks, rather than pasted as a flat overlay. Roads grade their decks and excavate terrain corridors; image-derived elevations remain outside those corridors. api.add returns an object ID; await api.ready() to finish terrain preparation inside the asynchronous construction wrapper. Connect also tracks readiness: wait for the final successful WORLD_RENDER before capture/export. Do not inspect the generated Group or its ready property. This API reads no World-panel controls or external catalogs and makes no extra downloads. Reusing the ID replaces the whole terrain; remove(id)/clear() removes it. Multiple terrain calls are independent. position/rotation/scale apply to the complete group once; keep gameplay terrain horizontal, using Y rotation for map orientation. size is [width,maxHeight,depth], with positive width/depth and nonnegative height. Heightmap defaults WHITE LOW (whiteHeight:0), BLACK HIGH (blackHeight:size[1]); swap explicit values for white-high maps. resolution is bounded by the engine cap; widthSegments/depthSegments can lower either axis. heightAtlas crops pixels; smoothHeightmap toggles interpolation; heightGamma shapes relief. colorLow/Middle/High controls elevation coloring with vertexColors:true. textureRepeat tiles the ground; material:'standard' enables lighting instead of default basic/unlit. collision:false disables terrain support sampling. The generated road colliders remain separate. roadWidth defaults to 8 m (>=.5); sidewalkWidth defaults to 2 m (>=0), with 0 disabling sidewalks. Optional roadTexture names a declared surface atlas with sidewalk/asphalt/sidewalk in three equal image bands, as used by the city generator. Without it, the engine creates a repeatable asphalt/curb/center-line texture. Omitting both roads and race produces terrain only; an empty city mask produces no city roads. A race route must contain usable roads. The host stores generated local paths/junctions in terrainRoadNetwork metadata; these are host diagnostics, not engine-object reads for construction scripts. Apply group transforms through approved calls before the host uses world-space traffic routes. lights defaults to true and uses the city generator's street lamps and junction traffic signals; false disables both. race is optional: race.roads names a route mask using the same black/transparent-background format and full-terrain coordinates as roads; race.banner names a declared image. Both masks are combined into one road graph, with one shared roadTexture and sidewalk width: overlapping roads are not duplicated. Race fencing uses the existing 2.4 m city guardrail module shape and placement, with the banner on its promotion panels. Optional race.guardrailTexture names the existing guardrail atlas image (512 x 512 concrete/steel/chainlink regions); without it the same geometry uses neutral materials and a generated transparent chainlink grid. Start and finish gates also show the banner. No GLB or remote asset downloads are required. race.start/finish are optional finite [x,y,z] local coordinates: they are projected to the nearest route segment, and gate elevation/heading follows the graded road deck (input Y is ignored). Without them the first traced route's endpoints are used; a loop can share a start/finish point. The host stores local paths and resolved start/finish transforms in terrainRace metadata (also terrainRoadNetwork.race). This creates the track, barriers, banners and markers; race timing, checkpoints, vehicles and player spawning are explicit host gameplay operations. Buildings and other city objects are placed explicitly with add(). For heightmap-only meshes, use add('terrain','heightmap',{heightmap:'height',texture:'ground',size:[200,20,200],resolution:128}). For manual roads, create mesh geometry that follows or cuts the terrain; a flat box is appropriate only over a flat corridor. Keep surfaces slightly above their support and connected at junctions. The full-game World panel generates cities from height, color, road and race images using its UI settings. Terrain editing, traffic and city lifecycle are host-managed; use approved terrain calls in construction scripts. ## Portable pool water Use `api.poolWater('water',{parent:'pool',profile:[[-4,-2],[4,-2],[4,2],[-4,2]],level:.1,depth:1.5,groundOpening:true,water:{color:'#45b7cf',deepColor:'#12627d',waveAmplitude:.008,opacity:.48,clarity:.9}})` after declaring its parent. The profile is a simple local XZ outline with 3–64 points. This approved call preserves a small metadata carrier through NEO export and optimization. The host creates a separate live water surface for each placement, follows rotation/scale/visibility, and removes it when that pool unloads. Thin flat ground/deck geometry crossing the opening is cut to expose the basin; collider sources are refreshed. Tall or non-flat terrain volumes require explicit excavation. Build tiled basin walls, a floor 1.5m below the waterline and submerged steps with approved geometry. The existing addWater global remains a host environment effect; poolWater is the portable asset form. No shader, renderer, timer or mesh inspection belongs in submitted source. ## Grass, water, weather, physics and sound These approved calls configure live host effects. Host environment state, physics, sound and game catalogs are not automatically packaged into an object NEO; do not assume a submitted prop recreates the surrounding game environment. Helpers are already initialized: do not create a second renderer, update loop or physics world. ```js addGrass('lawn',{size:[20,20],position:[0,.02,0],density:2,count:800, maxBlades:2000,bladeHeight:.5,bladeWidth:.05,color:'#287a32',tipColor:'#78b84d',windStrength:1}); addWater('pond',{size:[20,15],position:[25,-.2,0],color:'#1682a8',deepColor:'#042b42', quality:'medium',waveAmplitude:.12,waveLength:5,waveSpeed:1,opacity:.82}); ``` removeGrass(id)/removeWater(id) removes its field/surface; reusing an ID replaces it. Grass is instanced decorative geometry, not collision, and uses random placement; flat position/level does not conform it to a heightmap. Water size is [width,depth], position includes the actual level. Options include level,roughness,clarity,reflection,refraction,directionDegrees,flowSpeed,density,buoyancy,linearDrag,angularDrag,foam,caustics,underwater,boatWake,wakeLength/wakeWidth/wakeFoam. Waves, boat buoyancy/drag/wakes and wet surfaces are engine effects. window.setWind({enabled:true,directionDegrees:45,speed:5,gustStrength:3}), setRain({enabled:true,intensity:.5}), setFog({enabled:true,color:'#9fb0ba',density:.008}), setSky({timeOfDay:12,cloudCover:.15}), setLightning({enabled:true,frequency:2}), setWeatherPreset(name), setWaterPreset(name). Choose supported preset names from the host UI. Environment presets and state are host diagnostics, not an authoring API to inspect from submitted scripts; approved setters merge options. Rain/wind/sky options include wetness, clouds, storm audio and physical forces. add(...,{wind:true}) registers deformable foliage. The host manages vehicle physics, sound playback, inventory persistence, NPCs and multiplayer. Preserve finite geometry, ground clearance and moving hierarchies; let the host register them. Connect pairing does not create multiplayer players or game catalogs. Audio requires a user gesture. ## Image settings and export Original scenes use the current creation UI settings. Color images, including atlases, use the selected resolution and WebP quality; Original keeps their resolution. Heightmaps, road masks and normal/roughness/metalness/occlusion maps preserve pixel values with lossless storage. Higher-bit-depth or interlaced PNG data stays unchanged. Identical image resources share one copy. Optimization preserves existing images; aerial ground capture uses the selected floor resolution and quality. The browser captures six views from the final scene and saves the preview separately from the validated NEO. Scene images remain in the NEO or in verified declared image dependencies. GLB export supports geometry, materials, textures, skeletons, morphs and clips; it is a download control, not a NEO submission method. Follow the load/submit workflow above. ## Optimization output Only originals can be optimized. To revise a scene, edit and submit its original source, then optimize that original again. Optimizing the same original again replaces its existing optimized variant; an optimized variant cannot itself be optimized. Continue referencing optimized shared assets without rebuilding or flattening them. Optimize the approved-call original through the browser UI. Each independently placeable asset needs its own scene entry so it gets its own far representation. Interior/exterior splitting preserves approved construction, materials, UVs and interactions, using mesh/triangle selection parameters where applicable. Shared assets retain explicit approved asset calls, their selected entries and transforms; the optimizer may rewrite those calls to assign them to the appropriate split. It must not replace construction with raw generated mesh buffers. Exterior baking includes visible exterior shared assets, including storefront doors; interior furniture remains in the interior split and does not enter the exterior far representation. When there is no interior, the exterior retains the original appearance. All ordinary asset bake routes use the same selection policy: near-flat objects become one 16-by-16 image; other objects become simplified, untextured vertex-color shapes. The explicit aerialBake: true marker enables the advanced projection/silhouette bake for marked buildings such as hotels; legacy texturedBake flags alone do not enable it. The near-flat rule takes priority. Explicitly marked aerial ground keeps its separate capture and overlay workflow described below. Rigged and animated assets retain their runtime geometry and animation data. View BAKE displays the chosen far representation. Existing source image files are retained during optimization. Re-optimize the original to replace an older bake; saved optimized variants do not update automatically. ## Bounded scene feedback waiting The original client must stay alive with interactive stdin. A separate helper reads one queued scene feedback event without opening another socket: ```bash node client.cjs --wait --uid COMPLETE_UID --root "$PWD" --timeout 60 ``` Use this only when awaiting world_render or world_submit; use a bounded wait. On WORKSPACE_RECONNECTING, pause connected operations and keep the original client running; it automatically authenticates the same approved browser. Resume after Workspace reconnected securely and successful WORLD_RENDER. workspace_disconnected means an explicit or final stop. Restarting the client or reloading the browser requires manual pairing again. NEOME_CONNECT_INBOX_ROOT may select an existing writable private inbox folder for both processes without changing the shared workspace root. ## Approved terrain construction Keep `scene.txt` as a construction script and declare its images. Register textures with `window.NeomeScene.texture(name,path)` and create terrain with `api.add(id,'terrain',options)`. Terrain accepts the documented heightmap, texture, roads, roadTexture, size, resolution, roadWidth, sidewalkWidth, lights and race parameters. Await `api.ready()` to finish terrain preparation. Do not submit generated terrain/road buffers or a serialized scene. ## Additional approved geometry functions Use `api.geometry(id,definition,options)` to create an object through the approved geometry builder and receive its ID. Geometry definitions are that function's supported parameters; they do not describe a complete serialized scene. `api.ready()` waits for asynchronous construction. Use approved transforms and modeling functions to change geometry; direct Three.js constructors or live object edits are rejected. The archive manifest remains version 1. Every executable construction script, including bundled and hosted dependencies, is validated under `neome-approved-functions-v1`. Approved geometry parameters also include supported primitive geometry names with `args`, `merge` with parts containing geometry and 16-number matrices (`textured:true` preserves UVs), and `surfaceCleanup` with a recorded cleanup plan. These are inputs to api.geometry/api.surface, not alternate whole-scene formats. Approved geometry types: `foliageSurface`, `polygonPyramid`, `bakedHeightfield`, `lineSegmentsMerge`, `polyline`, `polygonMesh` (authored points and triangle faces), `palmTrunk`, `palmFronds`, `parametricSurface`, `seededTerrain`, `loft`, `branchTube`, `ribbon`, `coloredMerge`, `repeat`, `deform`, and `engineGeometry`. Use the documented primitive, transform, UV and modeling function calls with supported parameters. ```js const api=window.NeomeScene.create({THREE,root,add}); api.geometry('leaves',{ type: 'palmFronds', params: { crown: [0.65, 5.2, 0], segments: 10 } }); api.geometry('hill',{ type: 'seededTerrain', params: { size: [80, 10, 80], seed: 42, amplitude: 6, frequency: 0.06, octaves: 4, uSegments: 64, vSegments: 64 } }); ``` `parametricSurface.params.surface`: plane, wave, ellipsoid, dome, torus, helicoid; use size, uSegments/vSegments and optional amplitude/frequency/turns. `loft.params.sections` contains equal-size rings of [x,y,z] points; cap is optional. `branchTube`/`ribbon` use path points and segments, plus radius/endRadius/radialSegments or width. `polygonMesh` uses vertices, triangle faces and optional per-point UV pairs. `coloredMerge.parts` contains geometry, a 16-number matrix and optional #rrggbb color. `repeat` takes geometry plus count, step, rotationStep and scale. `deform` takes geometry plus mode (twist, taper, bend, mirror, noise), axis (x/y/z), range, amount and optional seed. `engineGeometry` takes shape (mesh, loft, smoothloft, bezierpatch, body, wing, extrude, sweep), name and the existing approved shape options. Child geometry definitions may be ordinary parameter objects passed to approved geometry functions. Export preserves those calls. Additional geometry parameters: `polyline.params.points` is a sequence of [x,y,z] points; display it through an appropriate Line surface. `lineSegmentsMerge.parts` contains polyline geometry and a 16-number matrix per part. `polygonPyramid.params` contains a footprint of [x,z] points, baseHeight and an [x,y,z] apex. `foliageSurface.params.kind` is leaf or palmFrond, with rect:[x,y,w,h] and atlasSize:[w,h]. `bakedHeightfield.params` contains bounds:{min,max}, resolution 1–16 and a resolution-squared heights array; null cells are empty. These are parameters to the same approved geometry function. Palm defaults match the supplied palm builder. palmTrunk parameters: height, lean, sway, baseRadius, topRadius, segments, radialSegments, groundRadius. palmFronds parameters: crown, segments, angleStep, optional colors and layers; each layer has count, length, lengthStep, width, rise, drop and angleOffset. Random-looking terrain/noise requires an explicit deterministic seed. Unknown types, fields, callbacks and excessive counts are rejected. Use these builders instead of manually filling generated BufferGeometry attributes. Self-contained city assemblies reuse bundled approved-call scripts. Each part has an isolated object/material namespace and an optional metre position, radian rotation, or scale. All referenced .txt files must be bundled and validate independently. The runtime allows 100,000 referenced scene instances per assembly load, with a bundled scene-chain depth limit of 32. The server separately limits each script to 256 discovered hosted dependencies. These are safety limits, not recommended scene sizes. Cyclic dependencies are rejected. Reusable asset packs: Put each independently placeable prop, tree, or flower in its own folder with its own scene.txt, centered at its placement origin. The root scene assembles these scenes for preview. Keep each asset as a separate scene dependency so optimization produces an individual bake. Share textures where appropriate. ```js await window.NeomeScene.assemble({"version":1,"name":"City","items":[{"id":"hotel","path":"files/hotel/scene.txt","position":[100,0,-200]}]}); ``` A hosted assembly item may include `sceneEntry`, the bundled TXT path of an individual asset inside that NEO. Optional `instances` is a list of placements, each with `position`, `rotation` (radians), and/or `scale`; the selected asset is placed once for each entry, using its existing bake records. For example: `{id:"trees",url:"https://gpt.neome.com/validate/uploads/?file=_optimized.neo",sceneEntry:"files/trees/olive/scene.txt",instances:[{position:[10,0,20]},{position:[30,0,40]}]}`. Export preserves the hosted URL, selected entry and placements. Omitting these fields loads the complete hosted NEO as before. `texturedBake` and `aerialBake` are independent boolean settings. For non-ground assets: | texturedBake | aerialBake | Result | | --- | --- | --- | | true | true | Textured silhouette-preserving far representation, retained to maximum render distance. Use both for a hotel that should stay visible. | | true | false or omitted | Textured far representation, hidden at the normal aerial cutoff. | | false or omitted | true | Simplified untextured far representation, retained to maximum render distance. | | false or omitted | false or omitted | Simplified untextured far representation, normal aerial cutoff. | Declare `userData:{texturedBake:true,aerialBake:true}` on the original asset's root owner group through an approved group/nodeState call before optimizing it. For bundled assemblies, `texturedBake` on an assembly item chooses its scene's bake mode; one independently placeable asset should have its own scene entry. `aerialBake` on the assembly item controls distance retention. A nearer explicit placement flag overrides a stored asset default. Asset placement also accepts these booleans as options. Optimize the original asset with texturedBake enabled before referencing its optimized package elsewhere. Marking an already-hosted simplified package does not generate a new textured bake inside that dependency; re-optimize the asset itself. Ground keeps its ground-image/cutting behavior and ignores texturedBake. `aerialBake: true` has two uses. Set it on the assembly item that owns the asset: | Assembly item | Meaning of `aerialBake: true` | | --- | --- | | Hotel or other non-ground asset | Keep its existing optimized far representation visible beyond the UI aerial cutoff, up to the UI maximum render distance. This flag does not create another hotel image. | | Ground | During optimization, create the level 2 aerial ground image and automatically cut transparent footprints beneath non-ground items marked `aerialBake: true` where their geometry meets the ground. | ```js await window.NeomeScene.assemble({version:1,name:"City",items:[ {id:"scene_ground",path:"files/ground/scene.txt",aerialBake:true}, {id:"hotel",path:"files/hotel/scene.txt",position:[100,0,-200],texturedBake:true,aerialBake:true} ]}); ``` Ground must have a ground/terrain ID or scene path, or an explicit `userData.aerialBakeRole: "ground"` on its owner. The same flag on a hotel does not identify it as ground. Assembly flags apply to the owned asset and its placements; runtime retention uses the nearest flag on the optimized LOD owner or its ancestors. A flag on an arbitrary decorative child does not mark the whole hotel. Put the flag on the assembly item as shown. With distance LOD enabled, runtime distances follow the current UI settings. Aerial distance is clamped to at least High texture distance. Inside High texture distance, use full detail. From High texture distance to Aerial distance, use the existing optimized far representation when available. At and beyond Aerial distance, non-ground assets with `aerialBake: true` keep that representation until maximum render distance; unmarked assets are hidden. Beyond maximum render distance, these representations are hidden. Do not hardcode distance bands. The flag requires an optimized far representation to retain; marking an original scene does not create one. Build and submit the original approved-call scene first, then optimize it through the browser UI. Ground optimization produces one top-down WebP using the selected floor texture resolution and quality, represented by a two-triangle textured surface in the optimized NEO. The cuts are transparency in this image, not holes cut into the original ground geometry. Rotated/scaled placements and instances are included. Existing gaps and placement are preserved; other scene items and hosted asset bakes are preserved. An unchanged optimized ground can reuse its existing image. The original NEO is unchanged. After editing the layout, optimize the original editable NEO so footprints are rebuilt from its uncut ground. `aerialOverlay: true` has a separate purpose: include that item's top-down appearance in the ground image during optimization. `aerialBake: true` alone retains a non-ground item's own far representation and cuts its footprint from the ground image; it does not photograph that item into the ground. Unmarked additions are not captured. All three flags—texturedBake, aerialBake and aerialOverlay—are optional booleans and survive export. ## Shared texture packages (image-only dependencies) Approved construction scripts may register images from published image-only packages. Keep the dependency declarations in the consumer manifest and register its virtual image path with `NeomeScene.texture`. Loading these images never executes the dependency's scene. Declare the dependency and virtual image aliases in the consumer `manifest.json`: ```json { "texturePackages": [{ "id": "world", "url": "https://gpt.neome.com/validate/uploads/91c461dac3c6e31bd8f5effefec15b67f9d5e0622af61c62af757a6e393c551b.neo" }], "textureReferences": [{ "path": "shared-textures/world/furniture-1.png", "package": "world", "resource": "files/textures/03-furniture-fabrics-1.png" }] } ``` The consumer calls `await window.NeomeScene.texture('fabric','shared-textures/world/furniture-1.png');`. The virtual path is not a bundled resource: do not put it in manifest.resources or embed a dummy image. Use the atlas catalog's uvRect through approved geometry UV operations; do not crop each tile into a separate texture if full-atlas GPU sharing is required. Only supported published NEOME package URLs are accepted (maximum 16 packages and 1024 image aliases). SHA-256 values are optional content metadata, not file acceptance checks. Hash-based filenames identify storage locations and support deduplication; changing the content at the same URL does not require changing consumer hotels. The dependency's scene is never executed. A texture request lazily acquires the archive through the shared package cache and decodes only the requested image. Concurrent consumers reuse the in-flight download and decoded Source. Matching sampler settings share the renderer's GPU storage; UV offsets remain independent. All consumers must use consistent colour space, filters, wrap, format and type for a single GPU allocation. Texture allocation and sampler sharing are engine responsibilities. Use approved texture and UV functions rather than copying pixels in submitted source. Unloading one asset releases only its references. The final texture user frees the decoded image/GPU storage; cached archive bytes can remain for later reuse. Memory pressure can evict an unused archive, so "download once" means once while cached/in use, not permanent unlimited retention. Failed requests can retry, and cancellation of one consumer does not cancel another consumer's request. Keep the consumer manifest when exporting, using the eligible preserved-package workflow described above. Flattening a scene can lose the original dependency declaration. Do not put a virtual image alias in `manifest.resources` or add a dummy file. A self-contained fallback uses actual image files and removes the conflicting external alias declarations; disclose that the images are now bundled. If a virtual texture cannot be loaded, check the exact dependency URL, image resource path and alias. Preserve a reference-only requirement throughout retries; correct the declared image reference through the same approved texture function. ## Scene-placement far distance Set `farDistance:1000` on a hosted road placement in the world scene: `await window.NeomeScene.asset(roadUrl,{sceneEntry:"files/straight/scene.txt",deformable:true,farDistance:1000});` Hosted `assemble` items support the same numeric option. The engine does not read this setting from the asset catalog or package manifest. Detailed geometry appears within High texture distance; the optimized bake appears above High texture distance through the larger of High texture distance and farDistance, capped by Render distance. Default asset loading is unchanged when the placement omits farDistance. `bake:false` disables the far representation. Optimize road packs normally to generate their near-flat bake. Curved roads must curve both tiers; keep each curvePath call within its existing 256-mesh budget. Capture/export remains independent of live distance visibility. ## Starship destination travel (solo game host) The existing NPC form of `walkto({target,position,...})` is unchanged. A Starship instead uses: ```js await window.NeomeScene.walkto({target:'starship',destination:'moon_base_02',wait:true}); ``` This is a running-game operation. Offline export preserves it without starting travel. `wait:true` resolves `true` after touchdown and collision readiness, or `false` on reset/unload. Setup failures reject before launch. Connected multiplayer currently rejects with `MULTIPLAYER_UNAVAILABLE`; its separate authority service must be implemented first. Declare the ship's data before readiness: ```js api.nodeState('starship',{userData:{starshipTravel:{version:1,world:'vegas',parts:{hatch:'entry_door',arm:'access_arm',flames:'flames'},cabin:{min:[-3.5,36,-3.5],max:[3.5,39.4,4]}}}}); api.nodeState('access_arm',{userData:{starshipPart:{owner:'starship',role:'arm'}}}); api.interaction('launch_button',{type:'button',label:'Go to the Moon',action:{type:'walkto',target:'starship',destination:'moon_base_02'}}); ``` The ship root owns all moving ship visuals, cabin, hatch and exhaust. The tower arm stays outside it in the same declared scene scope. A travel button may reference the ship across nested parents in that scope. Do not combine the moving owners into static geometry. Flame geometry and materials remain authored asset resources; the host advances their effect without asset callbacks or timers. Landing bases declare stable pad IDs and actual authored placements: ```js api.group('moon_base_02',{position:[1000,0,0],userData:{starshipPad:{version:1,world:'moon',approachHeight:80,clearanceRadius:12}}}); ``` The position above is an illustrative local test placement, not a deployed Moon base. The pad origin is the final ship-root origin; `shipOriginOffset:[x,y,z]` optionally adjusts it. Pads must be upright, uniquely identified and outside the travelling ship. Optional `arm` references an access arm in the pad's own scene scope. Routes are restricted to Vegas ↔ Moon. Host configuration `/biffvegas/starship-routes.json` can list destinations with `id`, `world` and an optional verified published `url` plus explicit `placement` and optional `rotation`; configured worlds must agree with pad metadata. No Moon base or ship placement is provisioned by the API itself. The engine prepares moving collision geometry, closes and locks the hatch, ignites exhaust, retracts the departure arm, ascends, transfers to the registered approach, descends and lands. The local player inside the declared cabin is held relative to the ship during travel and released after landing. A `starship-travel-world` host event identifies the destination world for any additional world/environment presentation. Free-flight physics, cross-server passenger handoff and server-authoritative multiplayer are outside this initial solo controller. ## Spherical Moon player gravity A spherical Moon can declare a local-player field on its center owner group: ```js api.group('moon_planet',{position:[0,-512,0],userData:{radialGravity:{version:1,world:'moon',radius:512,strength:1.62,altitude:250}}}); ``` Radius and altitude are metres, strength is metres per second squared. The group origin is the center of gravity; its parent transforms determine the actual world center. Use positive uniform scaling. Gravity points toward the center inside radius+altitude. Declare the visible sphere and its static trimesh collider separately, with `userData:{forceTrimesh:true}` on the spherical surface. The host scans fields on scene registration and releases them on unload. No asset callbacks or render loops are needed. Local player movement uses a tangent frame, a radially oriented Rapier capsule, inward gravity, outward jump, and a camera/character aligned to local up. Vegas keeps its original Y-up movement. The generated editor floor is excluded from Moon character collision and hidden in Moon views. This first implementation applies to local player walking; NPC navigation, vehicles, ragdolls and multiplayer orientation remain their existing systems and are not advertised as spherical-world support. Game testing on WebGL hardware is required before considering the full spherical gameplay verified. ## Visibility is an explicit placement setting Use `bake:true` on a hosted Moon, ground or terrain placement to retain a distant representation within Render distance. The engine uses a packaged bake when present; otherwise it retains original geometry. Catalog categories and names do not grant or deny distant visibility. `bake:false` and omitted flags use the existing High texture distance geometry gate. Each hosted child resolves its own flag. Gallery previews retain the selected package with `bake:true`. Frustum culling, texture detail controls and Render distance still apply. ## Sphere optimization Declare `userData:{bake:true,sphereBake:true}` on an original planet's root owner group before submitting it. Optimize that new original normally. `sphereBake` takes priority over flat and advanced projection baking: original sphere geometry, UVs, transforms, materials, atmosphere and collision inputs are retained and compiled normally. No projection/flat far representation is generated. Color/emissive textures larger than 1024 pixels receive an aspect-preserving 1K WebP variant; the original near texture bytes remain unchanged (including a 4K original). Data maps stay unchanged. Mipmaps and the existing texture-distance hysteresis select resolution on the same mesh. Each hosted planet placement still uses `bake:true` for distant visibility. Legacy hotel projection baking is unchanged for unmarked assets. Gallery orbit previews ignore gameplay render-distance caps; camera clipping continues to expand with zoom. Entering gameplay restores the configured distance limits. ## Reusable VFX (20 procedural presets) Use api.vfx to declare a portable visual effect. It saves settings in the NEO; the shared host engine handles particles, procedural shaders, playback and cleanup. No external texture files or callbacks are required. Supported presets: firePortal, energyPortal, fire, smoke, explosion, sparks, electricArc, energyBeam, projectileTrail, impactBurst, shockwave, energyShield, magicAura, magicCircle, teleport, hologram, dissolve, waterSplash, dustCloud, confetti. ```javascript window.__studioReady=(async()=>{ const api=window.NeomeScene.create({THREE,root,add}); api.vfx('portal',{preset:'firePortal',position:[0,2,0],radius:1.5, color:'#ff7200',secondaryColor:'#fff0b0',seed:42,quality:'high'}); api.vfx('burst',{preset:'explosion',position:[4,1,0],autoplay:false}); api.vfxControl('burst',{playing:true,restart:true}); await api.ready(); })(); ``` All positions are metres and rotations radians. A portal lies in local XY; rotate its owner to orient the opening. Beams/arcs run from the owner's origin to the local `end:[x,y,z]` endpoint. `direction` controls fire/smoke/dust emission. Attach a projectileTrail to a moving parent to retain a bounded world-space trail. Magic circles, splash rings and shockwaves lie in local XZ. Common options: parent, position, rotation, scale (positive vector), radius (.01–100), color and secondaryColor (#rrggbb), intensity (0–10), speed (0–10), duration (.05–120 seconds), loop, autoplay, seed (uint32), quality (low/medium/high), particleCount (1–1024), distance (1–500), direction (nonzero vector) and end (nonzero vector). Fire, portals, smoke, sparks, arcs, beams, trails, shields, auras, magic circles and holograms loop by default. The other effects play once. Override loop when needed. `api.vfxControl(id,{playing:false})` hides and pauses an effect; `api.vfxControl(id,{playing:true,restart:true})` replays it from the start. For declarative event/button actions use `{type:'vfx',target:'burst',playing:true,restart:true}`. The first release provides visual emitters and procedural shells. Teleport does not move a player; dissolve does not alter another object's materials; hologram is a procedural shell. Gameplay travel, target-material replacement, screen-space refraction and collision/damage are separate systems. Effects have no colliders. The host caps the session at 64 active effect allocations, 12,000 particles and four VFX point lights. Quality reduces each preset's requested particle count. Distance and ancestor visibility hide live rendering. Export stores descriptors, not runtime GPU objects. Optimization retains VFX geometry/settings rather than baking animation into static pictures. Preview/gallery: /validate/vfx.php. ## Paintball markers Use api.interaction(id,{type:'pickup',weaponType:'paintball'}) on an authored marker group. Its userData.neomeWeapon metadata contains type:'paintball', gripPoint:[x,y,z], paintColor:'#rrggbb', optional paintSpeed (12–90 metres/second), magSize and reserve. Author the marker at physical size with the muzzle along -Z. A child named with muzzle marks the barrel exit. Nodes may declare userData.paintballPart:'body'|'trigger'|'bolt'|'lid' for host-managed firing/reload motion. Paintball uses the existing combat controls: E pickup, left click fire, right click aim, R reload, Tab or inventory select. Paintball is non-damaging: balls travel and collide before colored splats appear. Paintball weapon metadata may also declare lod:{version:1,near:'near_owner_id',far:'far_owner_id',distancePolicy:'high-texture'}. Near and far must be sibling groups in the same attachment frame. Author a single combined vertex-color far mesh and initially hide its group. CombatSystem binds both to the renderer's native LOD traversal when the weapon is registered, including remote clones; it uses the existing lodTextureDistance setting with hysteresis. The far representation stays attached to the moving owner. Keep the near group's body/trigger/bolt/lid hierarchy intact. Saving or dropping the weapon preserves the LOD metadata and both representations. Add userData.paintballSurface:'wall'|'metal'|'wood'|'glass' to range surfaces to retain material identity through collision proxies. The host caps projectiles at 64, air puffs at 24 and splats at 96; effects expire and are excluded from export/collision. No scene callbacks, timers or render loops.