Worker message protocol
Play uses a module Worker to run MuJoCo and communicate with the main thread.
Messages use postMessage and are validated/encoded by generated helpers.
Message shapes
Commands: main → worker. Objects with
{ cmd: string, ...payload }Events: worker → main. Objects with
{ kind: string, ...payload }
The canonical protocol spec is the JSON IDL:
{
"version": 1,
"commands": [
{ "name": "strictReport", "required": ["id"] },
{ "name": "load", "required": ["xmlText"] },
{ "name": "snapshot" },
{ "name": "setPaused", "required": ["paused"] },
{ "name": "setRate", "required": ["rate"] },
{ "name": "setSnapshotHz", "required": ["hz"] },
{ "name": "setCameraMode", "required": ["mode"] },
{ "name": "setField", "required": ["target", "path", "kind", "value"] },
{ "name": "setLabelMode", "required": ["mode"] },
{ "name": "setFrameMode", "required": ["mode"] },
{ "name": "setVisualOption", "required": ["field", "value"] },
{ "name": "setVoptFlag", "required": ["index", "enabled"] },
{ "name": "setSceneFlag", "required": ["index", "enabled"] },
{ "name": "setGroupState", "required": ["group", "index", "enabled"] },
{ "name": "setCtrl", "required": ["index", "value"] },
{ "name": "setQpos", "required": ["index", "value"] },
{ "name": "setEqualityActive", "required": ["index", "active"] },
{ "name": "historyScrub", "required": ["offset"] },
{ "name": "historyConfig", "required": ["captureHz", "capacity"] },
{ "name": "keyframeSelect", "required": ["index"] },
{ "name": "keyframeSave", "required": ["index"] },
{ "name": "keyframeLoad", "required": ["index"] },
{ "name": "setWatch", "required": ["field", "index"] },
{ "name": "step", "required": ["n"] },
{ "name": "reset" },
{ "name": "gesture", "required": ["gesture"] },
{ "name": "align" },
{ "name": "copyState" },
{ "name": "applyPerturb", "required": ["phase"] },
{ "name": "setSelection", "required": ["bodyId"], "optional": ["localpos"] },
{ "name": "selectAt", "required": ["relx", "rely", "aspect"] },
{ "name": "setCtrlNoise", "required": ["std", "rate"] }
],
"events": [
{ "name": "strict_report", "required": ["id", "report"] },
{ "name": "run_state", "required": ["running"] },
{ "name": "ready", "required": ["abi", "dt", "ngeom", "optionSupport", "visual", "statistic"] },
{ "name": "struct_state", "required": ["scope", "value"] },
{ "name": "meta_cameras", "required": ["cameras"] },
{ "name": "meta_geoms", "required": ["geoms"] },
{ "name": "meta_joints", "required": ["ngeom", "nbody", "njnt", "geom_bodyid", "body_jntadr", "body_jntnum", "body_parentid", "jtype", "jnt_qposadr", "jnt_range", "jnt_names"] },
{ "name": "meta", "required": ["actuators"] },
{ "name": "snapshot", "required": ["tSim", "ngeom", "scn_ngeom", "nq", "nv", "nbody", "xpos", "xmat", "bxpos", "bxmat", "gesture", "drag", "viewerCamera", "voptFlags", "sceneFlags", "labelMode", "frameMode", "cameraMode", "frameId", "optionSupport", "paused", "pausedSource", "rate", "measuredSlowdown", "qpos", "options", "history", "keyframes", "watchSources"] },
{ "name": "keyframes", "required": ["capacity", "count", "labels", "slots", "lastSaved", "lastLoaded", "keyIndex"] },
{ "name": "history", "required": ["captureHz", "capacity", "count", "horizon", "scrubIndex", "live"] },
{ "name": "watch", "required": ["field", "index", "status", "valid", "summary"] },
{ "name": "render_assets", "required": ["assets"] },
{ "name": "gesture", "required": ["gesture", "drag"] },
{ "name": "align", "required": ["seq", "center", "radius", "timestamp"] },
{ "name": "copyState", "required": ["seq", "precision", "nq", "nv", "timestamp", "complete"] },
{ "name": "options", "required": ["voptFlags", "sceneFlags", "labelMode", "frameMode", "cameraMode", "groups", "options"] },
{ "name": "selection", "required": ["seq", "bodyId", "geomId", "flexId", "skinId", "point", "localpos", "timestamp"] },
{ "name": "latency_probe", "required": ["sentWallMs"], "optional": ["frameId"] },
{ "name": "log", "required": ["message"] },
{ "name": "error", "required": ["message"] }
],
"snapshot_view_fields": [
{
"ctor": "Float64Array",
"keys": [
"xpos",
"xmat",
"gsize",
"qpos",
"bxpos",
"bxmat",
"jnt_range"
]
},
{
"ctor": "Float32Array",
"keys": [
"scn_pos",
"scn_mat",
"scn_size",
"scn_rgba",
"flexvert_xpos",
"light_xpos",
"light_xdir"
]
},
{
"ctor": "Int32Array",
"keys": [
"scn_type",
"scn_matid",
"scn_dataid",
"scn_objtype",
"scn_objid",
"scn_category",
"scn_geomorder",
"gtype",
"geom_bodyid",
"body_parentid",
"body_jntadr",
"body_jntnum",
"jtype",
"jnt_qposadr",
"eq_type",
"eq_obj1id",
"eq_obj2id",
"eq_objtype"
]
},
{
"ctor": "Uint8Array",
"keys": [
"scn_label",
"eq_active"
]
}
],
"geom_view_fields_optional": [
{
"ctor": "Float64Array",
"keys": [
"bxpos",
"bxmat",
"qpos"
]
},
{
"ctor": "Float32Array",
"keys": [
"scn_pos",
"scn_mat",
"scn_size",
"scn_rgba",
"flexvert_xpos",
"light_xpos",
"light_xdir"
]
},
{
"ctor": "Int32Array",
"keys": [
"scn_type",
"scn_matid",
"scn_dataid",
"scn_objtype",
"scn_objid",
"scn_category",
"scn_geomorder",
"eq_type",
"eq_obj1id",
"eq_obj2id",
"eq_objtype"
]
},
{
"ctor": "Uint8Array",
"keys": [
"scn_label",
"eq_active"
]
}
],
"geom_view_fields_always": [
{
"ctor": "Float64Array",
"keys": [
"gsize"
]
},
{
"ctor": "Int32Array",
"keys": [
"gtype"
]
}
],
"transfer_fields": {
"snapshot": [
"xpos",
"xmat",
"gsize",
"gtype",
"bxpos",
"bxmat",
"qpos",
"scn_type",
"scn_pos",
"scn_mat",
"scn_size",
"scn_rgba",
"scn_matid",
"scn_dataid",
"scn_objtype",
"scn_objid",
"scn_category",
"scn_geomorder",
"scn_label",
"flexvert_xpos",
"eq_type",
"eq_obj1id",
"eq_obj2id",
"eq_objtype",
"eq_active",
"light_xpos",
"light_xdir",
"ctrl"
],
"contacts": []
}
}
Generated helpers
tools/generate_worker_protocol.mjs generates:
worker/protocol.gen.mjs: lists and field specsworker/dispatch.gen.mjs: encode/decode/dispatch helpers
These modules:
reject unknown command/event kinds
assert required payload fields
Command/event catalogs
The spec above is the single source of truth. For quick scanning, these are the current catalogs:
Commands: main → worker
strictReport, load, snapshot, setPaused, setRate, setSnapshotHz, setCameraMode,
setField, setLabelMode, setFrameMode, setVisualOption, setVoptFlag, setSceneFlag,
setGroupState, setCtrl, setQpos, setEqualityActive, historyScrub, historyConfig,
keyframeSelect, keyframeSave, keyframeLoad, setWatch, step, reset, gesture,
align, copyState, applyPerturb, setSelection, selectAt, setCtrlNoise
Events: worker → main
strict_report, run_state, ready, struct_state, meta_cameras, meta_geoms,
meta_joints, meta, snapshot, keyframes, history, watch, render_assets,
gesture, align, copyState, options, selection, latency_probe, log, error