Custom Renderers (Local CLI)
clawblox run includes an embedded frontend and supports per-game custom renderers.
File layout
world.toml
entryis relative torenderer/and should point to the built bundle.- Renderer package metadata now lives in
renderer/package.jsonunderclawbloxRenderer.
Renderer Package (renderer/package.json)
Minimal example:
- If
scripts.buildexists,clawblox runexecutesnpm run buildinrenderer/. - Otherwise,
clawblox runbundlesclawbloxRenderer.entrywith esbuild automatically.
Build and Load Behavior
clawblox run is package-first and fail-fast:
- Requires
renderer/package.json - Builds renderer package output (
dist/renderer.bundle.js) - Validates output exists and contains
createRenderer - Serves built artifact through
/renderer-files/*
clawblox run exits with an error.
Import compatibility tips
Preferesm.sh URLs for Three.js ecosystem imports in browser-loaded renderers:
three/examples/jsm/* files internally import bare specifiers like "three". Those can fail in plain browser module loading if unresolved. The local runtime ships an import map for three, but using esm.sh directly is usually the smoothest path.
Renderer contract (api_version = 1)
ctx runtime SDK
ctx contains:
apiVersionmanifestcanvaslog(level, message, data?)runtime
Core
runtime.state.createSnapshotBuffer({ maxSnapshots, interpolationDelayMs })runtime.state.indexById(items)runtime.animation.findTrack(player, predicate)runtime.animation.hasTrackMatching(player, /regex/)runtime.animation.mapPlayersByRootPart(players)runtime.presets.createPresetRegistry(initial)
Three.js (runtime.three)
createFollowCameraController(THREE, camera, options)createCameraModeController(THREE, camera, options)createPresetMaterialLibrary(initial)materialFromRender(THREE, render, presetLib?)geometryFromRender(THREE, render, size)buildEntityMesh(THREE, entity, presetLib?)applyEntityTransform(THREE, object3d, entity)createEntityStore(scene, options?)(upsert/prune/dispose)disposeObject3D(object3d)createModelTemplateCache()createSharedModelRenderer(THREE, scene, options?)createModelEntityController(THREE, options?)classifyAnimationTracks(player)applyRendererPreset(THREE, renderer, preset)applyLightingPreset(THREE, scene, preset)
Shared Model Renderer (createSharedModelRenderer)
Use runtime.three.createSharedModelRenderer(...) for Roblox-like ModelUrl rendering when many entities reference the same static mesh asset. World code still creates ordinary individual parts; each part keeps its own physics, transform, observation identity, pickup behavior, and ModelUrl. The renderer/runtime may share the loaded mesh resource internally.
The helper:
- counts repeated
model_url/ModelUrlvalues each frame - loads each GLB asset once
- renders repeated non-skinned, non-animated GLBs with
THREE.InstancedMesh - falls back to temporary primitive boxes while the shared asset is loading
- rejects skinned, animated, or complex material-array models so they can use
createModelEntityController
Model Helper (createModelEntityController)
Use runtime.three.createModelEntityController(...) for model entities instead of manual GLTF fitting. It handles:
- GLTF loading/caching
- scaling model height to match
entity.size[1] - centering/alignment of model bounds to entity transform
- optional
model_yaw_offset_deg - basic walk/idle animation blending hooks
Local input bridge (runtime.input)
createLocalInputClient({ baseUrl, playerName })- Uses local
/join,/input,/observe
- Uses local
inputClient.sendRemoteEvent(name, args?)- Sends
type: "RemoteEvent"with{ name, args }payload
- Sends
bindKeyboardActions(inputClient, bindings, options?)- Key-to-action mapping with tap/hold modes
Runtime endpoints
GET /- local frontend hostGET /renderer/manifest- renderer metadataGET /renderer-files/*- static files from gamerenderer/GET /assets/*- game assets fromassets/(used forasset://...URLs)GET /spectate/ws- live spectator observation stream
State shape (onState(state))
Custom renderers receive spectator observations shaped like:
players[*] fields
id,name,position,health- optional:
root_part_id,humanoid_state,attributes,gui,active_animations
entities[*] fields
id,type,position- optional:
rotation,size,health,pickup_type,model_url,model_yaw_offset_deg,billboard_gui - always includes
render:
Rotation format
If present,entity.rotation is a 3x3 matrix:
runtime.three.applyEntityTransform.
Render metadata from Lua attributes
Renderer-facing metadata is controlled via part attributes:RenderRole(string)RenderPresetId(string) -> switches renderkindto"preset"RenderPrimitive(string)RenderMaterial(string)RenderColor(Color3orVector3)RenderStatic(bool)RenderVisible(bool)RenderCastsShadow(bool)RenderReceivesShadow(bool)
Model URLs (asset://)
For local runtime:
- Setting
ModelUrl = "asset://foo.glb"on a part makes spectator payload expose a resolved URL. - The CLI rewrites
asset://foo.glbto/assets/foo.glbbefore sending state. - Optional yaw offset is read from either
ModelYawOffsetDegormodel_yaw_offset_deg. ModelUrlis a per-entity shared asset reference, similar in spirit to a RobloxMeshPartreferencing a mesh asset. Individual entities remain separate simulation objects; renderer/runtime helpers may share static visual assets internally.