Operations
All operations follow the pattern: dtype.verb.
Operations marked as LWW use Last-Write-Wins semantics based on Lamport time.
Additive operations accumulate regardless of order.
Unified Schema
All operations are flat (no nested objects) except for node operations that send node data. Common fields:
interface BaseOp {
otype: string; // Operation type: "dtype.verb"
key?: string; // Target node key ("." = root, default)
path?: string; // Property path on the node ("." = node root)
value?: unknown; // Value to apply (type depends on otype)
// Additional flat fields per operation type:
// index, from, to, alpha, separator, toPath, etc.
}Examples:
// Set position
{ otype: 'vector3.set', key: 'player-1', path: 'position', value: [1, 2, 3] }
// Blend color with alpha
{ otype: 'color.blend', key: 'sky', path: 'color', value: '#00ff00', alpha: 0.5 }
// Move array item
{ otype: 'array.move', key: 'item', path: 'tags', from: 0, to: 2 }
// Move node to new parent (flat)
{ otype: 'node.move', key: 'cube-1', to: 'group-1', toPath: 'children' }
// Node insert (nested value for node data)
{ otype: 'node.insert', key: '.', path: 'children', value: { key: 'cube-1', tag: 'Mesh' } }Number
| Type | Description | Example |
|---|---|---|
number.set | Set numeric value (LWW) | { otype: "number.set", key: "light-1", path: "intensity", value: 0.5 } |
number.add | Add to numeric value (additive) | { otype: "number.add", key: "player", path: "score", value: 10 } |
number.multiply | Multiply numeric value | { otype: "number.multiply", key: "sprite", path: "scale", value: 2 } |
number.min | Set to min(current, value) | { otype: "number.min", key: "enemy", path: "health", value: 0 } |
number.max | Set to max(current, value) | { otype: "number.max", key: "enemy", path: "health", value: 100 } |
Vector3
| Type | Description | Example |
|---|---|---|
vector3.set | Set position/scale (LWW) | { otype: "vector3.set", key: "cube", path: "position", value: [1, 2, 3] } |
vector3.add | Add to vector (additive) | { otype: "vector3.add", key: "player", path: "position", value: [0, 1, 0] } |
vector3.multiply | Component-wise multiply | { otype: "vector3.multiply", key: "mesh", path: "scale", value: [2, 2, 2] } |
vector3.applyEuler | Rotate by euler angles (radians) | { otype: "vector3.applyEuler", key: "arrow", path: "direction", value: [0, 1.57, 0], order: "YXZ" } |
vector3.applyQuaternion | Rotate by quaternion | { otype: "vector3.applyQuaternion", key: "arrow", path: "direction", value: [0, 0.7, 0, 0.7] } |
Euler
| Type | Description | Example |
|---|---|---|
euler.set | Set euler angles (LWW) | { otype: "euler.set", key: "camera", path: "rotation", value: [0, 1.57, 0] } |
euler.add | Add to euler angles (additive) | { otype: "euler.add", key: "turret", path: "rotation", value: [0.1, 0, 0] } |
Quaternion
| Type | Description | Example |
|---|---|---|
quaternion.set | Set rotation (LWW) | { otype: "quaternion.set", key: "bone", path: "rotation", value: [0, 0, 0, 1] } |
quaternion.multiply | Compose rotations | { otype: "quaternion.multiply", key: "joint", path: "rotation", value: [0, 0.7, 0, 0.7] } |
Color
| Type | Description | Example |
|---|---|---|
color.set | Set hex color (LWW) | { otype: "color.set", key: "material-1", path: "color", value: "#ff0000" } |
color.blend | Blend towards color | { otype: "color.blend", key: "sky", path: "color", value: "#00ff00", alpha: 0.5 } |
String
For move operations, to refers to the current index (before the move), not the post-move index. Negative indices count from the end.
"Hello World" → move [0:5] to index 6 → " WorldHello"
"Hello World" → move [0:5] to index -1 → " WorldHello"| Type | Description | Example |
|---|---|---|
string.set | Set string value (LWW) | { otype: "string.set", key: "label", path: "text", value: "Player 1" } |
string.concat | Append to string | { otype: "string.concat", key: "console", path: "log", value: "event", separator: "\n" } |
string.insert | Insert at index | { otype: "string.insert", key: "label", path: "text", value: "Hello ", index: 0 } |
string.cut | Remove substring | { otype: "string.cut", key: "label", path: "text", from: 0, to: 5 } |
string.replace | Replace substring | { otype: "string.replace", key: "label", path: "text", from: 0, to: 5, value: "Hi" } |
string.move | Move substring (to = current index) | { otype: "string.move", key: "label", path: "text", from: 0, to: 5, index: 10 } |
Boolean
| Type | Description | Example |
|---|---|---|
boolean.set | Set boolean (LWW) | { otype: "boolean.set", key: "mesh", path: "visible", value: true } |
boolean.or | OR operation | { otype: "boolean.or", key: "state", path: "dirty", value: true } |
boolean.and | AND operation | { otype: "boolean.and", key: "button", path: "enabled", value: false } |
boolean.xor | XOR operation (toggle) | { otype: "boolean.xor", key: "light", path: "on", value: true } |
Array
For move operations, to refers to the current index (before the move), not the post-move index. Negative indices count from the end.
["a", "b", "c", "d"] → move index 0 to index 2 → ["b", "c", "a", "d"]
["a", "b", "c", "d"] → move index 0 to index -2 → ["b", "c", "a", "d"]| Type | Description | Example |
|---|---|---|
array.set | Replace array (LWW) | { otype: "array.set", key: "item", path: "tags", value: ["a", "b"] } |
array.push | Append item | { otype: "array.push", key: "item", path: "tags", value: "new-tag" } |
array.insert | Insert at index | { otype: "array.insert", key: "item", path: "tags", value: "tag", index: 1 } |
array.remove | Remove by value or index | { otype: "array.remove", key: "item", path: "tags", value: "old-tag", index: 2 } |
array.move | Move item (to = current index) | { otype: "array.move", key: "item", path: "tags", from: 0, to: 2 } |
array.union | Add unique items | { otype: "array.union", key: "item", path: "tags", value: ["x", "y"] } |
Object
| Type | Description | Example |
|---|---|---|
object.set | Replace object (LWW) | { otype: "object.set", key: "entity", path: "metadata", value: { a: 1 } } |
object.update | Shallow merge | { otype: "object.update", key: "entity", path: ".", value: { visible: true } } |
object.remove | Remove key from object | { otype: "object.remove", key: "entity", path: "metadata.tempKey" } |
Node
Structural operations for the scene graph.
| Type | Description | Example |
|---|---|---|
node.insert | Insert child node under parent | { otype: "node.insert", key: ".", path: "children", value: { key: "cube-1", tag: "Mesh" } } |
node.remove | Delete node (tombstone) | { otype: "node.remove", key: "cube-1", path: "." } |
node.upsert | Insert or merge if exists | { otype: "node.upsert", key: "scene", path: "children", value: { key: "cube-1", position: [1,2,3] } } |
node.inset | Insert or set if exists | { otype: "node.inset", key: "scene", path: "children", value: { key: "cube-1", tag: "Mesh", visible: true } } |
node.move | Move node to new parent | { otype: "node.move", key: "cube-1", to: "group-1", toPath: "children" } |
Message Format
Operations are wrapped in a CRDTMessage that includes metadata for conflict resolution:
interface CRDTMessage {
id: string; // Unique message ID
sessionId: string; // Client session ID
clock: VectorClock; // Vector clock for causality
lamportTime: number; // Lamport time for total ordering
timestamp: number; // Wall-clock time
ops: Operation[]; // Batch of operations
}Euler Rotation Order
The vector3.applyEuler operation supports two optional parameters:
order: Rotation order -'XYZ'(default),'YXZ','ZXY','ZYX','YZX','XZY'intrinsic:true(default) for intrinsic rotation,falsefor extrinsic
Intrinsic vs. Extrinsic Rotations
- Intrinsic (default): Axes move with the body. Rotate around body's X axis, then around the new Y axis, then around the new Z axis.
- Extrinsic: Axes stay fixed in space. Rotate around fixed X, then fixed Y, then fixed Z.
Note: Intrinsic XYZ is equivalent to Extrinsic ZYX (reversed order).
// Intrinsic YXZ rotation (common for cameras/FPS)
{ otype: 'vector3.applyEuler', key: 'camera', path: 'direction', value: [pitch, yaw, roll], order: 'YXZ' }
// Extrinsic XYZ rotation (fixed-axis)
{ otype: 'vector3.applyEuler', key: 'arm', path: 'direction', value: [x, y, z], intrinsic: false }Common Use Cases
| Order | Use Case |
|---|---|
XYZ | Default, general 3D rotations |
YXZ | First-person cameras (yaw-pitch-roll) |
ZYX | Aircraft/aerospace conventions |
ZXY | Some motion capture systems |