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 specs

  • worker/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