tfrere HF Staff Cursor commited on
Commit
2470ca0
·
1 Parent(s): a370543

feat: legs <-> rollers locomotion switch (M key / DpadUp hold)

Browse files

Add the Microduck's wheeled skating variant alongside the walking legs:
- robot_allcollisions_rollers.xml + 5 new meshes (decimated to match the
existing set) + kinematics_rollers.json for the render rig
- BEST_roller.onnx (Drive: velocity-tracking skating, 0.6 m/s) and
BEST_roller_crouch.onnx (one-shot phase-driven crouch-glide on R/X);
both share the legs policies' exact 61D obs / 14-action interface
- M (or gamepad DpadUp held ~1 s) toggles variants: swaps the compiled
MuJoCo model+data, ONNX sessions and three.js rig wholesale; roller
assets lazy-load on first switch (BIOS-style LOADING ROLLERS OSD) so
the default boot is untouched, then stay resident
- passive wheel hinges synced to the rig so the wheels visibly spin
- roller command limits from the robot runtime (0.6/-0.5 m/s, +-1 rad/s);
kicks/sit disabled in roller mode (hints fade), ball played by driving
- ghosts broadcast an optional locomotion flag; roller peers render with
the roller rig when this tab has it, leg rig fallback otherwise

Co-authored-by: Cursor <cursoragent@cursor.com>

README.md CHANGED
@@ -13,14 +13,21 @@ Real trained RL policies for the Microduck robot, running fully in the
13
  browser: MuJoCo compiled to WebAssembly steps the physics, onnxruntime-web
14
  runs the policy network at 50 Hz. No server, no backend.
15
 
 
 
 
 
 
16
  ## Policies
17
 
18
- | Button | Checkpoint | What it does |
19
  |--------|-----------|--------------|
20
- | Run | `BEST_alpha_walking.onnx` | Velocity-tracking locomotion (arrows / WASD to steer) |
21
  | Sit | `BEST_alpha_sitstand.onnx` | Sits down on its hull, stands back up |
22
  | Roll | `roulade.onnx` | Rolls over and recovers |
23
  | Kick | `ball_kick_left.onnx` / `ball_kick_right.onnx` | Blind one-shot kick (0.5 s window, zeroed commands), left or right leg |
 
 
24
 
25
  Policies and MJCF model from
26
  [apirrone/microduck_runtime](https://github.com/apirrone/microduck_runtime)
@@ -29,14 +36,18 @@ and [apirrone/mjlab_microduck](https://github.com/apirrone/mjlab_microduck).
29
  ## Controls
30
 
31
  - Arrows or WASD (ZQSD): forward / back + turn
32
- - Q / E (A / E on AZERTY): kick left / right
33
- - R: roll (one barrel roll, then back to running)
 
34
  - B: pop / respawn a kickable ball in front of the duck
35
  - C: toggle the chase camera (on by default; dragging detaches it)
36
  - Space: reset
37
  - Drag to orbit, scroll to zoom
38
  - Colour dots: repaint the duck (it quacks)
39
 
 
 
 
40
  The ball is local-only: it lives in your tab's physics and is not shared
41
  with the multiplayer ghosts. A square arena (3 x 3 m) fences the play
42
  area so neither the ball nor the duck can wander off.
@@ -48,10 +59,11 @@ Plug in a controller and the same mapping as the real robot runtime applies:
48
  - Left stick: forward / back + turn
49
  - Right stick: orbit the camera (detaches the chase cam)
50
  - R3 (right stick click): toggle the chase cam back on
51
- - X: roll
52
  - Y: pop / respawn the ball
53
- - RB / LB: right / left kick
54
- - D-pad down: sit / stand toggle
 
55
  - Right trigger: mouth (analog) + quack
56
 
57
  ## Multiplayer ghosts
@@ -60,16 +72,29 @@ Other people visiting the Space at the same time show up as translucent
60
  ducks, live. Peer-to-peer WebRTC via [Trystero](https://github.com/dmotz/trystero)
61
  (serverless signaling over public Nostr relays), so it works from a static
62
  Space with no backend. Each tab broadcasts its duck's pose (trunk + 14
63
- joints + jaw + colour) at 10 Hz; up to 3 ghosts are rendered, extra peers
64
- stay connected but invisible. The camcorder-style OSD in the top-right
65
- corner shows "N ONLINE" when peers are around.
 
 
 
 
 
66
 
67
  ## How it works
68
 
69
- - `rl.js` fetches the MJCF (`robot_allcollisions.xml`), strips the visual
70
- geoms, injects a floor and a STAND keyframe, and compiles it with the
71
- official `@mujoco/mujoco` WASM bindings.
72
- - The 61D observation (gyro, projected gravity, joint pos/vel, last action,
73
- command) matches `mjlab_microduck/scripts/infer_policy.py`.
74
- - Rendering is a three.js rig built from `kinematics.json` + decimated STL
75
- meshes, driven directly from MuJoCo `qpos`.
 
 
 
 
 
 
 
 
 
13
  browser: MuJoCo compiled to WebAssembly steps the physics, onnxruntime-web
14
  runs the policy network at 50 Hz. No server, no backend.
15
 
16
+ Two locomotion variants of the same robot are included: **legs** (walking,
17
+ the default) and **rollers** (the wheeled skating variant). Press `M` (or
18
+ hold D-pad up ~1 s on a gamepad) to switch; the roller model, meshes and
19
+ policies are lazy-loaded on the first switch.
20
+
21
  ## Policies
22
 
23
+ | Mode | Checkpoint | What it does |
24
  |--------|-----------|--------------|
25
+ | Run (legs) | `BEST_alpha_walking.onnx` | Velocity-tracking locomotion (arrows / WASD to steer) |
26
  | Sit | `BEST_alpha_sitstand.onnx` | Sits down on its hull, stands back up |
27
  | Roll | `roulade.onnx` | Rolls over and recovers |
28
  | Kick | `ball_kick_left.onnx` / `ball_kick_right.onnx` | Blind one-shot kick (0.5 s window, zeroed commands), left or right leg |
29
+ | Drive (rollers) | `BEST_roller.onnx` | Velocity-tracking skating on 4 passive wheels (higher top speed: 0.6 m/s) |
30
+ | Crouch (rollers) | `BEST_roller_crouch.onnx` | One-shot crouch-glide: sinks low over ~3.5 s and stands back up (phase-encoded command) |
31
 
32
  Policies and MJCF model from
33
  [apirrone/microduck_runtime](https://github.com/apirrone/microduck_runtime)
 
36
  ## Controls
37
 
38
  - Arrows or WASD (ZQSD): forward / back + turn
39
+ - M: switch legs <-> rollers
40
+ - Q / E (A / E on AZERTY): kick left / right (legs only)
41
+ - R: roll (legs) / crouch-glide (rollers)
42
  - B: pop / respawn a kickable ball in front of the duck
43
  - C: toggle the chase camera (on by default; dragging detaches it)
44
  - Space: reset
45
  - Drag to orbit, scroll to zoom
46
  - Colour dots: repaint the duck (it quacks)
47
 
48
+ In roller mode the legs-only actions (kicks, sit) are disabled and their
49
+ hints fade out; play the ball by driving into it.
50
+
51
  The ball is local-only: it lives in your tab's physics and is not shared
52
  with the multiplayer ghosts. A square arena (3 x 3 m) fences the play
53
  area so neither the ball nor the duck can wander off.
 
59
  - Left stick: forward / back + turn
60
  - Right stick: orbit the camera (detaches the chase cam)
61
  - R3 (right stick click): toggle the chase cam back on
62
+ - X: roll (legs) / crouch-glide (rollers)
63
  - Y: pop / respawn the ball
64
+ - RB / LB: right / left kick (legs only)
65
+ - D-pad down: sit / stand toggle (legs only)
66
+ - D-pad up: hold ~1 s to switch legs <-> rollers (the real robot uses a 3 s hold)
67
  - Right trigger: mouth (analog) + quack
68
 
69
  ## Multiplayer ghosts
 
72
  ducks, live. Peer-to-peer WebRTC via [Trystero](https://github.com/dmotz/trystero)
73
  (serverless signaling over public Nostr relays), so it works from a static
74
  Space with no backend. Each tab broadcasts its duck's pose (trunk + 14
75
+ joints + jaw + colour + locomotion variant) at 10 Hz; up to 3 ghosts are
76
+ rendered, extra peers stay connected but invisible. The camcorder-style OSD
77
+ in the top-right corner shows "N ONLINE" when peers are around.
78
+
79
+ Ghost limitations in v1: a peer in roller mode renders with the roller rig
80
+ only if your tab has already loaded it (otherwise it falls back to the leg
81
+ rig), and ghost wheels don't spin (passive wheel joints aren't broadcast).
82
+ Old clients simply ignore the new variant flag.
83
 
84
  ## How it works
85
 
86
+ - `rl.js` fetches the MJCF (`robot_allcollisions.xml`, or
87
+ `robot_allcollisions_rollers.xml` for the roller variant), strips the
88
+ visual geoms, injects a floor, arena walls, a ball and a STAND keyframe,
89
+ and compiles it with the official `@mujoco/mujoco` WASM bindings.
90
+ - Both variants share the exact same policy interface: 61D observation
91
+ (gyro, projected gravity, 14 joint pos/vel, last action, 13D command)
92
+ and 14 position-targets, matching `mjlab_microduck/scripts/infer_policy.py`.
93
+ The roller variant adds 4 passive wheel hinges that appear in `qpos`
94
+ (zeroed in the keyframe) but not in the observation.
95
+ - Rendering is a three.js rig built from `kinematics.json` /
96
+ `kinematics_rollers.json` + decimated STL meshes, driven directly from
97
+ MuJoCo `qpos` (including the passive wheel spin).
98
+ - Switching variants swaps the compiled model + data + rig + ONNX sessions
99
+ wholesale; both stay resident after the first load so toggling back and
100
+ forth is instant.
ghosts.js CHANGED
@@ -31,6 +31,10 @@ export async function initGhosts(env) {
31
  }
32
 
33
  const { scene, cloneRig, setJoint, setJawOpen, applyVariant, jointNames, getLocalState } = env;
 
 
 
 
34
  // trystero 0.25 (backed by @trystero-p2p): makeAction returns
35
  // { send, onMessage, onReceiveProgress } where onMessage is a SETTER -
36
  // the receive handler is registered by assignment, not by calling it.
@@ -60,7 +64,7 @@ export async function initGhosts(env) {
60
 
61
  const ghosts = new Map(); // peerId -> ghost
62
  const makeGhost = (state) => {
63
- const rig = cloneRig(env.rig);
64
  applyVariant(rig, state.v);
65
  ghostify(rig);
66
  scene.add(rig.placer);
@@ -68,7 +72,10 @@ export async function initGhosts(env) {
68
  // Snap straight to the first received pose: no fly-in from the origin.
69
  trunk.position.set(state.p[0], state.p[1], state.p[2]);
70
  trunk.quaternion.set(state.p[4], state.p[5], state.p[6], state.p[3]);
71
- return { rig, trunk, target: state, joints: state.j.slice(), jaw: state.w ?? 0, variant: state.v };
 
 
 
72
  };
73
 
74
  const removeGhost = (peerId) => {
@@ -101,6 +108,16 @@ export async function initGhosts(env) {
101
  }
102
  g.lastSeen = performance.now();
103
  g.target = state;
 
 
 
 
 
 
 
 
 
 
104
  if (state.v !== g.variant) {
105
  g.variant = state.v;
106
  applyVariant(g.rig, state.v);
 
31
  }
32
 
33
  const { scene, cloneRig, setJoint, setJawOpen, applyVariant, jointNames, getLocalState } = env;
34
+ // Locomotion-variant rig source: state.l 1 = rollers, 0/absent = legs
35
+ // (old clients never send it). Falls back to the leg rig when the local
36
+ // tab hasn't built the roller rig yet - known v1 limitation.
37
+ const rigFor = (l) => (env.getRigFor ? env.getRigFor(l ?? 0) : env.rig);
38
  // trystero 0.25 (backed by @trystero-p2p): makeAction returns
39
  // { send, onMessage, onReceiveProgress } where onMessage is a SETTER -
40
  // the receive handler is registered by assignment, not by calling it.
 
64
 
65
  const ghosts = new Map(); // peerId -> ghost
66
  const makeGhost = (state) => {
67
+ const rig = cloneRig(rigFor(state.l));
68
  applyVariant(rig, state.v);
69
  ghostify(rig);
70
  scene.add(rig.placer);
 
72
  // Snap straight to the first received pose: no fly-in from the origin.
73
  trunk.position.set(state.p[0], state.p[1], state.p[2]);
74
  trunk.quaternion.set(state.p[4], state.p[5], state.p[6], state.p[3]);
75
+ return {
76
+ rig, trunk, target: state, joints: state.j.slice(), jaw: state.w ?? 0,
77
+ variant: state.v, loco: state.l ?? 0,
78
+ };
79
  };
80
 
81
  const removeGhost = (peerId) => {
 
108
  }
109
  g.lastSeen = performance.now();
110
  g.target = state;
111
+ // Peer switched legs <-> rollers: rebuild its ghost on the other rig
112
+ // (cheap - cloneRig shares geometry).
113
+ if ((state.l ?? 0) !== g.loco) {
114
+ const seen = g.lastSeen;
115
+ removeGhost(peerId);
116
+ g = makeGhost(state);
117
+ g.lastSeen = seen;
118
+ ghosts.set(peerId, g);
119
+ return;
120
+ }
121
  if (state.v !== g.variant) {
122
  g.variant = state.v;
123
  applyVariant(g.rig, state.v);
index.html CHANGED
@@ -299,6 +299,22 @@
299
  font-variant-numeric: tabular-nums;
300
  text-shadow: 0 0 6px rgba(255, 255, 255, 0.18);
301
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  @media (max-width: 700px) {
303
  #hud { flex-direction: column; align-items: flex-start; }
304
  #hud .keys { display: none; }
@@ -588,6 +604,7 @@
588
  </div>
589
  <div class="mode" id="mode-label"><span class="mode-text">Run</span></div>
590
  <div id="stats"></div>
 
591
  </div>
592
  <div id="hud" hidden>
593
  <div class="stack">
@@ -608,20 +625,21 @@
608
  </div>
609
  <span class="hint"><span class="key" id="key-fwd">&uarr;</span><span class="key" id="key-back">&darr;</span> move</span> &nbsp;
610
  <span class="hint"><span class="key" id="key-turnl">&larr;</span><span class="key" id="key-turnr">&rarr;</span> turn</span><br />
611
- <span class="hint"><span class="key" id="key-roll">R</span> roll</span> &nbsp;
612
- <span class="hint"><span class="key" id="key-kickl">A</span><span class="key" id="key-kickr">E</span> kick</span> &nbsp;
613
  <span class="hint"><span class="key" id="key-ball">B</span> ball</span> &nbsp;
614
  <span class="hint"><span class="key" id="key-cam">C</span> camera</span> &nbsp;
 
615
  <span class="hint"><span class="key" id="key-reset">Space</span> reset</span> &nbsp;
616
  drag to orbit &middot; scroll to zoom<br />
617
  <span id="pad-hint">gamepad:
618
- <span class="hint"><span class="key" id="key-pad-x">X</span> roll</span> &nbsp;
619
  <span class="hint"><span class="key" id="key-pad-y">Y</span> ball</span> &nbsp;
620
- <span class="hint"><span class="key" id="key-pad-rb">RB</span><span class="key" id="key-pad-lb">LB</span> kick</span> &nbsp;
621
  <span class="hint"><span class="key" id="key-pad-rs">RS</span> camera</span> &nbsp;
622
  <span class="hint"><span class="key" id="key-pad-r3">R3</span> chase</span> &nbsp;
623
- <span class="hint"><span class="key" id="key-pad-sit">&dArr;</span> sit</span> &nbsp;
624
- <span class="hint"><span class="key" id="key-pad-run">&uArr;</span> run</span> &nbsp;
625
  <span class="hint"><span class="key" id="key-pad-rt">RT</span> quack</span>
626
  </span>
627
  </div>
@@ -679,7 +697,7 @@
679
  <div class="t-hint">fresh start</div>
680
  </div>
681
  </div>
682
- <div class="orbit-hint">drag to orbit &middot; scroll to zoom</div>
683
  </section>
684
  <section class="extras">
685
  <div class="x-item">
@@ -746,7 +764,7 @@
746
  appended: a plain src attribute can 401 when the browser blocks the
747
  *.static.hf.space auth cookie inside the hub iframe. -->
748
  <script type="module">
749
- const V = "33"; // bump on rl.js changes to defeat heuristic caching
750
  const sign = new URLSearchParams(location.search).get("__sign");
751
  const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
752
  import(`./rl.js?v=${V}${suffix}`);
 
299
  font-variant-numeric: tabular-nums;
300
  text-shadow: 0 0 6px rgba(255, 255, 255, 0.18);
301
  }
302
+ /* BIOS-style line while the roller variant streams in on first switch */
303
+ #osd-load[hidden] { display: none; }
304
+ #osd-load {
305
+ font-size: 0.68rem;
306
+ letter-spacing: 0.14em;
307
+ color: var(--yellow, #ffd23f);
308
+ text-shadow: 0 0 8px rgba(255, 210, 63, 0.4);
309
+ }
310
+ .osd-load-cursor {
311
+ display: inline-block;
312
+ margin-left: 0.15em;
313
+ animation: rec-blink 0.8s steps(1) infinite;
314
+ }
315
+ /* Roller mode: legs-only actions (kicks, sit) are inert - fade their
316
+ hints; rl.js toggles body.rollers on every variant switch. */
317
+ body.rollers .legs-only { opacity: 0.25; }
318
  @media (max-width: 700px) {
319
  #hud { flex-direction: column; align-items: flex-start; }
320
  #hud .keys { display: none; }
 
604
  </div>
605
  <div class="mode" id="mode-label"><span class="mode-text">Run</span></div>
606
  <div id="stats"></div>
607
+ <div id="osd-load" hidden>LOADING ROLLERS<span class="osd-load-cursor">&#9608;</span></div>
608
  </div>
609
  <div id="hud" hidden>
610
  <div class="stack">
 
625
  </div>
626
  <span class="hint"><span class="key" id="key-fwd">&uarr;</span><span class="key" id="key-back">&darr;</span> move</span> &nbsp;
627
  <span class="hint"><span class="key" id="key-turnl">&larr;</span><span class="key" id="key-turnr">&rarr;</span> turn</span><br />
628
+ <span class="hint"><span class="key" id="key-roll">R</span> <span id="key-roll-label">roll</span></span> &nbsp;
629
+ <span class="hint legs-only"><span class="key" id="key-kickl">A</span><span class="key" id="key-kickr">E</span> kick</span> &nbsp;
630
  <span class="hint"><span class="key" id="key-ball">B</span> ball</span> &nbsp;
631
  <span class="hint"><span class="key" id="key-cam">C</span> camera</span> &nbsp;
632
+ <span class="hint"><span class="key" id="key-loco">M</span> rollers</span> &nbsp;
633
  <span class="hint"><span class="key" id="key-reset">Space</span> reset</span> &nbsp;
634
  drag to orbit &middot; scroll to zoom<br />
635
  <span id="pad-hint">gamepad:
636
+ <span class="hint"><span class="key" id="key-pad-x">X</span> <span id="key-pad-x-label">roll</span></span> &nbsp;
637
  <span class="hint"><span class="key" id="key-pad-y">Y</span> ball</span> &nbsp;
638
+ <span class="hint legs-only"><span class="key" id="key-pad-rb">RB</span><span class="key" id="key-pad-lb">LB</span> kick</span> &nbsp;
639
  <span class="hint"><span class="key" id="key-pad-rs">RS</span> camera</span> &nbsp;
640
  <span class="hint"><span class="key" id="key-pad-r3">R3</span> chase</span> &nbsp;
641
+ <span class="hint legs-only"><span class="key" id="key-pad-sit">&dArr;</span> sit</span> &nbsp;
642
+ <span class="hint"><span class="key" id="key-pad-run">&uArr;</span> run &middot; hold: rollers</span> &nbsp;
643
  <span class="hint"><span class="key" id="key-pad-rt">RT</span> quack</span>
644
  </span>
645
  </div>
 
697
  <div class="t-hint">fresh start</div>
698
  </div>
699
  </div>
700
+ <div class="orbit-hint">drag to orbit &middot; scroll to zoom &middot; <b>M</b> swaps legs for roller skates</div>
701
  </section>
702
  <section class="extras">
703
  <div class="x-item">
 
764
  appended: a plain src attribute can 401 when the browser blocks the
765
  *.static.hf.space auth cookie inside the hub iframe. -->
766
  <script type="module">
767
+ const V = "35"; // bump on rl.js changes to defeat heuristic caching
768
  const sign = new URLSearchParams(location.search).get("__sign");
769
  const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
770
  import(`./rl.js?v=${V}${suffix}`);
policies/BEST_roller.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cf05651d2708a2f9364212e86b866c97a70ace8131c492500105e8f28bf99afd
3
+ size 793685
policies/BEST_roller_crouch.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a1a084be240469c76ac9d3fa44d4792f16d4b1da60398b3ecd3cfc5e2244d990
3
+ size 793685
rl.js CHANGED
@@ -13,6 +13,14 @@
13
  // command = [vx, vy, wz, head_pose(4), body_pose(6)]; for the sitstand
14
  // policy, command[0] is the posture flag (1 = sit, 0 = stand).
15
  // Action (14) = joint position targets relative to the default pose.
 
 
 
 
 
 
 
 
16
 
17
  import * as THREE from "three";
18
  import { OrbitControls } from "three/addons/controls/OrbitControls.js";
@@ -53,6 +61,11 @@ const POLICIES = {
53
  // the runtime swaps these in for a 0.5 s window, commands zeroed.
54
  kickL: `${POLICY_DIR}/ball_kick_left.onnx`,
55
  kickR: `${POLICY_DIR}/ball_kick_right.onnx`,
 
 
 
 
 
56
  };
57
 
58
  // From the ONNX metadata (identical for all alpha policies) and the STAND
@@ -79,6 +92,15 @@ const CTRL_DT = TIMESTEP * DECIMATION; // 50 Hz
79
  // Velocity command limits, same as infer_policy.py's keyboard mapping.
80
  // No strafe input anymore: the lateral cmd slot stays zeroed for the obs.
81
  const VEL_FWD = 0.25, VEL_BACK = -0.2, VEL_ANG = 1.0;
 
 
 
 
 
 
 
 
 
82
 
83
  // Kickable ball: radius and parking spot (far away = hidden by default).
84
  const BALL_RADIUS = 0.05;
@@ -189,8 +211,11 @@ const traced = (label, p) => {
189
  // explicit <inertial>, and visual geoms have contype=0 conaffinity=0.
190
  // Stripping them means the MuJoCo VFS only needs the ~10 meshes referenced
191
  // by collision geoms.
192
- async function buildPhysicsXml() {
193
- const src = await (await fetch(signed(`${MODEL_DIR}/robot_allcollisions.xml`))).text();
 
 
 
194
  const doc = new DOMParser().parseFromString(src, "text/xml");
195
  for (const g of [...doc.querySelectorAll('geom[class="visual"]')]) g.remove();
196
  const usedMeshes = new Set(
@@ -243,15 +268,23 @@ async function buildPhysicsXml() {
243
  mass: "0.03", friction: "0.4 0.01 0.003", solref: "0.03 0.4", condim: "6",
244
  }));
245
  doc.querySelector("worldbody").appendChild(ballBody);
246
- // STAND keyframe from mjlab's scene_walk.xml (STAND2 pose). The ball's
247
- // 7 free-joint values MUST be appended or nq (21 + 7 = 28) won't match
248
- // and the model won't compile; parked 50 m away = effectively absent.
 
 
 
 
249
  const qposFree = "0 0 0.12 1 0 0 0";
 
 
 
 
250
  const pose14 = Array.from(DEFAULT_POSE).join(" ");
251
  const kf = doc.createElement("keyframe");
252
  kf.appendChild(el("key", {
253
  name: "STAND",
254
- qpos: `${qposFree} ${pose14} ${BALL_PARK_POS} 1 0 0 0`,
255
  ctrl: pose14,
256
  }));
257
  root.appendChild(kf);
@@ -262,21 +295,29 @@ async function buildPhysicsXml() {
262
  // ── Boot physics + policy in parallel with the render rig ──────────────
263
  const [mujoco, { xml, meshFiles }, k] = await Promise.all([
264
  traced("MUJOCO WASM", loadMujoco()),
265
- traced("PHYSICS MJCF", buildPhysicsXml()),
266
  traced("KINEMATICS", loadKinematics(`${MODEL_DIR}/kinematics.json`)),
267
  ]);
268
 
269
  const doneMeshes = bootLine("MESH ASSETS");
270
  const vfs = new mujoco.MjVFS();
271
- await Promise.all(
272
- meshFiles.map(async (f) => {
273
- // Same cache-busted URL as duck.js so the browser reuses the render
274
- // meshes instead of downloading the collision subset a second time.
275
- const buf = await (await fetch(signed(`${MODEL_DIR}/meshes/${f}?v=${MESH_VERSION}`), { cache: "force-cache" })).arrayBuffer();
276
- // meshdir="assets" in the MJCF, so the compiler looks up "assets/<f>".
277
- vfs.addBuffer(`assets/${f}`, new Uint8Array(buf));
278
- }),
279
- );
 
 
 
 
 
 
 
 
280
  doneMeshes(`${meshFiles.length} FILES`);
281
 
282
  const sessions = {};
@@ -300,20 +341,42 @@ const sessionOpts = { executionProviders: ["wasm"] };
300
  donePolicies("5/5");
301
 
302
  const doneCompile = bootLine("COMPILING PHYSICS");
303
- const model = mujoco.MjModel.from_xml_string(xml, vfs);
304
- const data = new mujoco.MjData(model);
305
  doneCompile("COMPILED");
306
 
307
- // Addresses resolved once. qpos/qvel/sensordata views are re-read at each
308
- // use: the WASM heap can grow and detach earlier TypedArray views.
 
309
  // NOTE: unlike the Python bindings, these accessor fields are plain numbers.
310
- const qposAdr = JOINT_NAMES.map((n) => model.jnt(n).qposadr);
311
- const dofAdr = JOINT_NAMES.map((n) => model.jnt(n).dofadr);
312
- const gyroAdr = model.sensor("imu_ang_vel").adr;
313
- const trunkId = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_BODY.value, "trunk_base");
314
- const standKeyId = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_KEY.value, "STAND");
315
- const ballQposAdr = model.jnt("ball_freejoint").qposadr;
316
- const ballDofAdr = model.jnt("ball_freejoint").dofadr;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
  let uiReady = false;
319
  const lastAction = new Float32Array(NUM_JOINTS);
@@ -324,15 +387,25 @@ const velCmd = new Float32Array(3); // twist command, driven by held keys
324
  // the control loop reads it before the input section below has evaluated.
325
  const padCmd = new Float32Array(3);
326
  let padActive = false;
327
- const padPrev = { x: false, y: false, rb: false, lb: false, r3: false, dpadDown: false, dpadUp: false, rtArmed: true };
 
 
 
328
  // Right-stick camera state, read by renderStats before the gamepad
329
  // section below has evaluated (same reason as padCmd above).
330
  let padOrbitLive = false; // HUD: RS keycap lit while deflected
331
  // Declared before the control loop starts: buildObs reads it via
332
  // effectiveCmd on the very first control step.
333
  const held = new Set();
334
-
335
- let mode = "walk"; // "walk" | "sitstand" | "roll" | "kickL" | "kickR"
 
 
 
 
 
 
 
336
  let sitFlag = 0;
337
  const isKick = () => mode === "kickL" || mode === "kickR";
338
  // Local-only kickable ball: false while parked at the keyframe spot
@@ -346,13 +419,18 @@ let ballActive = false;
346
  // hands back to walk on its own - steering would only knock the roll over.
347
  const ZERO_CMD = new Float32Array(3);
348
  function effectiveCmd() {
349
- if (mode === "roll" || isKick() || postKickLock > 0) return ZERO_CMD;
 
350
  return padActive ? padCmd : velCmd;
351
  }
352
  // One-shot roll tracking: trigger time + whether the trunk actually
353
  // tipped over yet. Set by triggerRoll, cleared when we hand back to walk.
354
  let rollRun = null;
355
  let rollSource = "kb"; // which hint lights up: keyboard Space or pad X
 
 
 
 
356
  // One-shot kick tracking: the runtime swaps the kick policy in for a fixed
357
  // 0.5 s window (25 control steps at 50 Hz) with zeroed commands, then hands
358
  // straight back to walking. lastAction stays continuous across both swaps.
@@ -383,6 +461,7 @@ function resetSim() {
383
  clearModeTimers();
384
  rollRun = null;
385
  kickRun = null;
 
386
  postKickLock = 0;
387
  mode = "walk";
388
  mujoco.mj_resetDataKeyframe(model, data, standKeyId);
@@ -402,6 +481,7 @@ resetSim();
402
  // nearby instead of always on the same spot. qvel is zeroed so a respawn
403
  // doesn't carry the old momentum.
404
  function spawnBall() {
 
405
  const qpos = data.qpos, qvel = data.qvel;
406
  // Trunk yaw from the free-joint quaternion (qpos[3..6] = w x y z);
407
  // the duck walks toward its local +X.
@@ -444,11 +524,16 @@ function buildObs() {
444
  for (let j = 0; j < NUM_JOINTS; j++) obs[i++] = qpos[qposAdr[j]] - DEFAULT_POSE[j];
445
  for (let j = 0; j < NUM_JOINTS; j++) obs[i++] = qvel[dofAdr[j]];
446
  for (let j = 0; j < NUM_JOINTS; j++) obs[i++] = lastAction[j];
447
- // command: walking/roll use the twist; sitstand uses cmd[0] as the
448
- // posture flag.
 
449
  cmd.fill(0, 0, 3);
450
  if (mode === "sitstand") {
451
  cmd[0] = sitFlag;
 
 
 
 
452
  } else {
453
  // walk uses the live twist; roll and kick see all-zero commands
454
  // (via effectiveCmd), matching how they were trained.
@@ -459,13 +544,18 @@ function buildObs() {
459
  return obs;
460
  }
461
 
 
 
 
 
 
462
  // ── Control loop (50 Hz, async because ONNX inference is async) ────────
463
  let ctrlHz = 0;
464
  let fallenSince = null;
465
 
466
  async function controlStep() {
467
  const feeds = { obs: new ort.Tensor("float32", buildObs(), [1, OBS_SIZE]) };
468
- const out = await sessions[mode].run(feeds);
469
  const act = out.actions.data;
470
  lastAction.set(act);
471
  const ctrl = data.ctrl;
@@ -522,6 +612,19 @@ async function controlStep() {
522
  }
523
  }
524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  if (mode === "roll" && rollRun) {
526
  rollRun.steps++;
527
  if (obs[5] > -0.3) rollRun.tipped = true;
@@ -603,7 +706,9 @@ function makeInfiniteGrid() {
603
  uFadeDist: { value: 3.0 },
604
  uFocus: { value: new THREE.Vector3() },
605
  // Entrance draw-in progress; 1 = steady state (branch skipped).
606
- uReveal: { value: 1.0 },
 
 
607
  },
608
  vertexShader: /* glsl */ `
609
  varying vec3 vWorld;
@@ -643,7 +748,9 @@ function makeInfiniteGrid() {
643
  vec2 tile = floor(vWorld.xz / uSection);
644
  float jit = fract(sin(dot(tile, vec2(127.1, 311.7))) * 43758.5453);
645
  float at = rd * 0.78 + jit * 0.24;
646
- alpha *= smoothstep(at - 0.12, at, uReveal * 1.06);
 
 
647
  }
648
  if (alpha < 0.004) discard;
649
  gl_FragColor = vec4(col, alpha);
@@ -678,7 +785,8 @@ function makeWallGridMaterial(alongX) {
678
  uWallH: { value: ARENA_WALL_H },
679
  uAlongX: { value: alongX ? 1.0 : 0.0 },
680
  // Entrance draw-in progress; 1 = steady state (branch skipped).
681
- uReveal: { value: 1.0 },
 
682
  },
683
  vertexShader: /* glsl */ `
684
  varying vec3 vWorld;
@@ -719,7 +827,8 @@ function makeWallGridMaterial(alongX) {
719
  vec2 tile = floor(p / uSection);
720
  float jit = fract(sin(dot(tile, vec2(127.1, 311.7))) * 43758.5453);
721
  float at = hN * 0.76 + jit * 0.24;
722
- alpha *= smoothstep(at - 0.12, at, uReveal * 1.06);
 
723
  }
724
  if (alpha < 0.004) discard;
725
  gl_FragColor = vec4(col, alpha);
@@ -747,13 +856,89 @@ const wallMats = [];
747
  }
748
  }
749
 
750
- const rig = await rigPromise;
751
  scene.add(rig.placer);
752
- const trunkGroup = rig.bodies.get("trunk_base");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
753
 
754
  // ── Sci-fi entrance: Tron grid draw-in + duck dissolve-in ───────────────
755
  // Plays once per page load, right as the BIOS overlay fades (playBios
756
- // calls startEntrance). Gameplay stays live throughout. The duck dissolve
 
 
 
 
757
  // swaps each rig mesh onto a TEMPORARY clone of its material (the shared
758
  // matFor cache and ghost cloning stay untouched) and restores the
759
  // originals when the effect completes, so variants/ghosts behave exactly
@@ -798,7 +983,7 @@ if (uDissolveP < 1.0) {
798
  dsvEdge = 1.0 - smoothstep(0.015, 0.1, dsvThr - dsvN);
799
  }`)
800
  .replace("#include <dithering_fragment>", /* glsl */ `#include <dithering_fragment>
801
- gl_FragColor.rgb += dsvEdge * vec3(1.0, 0.824, 0.247);`);
802
  };
803
  m.needsUpdate = true;
804
  dissolveSaved.push([o, orig]);
@@ -813,6 +998,11 @@ function restoreDissolve() {
813
  }
814
  dissolveSaved = null;
815
  }
 
 
 
 
 
816
 
817
  const ENTRANCE_GRID_S = 1.5; // floor draw-in duration
818
  const ENTRANCE_WALL_DELAY_S = 0.4; // walls start after the floor
@@ -822,6 +1012,10 @@ const clamp01 = (x) => Math.min(Math.max(x, 0), 1);
822
  const easeOutCubic = (x) => 1 - Math.pow(1 - clamp01(x), 3);
823
  let entranceT0 = null; // wall-clock start while the sequence is playing
824
  let entranceDone = false; // latches: once per page load
 
 
 
 
825
  function startEntrance() {
826
  if (entranceT0 !== null || entranceDone) return;
827
  entranceT0 = performance.now();
@@ -839,12 +1033,15 @@ function updateEntrance() {
839
  const dp = clamp01((t - ENTRANCE_DISSOLVE_START_S) / ENTRANCE_DISSOLVE_S);
840
  dissolveP.value = dp;
841
  if (t >= ENTRANCE_WALL_DELAY_S + ENTRANCE_GRID_S && dp >= 1) {
842
- // Park everything in exact steady state and drop the temp materials.
 
843
  entranceT0 = null;
844
  entranceDone = true;
845
  grid.material.uniforms.uReveal.value = 1;
846
  for (const m of wallMats) m.uniforms.uReveal.value = 1;
847
  restoreDissolve();
 
 
848
  }
849
  }
850
 
@@ -1068,6 +1265,8 @@ function syncRig() {
1068
  trunkGroup.position.set(qpos[0], qpos[1], qpos[2]);
1069
  trunkGroup.quaternion.set(qpos[4], qpos[5], qpos[6], qpos[3]);
1070
  for (let j = 0; j < NUM_JOINTS; j++) setJoint(rig, JOINT_NAMES[j], qpos[qposAdr[j]]);
 
 
1071
  // Ball: raw MJCF pose inside the Z-up group, hidden while parked.
1072
  ballMesh.visible = ballActive;
1073
  if (ballActive) {
@@ -1147,12 +1346,23 @@ const keyEls = {
1147
  roll: el("key-roll"), reset: el("key-reset"),
1148
  kickl: el("key-kickl"), kickr: el("key-kickr"),
1149
  ball: el("key-ball"), cam: el("key-cam"),
 
1150
  padX: el("key-pad-x"), padSit: el("key-pad-sit"),
1151
  padRun: el("key-pad-run"), padRt: el("key-pad-rt"),
1152
  padRb: el("key-pad-rb"), padLb: el("key-pad-lb"),
1153
  padY: el("key-pad-y"), padRs: el("key-pad-rs"),
1154
  padR3: el("key-pad-r3"),
1155
  };
 
 
 
 
 
 
 
 
 
 
1156
  const STICK_R = 15; // px, max dot travel inside the 46px stick circle
1157
  let resetFlashAt = -Infinity;
1158
  let ballFlashAt = -Infinity;
@@ -1172,12 +1382,14 @@ function renderStats() {
1172
  `CTRL ${ctrlHz.toFixed(0)}HZ` + (peers ? ` \u00b7 ${peers + 1} ONLINE` : "");
1173
 
1174
  // Mini sticks: the dot mirrors the effective twist, lit yellow while the
1175
- // user is actually driving.
 
1176
  const manual = (padActive || held.size > 0) && mode !== "roll";
1177
- const yN = vx >= 0 ? vx / VEL_FWD : vx / -VEL_BACK;
 
1178
  // Move stick is vertical-only now that strafe is gone.
1179
  dotMove.style.transform = `translate(0px, ${-yN * STICK_R}px)`;
1180
- dotTurn.style.transform = `translate(${(-wz / VEL_ANG) * STICK_R}px, 0px)`;
1181
  boxMove.classList.toggle("live", manual && Math.abs(vx) > 0.01);
1182
  boxTurn.classList.toggle("live", manual && Math.abs(wz) > 0.01);
1183
 
@@ -1187,13 +1399,15 @@ function renderStats() {
1187
  for (const k of ["fwd", "back", "turnl", "turnr"]) {
1188
  keyEls[k].classList.toggle("lit", held.has(k));
1189
  }
1190
- keyEls.roll.classList.toggle("lit", mode === "roll" && rollSource === "kb");
 
1191
  keyEls.kickl.classList.toggle("lit", mode === "kickL" && kickSource === "kb");
1192
  keyEls.kickr.classList.toggle("lit", mode === "kickR" && kickSource === "kb");
1193
  keyEls.reset.classList.toggle("lit", performance.now() - resetFlashAt < 400);
1194
  keyEls.ball.classList.toggle("lit", performance.now() - ballFlashAt < 400);
1195
  keyEls.cam.classList.toggle("lit", chaseCam); // steady while chasing
1196
- keyEls.padX.classList.toggle("lit", mode === "roll" && rollSource === "pad");
 
1197
  keyEls.padY.classList.toggle("lit", performance.now() - padYFlashAt < 400);
1198
  keyEls.padRb.classList.toggle("lit", mode === "kickR" && kickSource === "pad");
1199
  keyEls.padLb.classList.toggle("lit", mode === "kickL" && kickSource === "pad");
@@ -1231,8 +1445,9 @@ loop();
1231
 
1232
  // ── Input: hold-to-command keys + HUD buttons ───────────────────────────
1233
  function refreshVelCmd() {
1234
- velCmd[0] = held.has("fwd") ? VEL_FWD : held.has("back") ? VEL_BACK : 0;
1235
- velCmd[2] = held.has("turnl") ? VEL_ANG : held.has("turnr") ? -VEL_ANG : 0;
 
1236
  }
1237
 
1238
  // e.code is physical position, so one map covers QWERTY and AZERTY:
@@ -1249,6 +1464,7 @@ window.addEventListener("keydown", (e) => {
1249
  if (e.code === "Space") { e.preventDefault(); resetSim(); resetFlashAt = performance.now(); return; }
1250
  if (e.code === "KeyB") { spawnBall(); ballFlashAt = performance.now(); return; }
1251
  if (e.code === "KeyC") { chaseCam = !chaseCam; return; }
 
1252
  if (e.code === "KeyR") { triggerRoll(); return; }
1253
  // Physical Q/E (A/E on AZERTY): explicit left / right kick, mirroring
1254
  // the pad's LB / RB.
@@ -1331,10 +1547,11 @@ pollPad = function pollGamepad() {
1331
  // (No strafe; the right stick no longer drives movement.)
1332
  const lx = dz(gp.axes[0] ?? 0), ly = dz(gp.axes[1] ?? 0);
1333
  const up = -ly; // browser sticks report up as -1
 
1334
  const target = [
1335
- up >= 0 ? up * VEL_FWD : up * -VEL_BACK,
1336
  0,
1337
- -lx * VEL_ANG,
1338
  ];
1339
  for (let i = 0; i < 3; i++) padCmd[i] += PAD_ALPHA * (target[i] - padCmd[i]);
1340
  // Sticks grab command authority on first input, release when back at rest
@@ -1374,16 +1591,26 @@ pollPad = function pollGamepad() {
1374
  padPrev.lb = lb;
1375
 
1376
  const dpadDown = !!gp.buttons[13]?.pressed;
1377
- if (dpadDown && !padPrev.dpadDown) {
1378
  const sitting = mode === "sitstand" && sitFlag === 1;
1379
  setMode(sitting ? "walk" : "sit");
1380
  }
1381
  padPrev.dpadDown = dpadDown;
1382
 
1383
- // DpadUp: straight back to running. Ignored mid-roll (it hands back
1384
- // to walk on its own, and switching on a tipped duck would floor it).
 
 
1385
  const dpadUp = !!gp.buttons[12]?.pressed;
1386
- if (dpadUp && !padPrev.dpadUp && mode !== "walk" && mode !== "roll") setMode("walk");
 
 
 
 
 
 
 
 
1387
  padPrev.dpadUp = dpadUp;
1388
 
1389
  // Triggers drive the mouth like the runtime (max of both), and the right
@@ -1403,11 +1630,15 @@ pollPad = function pollGamepad() {
1403
  const modeLabel = document.getElementById("mode-label");
1404
 
1405
  function setMode(next) {
 
1406
  // No policy switching mid-roll or mid-kick: both end on their own and
1407
- // return to walk - switching now would floor the duck.
1408
- if ((mode === "roll" && rollRun) || (isKick() && kickRun)) return;
 
 
1409
  clearModeTimers();
1410
  rollRun = null;
 
1411
  if (next !== "sit") {
1412
  // Leaving a sit: let the sitstand policy stand the duck back up first.
1413
  if (mode === "sitstand" && sitFlag === 1) {
@@ -1443,9 +1674,11 @@ function setMode(next) {
1443
  // continuous action history across policy switches, and the roll initiates
1444
  // more reliably mid-gait with the true last actions in the obs.
1445
  function triggerRoll(source = "kb") {
 
 
1446
  // Rolls only launch from a standing walk: from a sit (or mid sit/stand
1447
  // hand-over) the roll policy just faceplants the duck.
1448
- if (mode !== "walk" || standTimer) return;
1449
  clearModeTimers();
1450
  rollSource = source;
1451
  mode = "roll";
@@ -1454,12 +1687,25 @@ function triggerRoll(source = "kb") {
1454
  syncButtons();
1455
  }
1456
 
 
 
 
 
 
 
 
 
 
 
 
1457
  // One blind kick (the duck can't see any ball - it's a scripted boot),
1458
  // left or right leg. Same launch constraints as the roll. Returns whether
1459
  // the kick actually launched so the keyboard's foot alternation only
1460
  // advances on real kicks.
1461
  function triggerKick(foot, source = "kb") {
1462
- if (mode !== "walk" || standTimer) return false;
 
 
1463
  clearModeTimers();
1464
  kickSource = source;
1465
  mode = foot === "left" ? "kickL" : "kickR";
@@ -1487,7 +1733,14 @@ function setModeLabel(text) {
1487
 
1488
  function syncButtons() {
1489
  const sitting = mode === "sitstand" && sitFlag === 1;
1490
- setModeLabel(mode === "roll" ? "Roll" : isKick() ? "Kick" : sitting ? "Sit" : "Run");
 
 
 
 
 
 
 
1491
  }
1492
 
1493
  // ── Colour swatches: re-skin the rig live, with a quack ─────────────────
@@ -1521,12 +1774,20 @@ uiReady = true;
1521
  // Deterministic hooks for automated verification (rAF pauses in
1522
  // background tabs, and the control loop is async).
1523
  window.rl = {
1524
- model, data, mujoco, camera, controls,
 
 
 
1525
  get mode() { return mode; },
1526
  get sitFlag() { return sitFlag; },
1527
  buildObs, cmd,
1528
  velCmd, lastAction, resetSim,
1529
  spawnBall, triggerKick, sessions, ort,
 
 
 
 
 
1530
  get kickSteps() { return KICK_STEPS; },
1531
  set kickSteps(v) { KICK_STEPS = v; },
1532
  get ballActive() { return ballActive; },
@@ -1540,9 +1801,11 @@ window.rl = {
1540
  // One full render-loop iteration, for tests driving frames manually.
1541
  frame: () => { syncRig(); syncJaw(); controls.update(); updateChaseCam(); updateEntrance(); renderStats(); renderer.render(scene, camera); },
1542
  get ghosts() { return ghosts; },
 
1543
  // Deterministic entrance controls for screenshots/tests: setting values
1544
  // detaches the time-based driver; setDissolve(1) also restores the
1545
- // original materials (same cleanup as the real sequence).
 
1546
  entrance: {
1547
  start: startEntrance,
1548
  setReveal: (floor, wall = floor) => {
@@ -1552,7 +1815,7 @@ window.rl = {
1552
  },
1553
  setDissolve: (p) => {
1554
  entranceT0 = null;
1555
- if (p >= 1) { dissolveP.value = 1; restoreDissolve(); }
1556
  else { applyDissolve(); dissolveP.value = p; }
1557
  },
1558
  },
@@ -1563,10 +1826,18 @@ window.rl = {
1563
  // translucent ducks. Fire-and-forget: any failure just means no ghosts.
1564
  const r3 = (x) => Math.round(x * 1000) / 1000;
1565
  try {
 
 
 
1566
  const { initGhosts } = await import(signed(`./ghosts.js?v=${SELF_V}`));
1567
  ghosts = await initGhosts({
1568
  scene, rig, cloneRig, setJoint, setJawOpen, applyVariant,
1569
  jointNames: JOINT_NAMES,
 
 
 
 
 
1570
  getLocalState: () => {
1571
  const qpos = data.qpos;
1572
  const j = new Array(NUM_JOINTS);
@@ -1576,6 +1847,7 @@ try {
1576
  j,
1577
  w: r3(jawOpenNow()),
1578
  v: currentVariant,
 
1579
  };
1580
  },
1581
  });
 
13
  // command = [vx, vy, wz, head_pose(4), body_pose(6)]; for the sitstand
14
  // policy, command[0] is the posture flag (1 = sit, 0 = stand).
15
  // Action (14) = joint position targets relative to the default pose.
16
+ //
17
+ // Two locomotion variants share that exact interface (same 14 joints, same
18
+ // default pose, same 61D obs - verified from the ONNX metadata):
19
+ // legs - the walking robot (robot_allcollisions.xml), boot default
20
+ // rollers - the skating variant (robot_allcollisions_rollers.xml): the
21
+ // foot/sole assembly is replaced by a blade with 2 passive
22
+ // wheels per leg (extra unactuated hinges in qpos, zero in the
23
+ // keyframe). Lazy-loaded on the first M / DpadUp-hold switch.
24
 
25
  import * as THREE from "three";
26
  import { OrbitControls } from "three/addons/controls/OrbitControls.js";
 
61
  // the runtime swaps these in for a 0.5 s window, commands zeroed.
62
  kickL: `${POLICY_DIR}/ball_kick_left.onnx`,
63
  kickR: `${POLICY_DIR}/ball_kick_right.onnx`,
64
+ // Roller variant (lazy-loaded on first switch, never at boot):
65
+ // drive = velocity-tracking skating, crouch = one-shot crouch-glide
66
+ // driven by a phase encoding in the command slots (ground-pick style).
67
+ drive: `${POLICY_DIR}/BEST_roller.onnx`,
68
+ crouch: `${POLICY_DIR}/BEST_roller_crouch.onnx`,
69
  };
70
 
71
  // From the ONNX metadata (identical for all alpha policies) and the STAND
 
92
  // Velocity command limits, same as infer_policy.py's keyboard mapping.
93
  // No strafe input anymore: the lateral cmd slot stays zeroed for the obs.
94
  const VEL_FWD = 0.25, VEL_BACK = -0.2, VEL_ANG = 1.0;
95
+ // Roller mode limits, from the runtime's roller branch: asymmetric vx
96
+ // (0.6 push / 0.5 brake), no lateral, +-1.0 rad/s heading.
97
+ const RVEL_FWD = 0.6, RVEL_BACK = -0.5, RVEL_ANG = 1.0;
98
+ // Crouch-glide one-shot: command = [cos(2pi*phase), sin(2pi*phase), 0],
99
+ // phase advancing at 1/CROUCH_PERIOD_S per second and the cycle exiting
100
+ // at 0.7 - exactly the runtime's ground-pick slot the policy was trained
101
+ // against (mjlab CROUCH_PERIOD = 5.0, cycle end 0.7 => 3.5 s gesture).
102
+ const CROUCH_PERIOD_S = 5.0;
103
+ const CROUCH_END_PHASE = 0.7;
104
 
105
  // Kickable ball: radius and parking spot (far away = hidden by default).
106
  const BALL_RADIUS = 0.05;
 
211
  // explicit <inertial>, and visual geoms have contype=0 conaffinity=0.
212
  // Stripping them means the MuJoCo VFS only needs the ~10 meshes referenced
213
  // by collision geoms.
214
+ // Works for both variants: the roller XML only differs by the ankle/wheel
215
+ // subtree (4 extra passive hinges), which the keyframe builder below
216
+ // handles by walking the joints in document order.
217
+ async function buildPhysicsXml(xmlFile) {
218
+ const src = await (await fetch(signed(`${MODEL_DIR}/${xmlFile}`))).text();
219
  const doc = new DOMParser().parseFromString(src, "text/xml");
220
  for (const g of [...doc.querySelectorAll('geom[class="visual"]')]) g.remove();
221
  const usedMeshes = new Set(
 
268
  mass: "0.03", friction: "0.4 0.01 0.003", solref: "0.03 0.4", condim: "6",
269
  }));
270
  doc.querySelector("worldbody").appendChild(ballBody);
271
+ // STAND keyframe from mjlab's scene_walk.xml (STAND2 pose; the roller
272
+ // scene_rollers.xml STAND uses the same trunk height and 14-joint pose).
273
+ // qpos must cover every joint in document order: the 14 actuated hinges
274
+ // take DEFAULT_POSE by name, anything else (the roller variant's passive
275
+ // wheels) starts at zero. The ball's 7 free-joint values MUST be
276
+ // appended or nq won't match and the model won't compile; parked 50 m
277
+ // away = effectively absent.
278
  const qposFree = "0 0 0.12 1 0 0 0";
279
+ const poseByName = new Map(JOINT_NAMES.map((n, i) => [n, DEFAULT_POSE[i]]));
280
+ const qposJoints = [...doc.querySelectorAll("body > joint")]
281
+ .map((j) => poseByName.get(j.getAttribute("name")) ?? 0)
282
+ .join(" ");
283
  const pose14 = Array.from(DEFAULT_POSE).join(" ");
284
  const kf = doc.createElement("keyframe");
285
  kf.appendChild(el("key", {
286
  name: "STAND",
287
+ qpos: `${qposFree} ${qposJoints} ${BALL_PARK_POS} 1 0 0 0`,
288
  ctrl: pose14,
289
  }));
290
  root.appendChild(kf);
 
295
  // ── Boot physics + policy in parallel with the render rig ──────────────
296
  const [mujoco, { xml, meshFiles }, k] = await Promise.all([
297
  traced("MUJOCO WASM", loadMujoco()),
298
+ traced("PHYSICS MJCF", buildPhysicsXml("robot_allcollisions.xml")),
299
  traced("KINEMATICS", loadKinematics(`${MODEL_DIR}/kinematics.json`)),
300
  ]);
301
 
302
  const doneMeshes = bootLine("MESH ASSETS");
303
  const vfs = new mujoco.MjVFS();
304
+ // One shared VFS for both variants; already-loaded files are skipped so
305
+ // the roller lazy-load only fetches its 5 new meshes.
306
+ const vfsFiles = new Set();
307
+ async function addMeshesToVfs(files) {
308
+ await Promise.all(
309
+ files.map(async (f) => {
310
+ if (vfsFiles.has(f)) return;
311
+ vfsFiles.add(f);
312
+ // Same cache-busted URL as duck.js so the browser reuses the render
313
+ // meshes instead of downloading the collision subset a second time.
314
+ const buf = await (await fetch(signed(`${MODEL_DIR}/meshes/${f}?v=${MESH_VERSION}`), { cache: "force-cache" })).arrayBuffer();
315
+ // meshdir="assets" in the MJCF, so the compiler looks up "assets/<f>".
316
+ vfs.addBuffer(`assets/${f}`, new Uint8Array(buf));
317
+ }),
318
+ );
319
+ }
320
+ await addMeshesToVfs(meshFiles);
321
  doneMeshes(`${meshFiles.length} FILES`);
322
 
323
  const sessions = {};
 
341
  donePolicies("5/5");
342
 
343
  const doneCompile = bootLine("COMPILING PHYSICS");
344
+ let model = mujoco.MjModel.from_xml_string(xml, vfs);
345
+ let data = new mujoco.MjData(model);
346
  doneCompile("COMPILED");
347
 
348
+ // Addresses resolved once per compiled variant. qpos/qvel/sensordata views
349
+ // are re-read at each use: the WASM heap can grow and detach earlier
350
+ // TypedArray views.
351
  // NOTE: unlike the Python bindings, these accessor fields are plain numbers.
352
+ const JOINT_SET = new Set(JOINT_NAMES);
353
+ function resolveAddrs(model, kin) {
354
+ return {
355
+ qposAdr: JOINT_NAMES.map((n) => model.jnt(n).qposadr),
356
+ dofAdr: JOINT_NAMES.map((n) => model.jnt(n).dofadr),
357
+ gyroAdr: model.sensor("imu_ang_vel").adr,
358
+ trunkId: mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_BODY.value, "trunk_base"),
359
+ standKeyId: mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_KEY.value, "STAND"),
360
+ ballQposAdr: model.jnt("ball_freejoint").qposadr,
361
+ ballDofAdr: model.jnt("ball_freejoint").dofadr,
362
+ // Unactuated hinges (the roller variant's 4 passive wheels): not in
363
+ // the obs or ctrl, but synced to the render rig so the wheels spin.
364
+ extraJoints: kin.bodies
365
+ .filter((b) => b.joint && b.joint.type === "hinge" && !JOINT_SET.has(b.joint.name))
366
+ .map((b) => ({ name: b.joint.name, adr: model.jnt(b.joint.name).qposadr })),
367
+ };
368
+ }
369
+ // Active-variant address block, swapped wholesale by activateLoco.
370
+ let { qposAdr, dofAdr, gyroAdr, trunkId, standKeyId, ballQposAdr, ballDofAdr, extraJoints } =
371
+ resolveAddrs(model, k);
372
+
373
+ // Locomotion variants stay resident once built (model + data + rig +
374
+ // addresses); legs is registered when its render rig resolves below.
375
+ const locos = {};
376
+ let loco = "legs"; // "legs" | "rollers"
377
+ const velLims = () => (loco === "rollers"
378
+ ? [RVEL_FWD, RVEL_BACK, RVEL_ANG]
379
+ : [VEL_FWD, VEL_BACK, VEL_ANG]);
380
 
381
  let uiReady = false;
382
  const lastAction = new Float32Array(NUM_JOINTS);
 
387
  // the control loop reads it before the input section below has evaluated.
388
  const padCmd = new Float32Array(3);
389
  let padActive = false;
390
+ const padPrev = {
391
+ x: false, y: false, rb: false, lb: false, r3: false, dpadDown: false,
392
+ dpadUp: false, dpadUpAt: 0, dpadUpFired: false, rtArmed: true,
393
+ };
394
  // Right-stick camera state, read by renderStats before the gamepad
395
  // section below has evaluated (same reason as padCmd above).
396
  let padOrbitLive = false; // HUD: RS keycap lit while deflected
397
  // Declared before the control loop starts: buildObs reads it via
398
  // effectiveCmd on the very first control step.
399
  const held = new Set();
400
+ // Robot input gate: twist commands, mode changes, rolls, kicks and ball
401
+ // spawns all stay inert until the entrance sequence has fully played out
402
+ // (updateEntrance flips this exactly when the choreography completes).
403
+ let inputLocked = true;
404
+
405
+ // "walk" is the main velocity-tracking mode in BOTH variants (legs walking
406
+ // policy or roller drive policy - activeSession() picks); "crouch" is the
407
+ // roller-only one-shot; the rest are legs-only.
408
+ let mode = "walk"; // "walk" | "sitstand" | "roll" | "kickL" | "kickR" | "crouch"
409
  let sitFlag = 0;
410
  const isKick = () => mode === "kickL" || mode === "kickR";
411
  // Local-only kickable ball: false while parked at the keyframe spot
 
419
  // hands back to walk on its own - steering would only knock the roll over.
420
  const ZERO_CMD = new Float32Array(3);
421
  function effectiveCmd() {
422
+ if (inputLocked || mode === "roll" || mode === "crouch" || isKick() || postKickLock > 0)
423
+ return ZERO_CMD;
424
  return padActive ? padCmd : velCmd;
425
  }
426
  // One-shot roll tracking: trigger time + whether the trunk actually
427
  // tipped over yet. Set by triggerRoll, cleared when we hand back to walk.
428
  let rollRun = null;
429
  let rollSource = "kb"; // which hint lights up: keyboard Space or pad X
430
+ // One-shot crouch-glide tracking (roller variant): phase 0 -> 0.7 over
431
+ // 3.5 s, driven per control step. Shares rollSource for keycap lighting
432
+ // (same R / pad-X trigger).
433
+ let crouchRun = null;
434
  // One-shot kick tracking: the runtime swaps the kick policy in for a fixed
435
  // 0.5 s window (25 control steps at 50 Hz) with zeroed commands, then hands
436
  // straight back to walking. lastAction stays continuous across both swaps.
 
461
  clearModeTimers();
462
  rollRun = null;
463
  kickRun = null;
464
+ crouchRun = null;
465
  postKickLock = 0;
466
  mode = "walk";
467
  mujoco.mj_resetDataKeyframe(model, data, standKeyId);
 
481
  // nearby instead of always on the same spot. qvel is zeroed so a respawn
482
  // doesn't carry the old momentum.
483
  function spawnBall() {
484
+ if (inputLocked) return;
485
  const qpos = data.qpos, qvel = data.qvel;
486
  // Trunk yaw from the free-joint quaternion (qpos[3..6] = w x y z);
487
  // the duck walks toward its local +X.
 
524
  for (let j = 0; j < NUM_JOINTS; j++) obs[i++] = qpos[qposAdr[j]] - DEFAULT_POSE[j];
525
  for (let j = 0; j < NUM_JOINTS; j++) obs[i++] = qvel[dofAdr[j]];
526
  for (let j = 0; j < NUM_JOINTS; j++) obs[i++] = lastAction[j];
527
+ // command: walking/drive use the twist; sitstand uses cmd[0] as the
528
+ // posture flag; the crouch-glide one-shot carries its phase encoding in
529
+ // the vel slots (ground-pick convention: [cos, sin, 0]).
530
  cmd.fill(0, 0, 3);
531
  if (mode === "sitstand") {
532
  cmd[0] = sitFlag;
533
+ } else if (mode === "crouch" && crouchRun) {
534
+ const a = 2 * Math.PI * crouchRun.phase;
535
+ cmd[0] = Math.cos(a);
536
+ cmd[1] = Math.sin(a);
537
  } else {
538
  // walk uses the live twist; roll and kick see all-zero commands
539
  // (via effectiveCmd), matching how they were trained.
 
544
  return obs;
545
  }
546
 
547
+ // The ONNX session for the current mode: in the roller variant the main
548
+ // velocity mode runs the drive (skating) policy instead of the walker.
549
+ const activeSession = () =>
550
+ sessions[loco === "rollers" && mode === "walk" ? "drive" : mode];
551
+
552
  // ── Control loop (50 Hz, async because ONNX inference is async) ────────
553
  let ctrlHz = 0;
554
  let fallenSince = null;
555
 
556
  async function controlStep() {
557
  const feeds = { obs: new ort.Tensor("float32", buildObs(), [1, OBS_SIZE]) };
558
+ const out = await activeSession().run(feeds);
559
  const act = out.actions.data;
560
  lastAction.set(act);
561
  const ctrl = data.ctrl;
 
612
  }
613
  }
614
 
615
+ // Crouch-glide one-shot: advance the trained phase clock and hand back
616
+ // to the drive policy at the runtime's cycle end (0.7 x 5 s = 3.5 s:
617
+ // sink, glide low, stand back up). lastAction stays continuous, same as
618
+ // every other policy swap.
619
+ if (mode === "crouch" && crouchRun) {
620
+ crouchRun.phase += CTRL_DT / CROUCH_PERIOD_S;
621
+ if (crouchRun.phase >= CROUCH_END_PHASE) {
622
+ crouchRun = null;
623
+ mode = "walk";
624
+ if (uiReady) syncButtons();
625
+ }
626
+ }
627
+
628
  if (mode === "roll" && rollRun) {
629
  rollRun.steps++;
630
  if (obs[5] > -0.3) rollRun.tipped = true;
 
706
  uFadeDist: { value: 3.0 },
707
  uFocus: { value: new THREE.Vector3() },
708
  // Entrance draw-in progress; 1 = steady state (branch skipped).
709
+ // Starts at 0: the world stays hidden behind the welcome modal and
710
+ // the BIOS readout until playBios cues startEntrance.
711
+ uReveal: { value: 0.0 },
712
  },
713
  vertexShader: /* glsl */ `
714
  varying vec3 vWorld;
 
748
  vec2 tile = floor(vWorld.xz / uSection);
749
  float jit = fract(sin(dot(tile, vec2(127.1, 311.7))) * 43758.5453);
750
  float at = rd * 0.78 + jit * 0.24;
751
+ // step() clamps the smoothstep tail: center tiles have at < 0.12
752
+ // and would otherwise stay faintly visible at uReveal = 0.
753
+ alpha *= smoothstep(at - 0.12, at, uReveal * 1.06) * step(1e-4, uReveal);
754
  }
755
  if (alpha < 0.004) discard;
756
  gl_FragColor = vec4(col, alpha);
 
785
  uWallH: { value: ARENA_WALL_H },
786
  uAlongX: { value: alongX ? 1.0 : 0.0 },
787
  // Entrance draw-in progress; 1 = steady state (branch skipped).
788
+ // Starts at 0, same as the floor grid: hidden until startEntrance.
789
+ uReveal: { value: 0.0 },
790
  },
791
  vertexShader: /* glsl */ `
792
  varying vec3 vWorld;
 
827
  vec2 tile = floor(p / uSection);
828
  float jit = fract(sin(dot(tile, vec2(127.1, 311.7))) * 43758.5453);
829
  float at = hN * 0.76 + jit * 0.24;
830
+ // Same tail clamp as the floor: fully dark until reveal begins.
831
+ alpha *= smoothstep(at - 0.12, at, uReveal * 1.06) * step(1e-4, uReveal);
832
  }
833
  if (alpha < 0.004) discard;
834
  gl_FragColor = vec4(col, alpha);
 
856
  }
857
  }
858
 
859
+ let rig = await rigPromise;
860
  scene.add(rig.placer);
861
+ let trunkGroup = rig.bodies.get("trunk_base");
862
+ locos.legs = {
863
+ model, data, rig, trunkGroup,
864
+ qposAdr, dofAdr, gyroAdr, trunkId, standKeyId, ballQposAdr, ballDofAdr, extraJoints,
865
+ };
866
+
867
+ // ── Locomotion variant switching (legs <-> rollers) ─────────────────────
868
+ // The roller stack (XML + 5 extra meshes + kinematics + 2 ONNX policies)
869
+ // is lazy-loaded on the first switch so the default boot stays untouched,
870
+ // then kept resident: switching back and forth only swaps references.
871
+ let rollersLoading = null;
872
+ function ensureRollers() {
873
+ rollersLoading ??= (async () => {
874
+ const [{ xml: rXml, meshFiles: rMeshFiles }, rk] = await Promise.all([
875
+ buildPhysicsXml("robot_allcollisions_rollers.xml"),
876
+ loadKinematics(`${MODEL_DIR}/kinematics_rollers.json`),
877
+ ]);
878
+ const [rRig, sDrive, sCrouch] = await Promise.all([
879
+ buildRig(rk, { materialForMesh: materialHookFor(VARIANTS[currentVariant]) }),
880
+ ort.InferenceSession.create(signed(POLICIES.drive), sessionOpts),
881
+ ort.InferenceSession.create(signed(POLICIES.crouch), sessionOpts),
882
+ addMeshesToVfs(rMeshFiles),
883
+ ]);
884
+ sessions.drive = sDrive;
885
+ sessions.crouch = sCrouch;
886
+ const rModel = mujoco.MjModel.from_xml_string(rXml, vfs);
887
+ const rData = new mujoco.MjData(rModel);
888
+ locos.rollers = {
889
+ model: rModel, data: rData, rig: rRig, trunkGroup: rRig.bodies.get("trunk_base"),
890
+ ...resolveAddrs(rModel, rk),
891
+ };
892
+ })();
893
+ return rollersLoading;
894
+ }
895
+
896
+ function activateLoco(name) {
897
+ const L = locos[name];
898
+ loco = name;
899
+ scene.remove(rig.placer);
900
+ ({ model, data, rig, trunkGroup, qposAdr, dofAdr, gyroAdr, trunkId,
901
+ standKeyId, ballQposAdr, ballDofAdr, extraJoints } = L);
902
+ // The rig may have been built (or last shown) under another colourway.
903
+ applyVariant(rig, currentVariant);
904
+ scene.add(rig.placer);
905
+ document.body.classList.toggle("rollers", name === "rollers");
906
+ resetSim();
907
+ refreshVelCmd(); // held keys re-map to the new variant's velocity limits
908
+ if (uiReady) syncLocoHints();
909
+ }
910
+
911
+ // OSD line while the roller stack streams in, BIOS-style.
912
+ let osdLoadEl = null;
913
+ let locoSwitching = false;
914
+ async function toggleLoco() {
915
+ // No switching mid one-shot (roll/kick/crouch end on their own and a
916
+ // teardown now would strand their timers), or while already switching.
917
+ if (locoSwitching || inputLocked || rollRun || kickRun || crouchRun || standTimer) return;
918
+ locoSwitching = true;
919
+ try {
920
+ const next = loco === "legs" ? "rollers" : "legs";
921
+ if (next === "rollers" && !locos.rollers) {
922
+ osdLoadEl?.removeAttribute("hidden");
923
+ await ensureRollers();
924
+ }
925
+ activateLoco(next);
926
+ } catch (e) {
927
+ rollersLoading = null; // allow a retry on transient fetch failures
928
+ console.error("[rl] roller switch failed", e);
929
+ } finally {
930
+ osdLoadEl?.setAttribute("hidden", "");
931
+ locoSwitching = false;
932
+ }
933
+ }
934
 
935
  // ── Sci-fi entrance: Tron grid draw-in + duck dissolve-in ───────────────
936
  // Plays once per page load, right as the BIOS overlay fades (playBios
937
+ // calls startEntrance). Until then the world stays hidden (grids at
938
+ // reveal 0, duck fully dissolved) and every robot input is gated by
939
+ // inputLocked; the sim itself keeps running so the duck stands idle
940
+ // underneath and is mid-stride the moment it finishes materializing.
941
+ // The duck dissolve
942
  // swaps each rig mesh onto a TEMPORARY clone of its material (the shared
943
  // matFor cache and ghost cloning stay untouched) and restores the
944
  // originals when the effect completes, so variants/ghosts behave exactly
 
983
  dsvEdge = 1.0 - smoothstep(0.015, 0.1, dsvThr - dsvN);
984
  }`)
985
  .replace("#include <dithering_fragment>", /* glsl */ `#include <dithering_fragment>
986
+ gl_FragColor.rgb += dsvEdge * vec3(0.5, 0.41, 0.12);`);
987
  };
988
  m.needsUpdate = true;
989
  dissolveSaved.push([o, orig]);
 
998
  }
999
  dissolveSaved = null;
1000
  }
1001
+ // Boot hidden: the duck is fully dissolved from the first rendered frame
1002
+ // (the grids already start at uReveal 0), so nothing shows through the
1003
+ // welcome modal's blur or during the BIOS replay.
1004
+ dissolveP.value = 0;
1005
+ applyDissolve();
1006
 
1007
  const ENTRANCE_GRID_S = 1.5; // floor draw-in duration
1008
  const ENTRANCE_WALL_DELAY_S = 0.4; // walls start after the floor
 
1012
  const easeOutCubic = (x) => 1 - Math.pow(1 - clamp01(x), 3);
1013
  let entranceT0 = null; // wall-clock start while the sequence is playing
1014
  let entranceDone = false; // latches: once per page load
1015
+ // Resolved when the entrance completes; gates the ghosts join below so a
1016
+ // translucent peer can't float around in the still-hidden world.
1017
+ let entranceFinishedResolve;
1018
+ const entranceFinished = new Promise((r) => { entranceFinishedResolve = r; });
1019
  function startEntrance() {
1020
  if (entranceT0 !== null || entranceDone) return;
1021
  entranceT0 = performance.now();
 
1033
  const dp = clamp01((t - ENTRANCE_DISSOLVE_START_S) / ENTRANCE_DISSOLVE_S);
1034
  dissolveP.value = dp;
1035
  if (t >= ENTRANCE_WALL_DELAY_S + ENTRANCE_GRID_S && dp >= 1) {
1036
+ // Park everything in exact steady state, drop the temp materials and
1037
+ // hand control to the user.
1038
  entranceT0 = null;
1039
  entranceDone = true;
1040
  grid.material.uniforms.uReveal.value = 1;
1041
  for (const m of wallMats) m.uniforms.uReveal.value = 1;
1042
  restoreDissolve();
1043
+ inputLocked = false;
1044
+ entranceFinishedResolve();
1045
  }
1046
  }
1047
 
 
1265
  trunkGroup.position.set(qpos[0], qpos[1], qpos[2]);
1266
  trunkGroup.quaternion.set(qpos[4], qpos[5], qpos[6], qpos[3]);
1267
  for (let j = 0; j < NUM_JOINTS; j++) setJoint(rig, JOINT_NAMES[j], qpos[qposAdr[j]]);
1268
+ // Passive hinges (roller wheels): purely visual, driven straight from qpos.
1269
+ for (const ej of extraJoints) setJoint(rig, ej.name, qpos[ej.adr]);
1270
  // Ball: raw MJCF pose inside the Z-up group, hidden while parked.
1271
  ballMesh.visible = ballActive;
1272
  if (ballActive) {
 
1346
  roll: el("key-roll"), reset: el("key-reset"),
1347
  kickl: el("key-kickl"), kickr: el("key-kickr"),
1348
  ball: el("key-ball"), cam: el("key-cam"),
1349
+ loco: el("key-loco"),
1350
  padX: el("key-pad-x"), padSit: el("key-pad-sit"),
1351
  padRun: el("key-pad-run"), padRt: el("key-pad-rt"),
1352
  padRb: el("key-pad-rb"), padLb: el("key-pad-lb"),
1353
  padY: el("key-pad-y"), padRs: el("key-pad-rs"),
1354
  padR3: el("key-pad-r3"),
1355
  };
1356
+ osdLoadEl = el("osd-load");
1357
+ // Roller mode re-labels the trick hints (R / pad X trigger the crouch-glide
1358
+ // instead of the roll) and greys the legs-only actions via body.rollers.
1359
+ const rollLabelEl = el("key-roll-label");
1360
+ const padXLabelEl = el("key-pad-x-label");
1361
+ function syncLocoHints() {
1362
+ const rollers = loco === "rollers";
1363
+ if (rollLabelEl) rollLabelEl.textContent = rollers ? "crouch" : "roll";
1364
+ if (padXLabelEl) padXLabelEl.textContent = rollers ? "crouch" : "roll";
1365
+ }
1366
  const STICK_R = 15; // px, max dot travel inside the 46px stick circle
1367
  let resetFlashAt = -Infinity;
1368
  let ballFlashAt = -Infinity;
 
1382
  `CTRL ${ctrlHz.toFixed(0)}HZ` + (peers ? ` \u00b7 ${peers + 1} ONLINE` : "");
1383
 
1384
  // Mini sticks: the dot mirrors the effective twist, lit yellow while the
1385
+ // user is actually driving. Normalized against the ACTIVE variant's
1386
+ // velocity limits so full deflection reads the same in both.
1387
  const manual = (padActive || held.size > 0) && mode !== "roll";
1388
+ const [limF, limB, limA] = velLims();
1389
+ const yN = vx >= 0 ? vx / limF : vx / -limB;
1390
  // Move stick is vertical-only now that strafe is gone.
1391
  dotMove.style.transform = `translate(0px, ${-yN * STICK_R}px)`;
1392
+ dotTurn.style.transform = `translate(${(-wz / limA) * STICK_R}px, 0px)`;
1393
  boxMove.classList.toggle("live", manual && Math.abs(vx) > 0.01);
1394
  boxTurn.classList.toggle("live", manual && Math.abs(wz) > 0.01);
1395
 
 
1399
  for (const k of ["fwd", "back", "turnl", "turnr"]) {
1400
  keyEls[k].classList.toggle("lit", held.has(k));
1401
  }
1402
+ const trick = mode === "roll" || mode === "crouch"; // same R / pad-X slot
1403
+ keyEls.roll.classList.toggle("lit", trick && rollSource === "kb");
1404
  keyEls.kickl.classList.toggle("lit", mode === "kickL" && kickSource === "kb");
1405
  keyEls.kickr.classList.toggle("lit", mode === "kickR" && kickSource === "kb");
1406
  keyEls.reset.classList.toggle("lit", performance.now() - resetFlashAt < 400);
1407
  keyEls.ball.classList.toggle("lit", performance.now() - ballFlashAt < 400);
1408
  keyEls.cam.classList.toggle("lit", chaseCam); // steady while chasing
1409
+ keyEls.loco?.classList.toggle("lit", loco === "rollers" || locoSwitching);
1410
+ keyEls.padX.classList.toggle("lit", trick && rollSource === "pad");
1411
  keyEls.padY.classList.toggle("lit", performance.now() - padYFlashAt < 400);
1412
  keyEls.padRb.classList.toggle("lit", mode === "kickR" && kickSource === "pad");
1413
  keyEls.padLb.classList.toggle("lit", mode === "kickL" && kickSource === "pad");
 
1445
 
1446
  // ── Input: hold-to-command keys + HUD buttons ───────────────────────────
1447
  function refreshVelCmd() {
1448
+ const [limF, limB, limA] = velLims();
1449
+ velCmd[0] = held.has("fwd") ? limF : held.has("back") ? limB : 0;
1450
+ velCmd[2] = held.has("turnl") ? limA : held.has("turnr") ? -limA : 0;
1451
  }
1452
 
1453
  // e.code is physical position, so one map covers QWERTY and AZERTY:
 
1464
  if (e.code === "Space") { e.preventDefault(); resetSim(); resetFlashAt = performance.now(); return; }
1465
  if (e.code === "KeyB") { spawnBall(); ballFlashAt = performance.now(); return; }
1466
  if (e.code === "KeyC") { chaseCam = !chaseCam; return; }
1467
+ if (e.code === "KeyM") { toggleLoco(); return; }
1468
  if (e.code === "KeyR") { triggerRoll(); return; }
1469
  // Physical Q/E (A/E on AZERTY): explicit left / right kick, mirroring
1470
  // the pad's LB / RB.
 
1547
  // (No strafe; the right stick no longer drives movement.)
1548
  const lx = dz(gp.axes[0] ?? 0), ly = dz(gp.axes[1] ?? 0);
1549
  const up = -ly; // browser sticks report up as -1
1550
+ const [limF, limB, limA] = velLims();
1551
  const target = [
1552
+ up >= 0 ? up * limF : up * -limB,
1553
  0,
1554
+ -lx * limA,
1555
  ];
1556
  for (let i = 0; i < 3; i++) padCmd[i] += PAD_ALPHA * (target[i] - padCmd[i]);
1557
  // Sticks grab command authority on first input, release when back at rest
 
1591
  padPrev.lb = lb;
1592
 
1593
  const dpadDown = !!gp.buttons[13]?.pressed;
1594
+ if (dpadDown && !padPrev.dpadDown && loco === "legs") {
1595
  const sitting = mode === "sitstand" && sitFlag === 1;
1596
  setMode(sitting ? "walk" : "sit");
1597
  }
1598
  padPrev.dpadDown = dpadDown;
1599
 
1600
+ // DpadUp: short press = straight back to running (ignored mid-roll: it
1601
+ // hands back to walk on its own, and switching on a tipped duck would
1602
+ // floor it). HOLD ~1 s = legs <-> rollers switch, like the robot's 3 s
1603
+ // hold on the real controller (shortened for the web).
1604
  const dpadUp = !!gp.buttons[12]?.pressed;
1605
+ if (dpadUp && !padPrev.dpadUp) {
1606
+ padPrev.dpadUpAt = now;
1607
+ padPrev.dpadUpFired = false;
1608
+ if (mode !== "walk" && mode !== "roll" && mode !== "crouch") setMode("walk");
1609
+ }
1610
+ if (dpadUp && !padPrev.dpadUpFired && now - padPrev.dpadUpAt >= 1000) {
1611
+ padPrev.dpadUpFired = true; // latch: one switch per hold
1612
+ toggleLoco();
1613
+ }
1614
  padPrev.dpadUp = dpadUp;
1615
 
1616
  // Triggers drive the mouth like the runtime (max of both), and the right
 
1630
  const modeLabel = document.getElementById("mode-label");
1631
 
1632
  function setMode(next) {
1633
+ if (inputLocked) return;
1634
  // No policy switching mid-roll or mid-kick: both end on their own and
1635
+ // return to walk - switching now would floor the duck. Sitting is a
1636
+ // legs-only skill (the roller stance has no sitstand policy).
1637
+ if ((mode === "roll" && rollRun) || (isKick() && kickRun) || (mode === "crouch" && crouchRun)) return;
1638
+ if (next === "sit" && loco === "rollers") return;
1639
  clearModeTimers();
1640
  rollRun = null;
1641
+ crouchRun = null;
1642
  if (next !== "sit") {
1643
  // Leaving a sit: let the sitstand policy stand the duck back up first.
1644
  if (mode === "sitstand" && sitFlag === 1) {
 
1674
  // continuous action history across policy switches, and the roll initiates
1675
  // more reliably mid-gait with the true last actions in the obs.
1676
  function triggerRoll(source = "kb") {
1677
+ // Same trigger slot in the roller variant fires its own trick.
1678
+ if (loco === "rollers") return triggerCrouch(source);
1679
  // Rolls only launch from a standing walk: from a sit (or mid sit/stand
1680
  // hand-over) the roll policy just faceplants the duck.
1681
+ if (inputLocked || mode !== "walk" || standTimer) return;
1682
  clearModeTimers();
1683
  rollSource = source;
1684
  mode = "roll";
 
1687
  syncButtons();
1688
  }
1689
 
1690
+ // Roller-only one-shot: crouch, glide low, stand back up (phase-driven,
1691
+ // see the crouch constants up top). Reuses the roll's trigger + keycap.
1692
+ function triggerCrouch(source = "kb") {
1693
+ if (inputLocked || mode !== "walk" || locoSwitching) return;
1694
+ clearModeTimers();
1695
+ rollSource = source;
1696
+ mode = "crouch";
1697
+ crouchRun = { phase: 0 };
1698
+ syncButtons();
1699
+ }
1700
+
1701
  // One blind kick (the duck can't see any ball - it's a scripted boot),
1702
  // left or right leg. Same launch constraints as the roll. Returns whether
1703
  // the kick actually launched so the keyboard's foot alternation only
1704
  // advances on real kicks.
1705
  function triggerKick(foot, source = "kb") {
1706
+ // Kicks are legs-only: in roller mode the ball is played by driving.
1707
+ if (loco === "rollers") return false;
1708
+ if (inputLocked || mode !== "walk" || standTimer) return false;
1709
  clearModeTimers();
1710
  kickSource = source;
1711
  mode = foot === "left" ? "kickL" : "kickR";
 
1733
 
1734
  function syncButtons() {
1735
  const sitting = mode === "sitstand" && sitFlag === 1;
1736
+ setModeLabel(
1737
+ mode === "roll" ? "Roll"
1738
+ : mode === "crouch" ? "Crouch"
1739
+ : isKick() ? "Kick"
1740
+ : sitting ? "Sit"
1741
+ : loco === "rollers" ? "Drive"
1742
+ : "Run",
1743
+ );
1744
  }
1745
 
1746
  // ── Colour swatches: re-skin the rig live, with a quack ─────────────────
 
1774
  // Deterministic hooks for automated verification (rAF pauses in
1775
  // background tabs, and the control loop is async).
1776
  window.rl = {
1777
+ // model/data are getters: activateLoco swaps them wholesale.
1778
+ get model() { return model; },
1779
+ get data() { return data; },
1780
+ mujoco, camera, controls,
1781
  get mode() { return mode; },
1782
  get sitFlag() { return sitFlag; },
1783
  buildObs, cmd,
1784
  velCmd, lastAction, resetSim,
1785
  spawnBall, triggerKick, sessions, ort,
1786
+ get loco() { return loco; },
1787
+ get locoSwitching() { return locoSwitching; },
1788
+ toggleLoco, ensureRollers,
1789
+ triggerCrouch,
1790
+ get crouchPhase() { return crouchRun?.phase ?? null; },
1791
  get kickSteps() { return KICK_STEPS; },
1792
  set kickSteps(v) { KICK_STEPS = v; },
1793
  get ballActive() { return ballActive; },
 
1801
  // One full render-loop iteration, for tests driving frames manually.
1802
  frame: () => { syncRig(); syncJaw(); controls.update(); updateChaseCam(); updateEntrance(); renderStats(); renderer.render(scene, camera); },
1803
  get ghosts() { return ghosts; },
1804
+ get inputLocked() { return inputLocked; },
1805
  // Deterministic entrance controls for screenshots/tests: setting values
1806
  // detaches the time-based driver; setDissolve(1) also restores the
1807
+ // original materials and releases the input lock (same cleanup as the
1808
+ // real sequence).
1809
  entrance: {
1810
  start: startEntrance,
1811
  setReveal: (floor, wall = floor) => {
 
1815
  },
1816
  setDissolve: (p) => {
1817
  entranceT0 = null;
1818
+ if (p >= 1) { dissolveP.value = 1; restoreDissolve(); inputLocked = false; entranceFinishedResolve(); }
1819
  else { applyDissolve(); dissolveP.value = p; }
1820
  },
1821
  },
 
1826
  // translucent ducks. Fire-and-forget: any failure just means no ghosts.
1827
  const r3 = (x) => Math.round(x * 1000) / 1000;
1828
  try {
1829
+ // Ghosts only join once the entrance has fully played: the world (and
1830
+ // this duck) must stay hidden until then, translucent peers included.
1831
+ await entranceFinished;
1832
  const { initGhosts } = await import(signed(`./ghosts.js?v=${SELF_V}`));
1833
  ghosts = await initGhosts({
1834
  scene, rig, cloneRig, setJoint, setJawOpen, applyVariant,
1835
  jointNames: JOINT_NAMES,
1836
+ // Ghost rig per locomotion flag: peers in roller mode clone the roller
1837
+ // rig once this tab has built it, and fall back to the leg rig until
1838
+ // then (their wheels also don't spin - joints aren't broadcast for
1839
+ // the passive hinges). Known v1 limitation, documented in the README.
1840
+ getRigFor: (l) => (l && locos.rollers ? locos.rollers.rig : locos.legs.rig),
1841
  getLocalState: () => {
1842
  const qpos = data.qpos;
1843
  const j = new Array(NUM_JOINTS);
 
1847
  j,
1848
  w: r3(jawOpenNow()),
1849
  v: currentVariant,
1850
+ l: loco === "rollers" ? 1 : 0,
1851
  };
1852
  },
1853
  });
robot/mjlab/kinematics_rollers.json ADDED
@@ -0,0 +1,2267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bodies": [
3
+ {
4
+ "name": "trunk_base",
5
+ "parent": null,
6
+ "pos": [
7
+ 0.0,
8
+ 0.0,
9
+ 0.12
10
+ ],
11
+ "quat": [
12
+ 1.0,
13
+ 0.0,
14
+ 0.0,
15
+ 0.0
16
+ ],
17
+ "geoms": [
18
+ {
19
+ "type": "mesh",
20
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
21
+ "pos": [
22
+ 0.006,
23
+ 0.0175,
24
+ -0.005
25
+ ],
26
+ "quat": [
27
+ 1.0,
28
+ -0.0,
29
+ -0.0,
30
+ 0.0
31
+ ],
32
+ "color": [
33
+ 0.713725,
34
+ 0.760784,
35
+ 0.8,
36
+ 1.0
37
+ ]
38
+ },
39
+ {
40
+ "type": "mesh",
41
+ "mesh": "right_shell.stl",
42
+ "pos": [
43
+ 0.004,
44
+ -0.0005,
45
+ -0.0175
46
+ ],
47
+ "quat": [
48
+ 0.707107,
49
+ -0.0,
50
+ -0.0,
51
+ 0.707107
52
+ ],
53
+ "color": [
54
+ 0.8,
55
+ 0.8,
56
+ 0.8,
57
+ 1.0
58
+ ]
59
+ },
60
+ {
61
+ "type": "mesh",
62
+ "mesh": "banana_pcb_locker.stl",
63
+ "pos": [
64
+ 0.0042,
65
+ -0.0,
66
+ -0.0175215
67
+ ],
68
+ "quat": [
69
+ 0.707107,
70
+ -0.0,
71
+ 0.0,
72
+ 0.707107
73
+ ],
74
+ "color": [
75
+ 0.647059,
76
+ 0.647059,
77
+ 0.647059,
78
+ 1.0
79
+ ]
80
+ },
81
+ {
82
+ "type": "mesh",
83
+ "mesh": "trunk_base.stl",
84
+ "pos": [
85
+ 0.004,
86
+ -0.0,
87
+ -0.0175
88
+ ],
89
+ "quat": [
90
+ 0.707107,
91
+ -0.0,
92
+ -0.0,
93
+ 0.707107
94
+ ],
95
+ "color": [
96
+ 0.34902,
97
+ 0.376471,
98
+ 0.4,
99
+ 1.0
100
+ ]
101
+ },
102
+ {
103
+ "type": "mesh",
104
+ "mesh": "xl330.stl",
105
+ "pos": [
106
+ 0.006,
107
+ 0.0175,
108
+ 0.0115
109
+ ],
110
+ "quat": [
111
+ 0.0,
112
+ 0.707107,
113
+ -0.0,
114
+ 0.707107
115
+ ],
116
+ "color": [
117
+ 0.286275,
118
+ 0.286275,
119
+ 0.286275,
120
+ 1.0
121
+ ]
122
+ },
123
+ {
124
+ "type": "mesh",
125
+ "mesh": "power_support.stl",
126
+ "pos": [
127
+ 0.004,
128
+ -0.0,
129
+ -0.0175215
130
+ ],
131
+ "quat": [
132
+ 0.707107,
133
+ -0.0,
134
+ 0.0,
135
+ 0.707107
136
+ ],
137
+ "color": [
138
+ 0.901961,
139
+ 0.901961,
140
+ 0.901961,
141
+ 1.0
142
+ ]
143
+ },
144
+ {
145
+ "type": "mesh",
146
+ "mesh": "np_f970.stl",
147
+ "pos": [
148
+ -0.0359693,
149
+ 0.0,
150
+ -0.00385049
151
+ ],
152
+ "quat": [
153
+ 0.00617059,
154
+ 0.70708,
155
+ -0.70708,
156
+ -0.00617059
157
+ ],
158
+ "color": [
159
+ 0.901961,
160
+ 0.901961,
161
+ 0.901961,
162
+ 1.0
163
+ ]
164
+ },
165
+ {
166
+ "type": "mesh",
167
+ "mesh": "xl330.stl",
168
+ "pos": [
169
+ 0.006,
170
+ -0.0175,
171
+ 0.0115
172
+ ],
173
+ "quat": [
174
+ 0.0,
175
+ 0.707107,
176
+ -0.0,
177
+ 0.707107
178
+ ],
179
+ "color": [
180
+ 0.286275,
181
+ 0.286275,
182
+ 0.286275,
183
+ 1.0
184
+ ]
185
+ },
186
+ {
187
+ "type": "mesh",
188
+ "mesh": "left_shell.stl",
189
+ "pos": [
190
+ 0.004,
191
+ -0.0,
192
+ -0.0175785
193
+ ],
194
+ "quat": [
195
+ 0.707107,
196
+ 0.0,
197
+ -0.0,
198
+ 0.707107
199
+ ],
200
+ "color": [
201
+ 0.8,
202
+ 0.8,
203
+ 0.8,
204
+ 1.0
205
+ ]
206
+ },
207
+ {
208
+ "type": "mesh",
209
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
210
+ "pos": [
211
+ 0.006,
212
+ -0.0175,
213
+ -0.005
214
+ ],
215
+ "quat": [
216
+ 1.0,
217
+ -0.0,
218
+ -0.0,
219
+ 0.0
220
+ ],
221
+ "color": [
222
+ 0.713725,
223
+ 0.760784,
224
+ 0.8,
225
+ 1.0
226
+ ]
227
+ }
228
+ ],
229
+ "joint": null
230
+ },
231
+ {
232
+ "name": "yaw2roll",
233
+ "parent": "trunk_base",
234
+ "pos": [
235
+ 0.006,
236
+ 0.0175,
237
+ -0.005
238
+ ],
239
+ "quat": [
240
+ 0.0,
241
+ -0.707107,
242
+ -0.707107,
243
+ -0.0
244
+ ],
245
+ "geoms": [
246
+ {
247
+ "type": "mesh",
248
+ "mesh": "yaw2roll.stl",
249
+ "pos": [
250
+ -0.0175,
251
+ -0.002,
252
+ 0.0125
253
+ ],
254
+ "quat": [
255
+ 0.0,
256
+ 1.0,
257
+ 0.0,
258
+ 0.0
259
+ ],
260
+ "color": [
261
+ 0.647059,
262
+ 0.647059,
263
+ 0.647059,
264
+ 1.0
265
+ ]
266
+ },
267
+ {
268
+ "type": "mesh",
269
+ "mesh": "xl330.stl",
270
+ "pos": [
271
+ -0.0,
272
+ 0.0,
273
+ 0.0125
274
+ ],
275
+ "quat": [
276
+ 0.0,
277
+ -0.707107,
278
+ 0.707107,
279
+ -0.0
280
+ ],
281
+ "color": [
282
+ 0.286275,
283
+ 0.286275,
284
+ 0.286275,
285
+ 1.0
286
+ ]
287
+ },
288
+ {
289
+ "type": "mesh",
290
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
291
+ "pos": [
292
+ -0.0,
293
+ 0.0165,
294
+ 0.0125
295
+ ],
296
+ "quat": [
297
+ 0.5,
298
+ 0.5,
299
+ 0.5,
300
+ -0.5
301
+ ],
302
+ "color": [
303
+ 0.713725,
304
+ 0.760784,
305
+ 0.8,
306
+ 1.0
307
+ ]
308
+ },
309
+ {
310
+ "type": "mesh",
311
+ "mesh": "bearing_roll.stl",
312
+ "pos": [
313
+ -0.0175,
314
+ -0.002,
315
+ 0.0125
316
+ ],
317
+ "quat": [
318
+ 0.0,
319
+ -1.0,
320
+ -0.0,
321
+ -0.0
322
+ ],
323
+ "color": [
324
+ 0.262745,
325
+ 0.282353,
326
+ 0.301961,
327
+ 1.0
328
+ ]
329
+ }
330
+ ],
331
+ "joint": {
332
+ "name": "left_hip_yaw",
333
+ "axis": [
334
+ 0.0,
335
+ 0.0,
336
+ 1.0
337
+ ],
338
+ "type": "hinge",
339
+ "pos": [
340
+ 0,
341
+ 0,
342
+ 0
343
+ ],
344
+ "range": [
345
+ -0.4363323129985824,
346
+ 0.5235987755982988
347
+ ],
348
+ "actuator_index": 0
349
+ }
350
+ },
351
+ {
352
+ "name": "hip_l",
353
+ "parent": "yaw2roll",
354
+ "pos": [
355
+ 0.0,
356
+ 0.0165,
357
+ 0.0125
358
+ ],
359
+ "quat": [
360
+ 0.707107,
361
+ -0.707107,
362
+ -0.0,
363
+ -0.0
364
+ ],
365
+ "geoms": [
366
+ {
367
+ "type": "mesh",
368
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
369
+ "pos": [
370
+ 0.021,
371
+ 0.0,
372
+ -0.0185
373
+ ],
374
+ "quat": [
375
+ 0.5,
376
+ 0.5,
377
+ 0.5,
378
+ 0.5
379
+ ],
380
+ "color": [
381
+ 0.713725,
382
+ 0.760784,
383
+ 0.8,
384
+ 1.0
385
+ ]
386
+ },
387
+ {
388
+ "type": "mesh",
389
+ "mesh": "hip_l.stl",
390
+ "pos": [
391
+ -0.0175,
392
+ 0.0,
393
+ -0.0185
394
+ ],
395
+ "quat": [
396
+ 0.707107,
397
+ -0.707107,
398
+ 0.0,
399
+ -0.0
400
+ ],
401
+ "color": [
402
+ 0.901961,
403
+ 0.901961,
404
+ 0.901961,
405
+ 1.0
406
+ ]
407
+ }
408
+ ],
409
+ "joint": {
410
+ "name": "left_hip_roll",
411
+ "axis": [
412
+ 0.0,
413
+ 0.0,
414
+ 1.0
415
+ ],
416
+ "type": "hinge",
417
+ "pos": [
418
+ 0,
419
+ 0,
420
+ 0
421
+ ],
422
+ "range": [
423
+ -0.3839724354387525,
424
+ 0.3839724354387525
425
+ ],
426
+ "actuator_index": 1
427
+ }
428
+ },
429
+ {
430
+ "name": "upper_leg_left",
431
+ "parent": "hip_l",
432
+ "pos": [
433
+ 0.025,
434
+ 0.0,
435
+ -0.0185
436
+ ],
437
+ "quat": [
438
+ 0.5,
439
+ -0.5,
440
+ 0.5,
441
+ -0.5
442
+ ],
443
+ "geoms": [
444
+ {
445
+ "type": "mesh",
446
+ "mesh": "upper_leg_left.stl",
447
+ "pos": [
448
+ -0.0,
449
+ -0.0,
450
+ -0.0425
451
+ ],
452
+ "quat": [
453
+ 0.5,
454
+ -0.5,
455
+ -0.5,
456
+ -0.5
457
+ ],
458
+ "color": [
459
+ 0.866667,
460
+ 0.866667,
461
+ 0.866667,
462
+ 1.0
463
+ ]
464
+ },
465
+ {
466
+ "type": "mesh",
467
+ "mesh": "xl330.stl",
468
+ "pos": [
469
+ 0.022,
470
+ 0.0357771,
471
+ 0.0125
472
+ ],
473
+ "quat": [
474
+ 0.0,
475
+ -0.707107,
476
+ -0.0,
477
+ -0.707107
478
+ ],
479
+ "color": [
480
+ 0.286275,
481
+ 0.286275,
482
+ 0.286275,
483
+ 1.0
484
+ ]
485
+ },
486
+ {
487
+ "type": "mesh",
488
+ "mesh": "upper_leg_rigidity_plate.stl",
489
+ "pos": [
490
+ 0.0,
491
+ -0.0,
492
+ -0.0425
493
+ ],
494
+ "quat": [
495
+ 0.5,
496
+ -0.5,
497
+ -0.5,
498
+ -0.5
499
+ ],
500
+ "color": [
501
+ 0.537255,
502
+ 0.854902,
503
+ 0.827451,
504
+ 1.0
505
+ ]
506
+ },
507
+ {
508
+ "type": "mesh",
509
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
510
+ "pos": [
511
+ 0.022,
512
+ 0.0357771,
513
+ -0.004
514
+ ],
515
+ "quat": [
516
+ 0.0,
517
+ 0.0,
518
+ 0.0,
519
+ 1.0
520
+ ],
521
+ "color": [
522
+ 0.713725,
523
+ 0.760784,
524
+ 0.8,
525
+ 1.0
526
+ ]
527
+ },
528
+ {
529
+ "type": "mesh",
530
+ "mesh": "xl330.stl",
531
+ "pos": [
532
+ -0.0,
533
+ -0.0,
534
+ 0.0125
535
+ ],
536
+ "quat": [
537
+ 0.5,
538
+ 0.5,
539
+ -0.5,
540
+ 0.5
541
+ ],
542
+ "color": [
543
+ 0.286275,
544
+ 0.286275,
545
+ 0.286275,
546
+ 1.0
547
+ ]
548
+ }
549
+ ],
550
+ "joint": {
551
+ "name": "left_hip_pitch",
552
+ "axis": [
553
+ 0.0,
554
+ 0.0,
555
+ 1.0
556
+ ],
557
+ "type": "hinge",
558
+ "pos": [
559
+ 0,
560
+ 0,
561
+ 0
562
+ ],
563
+ "range": [
564
+ -1.570796326795005,
565
+ 1.5707963267947882
566
+ ],
567
+ "actuator_index": 2
568
+ }
569
+ },
570
+ {
571
+ "name": "leg",
572
+ "parent": "upper_leg_left",
573
+ "pos": [
574
+ 0.022,
575
+ 0.0357771,
576
+ -0.004
577
+ ],
578
+ "quat": [
579
+ 0.0,
580
+ 0.707107,
581
+ 0.707107,
582
+ 0.0
583
+ ],
584
+ "geoms": [
585
+ {
586
+ "type": "mesh",
587
+ "mesh": "leg.stl",
588
+ "pos": [
589
+ -0.022,
590
+ 0.0357771,
591
+ 0.0385
592
+ ],
593
+ "quat": [
594
+ 0.5,
595
+ 0.5,
596
+ 0.5,
597
+ -0.5
598
+ ],
599
+ "color": [
600
+ 0.811765,
601
+ 0.858824,
602
+ 0.898039,
603
+ 1.0
604
+ ]
605
+ },
606
+ {
607
+ "type": "mesh",
608
+ "mesh": "xl330.stl",
609
+ "pos": [
610
+ 0.0,
611
+ 0.042,
612
+ -0.0115
613
+ ],
614
+ "quat": [
615
+ 0.5,
616
+ -0.5,
617
+ -0.5,
618
+ -0.5
619
+ ],
620
+ "color": [
621
+ 0.286275,
622
+ 0.286275,
623
+ 0.286275,
624
+ 1.0
625
+ ]
626
+ }
627
+ ],
628
+ "joint": {
629
+ "name": "left_knee",
630
+ "axis": [
631
+ 0.0,
632
+ 0.0,
633
+ 1.0
634
+ ],
635
+ "type": "hinge",
636
+ "pos": [
637
+ 0,
638
+ 0,
639
+ 0
640
+ ],
641
+ "range": [
642
+ -1.570796326795012,
643
+ 1.570796326794781
644
+ ],
645
+ "actuator_index": 3
646
+ }
647
+ },
648
+ {
649
+ "name": "ankle_l_v1",
650
+ "parent": "leg",
651
+ "pos": [
652
+ 0.0,
653
+ 0.042,
654
+ -0.026
655
+ ],
656
+ "quat": [
657
+ 0.0,
658
+ 1.0,
659
+ -0.0,
660
+ 0.0
661
+ ],
662
+ "geoms": [
663
+ {
664
+ "type": "mesh",
665
+ "mesh": "roller_blade.stl",
666
+ "pos": [
667
+ -0.022,
668
+ 0.00622291,
669
+ -0.0647
670
+ ],
671
+ "quat": [
672
+ 0.5,
673
+ -0.5,
674
+ -0.5,
675
+ -0.5
676
+ ],
677
+ "color": [
678
+ 0.901961,
679
+ 0.901961,
680
+ 0.901961,
681
+ 1.0
682
+ ]
683
+ },
684
+ {
685
+ "type": "mesh",
686
+ "mesh": "seeed_bearing__configuration_default.stl",
687
+ "pos": [
688
+ 0.0,
689
+ 0.0,
690
+ -0.0314
691
+ ],
692
+ "quat": [
693
+ 0.0,
694
+ -0.0,
695
+ 0.0,
696
+ -1.0
697
+ ],
698
+ "color": [
699
+ 0.713725,
700
+ 0.760784,
701
+ 0.8,
702
+ 1.0
703
+ ]
704
+ },
705
+ {
706
+ "type": "mesh",
707
+ "mesh": "ankle_l_v1.stl",
708
+ "pos": [
709
+ -0.022,
710
+ 0.00622291,
711
+ -0.0647
712
+ ],
713
+ "quat": [
714
+ 0.5,
715
+ -0.5,
716
+ -0.5,
717
+ -0.5
718
+ ],
719
+ "color": [
720
+ 0.980392,
721
+ 0.713725,
722
+ 0.00392157,
723
+ 1.0
724
+ ]
725
+ }
726
+ ],
727
+ "joint": {
728
+ "name": "left_ankle",
729
+ "axis": [
730
+ 0.0,
731
+ 0.0,
732
+ 1.0
733
+ ],
734
+ "type": "hinge",
735
+ "pos": [
736
+ 0,
737
+ 0,
738
+ 0
739
+ ],
740
+ "range": [
741
+ -1.5707963267949054,
742
+ 1.5707963267948877
743
+ ],
744
+ "actuator_index": 4
745
+ }
746
+ },
747
+ {
748
+ "name": "tire",
749
+ "parent": "ankle_l_v1",
750
+ "pos": [
751
+ -0.0395,
752
+ -0.0325,
753
+ -0.0147
754
+ ],
755
+ "quat": [
756
+ 1.0,
757
+ -0.0,
758
+ 0.0,
759
+ -0.0
760
+ ],
761
+ "geoms": [
762
+ {
763
+ "type": "mesh",
764
+ "mesh": "tire.stl",
765
+ "pos": [
766
+ 0.0175,
767
+ 0.0387229,
768
+ -0.05
769
+ ],
770
+ "quat": [
771
+ 0.5,
772
+ -0.5,
773
+ -0.5,
774
+ -0.5
775
+ ],
776
+ "color": [
777
+ 0.34902,
778
+ 0.376471,
779
+ 0.4,
780
+ 1.0
781
+ ]
782
+ },
783
+ {
784
+ "type": "mesh",
785
+ "mesh": "rim.stl",
786
+ "pos": [
787
+ 0.0175,
788
+ 0.0387229,
789
+ -0.05
790
+ ],
791
+ "quat": [
792
+ 0.5,
793
+ -0.5,
794
+ -0.5,
795
+ -0.5
796
+ ],
797
+ "color": [
798
+ 0.729412,
799
+ 0.25098,
800
+ 0.105882,
801
+ 1.0
802
+ ]
803
+ }
804
+ ],
805
+ "joint": {
806
+ "name": "passive_LF_wheel",
807
+ "axis": [
808
+ 0.0,
809
+ 0.0,
810
+ 1.0
811
+ ],
812
+ "type": "hinge",
813
+ "pos": [
814
+ 0,
815
+ 0,
816
+ 0
817
+ ],
818
+ "range": null
819
+ }
820
+ },
821
+ {
822
+ "name": "tire_2",
823
+ "parent": "ankle_l_v1",
824
+ "pos": [
825
+ 0.0255,
826
+ -0.0325,
827
+ -0.0147
828
+ ],
829
+ "quat": [
830
+ 1.0,
831
+ -0.0,
832
+ 0.0,
833
+ -0.0
834
+ ],
835
+ "geoms": [
836
+ {
837
+ "type": "mesh",
838
+ "mesh": "rim.stl",
839
+ "pos": [
840
+ 0.0175,
841
+ 0.0387229,
842
+ -0.05
843
+ ],
844
+ "quat": [
845
+ 0.5,
846
+ -0.5,
847
+ -0.5,
848
+ -0.5
849
+ ],
850
+ "color": [
851
+ 0.729412,
852
+ 0.25098,
853
+ 0.105882,
854
+ 1.0
855
+ ]
856
+ },
857
+ {
858
+ "type": "mesh",
859
+ "mesh": "tire.stl",
860
+ "pos": [
861
+ 0.0175,
862
+ 0.0387229,
863
+ -0.05
864
+ ],
865
+ "quat": [
866
+ 0.5,
867
+ -0.5,
868
+ -0.5,
869
+ -0.5
870
+ ],
871
+ "color": [
872
+ 0.34902,
873
+ 0.376471,
874
+ 0.4,
875
+ 1.0
876
+ ]
877
+ }
878
+ ],
879
+ "joint": {
880
+ "name": "passive_LR_wheel",
881
+ "axis": [
882
+ 0.0,
883
+ 0.0,
884
+ 1.0
885
+ ],
886
+ "type": "hinge",
887
+ "pos": [
888
+ 0,
889
+ 0,
890
+ 0
891
+ ],
892
+ "range": null
893
+ }
894
+ },
895
+ {
896
+ "name": "neck",
897
+ "parent": "trunk_base",
898
+ "pos": [
899
+ 0.026,
900
+ 0.0145,
901
+ 0.0324215
902
+ ],
903
+ "quat": [
904
+ 0.0,
905
+ 0.0,
906
+ 0.707107,
907
+ -0.707107
908
+ ],
909
+ "geoms": [
910
+ {
911
+ "type": "mesh",
912
+ "mesh": "neck.stl",
913
+ "pos": [
914
+ 0.022,
915
+ 0.05,
916
+ 0.028
917
+ ],
918
+ "quat": [
919
+ 0.5,
920
+ 0.5,
921
+ 0.5,
922
+ -0.5
923
+ ],
924
+ "color": [
925
+ 0.8,
926
+ 0.8,
927
+ 0.8,
928
+ 1.0
929
+ ]
930
+ },
931
+ {
932
+ "type": "mesh",
933
+ "mesh": "xl330.stl",
934
+ "pos": [
935
+ 0.0,
936
+ 0.0,
937
+ 0.0145
938
+ ],
939
+ "quat": [
940
+ 0.5,
941
+ -0.5,
942
+ -0.5,
943
+ -0.5
944
+ ],
945
+ "color": [
946
+ 0.286275,
947
+ 0.286275,
948
+ 0.286275,
949
+ 1.0
950
+ ]
951
+ },
952
+ {
953
+ "type": "mesh",
954
+ "mesh": "xl330.stl",
955
+ "pos": [
956
+ 0.0,
957
+ -0.05,
958
+ 0.0145
959
+ ],
960
+ "quat": [
961
+ 0.5,
962
+ 0.5,
963
+ -0.5,
964
+ 0.5
965
+ ],
966
+ "color": [
967
+ 0.286275,
968
+ 0.286275,
969
+ 0.286275,
970
+ 1.0
971
+ ]
972
+ },
973
+ {
974
+ "type": "mesh",
975
+ "mesh": "neck.stl",
976
+ "pos": [
977
+ 0.022,
978
+ 0.05,
979
+ 0.003
980
+ ],
981
+ "quat": [
982
+ 0.5,
983
+ 0.5,
984
+ 0.5,
985
+ -0.5
986
+ ],
987
+ "color": [
988
+ 0.8,
989
+ 0.8,
990
+ 0.8,
991
+ 1.0
992
+ ]
993
+ }
994
+ ],
995
+ "joint": {
996
+ "name": "neck_pitch",
997
+ "axis": [
998
+ 0.0,
999
+ 0.0,
1000
+ 1.0
1001
+ ],
1002
+ "type": "hinge",
1003
+ "pos": [
1004
+ 0,
1005
+ 0,
1006
+ 0
1007
+ ],
1008
+ "range": [
1009
+ -1.5707963267948966,
1010
+ 1.0471975511965976
1011
+ ],
1012
+ "actuator_index": 5
1013
+ }
1014
+ },
1015
+ {
1016
+ "name": "neck_pitch",
1017
+ "parent": "neck",
1018
+ "pos": [
1019
+ 0.0,
1020
+ -0.05,
1021
+ -0.0
1022
+ ],
1023
+ "quat": [
1024
+ 0.0,
1025
+ 1.0,
1026
+ 0.0,
1027
+ 0.0
1028
+ ],
1029
+ "geoms": [
1030
+ {
1031
+ "type": "mesh",
1032
+ "mesh": "neck_pitch.stl",
1033
+ "pos": [
1034
+ -0.0,
1035
+ 0.0287931,
1036
+ -0.0145
1037
+ ],
1038
+ "quat": [
1039
+ 0.5,
1040
+ -0.5,
1041
+ -0.5,
1042
+ -0.5
1043
+ ],
1044
+ "color": [
1045
+ 0.34902,
1046
+ 0.376471,
1047
+ 0.4,
1048
+ 1.0
1049
+ ]
1050
+ },
1051
+ {
1052
+ "type": "mesh",
1053
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
1054
+ "pos": [
1055
+ -0.0,
1056
+ 0.0207931,
1057
+ -0.0145
1058
+ ],
1059
+ "quat": [
1060
+ 0.707107,
1061
+ 0.707107,
1062
+ 0.0,
1063
+ 0.0
1064
+ ],
1065
+ "color": [
1066
+ 0.713725,
1067
+ 0.760784,
1068
+ 0.8,
1069
+ 1.0
1070
+ ]
1071
+ }
1072
+ ],
1073
+ "joint": {
1074
+ "name": "head_pitch",
1075
+ "axis": [
1076
+ 0.0,
1077
+ 0.0,
1078
+ 1.0
1079
+ ],
1080
+ "type": "hinge",
1081
+ "pos": [
1082
+ 0,
1083
+ 0,
1084
+ 0
1085
+ ],
1086
+ "range": [
1087
+ -1.5707963267948974,
1088
+ 1.5707963267948957
1089
+ ],
1090
+ "actuator_index": 6
1091
+ }
1092
+ },
1093
+ {
1094
+ "name": "yaw_roll_motion",
1095
+ "parent": "neck_pitch",
1096
+ "pos": [
1097
+ -0.0,
1098
+ 0.0186931,
1099
+ -0.0145
1100
+ ],
1101
+ "quat": [
1102
+ 0.0,
1103
+ 0.0,
1104
+ -0.707107,
1105
+ -0.707107
1106
+ ],
1107
+ "geoms": [
1108
+ {
1109
+ "type": "mesh",
1110
+ "mesh": "yaw_roll_motion.stl",
1111
+ "pos": [
1112
+ 0.0,
1113
+ 0.0,
1114
+ 0.01
1115
+ ],
1116
+ "quat": [
1117
+ 0.707107,
1118
+ 0.0,
1119
+ -0.0,
1120
+ 0.707107
1121
+ ],
1122
+ "color": [
1123
+ 0.768627,
1124
+ 0.886275,
1125
+ 0.952941,
1126
+ 1.0
1127
+ ]
1128
+ },
1129
+ {
1130
+ "type": "mesh",
1131
+ "mesh": "xl330.stl",
1132
+ "pos": [
1133
+ 0.0,
1134
+ 0.0,
1135
+ 0.0145
1136
+ ],
1137
+ "quat": [
1138
+ 0.5,
1139
+ -0.5,
1140
+ -0.5,
1141
+ -0.5
1142
+ ],
1143
+ "color": [
1144
+ 0.286275,
1145
+ 0.286275,
1146
+ 0.286275,
1147
+ 1.0
1148
+ ]
1149
+ },
1150
+ {
1151
+ "type": "mesh",
1152
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
1153
+ "pos": [
1154
+ -0.016,
1155
+ 0.0,
1156
+ 0.0145
1157
+ ],
1158
+ "quat": [
1159
+ 0.707107,
1160
+ -0.0,
1161
+ -0.707107,
1162
+ -0.0
1163
+ ],
1164
+ "color": [
1165
+ 0.713725,
1166
+ 0.760784,
1167
+ 0.8,
1168
+ 1.0
1169
+ ]
1170
+ },
1171
+ {
1172
+ "type": "mesh",
1173
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
1174
+ "pos": [
1175
+ 0.018,
1176
+ 0.0,
1177
+ 0.0145
1178
+ ],
1179
+ "quat": [
1180
+ 0.707107,
1181
+ -0.0,
1182
+ -0.707107,
1183
+ -0.0
1184
+ ],
1185
+ "color": [
1186
+ 0.713725,
1187
+ 0.760784,
1188
+ 0.8,
1189
+ 1.0
1190
+ ]
1191
+ }
1192
+ ],
1193
+ "joint": {
1194
+ "name": "head_yaw",
1195
+ "axis": [
1196
+ 0.0,
1197
+ 0.0,
1198
+ 1.0
1199
+ ],
1200
+ "type": "hinge",
1201
+ "pos": [
1202
+ 0,
1203
+ 0,
1204
+ 0
1205
+ ],
1206
+ "range": [
1207
+ -2.967059728390373,
1208
+ 2.967059728390348
1209
+ ],
1210
+ "actuator_index": 7
1211
+ }
1212
+ },
1213
+ {
1214
+ "name": "jaw_soft",
1215
+ "parent": "yaw_roll_motion",
1216
+ "pos": [
1217
+ -0.0179,
1218
+ 0.0,
1219
+ 0.0145
1220
+ ],
1221
+ "quat": [
1222
+ 0.707107,
1223
+ -0.0,
1224
+ -0.707107,
1225
+ -0.0
1226
+ ],
1227
+ "geoms": [
1228
+ {
1229
+ "type": "mesh",
1230
+ "mesh": "lens.stl",
1231
+ "pos": [
1232
+ 0.0155,
1233
+ -9.13778e-05,
1234
+ -0.05548
1235
+ ],
1236
+ "quat": [
1237
+ 0.5,
1238
+ -0.5,
1239
+ 0.5,
1240
+ -0.5
1241
+ ],
1242
+ "color": [
1243
+ 0.262745,
1244
+ 0.282353,
1245
+ 0.301961,
1246
+ 1.0
1247
+ ]
1248
+ },
1249
+ {
1250
+ "type": "mesh",
1251
+ "mesh": "face_part.stl",
1252
+ "pos": [
1253
+ -0.0045,
1254
+ -9.13778e-05,
1255
+ -0.0178
1256
+ ],
1257
+ "quat": [
1258
+ 0.5,
1259
+ 0.5,
1260
+ 0.5,
1261
+ 0.5
1262
+ ],
1263
+ "color": [
1264
+ 0.34902,
1265
+ 0.376471,
1266
+ 0.4,
1267
+ 1.0
1268
+ ]
1269
+ },
1270
+ {
1271
+ "type": "mesh",
1272
+ "mesh": "xl330.stl",
1273
+ "pos": [
1274
+ 0.003,
1275
+ 0.0255,
1276
+ -0.018
1277
+ ],
1278
+ "quat": [
1279
+ 0.707107,
1280
+ 0.0,
1281
+ -0.0,
1282
+ -0.707107
1283
+ ],
1284
+ "color": [
1285
+ 0.286275,
1286
+ 0.286275,
1287
+ 0.286275,
1288
+ 1.0
1289
+ ]
1290
+ },
1291
+ {
1292
+ "type": "mesh",
1293
+ "mesh": "jaw_soft.stl",
1294
+ "pos": [
1295
+ -0.0046,
1296
+ 0.0,
1297
+ -0.0179996
1298
+ ],
1299
+ "quat": [
1300
+ 0.5,
1301
+ 0.5,
1302
+ 0.5,
1303
+ 0.5
1304
+ ],
1305
+ "color": [
1306
+ 0.85098,
1307
+ 0.756863,
1308
+ 0.866667,
1309
+ 1.0
1310
+ ]
1311
+ },
1312
+ {
1313
+ "type": "mesh",
1314
+ "mesh": "motor_support.stl",
1315
+ "pos": [
1316
+ -0.0045,
1317
+ 0.0,
1318
+ -0.018
1319
+ ],
1320
+ "quat": [
1321
+ 0.5,
1322
+ 0.5,
1323
+ 0.5,
1324
+ 0.5
1325
+ ],
1326
+ "color": [
1327
+ 0.262745,
1328
+ 0.282353,
1329
+ 0.301961,
1330
+ 1.0
1331
+ ]
1332
+ },
1333
+ {
1334
+ "type": "mesh",
1335
+ "mesh": "top_head_shell.stl",
1336
+ "pos": [
1337
+ -0.0045,
1338
+ -0.0,
1339
+ -0.018
1340
+ ],
1341
+ "quat": [
1342
+ 0.5,
1343
+ 0.5,
1344
+ 0.5,
1345
+ 0.5
1346
+ ],
1347
+ "color": [
1348
+ 0.901961,
1349
+ 0.901961,
1350
+ 0.901961,
1351
+ 1.0
1352
+ ]
1353
+ },
1354
+ {
1355
+ "type": "mesh",
1356
+ "mesh": "xl330.stl",
1357
+ "pos": [
1358
+ 0.0,
1359
+ 0.0,
1360
+ 0.0145
1361
+ ],
1362
+ "quat": [
1363
+ 0.5,
1364
+ -0.5,
1365
+ -0.5,
1366
+ -0.5
1367
+ ],
1368
+ "color": [
1369
+ 0.286275,
1370
+ 0.286275,
1371
+ 0.286275,
1372
+ 1.0
1373
+ ]
1374
+ },
1375
+ {
1376
+ "type": "mesh",
1377
+ "mesh": "seeed_bearing__configuration_default.stl",
1378
+ "pos": [
1379
+ 0.0032,
1380
+ -0.04,
1381
+ -0.018
1382
+ ],
1383
+ "quat": [
1384
+ 0.5,
1385
+ -0.5,
1386
+ 0.5,
1387
+ 0.5
1388
+ ],
1389
+ "color": [
1390
+ 0.713725,
1391
+ 0.760784,
1392
+ 0.8,
1393
+ 1.0
1394
+ ]
1395
+ },
1396
+ {
1397
+ "type": "mesh",
1398
+ "mesh": "elec_rpi_robot_hat_pcb.stl",
1399
+ "pos": [
1400
+ 0.02694,
1401
+ 0.0289936,
1402
+ -0.0519
1403
+ ],
1404
+ "quat": [
1405
+ 0.707107,
1406
+ 0.0,
1407
+ 0.0,
1408
+ -0.707107
1409
+ ],
1410
+ "color": [
1411
+ 0.313725,
1412
+ 0.486275,
1413
+ 0.411765,
1414
+ 1.0
1415
+ ]
1416
+ },
1417
+ {
1418
+ "type": "mesh",
1419
+ "mesh": "soft_mouth_top.stl",
1420
+ "pos": [
1421
+ -0.00420071,
1422
+ 1.7776e-08,
1423
+ -0.0180814
1424
+ ],
1425
+ "quat": [
1426
+ 0.5,
1427
+ 0.5,
1428
+ 0.5,
1429
+ 0.5
1430
+ ],
1431
+ "color": [
1432
+ 0.85098,
1433
+ 0.756863,
1434
+ 0.866667,
1435
+ 1.0
1436
+ ]
1437
+ },
1438
+ {
1439
+ "type": "mesh",
1440
+ "mesh": "noenoeil.stl",
1441
+ "pos": [
1442
+ -0.0045,
1443
+ -9.13778e-05,
1444
+ -0.0178
1445
+ ],
1446
+ "quat": [
1447
+ 0.5,
1448
+ 0.5,
1449
+ 0.5,
1450
+ 0.5
1451
+ ],
1452
+ "color": [
1453
+ 0.262745,
1454
+ 0.282353,
1455
+ 0.301961,
1456
+ 1.0
1457
+ ]
1458
+ },
1459
+ {
1460
+ "type": "mesh",
1461
+ "mesh": "jaw.stl",
1462
+ "pos": [
1463
+ -0.0045,
1464
+ 0.0,
1465
+ -0.018
1466
+ ],
1467
+ "quat": [
1468
+ 0.5,
1469
+ 0.5,
1470
+ 0.5,
1471
+ 0.5
1472
+ ],
1473
+ "color": [
1474
+ 0.980392,
1475
+ 0.713725,
1476
+ 0.00392157,
1477
+ 1.0
1478
+ ]
1479
+ },
1480
+ {
1481
+ "type": "mesh",
1482
+ "mesh": "bottom_head_shell.stl",
1483
+ "pos": [
1484
+ -0.0043,
1485
+ -0.0,
1486
+ -0.018
1487
+ ],
1488
+ "quat": [
1489
+ 0.5,
1490
+ 0.5,
1491
+ 0.5,
1492
+ 0.5
1493
+ ],
1494
+ "color": [
1495
+ 0.980392,
1496
+ 0.713725,
1497
+ 0.00392157,
1498
+ 1.0
1499
+ ]
1500
+ },
1501
+ {
1502
+ "type": "mesh",
1503
+ "mesh": "m12_lens_holder.stl",
1504
+ "pos": [
1505
+ 0.0155,
1506
+ -9.13778e-05,
1507
+ -0.05558
1508
+ ],
1509
+ "quat": [
1510
+ 0.5,
1511
+ -0.5,
1512
+ 0.5,
1513
+ -0.5
1514
+ ],
1515
+ "color": [
1516
+ 0.262745,
1517
+ 0.282353,
1518
+ 0.301961,
1519
+ 1.0
1520
+ ]
1521
+ },
1522
+ {
1523
+ "type": "mesh",
1524
+ "mesh": "pcb__raspberry_pi_zero_2_w.stl",
1525
+ "pos": [
1526
+ 0.015435,
1527
+ -1.13778e-05,
1528
+ -0.06001
1529
+ ],
1530
+ "quat": [
1531
+ 0.5,
1532
+ -0.5,
1533
+ -0.5,
1534
+ 0.5
1535
+ ],
1536
+ "color": [
1537
+ 0.498039,
1538
+ 0.498039,
1539
+ 0.498039,
1540
+ 1.0
1541
+ ]
1542
+ },
1543
+ {
1544
+ "type": "mesh",
1545
+ "mesh": "speaker.stl",
1546
+ "pos": [
1547
+ 0.01,
1548
+ -0.016786,
1549
+ 0.0260226
1550
+ ],
1551
+ "quat": [
1552
+ 0.5,
1553
+ 0.5,
1554
+ 0.5,
1555
+ 0.5
1556
+ ],
1557
+ "color": [
1558
+ 0.294118,
1559
+ 0.290196,
1560
+ 0.290196,
1561
+ 1.0
1562
+ ]
1563
+ }
1564
+ ],
1565
+ "joint": {
1566
+ "name": "head_roll",
1567
+ "axis": [
1568
+ 0.0,
1569
+ 0.0,
1570
+ 1.0
1571
+ ],
1572
+ "type": "hinge",
1573
+ "pos": [
1574
+ 0,
1575
+ 0,
1576
+ 0
1577
+ ],
1578
+ "range": [
1579
+ -0.43633231299859127,
1580
+ 0.4363323129985735
1581
+ ],
1582
+ "actuator_index": 8
1583
+ }
1584
+ },
1585
+ {
1586
+ "name": "bearing_roll",
1587
+ "parent": "trunk_base",
1588
+ "pos": [
1589
+ 0.006,
1590
+ -0.0175,
1591
+ -0.005
1592
+ ],
1593
+ "quat": [
1594
+ 0.0,
1595
+ 0.707107,
1596
+ 0.707107,
1597
+ 0.0
1598
+ ],
1599
+ "geoms": [
1600
+ {
1601
+ "type": "mesh",
1602
+ "mesh": "yaw2roll.stl",
1603
+ "pos": [
1604
+ -0.0175,
1605
+ -0.002,
1606
+ 0.0125
1607
+ ],
1608
+ "quat": [
1609
+ 0.0,
1610
+ 1.0,
1611
+ 0.0,
1612
+ 0.0
1613
+ ],
1614
+ "color": [
1615
+ 0.647059,
1616
+ 0.647059,
1617
+ 0.647059,
1618
+ 1.0
1619
+ ]
1620
+ },
1621
+ {
1622
+ "type": "mesh",
1623
+ "mesh": "xl330.stl",
1624
+ "pos": [
1625
+ -0.0,
1626
+ 0.0,
1627
+ 0.0125
1628
+ ],
1629
+ "quat": [
1630
+ 0.0,
1631
+ -0.707107,
1632
+ 0.707107,
1633
+ -0.0
1634
+ ],
1635
+ "color": [
1636
+ 0.286275,
1637
+ 0.286275,
1638
+ 0.286275,
1639
+ 1.0
1640
+ ]
1641
+ },
1642
+ {
1643
+ "type": "mesh",
1644
+ "mesh": "bearing_roll.stl",
1645
+ "pos": [
1646
+ -0.0175,
1647
+ -0.002,
1648
+ 0.0125
1649
+ ],
1650
+ "quat": [
1651
+ 0.0,
1652
+ -1.0,
1653
+ -0.0,
1654
+ -0.0
1655
+ ],
1656
+ "color": [
1657
+ 0.262745,
1658
+ 0.282353,
1659
+ 0.301961,
1660
+ 1.0
1661
+ ]
1662
+ },
1663
+ {
1664
+ "type": "mesh",
1665
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
1666
+ "pos": [
1667
+ -0.0,
1668
+ 0.0165,
1669
+ 0.0125
1670
+ ],
1671
+ "quat": [
1672
+ 0.5,
1673
+ 0.5,
1674
+ 0.5,
1675
+ -0.5
1676
+ ],
1677
+ "color": [
1678
+ 0.713725,
1679
+ 0.760784,
1680
+ 0.8,
1681
+ 1.0
1682
+ ]
1683
+ }
1684
+ ],
1685
+ "joint": {
1686
+ "name": "right_hip_yaw",
1687
+ "axis": [
1688
+ 0.0,
1689
+ 0.0,
1690
+ 1.0
1691
+ ],
1692
+ "type": "hinge",
1693
+ "pos": [
1694
+ 0,
1695
+ 0,
1696
+ 0
1697
+ ],
1698
+ "range": [
1699
+ -0.5235987755982988,
1700
+ 0.4363323129985824
1701
+ ],
1702
+ "actuator_index": 9
1703
+ }
1704
+ },
1705
+ {
1706
+ "name": "hip_l_2",
1707
+ "parent": "bearing_roll",
1708
+ "pos": [
1709
+ -0.0,
1710
+ 0.0165,
1711
+ 0.0125
1712
+ ],
1713
+ "quat": [
1714
+ 0.0,
1715
+ -0.0,
1716
+ 0.707107,
1717
+ 0.707107
1718
+ ],
1719
+ "geoms": [
1720
+ {
1721
+ "type": "mesh",
1722
+ "mesh": "hip_l.stl",
1723
+ "pos": [
1724
+ -0.0175,
1725
+ 0.0,
1726
+ -0.0185
1727
+ ],
1728
+ "quat": [
1729
+ 0.707107,
1730
+ -0.707107,
1731
+ -0.0,
1732
+ -0.0
1733
+ ],
1734
+ "color": [
1735
+ 0.901961,
1736
+ 0.901961,
1737
+ 0.901961,
1738
+ 1.0
1739
+ ]
1740
+ },
1741
+ {
1742
+ "type": "mesh",
1743
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
1744
+ "pos": [
1745
+ 0.025,
1746
+ 0.0,
1747
+ -0.0185
1748
+ ],
1749
+ "quat": [
1750
+ 0.5,
1751
+ 0.5,
1752
+ -0.5,
1753
+ -0.5
1754
+ ],
1755
+ "color": [
1756
+ 0.713725,
1757
+ 0.760784,
1758
+ 0.8,
1759
+ 1.0
1760
+ ]
1761
+ }
1762
+ ],
1763
+ "joint": {
1764
+ "name": "right_hip_roll",
1765
+ "axis": [
1766
+ 0.0,
1767
+ 0.0,
1768
+ 1.0
1769
+ ],
1770
+ "type": "hinge",
1771
+ "pos": [
1772
+ 0,
1773
+ 0,
1774
+ 0
1775
+ ],
1776
+ "range": [
1777
+ -0.3839724354387525,
1778
+ 0.3839724354387525
1779
+ ],
1780
+ "actuator_index": 10
1781
+ }
1782
+ },
1783
+ {
1784
+ "name": "upper_leg_right",
1785
+ "parent": "hip_l_2",
1786
+ "pos": [
1787
+ 0.025,
1788
+ 0.0,
1789
+ -0.0185
1790
+ ],
1791
+ "quat": [
1792
+ 0.5,
1793
+ -0.5,
1794
+ 0.5,
1795
+ -0.5
1796
+ ],
1797
+ "geoms": [
1798
+ {
1799
+ "type": "mesh",
1800
+ "mesh": "xl330.stl",
1801
+ "pos": [
1802
+ -0.022,
1803
+ 0.0357771,
1804
+ 0.0125
1805
+ ],
1806
+ "quat": [
1807
+ 0.707107,
1808
+ -0.0,
1809
+ -0.707107,
1810
+ -0.0
1811
+ ],
1812
+ "color": [
1813
+ 0.286275,
1814
+ 0.286275,
1815
+ 0.286275,
1816
+ 1.0
1817
+ ]
1818
+ },
1819
+ {
1820
+ "type": "mesh",
1821
+ "mesh": "seeed_bearing__configuration__22x16x4.stl",
1822
+ "pos": [
1823
+ -0.022,
1824
+ 0.0357771,
1825
+ 0.0
1826
+ ],
1827
+ "quat": [
1828
+ 0.0,
1829
+ 1.0,
1830
+ -0.0,
1831
+ -0.0
1832
+ ],
1833
+ "color": [
1834
+ 0.713725,
1835
+ 0.760784,
1836
+ 0.8,
1837
+ 1.0
1838
+ ]
1839
+ },
1840
+ {
1841
+ "type": "mesh",
1842
+ "mesh": "upper_leg_rigidity_plate.stl",
1843
+ "pos": [
1844
+ 0.0,
1845
+ 0.0,
1846
+ 0.0435
1847
+ ],
1848
+ "quat": [
1849
+ 0.5,
1850
+ -0.5,
1851
+ 0.5,
1852
+ 0.5
1853
+ ],
1854
+ "color": [
1855
+ 0.537255,
1856
+ 0.854902,
1857
+ 0.827451,
1858
+ 1.0
1859
+ ]
1860
+ },
1861
+ {
1862
+ "type": "mesh",
1863
+ "mesh": "upper_leg_right.stl",
1864
+ "pos": [
1865
+ -0.0,
1866
+ 0.0,
1867
+ -0.0425
1868
+ ],
1869
+ "quat": [
1870
+ 0.5,
1871
+ -0.5,
1872
+ 0.5,
1873
+ 0.5
1874
+ ],
1875
+ "color": [
1876
+ 0.866667,
1877
+ 0.866667,
1878
+ 0.866667,
1879
+ 1.0
1880
+ ]
1881
+ },
1882
+ {
1883
+ "type": "mesh",
1884
+ "mesh": "xl330.stl",
1885
+ "pos": [
1886
+ 0.0,
1887
+ 0.0,
1888
+ 0.0125
1889
+ ],
1890
+ "quat": [
1891
+ 0.5,
1892
+ 0.5,
1893
+ -0.5,
1894
+ 0.5
1895
+ ],
1896
+ "color": [
1897
+ 0.286275,
1898
+ 0.286275,
1899
+ 0.286275,
1900
+ 1.0
1901
+ ]
1902
+ }
1903
+ ],
1904
+ "joint": {
1905
+ "name": "right_hip_pitch",
1906
+ "axis": [
1907
+ 0.0,
1908
+ 0.0,
1909
+ 1.0
1910
+ ],
1911
+ "type": "hinge",
1912
+ "pos": [
1913
+ 0,
1914
+ 0,
1915
+ 0
1916
+ ],
1917
+ "range": [
1918
+ -1.5707963267949268,
1919
+ 1.5707963267948664
1920
+ ],
1921
+ "actuator_index": 11
1922
+ }
1923
+ },
1924
+ {
1925
+ "name": "leg_2",
1926
+ "parent": "upper_leg_right",
1927
+ "pos": [
1928
+ -0.022,
1929
+ 0.0357771,
1930
+ -0.004
1931
+ ],
1932
+ "quat": [
1933
+ 0.0,
1934
+ 1.0,
1935
+ -0.0,
1936
+ -0.0
1937
+ ],
1938
+ "geoms": [
1939
+ {
1940
+ "type": "mesh",
1941
+ "mesh": "xl330.stl",
1942
+ "pos": [
1943
+ -0.042,
1944
+ 0.0,
1945
+ -0.0115
1946
+ ],
1947
+ "quat": [
1948
+ 0.707107,
1949
+ -0.0,
1950
+ -0.707107,
1951
+ -0.0
1952
+ ],
1953
+ "color": [
1954
+ 0.286275,
1955
+ 0.286275,
1956
+ 0.286275,
1957
+ 1.0
1958
+ ]
1959
+ },
1960
+ {
1961
+ "type": "mesh",
1962
+ "mesh": "leg.stl",
1963
+ "pos": [
1964
+ -0.0357771,
1965
+ -0.022,
1966
+ 0.0385
1967
+ ],
1968
+ "quat": [
1969
+ 0.707107,
1970
+ 0.0,
1971
+ 0.707107,
1972
+ -0.0
1973
+ ],
1974
+ "color": [
1975
+ 0.811765,
1976
+ 0.858824,
1977
+ 0.898039,
1978
+ 1.0
1979
+ ]
1980
+ }
1981
+ ],
1982
+ "joint": {
1983
+ "name": "right_knee",
1984
+ "axis": [
1985
+ 0.0,
1986
+ 0.0,
1987
+ 1.0
1988
+ ],
1989
+ "type": "hinge",
1990
+ "pos": [
1991
+ 0,
1992
+ 0,
1993
+ 0
1994
+ ],
1995
+ "range": [
1996
+ -1.570796326794932,
1997
+ 1.570796326794861
1998
+ ],
1999
+ "actuator_index": 12
2000
+ }
2001
+ },
2002
+ {
2003
+ "name": "ankle_r_v1",
2004
+ "parent": "leg_2",
2005
+ "pos": [
2006
+ -0.042,
2007
+ 0.0,
2008
+ -0.026
2009
+ ],
2010
+ "quat": [
2011
+ 0.0,
2012
+ -0.707107,
2013
+ -0.707107,
2014
+ 0.0
2015
+ ],
2016
+ "geoms": [
2017
+ {
2018
+ "type": "mesh",
2019
+ "mesh": "ankle_r_v1.stl",
2020
+ "pos": [
2021
+ 0.022,
2022
+ 0.00622291,
2023
+ -0.0647
2024
+ ],
2025
+ "quat": [
2026
+ 0.5,
2027
+ -0.5,
2028
+ 0.5,
2029
+ 0.5
2030
+ ],
2031
+ "color": [
2032
+ 0.980392,
2033
+ 0.713725,
2034
+ 0.00392157,
2035
+ 1.0
2036
+ ]
2037
+ },
2038
+ {
2039
+ "type": "mesh",
2040
+ "mesh": "seeed_bearing__configuration_default.stl",
2041
+ "pos": [
2042
+ 0.0,
2043
+ 0.0,
2044
+ -0.0284
2045
+ ],
2046
+ "quat": [
2047
+ 0.0,
2048
+ 1.0,
2049
+ 0.0,
2050
+ -0.0
2051
+ ],
2052
+ "color": [
2053
+ 0.713725,
2054
+ 0.760784,
2055
+ 0.8,
2056
+ 1.0
2057
+ ]
2058
+ },
2059
+ {
2060
+ "type": "mesh",
2061
+ "mesh": "roller_blade.stl",
2062
+ "pos": [
2063
+ 0.022,
2064
+ 0.00622291,
2065
+ 0.0355181
2066
+ ],
2067
+ "quat": [
2068
+ 0.5,
2069
+ -0.5,
2070
+ 0.5,
2071
+ 0.5
2072
+ ],
2073
+ "color": [
2074
+ 0.901961,
2075
+ 0.901961,
2076
+ 0.901961,
2077
+ 1.0
2078
+ ]
2079
+ }
2080
+ ],
2081
+ "joint": {
2082
+ "name": "right_ankle",
2083
+ "axis": [
2084
+ 0.0,
2085
+ 0.0,
2086
+ 1.0
2087
+ ],
2088
+ "type": "hinge",
2089
+ "pos": [
2090
+ 0,
2091
+ 0,
2092
+ 0
2093
+ ],
2094
+ "range": [
2095
+ -1.5707963267949063,
2096
+ 1.5707963267948868
2097
+ ],
2098
+ "actuator_index": 13
2099
+ }
2100
+ },
2101
+ {
2102
+ "name": "tire_3",
2103
+ "parent": "ankle_r_v1",
2104
+ "pos": [
2105
+ 0.0395,
2106
+ -0.0325,
2107
+ -0.0144819
2108
+ ],
2109
+ "quat": [
2110
+ 0.0,
2111
+ -0.0,
2112
+ 1.0,
2113
+ -0.0
2114
+ ],
2115
+ "geoms": [
2116
+ {
2117
+ "type": "mesh",
2118
+ "mesh": "tire.stl",
2119
+ "pos": [
2120
+ 0.0175,
2121
+ 0.0387229,
2122
+ -0.05
2123
+ ],
2124
+ "quat": [
2125
+ 0.5,
2126
+ -0.5,
2127
+ -0.5,
2128
+ -0.5
2129
+ ],
2130
+ "color": [
2131
+ 0.34902,
2132
+ 0.376471,
2133
+ 0.4,
2134
+ 1.0
2135
+ ]
2136
+ },
2137
+ {
2138
+ "type": "mesh",
2139
+ "mesh": "rim.stl",
2140
+ "pos": [
2141
+ 0.0175,
2142
+ 0.0387229,
2143
+ -0.05
2144
+ ],
2145
+ "quat": [
2146
+ 0.5,
2147
+ -0.5,
2148
+ -0.5,
2149
+ -0.5
2150
+ ],
2151
+ "color": [
2152
+ 0.729412,
2153
+ 0.25098,
2154
+ 0.105882,
2155
+ 1.0
2156
+ ]
2157
+ }
2158
+ ],
2159
+ "joint": {
2160
+ "name": "passive_RF_wheel",
2161
+ "axis": [
2162
+ 0.0,
2163
+ 0.0,
2164
+ 1.0
2165
+ ],
2166
+ "type": "hinge",
2167
+ "pos": [
2168
+ 0,
2169
+ 0,
2170
+ 0
2171
+ ],
2172
+ "range": null
2173
+ }
2174
+ },
2175
+ {
2176
+ "name": "tire_4",
2177
+ "parent": "ankle_r_v1",
2178
+ "pos": [
2179
+ -0.0255,
2180
+ -0.0325,
2181
+ -0.0144819
2182
+ ],
2183
+ "quat": [
2184
+ 0.0,
2185
+ -0.0,
2186
+ 1.0,
2187
+ -0.0
2188
+ ],
2189
+ "geoms": [
2190
+ {
2191
+ "type": "mesh",
2192
+ "mesh": "rim.stl",
2193
+ "pos": [
2194
+ 0.0175,
2195
+ 0.0387229,
2196
+ -0.05
2197
+ ],
2198
+ "quat": [
2199
+ 0.5,
2200
+ -0.5,
2201
+ -0.5,
2202
+ -0.5
2203
+ ],
2204
+ "color": [
2205
+ 0.729412,
2206
+ 0.25098,
2207
+ 0.105882,
2208
+ 1.0
2209
+ ]
2210
+ },
2211
+ {
2212
+ "type": "mesh",
2213
+ "mesh": "tire.stl",
2214
+ "pos": [
2215
+ 0.0175,
2216
+ 0.0387229,
2217
+ -0.05
2218
+ ],
2219
+ "quat": [
2220
+ 0.5,
2221
+ -0.5,
2222
+ -0.5,
2223
+ -0.5
2224
+ ],
2225
+ "color": [
2226
+ 0.34902,
2227
+ 0.376471,
2228
+ 0.4,
2229
+ 1.0
2230
+ ]
2231
+ }
2232
+ ],
2233
+ "joint": {
2234
+ "name": "passive_RR_wheel",
2235
+ "axis": [
2236
+ 0.0,
2237
+ 0.0,
2238
+ 1.0
2239
+ ],
2240
+ "type": "hinge",
2241
+ "pos": [
2242
+ 0,
2243
+ 0,
2244
+ 0
2245
+ ],
2246
+ "range": null
2247
+ }
2248
+ }
2249
+ ],
2250
+ "actuated_joints": [
2251
+ "left_hip_yaw",
2252
+ "left_hip_roll",
2253
+ "left_hip_pitch",
2254
+ "left_knee",
2255
+ "left_ankle",
2256
+ "neck_pitch",
2257
+ "head_pitch",
2258
+ "head_yaw",
2259
+ "head_roll",
2260
+ "right_hip_yaw",
2261
+ "right_hip_roll",
2262
+ "right_hip_pitch",
2263
+ "right_knee",
2264
+ "right_ankle"
2265
+ ],
2266
+ "mesh_dir": "/robot/mjlab/meshes"
2267
+ }
robot/mjlab/meshes/ankle_l_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:74f9a51e15ac8fef93fc2efa35b61dc53cf832d798f75c3229700bd9b3aed4c4
3
+ size 79484
robot/mjlab/meshes/ankle_r_v1.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c200102fb6f067cbec73f048fe2020b127e61d47fcffd7dbd84c90439767fd3f
3
+ size 69484
robot/mjlab/meshes/rim.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a6d58f80ec60041e721cba1de7af5c834700166a5c10e7f68d1a2b79c7b78cb0
3
+ size 28884
robot/mjlab/meshes/roller_blade.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a8b0e6dbdb593eceaba1a0cc2a70ad8aac56f4c3b22676dbfb97409c1a094615
3
+ size 262184
robot/mjlab/meshes/tire.stl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:14c08d73185a4b521897aca74fb8b2a6d306a20d80e8822aa56cf1b4ace29bec
3
+ size 88284
robot/mjlab/robot_allcollisions_rollers.xml ADDED
@@ -0,0 +1,470 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" ?>
2
+ <!-- Generated using onshape-to-robot -->
3
+ <!-- Onshape https://cad.onshape.com/documents/804927696f06d877f3f1803e/w/5b75db19292e71970de02dee/e/ed34b749f5a3718f68024fd5 -->
4
+ <mujoco model="microduck">
5
+ <compiler angle="radian" meshdir="assets" autolimits="true"/>
6
+ <default>
7
+ <default class="microduck">
8
+ <joint frictionloss="0.1" armature="0.005"/>
9
+ <position kp="50" dampratio="1"/>
10
+ <default class="visual">
11
+ <geom type="mesh" contype="0" conaffinity="0" group="2"/>
12
+ </default>
13
+ <default class="collision">
14
+ <geom group="3"/>
15
+ </default>
16
+ </default>
17
+ </default>
18
+ <!-- Additional joints_properties.xml -->
19
+ <default>
20
+ <default class="perfect_actuator">
21
+ <geom contype="0" conaffinity="0"/>
22
+ <joint damping="0.15" frictionloss="0.1" armature="0.001"/>
23
+ <position kp="10.0" kv="0.1" forcerange="-20.0 20.0"/>
24
+ </default>
25
+ <default class="chosen_actuator_old">
26
+ <geom contype="0" conaffinity="0"/>
27
+ <joint damping="0.048" frictionloss="0.006" armature="0.002"/>
28
+ <position kp="0.52" kv="0.0" forcerange="-0.91 0.91" ctrlrange="-10.0 10.0"/>
29
+ </default>
30
+ <default class="chosen_actuator_new">
31
+ <!-- removing contaminated data -->
32
+ <geom contype="0" conaffinity="0"/>
33
+ <joint damping="0.041" frictionloss="0.032" armature="0.002"/>
34
+ <position kp="0.386" kv="0.0" forcerange="-0.67 0.67" ctrlrange="-10.0 10.0"/>
35
+ </default>
36
+ <default class="chosen_actuator_antoine">
37
+ <geom contype="0" conaffinity="0"/>
38
+ <joint damping="0.044" frictionloss="0.013" armature="0.0017"/>
39
+ <position kp="0.43" kv="0.0" forcerange="-0.75 0.75" ctrlrange="-10.0 10.0"/>
40
+ </default>
41
+ <default class="chosen_actuator">
42
+ <!-- marc -->
43
+ <geom contype="0" conaffinity="0"/>
44
+ <joint damping="0.053" frictionloss="0.0048" armature="0.0018"/>
45
+ <!-- 200 kp -->
46
+ <position kp="0.55" kv="0.0" forcerange="-0.96 0.96" ctrlrange="-10.0 10.0"/>
47
+ <!-- 125 kp -->
48
+ <!-- <position kp="0.35" kv="0.0" forcerange="-0.96 0.96" ctrlrange="-10.0 10.0"/> -->
49
+ </default>
50
+ <default class="passive_wheel">
51
+ <geom contype="0" conaffinity="0"/>
52
+ <joint damping="0.0" frictionloss="0.0" armature="0.0001"/>
53
+ </default>
54
+ <default class="passive_joint">
55
+ <geom contype="0" conaffinity="0"/>
56
+ <joint damping="0.0" frictionloss="0.0" armature="0.0001"/>
57
+ </default>
58
+ </default>
59
+ <!-- Additional sensors.xml -->
60
+ <sensor>
61
+ <framequat name="orientation" objtype="site" noise="0.001" objname="imu"/>
62
+ <gyro name="angular-velocity" site="imu" noise="0.005"/>
63
+ <gyro name="imu_ang_vel" site="imu"/>
64
+ <velocimeter name="imu_lin_vel" site="imu"/>
65
+ <accelerometer name="imu_accel" site="imu"/>
66
+ <subtreeangmom name="root_angmom" body="trunk_base"/>
67
+ </sensor>
68
+ <!-- Additional additional.xml -->
69
+ <default>
70
+ <default class="self_collision_only">
71
+ <geom group="3" contype="2" conaffinity="2"/>
72
+ </default>
73
+ <equality solref="0.002 1" solimp="0.99 0.999 0.0005 0.5 2"/>
74
+ </default>
75
+ <!-- <contact> -->
76
+ <!-- Jaw collision mesh overlaps the bottom head shell by ~2cm because the closed -->
77
+ <!-- loop linkage positions it inside the head. Without these excludes the -->
78
+ <!-- self-collision sensor reports a constant 2 contacts, giving a -->
79
+ <!-- permanent ~-2.0 reward penalty in the standup env. -->
80
+ <!-- <exclude body1="jaw" body2="bottom_head_shell"/> -->
81
+ <!-- </contact> -->
82
+ <worldbody>
83
+ <!-- Link trunk_base -->
84
+ <body name="trunk_base" pos="0 0 0.12" quat="1 0 0 0" childclass="microduck">
85
+ <freejoint name="trunk_base_freejoint"/>
86
+ <inertial pos="-0.0226332 -2.70828e-05 0.00280064" mass="0.199224" fullinertia="0.000123975 0.000145931 0.000115351 7.1651e-08 -2.06915e-05 1.32429e-07"/>
87
+ <!-- Part seeed_bearing__configuration__22x16x4 -->
88
+ <geom type="mesh" class="visual" pos="0.006 0.0175 -0.005" quat="1 -0 -0 0" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
89
+ <!-- Part right_shell -->
90
+ <geom type="mesh" class="visual" pos="0.004 -0.0005 -0.0175" quat="0.707107 -0 -0 0.707107" mesh="right_shell" material="right_shell_material"/>
91
+ <!-- Part banana_pcb_locker -->
92
+ <geom type="mesh" class="visual" pos="0.0042 -0 -0.0175215" quat="0.707107 -0 0 0.707107" mesh="banana_pcb_locker" material="banana_pcb_locker_material"/>
93
+ <!-- Part trunk_base -->
94
+ <geom type="mesh" class="visual" pos="0.004 -0 -0.0175" quat="0.707107 -0 -0 0.707107" mesh="trunk_base" material="trunk_base_material"/>
95
+ <!-- Part xl330 -->
96
+ <geom type="mesh" class="visual" pos="0.006 0.0175 0.0115" quat="0 0.707107 -0 0.707107" mesh="xl330" material="xl330_material"/>
97
+ <!-- Part power_support -->
98
+ <geom type="mesh" class="visual" pos="0.004 -0 -0.0175215" quat="0.707107 -0 0 0.707107" mesh="power_support" material="power_support_material"/>
99
+ <geom type="mesh" class="self_collision_only" pos="0.004 -0 -0.0175215" quat="0.707107 -0 0 0.707107" mesh="power_support" material="power_support_material"/>
100
+ <!-- Part np_f970 -->
101
+ <geom type="mesh" class="visual" pos="-0.0359693 0 -0.00385049" quat="0.00617059 0.70708 -0.70708 -0.00617059" mesh="np_f970" material="np_f970_material"/>
102
+ <geom type="mesh" class="collision" pos="-0.0359693 0 -0.00385049" quat="0.00617059 0.70708 -0.70708 -0.00617059" mesh="np_f970" material="np_f970_material"/>
103
+ <!-- Part xl330_2 -->
104
+ <geom type="mesh" class="visual" pos="0.006 -0.0175 0.0115" quat="0 0.707107 -0 0.707107" mesh="xl330" material="xl330_material"/>
105
+ <!-- Part left_shell -->
106
+ <geom type="mesh" class="visual" pos="0.004 -0 -0.0175785" quat="0.707107 0 -0 0.707107" mesh="left_shell" material="left_shell_material"/>
107
+ <!-- Part seeed_bearing__configuration__22x16x4_2 -->
108
+ <geom type="mesh" class="visual" pos="0.006 -0.0175 -0.005" quat="1 -0 -0 0" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
109
+ <!-- Frame imu -->
110
+ <site group="3" name="imu" pos="-0.021 6.64098e-05 -0.0146984" quat="1 -0 -0 0"/>
111
+ <!-- Link yaw2roll -->
112
+ <body name="yaw2roll" pos="0.006 0.0175 -0.005" quat="0 -0.707107 -0.707107 -0">
113
+ <!-- Joint from trunk_base to yaw2roll -->
114
+ <joint axis="0 0 1" name="left_hip_yaw" type="hinge" range="-0.4363323129985824 0.5235987755982988" class="chosen_actuator"/>
115
+ <inertial pos="-8.8e-11 0.00099414 0.0168917" mass="0.0230406" fullinertia="4.1964e-06 3.41752e-06 2.3265e-06 -0 -0 -5.0881e-08"/>
116
+ <!-- Part yaw2roll -->
117
+ <geom type="mesh" class="visual" pos="-0.0175 -0.002 0.0125" quat="0 1 0 0" mesh="yaw2roll" material="yaw2roll_material"/>
118
+ <!-- Part xl330_3 -->
119
+ <geom type="mesh" class="visual" pos="-0 0 0.0125" quat="0 -0.707107 0.707107 -0" mesh="xl330" material="xl330_material"/>
120
+ <!-- Part seeed_bearing__configuration__22x16x4_3 -->
121
+ <geom type="mesh" class="visual" pos="-0 0.0165 0.0125" quat="0.5 0.5 0.5 -0.5" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
122
+ <!-- Part bearing_roll -->
123
+ <geom type="mesh" class="visual" pos="-0.0175 -0.002 0.0125" quat="0 -1 -0 -0" mesh="bearing_roll" material="bearing_roll_material"/>
124
+ <!-- Link hip_l -->
125
+ <body name="hip_l" pos="0 0.0165 0.0125" quat="0.707107 -0.707107 -0 -0">
126
+ <!-- Joint from yaw2roll to hip_l -->
127
+ <joint axis="0 0 1" name="left_hip_roll" type="hinge" range="-0.3839724354387525 0.3839724354387525" class="chosen_actuator"/>
128
+ <inertial pos="0.014815 1.6179e-08 -0.00829992" mass="0.00618934" fullinertia="8.60626e-07 1.15753e-06 6.51588e-07 1e-12 3.89882e-07 -1e-12"/>
129
+ <!-- Part seeed_bearing__configuration__22x16x4_4 -->
130
+ <geom type="mesh" class="visual" pos="0.021 0 -0.0185" quat="0.5 0.5 0.5 0.5" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
131
+ <!-- Part hip_l -->
132
+ <geom type="mesh" class="visual" pos="-0.0175 0 -0.0185" quat="0.707107 -0.707107 0 -0" mesh="hip_l" material="hip_l_material"/>
133
+ <geom type="mesh" class="collision" pos="-0.0175 0 -0.0185" quat="0.707107 -0.707107 0 -0" mesh="hip_l" material="hip_l_material"/>
134
+ <!-- Link upper_leg_left -->
135
+ <body name="upper_leg_left" pos="0.025 0 -0.0185" quat="0.5 -0.5 0.5 -0.5">
136
+ <!-- Joint from hip_l to upper_leg_left -->
137
+ <joint axis="0 0 1" name="left_hip_pitch" type="hinge" range="-1.570796326795005 1.5707963267947882" class="chosen_actuator"/>
138
+ <inertial pos="0.006766 0.0228838 0.0134531" mass="0.0482067" fullinertia="1.68753e-05 1.03691e-05 1.98269e-05 -4.41852e-06 2.27982e-07 3.89298e-07"/>
139
+ <!-- Part upper_leg_left -->
140
+ <geom type="mesh" class="visual" pos="-0 -0 -0.0425" quat="0.5 -0.5 -0.5 -0.5" mesh="upper_leg_left" material="upper_leg_left_material"/>
141
+ <!-- Part xl330_4 -->
142
+ <geom type="mesh" class="visual" pos="0.022 0.0357771 0.0125" quat="0 -0.707107 -0 -0.707107" mesh="xl330" material="xl330_material"/>
143
+ <!-- Part upper_leg_rigidity_plate -->
144
+ <geom type="mesh" class="visual" pos="0 -0 -0.0425" quat="0.5 -0.5 -0.5 -0.5" mesh="upper_leg_rigidity_plate" material="upper_leg_rigidity_plate_material"/>
145
+ <!-- Part seeed_bearing__configuration__22x16x4_5 -->
146
+ <geom type="mesh" class="visual" pos="0.022 0.0357771 -0.004" quat="0 0 0 1" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
147
+ <!-- Part xl330_5 -->
148
+ <geom type="mesh" class="visual" pos="-0 -0 0.0125" quat="0.5 0.5 -0.5 0.5" mesh="xl330" material="xl330_material"/>
149
+ <!-- Link leg -->
150
+ <body name="leg" pos="0.022 0.0357771 -0.004" quat="0 0.707107 0.707107 0">
151
+ <!-- Joint from upper_leg_left to leg -->
152
+ <joint axis="0 0 1" name="left_knee" type="hinge" range="-1.570796326795012 1.570796326794781" class="chosen_actuator"/>
153
+ <inertial pos="-1.52e-10 0.0318937 -0.00953505" mass="0.0215844" fullinertia="5.25056e-06 2.15507e-06 4.33925e-06 -1e-12 0 7.65005e-07"/>
154
+ <!-- Part leg -->
155
+ <geom type="mesh" class="visual" pos="-0.022 0.0357771 0.0385" quat="0.5 0.5 0.5 -0.5" mesh="leg" material="leg_material"/>
156
+ <geom type="mesh" class="collision" pos="-0.022 0.0357771 0.0385" quat="0.5 0.5 0.5 -0.5" mesh="leg" material="leg_material"/>
157
+ <!-- Part xl330_6 -->
158
+ <geom type="mesh" class="visual" pos="0 0.042 -0.0115" quat="0.5 -0.5 -0.5 -0.5" mesh="xl330" material="xl330_material"/>
159
+ <!-- Link ankle_l_v1 -->
160
+ <body name="ankle_l_v1" pos="0 0.042 -0.026" quat="0 1 -0 0">
161
+ <!-- Joint from leg to ankle_l_v1 -->
162
+ <joint axis="0 0 1" name="left_ankle" type="hinge" range="-1.5707963267949054 1.5707963267948877" class="chosen_actuator"/>
163
+ <inertial pos="-0.00680626 -0.0143677 -0.0151851" mass="0.02087" fullinertia="4.85529e-06 9.6767e-06 8.22681e-06 -3.18897e-07 5.6657e-08 1.31522e-07"/>
164
+ <!-- Part roller_blade -->
165
+ <geom type="mesh" class="visual" pos="-0.022 0.00622291 -0.0647" quat="0.5 -0.5 -0.5 -0.5" mesh="roller_blade" material="roller_blade_material"/>
166
+ <!-- Part seeed_bearing__configuration_default -->
167
+ <geom type="mesh" class="visual" pos="0 0 -0.0314" quat="0 -0 0 -1" mesh="seeed_bearing__configuration_default" material="seeed_bearing__configuration_default_material"/>
168
+ <!-- Part ankle_l_v1 -->
169
+ <geom type="mesh" class="visual" pos="-0.022 0.00622291 -0.0647" quat="0.5 -0.5 -0.5 -0.5" mesh="ankle_l_v1" material="ankle_l_v1_material"/>
170
+ <!-- Frame left_foot -->
171
+ <site group="3" name="left_foot" pos="-0 -0.0475 -0.0147" quat="0 -0 -0.707107 -0.707107"/>
172
+ <!-- Link tire -->
173
+ <body name="tire" pos="-0.0395 -0.0325 -0.0147" quat="1 -0 0 -0">
174
+ <!-- Joint from ankle_l_v1 to tire -->
175
+ <joint axis="0 0 1" name="passive_LF_wheel" class="passive_joint"/>
176
+ <inertial pos="-0 0 0" mass="0.00447401" fullinertia="2.8556e-07 2.8556e-07 5.3687e-07 -0 -6e-12 0"/>
177
+ <!-- Part tire -->
178
+ <geom type="mesh" class="visual" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="tire" material="tire_material"/>
179
+ <geom type="mesh" class="collision" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="tire" material="tire_material"/>
180
+ <!-- Part rim -->
181
+ <geom type="mesh" class="visual" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="rim" material="rim_material"/>
182
+ </body>
183
+ <!-- Link tire_2 -->
184
+ <body name="tire_2" pos="0.0255 -0.0325 -0.0147" quat="1 -0 0 -0">
185
+ <!-- Joint from ankle_l_v1 to tire_2 -->
186
+ <joint axis="0 0 1" name="passive_LR_wheel" class="passive_joint"/>
187
+ <inertial pos="-0 0 -0" mass="0.00447401" fullinertia="2.8556e-07 2.8556e-07 5.3687e-07 -0 -6e-12 0"/>
188
+ <!-- Part rim_2 -->
189
+ <geom type="mesh" class="visual" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="rim" material="rim_material"/>
190
+ <!-- Part tire_2 -->
191
+ <geom type="mesh" class="visual" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="tire" material="tire_material"/>
192
+ <geom type="mesh" class="collision" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="tire" material="tire_material"/>
193
+ </body>
194
+ </body>
195
+ </body>
196
+ </body>
197
+ </body>
198
+ </body>
199
+ <!-- Link neck -->
200
+ <body name="neck" pos="0.026 0.0145 0.0324215" quat="0 0 0.707107 -0.707107">
201
+ <!-- Joint from trunk_base to neck -->
202
+ <joint axis="0 0 1" name="neck_pitch" type="hinge" range="-1.5707963267948966 1.0471975511965976" class="chosen_actuator"/>
203
+ <inertial pos="0 -0.025 0.0141665" mass="0.0368414" fullinertia="1.72925e-05 3.12694e-06 1.63707e-05 0 0 0"/>
204
+ <!-- Part neck -->
205
+ <geom type="mesh" class="visual" pos="0.022 0.05 0.028" quat="0.5 0.5 0.5 -0.5" mesh="neck" material="neck_material"/>
206
+ <!-- Part xl330_7 -->
207
+ <geom type="mesh" class="visual" pos="0 0 0.0145" quat="0.5 -0.5 -0.5 -0.5" mesh="xl330" material="xl330_material"/>
208
+ <!-- Part xl330_8 -->
209
+ <geom type="mesh" class="visual" pos="0 -0.05 0.0145" quat="0.5 0.5 -0.5 0.5" mesh="xl330" material="xl330_material"/>
210
+ <!-- Part neck_2 -->
211
+ <geom type="mesh" class="visual" pos="0.022 0.05 0.003" quat="0.5 0.5 0.5 -0.5" mesh="neck" material="neck_material"/>
212
+ <!-- Link neck_pitch -->
213
+ <body name="neck_pitch" pos="0 -0.05 -0" quat="0 1 0 0">
214
+ <!-- Joint from neck to neck_pitch -->
215
+ <joint axis="0 0 1" name="head_pitch" type="hinge" range="-1.5707963267948974 1.5707963267948957" class="chosen_actuator"/>
216
+ <inertial pos="-6.533e-09 0.0116641 -0.0145" mass="0.00572" fullinertia="1.09186e-06 9.66855e-07 5.12111e-07 0 0 0"/>
217
+ <!-- Part neck_pitch -->
218
+ <geom type="mesh" class="visual" pos="-0 0.0287931 -0.0145" quat="0.5 -0.5 -0.5 -0.5" mesh="neck_pitch" material="neck_pitch_material"/>
219
+ <!-- Part seeed_bearing__configuration__22x16x4_6 -->
220
+ <geom type="mesh" class="visual" pos="-0 0.0207931 -0.0145" quat="0.707107 0.707107 0 0" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
221
+ <!-- Link yaw_roll_motion -->
222
+ <body name="yaw_roll_motion" pos="-0 0.0186931 -0.0145" quat="0 0 -0.707107 -0.707107">
223
+ <!-- Joint from neck_pitch to yaw_roll_motion -->
224
+ <joint axis="0 0 1" name="head_yaw" type="hinge" range="-2.967059728390373 2.967059728390348" class="chosen_actuator"/>
225
+ <inertial pos="-0.000166657 -0.00500964 0.0178065" mass="0.0486" fullinertia="6.97903e-06 8.10293e-06 9.57884e-06 3.21657e-07 -3.4797e-08 3.4295e-07"/>
226
+ <!-- Part yaw_roll_motion -->
227
+ <geom type="mesh" class="visual" pos="0 0 0.01" quat="0.707107 0 -0 0.707107" mesh="yaw_roll_motion" material="yaw_roll_motion_material"/>
228
+ <!-- Part xl330_9 -->
229
+ <geom type="mesh" class="visual" pos="0 0 0.0145" quat="0.5 -0.5 -0.5 -0.5" mesh="xl330" material="xl330_material"/>
230
+ <!-- Part seeed_bearing__configuration__22x16x4_7 -->
231
+ <geom type="mesh" class="visual" pos="-0.016 0 0.0145" quat="0.707107 -0 -0.707107 -0" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
232
+ <!-- Part seeed_bearing__configuration__22x16x4_8 -->
233
+ <geom type="mesh" class="visual" pos="0.018 0 0.0145" quat="0.707107 -0 -0.707107 -0" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
234
+ <!-- Link jaw_soft -->
235
+ <body name="jaw_soft" pos="-0.0179 0 0.0145" quat="0.707107 -0 -0.707107 -0">
236
+ <!-- Joint from yaw_roll_motion to jaw_soft -->
237
+ <joint axis="0 0 1" name="head_roll" type="hinge" range="-0.43633231299859127 0.4363323129985735" class="chosen_actuator"/>
238
+ <inertial pos="0.00460523 0.00120047 -0.0324761" mass="0.188766" fullinertia="0.000320812 0.0002541 0.000149882 -6.32292e-07 6.57154e-06 4.18476e-06"/>
239
+ <!-- Part lens -->
240
+ <geom type="mesh" class="visual" pos="0.0155 -9.13778e-05 -0.05548" quat="0.5 -0.5 0.5 -0.5" mesh="lens" material="lens_material"/>
241
+ <!-- Part face_part -->
242
+ <geom type="mesh" class="visual" pos="-0.0045 -9.13778e-05 -0.0178" quat="0.5 0.5 0.5 0.5" mesh="face_part" material="face_part_material"/>
243
+ <!-- Part xl330_10 -->
244
+ <geom type="mesh" class="visual" pos="0.003 0.0255 -0.018" quat="0.707107 0 -0 -0.707107" mesh="xl330" material="xl330_material"/>
245
+ <!-- Part jaw_soft -->
246
+ <geom type="mesh" class="visual" pos="-0.0046 0 -0.0179996" quat="0.5 0.5 0.5 0.5" mesh="jaw_soft" material="jaw_soft_material"/>
247
+ <!-- Part motor_support -->
248
+ <geom type="mesh" class="visual" pos="-0.0045 0 -0.018" quat="0.5 0.5 0.5 0.5" mesh="motor_support" material="motor_support_material"/>
249
+ <!-- Part top_head_shell -->
250
+ <geom type="mesh" class="visual" pos="-0.0045 -0 -0.018" quat="0.5 0.5 0.5 0.5" mesh="top_head_shell" material="top_head_shell_material"/>
251
+ <geom type="mesh" class="collision" pos="-0.0045 -0 -0.018" quat="0.5 0.5 0.5 0.5" mesh="top_head_shell" material="top_head_shell_material"/>
252
+ <!-- Part xl330_11 -->
253
+ <geom type="mesh" class="visual" pos="0 0 0.0145" quat="0.5 -0.5 -0.5 -0.5" mesh="xl330" material="xl330_material"/>
254
+ <!-- Part seeed_bearing__configuration_default_2 -->
255
+ <geom type="mesh" class="visual" pos="0.0032 -0.04 -0.018" quat="0.5 -0.5 0.5 0.5" mesh="seeed_bearing__configuration_default" material="seeed_bearing__configuration_default_material"/>
256
+ <!-- Part elec_rpi_robot_hat_pcb -->
257
+ <geom type="mesh" class="visual" pos="0.02694 0.0289936 -0.0519" quat="0.707107 0 0 -0.707107" mesh="elec_rpi_robot_hat_pcb" material="elec_rpi_robot_hat_pcb_material"/>
258
+ <!-- Part soft_mouth_top -->
259
+ <geom type="mesh" class="visual" pos="-0.00420071 1.7776e-08 -0.0180814" quat="0.5 0.5 0.5 0.5" mesh="soft_mouth_top" material="soft_mouth_top_material"/>
260
+ <!-- Part noenoeil -->
261
+ <geom type="mesh" class="visual" pos="-0.0045 -9.13778e-05 -0.0178" quat="0.5 0.5 0.5 0.5" mesh="noenoeil" material="noenoeil_material"/>
262
+ <!-- Part jaw -->
263
+ <geom type="mesh" class="visual" pos="-0.0045 0 -0.018" quat="0.5 0.5 0.5 0.5" mesh="jaw" material="jaw_material"/>
264
+ <geom type="mesh" class="collision" pos="-0.0045 0 -0.018" quat="0.5 0.5 0.5 0.5" mesh="jaw" material="jaw_material"/>
265
+ <!-- Part bottom_head_shell -->
266
+ <geom type="mesh" class="visual" pos="-0.0043 -0 -0.018" quat="0.5 0.5 0.5 0.5" mesh="bottom_head_shell" material="bottom_head_shell_material"/>
267
+ <geom type="mesh" class="collision" pos="-0.0043 -0 -0.018" quat="0.5 0.5 0.5 0.5" mesh="bottom_head_shell" material="bottom_head_shell_material"/>
268
+ <!-- Part m12_lens_holder -->
269
+ <geom type="mesh" class="visual" pos="0.0155 -9.13778e-05 -0.05558" quat="0.5 -0.5 0.5 -0.5" mesh="m12_lens_holder" material="m12_lens_holder_material"/>
270
+ <!-- Part pcb__raspberry_pi_zero_2_w -->
271
+ <geom type="mesh" class="visual" pos="0.015435 -1.13778e-05 -0.06001" quat="0.5 -0.5 -0.5 0.5" mesh="pcb__raspberry_pi_zero_2_w" material="pcb__raspberry_pi_zero_2_w_material"/>
272
+ <!-- Part speaker -->
273
+ <geom type="mesh" class="visual" pos="0.01 -0.016786 0.0260226" quat="0.5 0.5 0.5 0.5" mesh="speaker" material="speaker_material"/>
274
+ <!-- Frame head_camera -->
275
+ <site group="3" name="head_camera" pos="0.0155 -9.13778e-05 -0.0733" quat="0.707107 0 0.707107 -0"/>
276
+ <camera name="head_camera" pos="0.0155 -9.13778e-05 -0.0733" quat="0 0 -1 0"/>
277
+ <!-- Frame tof -->
278
+ <site group="3" name="tof" pos="0.0135 0.0224086 -0.0733" quat="0.707107 0 0.707107 -0"/>
279
+ <!-- Frame head_imu -->
280
+ <site group="3" name="head_imu" pos="0.0152323 0.000111069 -0.05106" quat="1 0 0 -0"/>
281
+ <!-- Frame mouth_tip -->
282
+ <site group="3" name="mouth_tip" pos="-0.00809334 -0 -0.0777383" quat="0.704015 0 0.710185 -0"/>
283
+ </body>
284
+ </body>
285
+ </body>
286
+ </body>
287
+ <!-- Link bearing_roll -->
288
+ <body name="bearing_roll" pos="0.006 -0.0175 -0.005" quat="0 0.707107 0.707107 0">
289
+ <!-- Joint from trunk_base to bearing_roll -->
290
+ <joint axis="0 0 1" name="right_hip_yaw" type="hinge" range="-0.5235987755982988 0.4363323129985824" class="chosen_actuator"/>
291
+ <inertial pos="-8.8e-11 0.00099414 0.0168917" mass="0.0230406" fullinertia="4.1964e-06 3.41752e-06 2.3265e-06 -0 -0 -5.0881e-08"/>
292
+ <!-- Part yaw2roll_2 -->
293
+ <geom type="mesh" class="visual" pos="-0.0175 -0.002 0.0125" quat="0 1 0 0" mesh="yaw2roll" material="yaw2roll_material"/>
294
+ <!-- Part xl330_12 -->
295
+ <geom type="mesh" class="visual" pos="-0 0 0.0125" quat="0 -0.707107 0.707107 -0" mesh="xl330" material="xl330_material"/>
296
+ <!-- Part bearing_roll_2 -->
297
+ <geom type="mesh" class="visual" pos="-0.0175 -0.002 0.0125" quat="0 -1 -0 -0" mesh="bearing_roll" material="bearing_roll_material"/>
298
+ <!-- Part seeed_bearing__configuration__22x16x4_9 -->
299
+ <geom type="mesh" class="visual" pos="-0 0.0165 0.0125" quat="0.5 0.5 0.5 -0.5" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
300
+ <!-- Link hip_l_2 -->
301
+ <body name="hip_l_2" pos="-0 0.0165 0.0125" quat="0 -0 0.707107 0.707107">
302
+ <!-- Joint from bearing_roll to hip_l_2 -->
303
+ <joint axis="0 0 1" name="right_hip_roll" type="hinge" range="-0.3839724354387525 0.3839724354387525" class="chosen_actuator"/>
304
+ <inertial pos="0.014815 1.6212e-08 -0.00829992" mass="0.00618934" fullinertia="8.60626e-07 1.15753e-06 6.51588e-07 1e-12 3.89882e-07 -1e-12"/>
305
+ <!-- Part hip_l_2 -->
306
+ <geom type="mesh" class="visual" pos="-0.0175 0 -0.0185" quat="0.707107 -0.707107 -0 -0" mesh="hip_l" material="hip_l_material"/>
307
+ <geom type="mesh" class="collision" pos="-0.0175 0 -0.0185" quat="0.707107 -0.707107 -0 -0" mesh="hip_l" material="hip_l_material"/>
308
+ <!-- Part seeed_bearing__configuration__22x16x4_10 -->
309
+ <geom type="mesh" class="visual" pos="0.025 0 -0.0185" quat="0.5 0.5 -0.5 -0.5" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
310
+ <!-- Link upper_leg_right -->
311
+ <body name="upper_leg_right" pos="0.025 0 -0.0185" quat="0.5 -0.5 0.5 -0.5">
312
+ <!-- Joint from hip_l_2 to upper_leg_right -->
313
+ <joint axis="0 0 1" name="right_hip_pitch" type="hinge" range="-1.5707963267949268 1.5707963267948664" class="chosen_actuator"/>
314
+ <inertial pos="-0.006766 0.0228838 0.0134531" mass="0.0482067" fullinertia="1.68753e-05 1.03691e-05 1.98269e-05 4.41852e-06 -2.27982e-07 3.89298e-07"/>
315
+ <!-- Part xl330_13 -->
316
+ <geom type="mesh" class="visual" pos="-0.022 0.0357771 0.0125" quat="0.707107 -0 -0.707107 -0" mesh="xl330" material="xl330_material"/>
317
+ <!-- Part seeed_bearing__configuration__22x16x4_11 -->
318
+ <geom type="mesh" class="visual" pos="-0.022 0.0357771 0" quat="0 1 -0 -0" mesh="seeed_bearing__configuration__22x16x4" material="seeed_bearing__configuration__22x16x4_material"/>
319
+ <!-- Part upper_leg_rigidity_plate_2 -->
320
+ <geom type="mesh" class="visual" pos="0 0 0.0435" quat="0.5 -0.5 0.5 0.5" mesh="upper_leg_rigidity_plate" material="upper_leg_rigidity_plate_material"/>
321
+ <!-- Part upper_leg_right -->
322
+ <geom type="mesh" class="visual" pos="-0 0 -0.0425" quat="0.5 -0.5 0.5 0.5" mesh="upper_leg_right" material="upper_leg_right_material"/>
323
+ <!-- Part xl330_14 -->
324
+ <geom type="mesh" class="visual" pos="0 0 0.0125" quat="0.5 0.5 -0.5 0.5" mesh="xl330" material="xl330_material"/>
325
+ <!-- Link leg_2 -->
326
+ <body name="leg_2" pos="-0.022 0.0357771 -0.004" quat="0 1 -0 -0">
327
+ <!-- Joint from upper_leg_right to leg_2 -->
328
+ <joint axis="0 0 1" name="right_knee" type="hinge" range="-1.570796326794932 1.570796326794861" class="chosen_actuator"/>
329
+ <inertial pos="-0.0318937 -1.52e-10 -0.00953505" mass="0.0215844" fullinertia="2.15507e-06 5.25056e-06 4.33925e-06 1e-12 -7.65005e-07 0"/>
330
+ <!-- Part xl330_15 -->
331
+ <geom type="mesh" class="visual" pos="-0.042 0 -0.0115" quat="0.707107 -0 -0.707107 -0" mesh="xl330" material="xl330_material"/>
332
+ <!-- Part leg_2 -->
333
+ <geom type="mesh" class="visual" pos="-0.0357771 -0.022 0.0385" quat="0.707107 0 0.707107 -0" mesh="leg" material="leg_material"/>
334
+ <geom type="mesh" class="collision" pos="-0.0357771 -0.022 0.0385" quat="0.707107 0 0.707107 -0" mesh="leg" material="leg_material"/>
335
+ <!-- Link ankle_r_v1 -->
336
+ <body name="ankle_r_v1" pos="-0.042 0 -0.026" quat="0 -0.707107 -0.707107 0">
337
+ <!-- Joint from leg_2 to ankle_r_v1 -->
338
+ <joint axis="0 0 1" name="right_ankle" type="hinge" range="-1.5707963267949063 1.5707963267948868" class="chosen_actuator"/>
339
+ <inertial pos="0.00680653 -0.0143679 -0.0149822" mass="0.0208701" fullinertia="4.8595e-06 9.68066e-06 8.22663e-06 3.18865e-07 -6.7107e-08 1.66791e-07"/>
340
+ <!-- Part ankle_r_v1 -->
341
+ <geom type="mesh" class="visual" pos="0.022 0.00622291 -0.0647" quat="0.5 -0.5 0.5 0.5" mesh="ankle_r_v1" material="ankle_r_v1_material"/>
342
+ <!-- Part seeed_bearing__configuration_default_3 -->
343
+ <geom type="mesh" class="visual" pos="0 0 -0.0284" quat="0 1 0 -0" mesh="seeed_bearing__configuration_default" material="seeed_bearing__configuration_default_material"/>
344
+ <!-- Part roller_blade_2 -->
345
+ <geom type="mesh" class="visual" pos="0.022 0.00622291 0.0355181" quat="0.5 -0.5 0.5 0.5" mesh="roller_blade" material="roller_blade_material"/>
346
+ <!-- Frame right_foot -->
347
+ <site group="3" name="right_foot" pos="0 -0.0475 -0.0144819" quat="0.707107 -0.707107 -0 0"/>
348
+ <!-- Link tire_3 -->
349
+ <body name="tire_3" pos="0.0395 -0.0325 -0.0144819" quat="0 -0 1 -0">
350
+ <!-- Joint from ankle_r_v1 to tire_3 -->
351
+ <joint axis="0 0 1" name="passive_RF_wheel" class="passive_joint"/>
352
+ <inertial pos="-0 -0 0" mass="0.00447401" fullinertia="2.8556e-07 2.8556e-07 5.3687e-07 -0 -6e-12 -0"/>
353
+ <!-- Part tire_3 -->
354
+ <geom type="mesh" class="visual" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="tire" material="tire_material"/>
355
+ <geom type="mesh" class="collision" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="tire" material="tire_material"/>
356
+ <!-- Part rim_3 -->
357
+ <geom type="mesh" class="visual" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="rim" material="rim_material"/>
358
+ </body>
359
+ <!-- Link tire_4 -->
360
+ <body name="tire_4" pos="-0.0255 -0.0325 -0.0144819" quat="0 -0 1 -0">
361
+ <!-- Joint from ankle_r_v1 to tire_4 -->
362
+ <joint axis="0 0 1" name="passive_RR_wheel" class="passive_joint"/>
363
+ <inertial pos="-0 -0 0" mass="0.00447401" fullinertia="2.8556e-07 2.8556e-07 5.3687e-07 -0 -6e-12 -0"/>
364
+ <!-- Part rim_4 -->
365
+ <geom type="mesh" class="visual" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="rim" material="rim_material"/>
366
+ <!-- Part tire_4 -->
367
+ <geom type="mesh" class="visual" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="tire" material="tire_material"/>
368
+ <geom type="mesh" class="collision" pos="0.0175 0.0387229 -0.05" quat="0.5 -0.5 -0.5 -0.5" mesh="tire" material="tire_material"/>
369
+ </body>
370
+ </body>
371
+ </body>
372
+ </body>
373
+ </body>
374
+ </body>
375
+ </body>
376
+ </worldbody>
377
+ <asset>
378
+ <mesh file="elec_rpi_robot_hat_pcb.stl"/>
379
+ <mesh file="soft_mouth_top.stl"/>
380
+ <mesh file="speaker.stl"/>
381
+ <mesh file="top_head_shell.stl"/>
382
+ <mesh file="power_support.stl"/>
383
+ <mesh file="motor_support.stl"/>
384
+ <mesh file="upper_leg_left.stl"/>
385
+ <mesh file="roller_blade.stl"/>
386
+ <mesh file="m12_lens_holder.stl"/>
387
+ <mesh file="hip_l.stl"/>
388
+ <mesh file="lens.stl"/>
389
+ <mesh file="xl330.stl"/>
390
+ <mesh file="bearing_roll.stl"/>
391
+ <mesh file="bottom_head_shell.stl"/>
392
+ <mesh file="ankle_r_v1.stl"/>
393
+ <mesh file="jaw_soft.stl"/>
394
+ <mesh file="ankle_l_v1.stl"/>
395
+ <mesh file="yaw_roll_motion.stl"/>
396
+ <mesh file="trunk_base.stl"/>
397
+ <mesh file="left_shell.stl"/>
398
+ <mesh file="np_f970.stl"/>
399
+ <mesh file="yaw2roll.stl"/>
400
+ <mesh file="tire.stl"/>
401
+ <mesh file="upper_leg_right.stl"/>
402
+ <mesh file="banana_pcb_locker.stl"/>
403
+ <mesh file="pcb__raspberry_pi_zero_2_w.stl"/>
404
+ <mesh file="leg.stl"/>
405
+ <mesh file="neck_pitch.stl"/>
406
+ <mesh file="jaw.stl"/>
407
+ <mesh file="neck.stl"/>
408
+ <mesh file="face_part.stl"/>
409
+ <mesh file="right_shell.stl"/>
410
+ <mesh file="seeed_bearing__configuration__22x16x4.stl"/>
411
+ <mesh file="upper_leg_rigidity_plate.stl"/>
412
+ <mesh file="seeed_bearing__configuration_default.stl"/>
413
+ <mesh file="noenoeil.stl"/>
414
+ <mesh file="rim.stl"/>
415
+ <material name="seeed_bearing__configuration__22x16x4_material" rgba="0.713725 0.760784 0.8 1"/>
416
+ <material name="right_shell_material" rgba="0.8 0.8 0.8 1"/>
417
+ <material name="banana_pcb_locker_material" rgba="0.647059 0.647059 0.647059 1"/>
418
+ <material name="trunk_base_material" rgba="0.34902 0.376471 0.4 1"/>
419
+ <material name="xl330_material" rgba="0.286275 0.286275 0.286275 1"/>
420
+ <material name="power_support_material" rgba="0.901961 0.901961 0.901961 1"/>
421
+ <material name="np_f970_material" rgba="0.901961 0.901961 0.901961 1"/>
422
+ <material name="left_shell_material" rgba="0.8 0.8 0.8 1"/>
423
+ <material name="yaw2roll_material" rgba="0.647059 0.647059 0.647059 1"/>
424
+ <material name="bearing_roll_material" rgba="0.262745 0.282353 0.301961 1"/>
425
+ <material name="hip_l_material" rgba="0.901961 0.901961 0.901961 1"/>
426
+ <material name="upper_leg_left_material" rgba="0.866667 0.866667 0.866667 1"/>
427
+ <material name="upper_leg_rigidity_plate_material" rgba="0.537255 0.854902 0.827451 1"/>
428
+ <material name="leg_material" rgba="0.811765 0.858824 0.898039 1"/>
429
+ <material name="roller_blade_material" rgba="0.901961 0.901961 0.901961 1"/>
430
+ <material name="seeed_bearing__configuration_default_material" rgba="0.713725 0.760784 0.8 1"/>
431
+ <material name="ankle_l_v1_material" rgba="0.980392 0.713725 0.00392157 1"/>
432
+ <material name="tire_material" rgba="0.34902 0.376471 0.4 1"/>
433
+ <material name="rim_material" rgba="0.729412 0.25098 0.105882 1"/>
434
+ <material name="neck_material" rgba="0.8 0.8 0.8 1"/>
435
+ <material name="neck_pitch_material" rgba="0.34902 0.376471 0.4 1"/>
436
+ <material name="yaw_roll_motion_material" rgba="0.768627 0.886275 0.952941 1"/>
437
+ <material name="lens_material" rgba="0.262745 0.282353 0.301961 1"/>
438
+ <material name="face_part_material" rgba="0.34902 0.376471 0.4 1"/>
439
+ <material name="jaw_soft_material" rgba="0.85098 0.756863 0.866667 1"/>
440
+ <material name="motor_support_material" rgba="0.262745 0.282353 0.301961 1"/>
441
+ <material name="top_head_shell_material" rgba="0.901961 0.901961 0.901961 1"/>
442
+ <material name="elec_rpi_robot_hat_pcb_material" rgba="0.313725 0.486275 0.411765 1"/>
443
+ <material name="soft_mouth_top_material" rgba="0.85098 0.756863 0.866667 1"/>
444
+ <material name="noenoeil_material" rgba="0.262745 0.282353 0.301961 1"/>
445
+ <material name="jaw_material" rgba="0.980392 0.713725 0.00392157 1"/>
446
+ <material name="bottom_head_shell_material" rgba="0.980392 0.713725 0.00392157 1"/>
447
+ <material name="m12_lens_holder_material" rgba="0.262745 0.282353 0.301961 1"/>
448
+ <material name="pcb__raspberry_pi_zero_2_w_material" rgba="0.498039 0.498039 0.498039 1"/>
449
+ <material name="speaker_material" rgba="0.294118 0.290196 0.290196 1"/>
450
+ <material name="upper_leg_right_material" rgba="0.866667 0.866667 0.866667 1"/>
451
+ <material name="ankle_r_v1_material" rgba="0.980392 0.713725 0.00392157 1"/>
452
+ </asset>
453
+ <actuator>
454
+ <position class="chosen_actuator" name="left_hip_yaw" joint="left_hip_yaw"/>
455
+ <position class="chosen_actuator" name="left_hip_roll" joint="left_hip_roll"/>
456
+ <position class="chosen_actuator" name="left_hip_pitch" joint="left_hip_pitch"/>
457
+ <position class="chosen_actuator" name="left_knee" joint="left_knee"/>
458
+ <position class="chosen_actuator" name="left_ankle" joint="left_ankle"/>
459
+ <position class="chosen_actuator" name="neck_pitch" joint="neck_pitch"/>
460
+ <position class="chosen_actuator" name="head_pitch" joint="head_pitch"/>
461
+ <position class="chosen_actuator" name="head_yaw" joint="head_yaw"/>
462
+ <position class="chosen_actuator" name="head_roll" joint="head_roll"/>
463
+ <position class="chosen_actuator" name="right_hip_yaw" joint="right_hip_yaw"/>
464
+ <position class="chosen_actuator" name="right_hip_roll" joint="right_hip_roll"/>
465
+ <position class="chosen_actuator" name="right_hip_pitch" joint="right_hip_pitch"/>
466
+ <position class="chosen_actuator" name="right_knee" joint="right_knee"/>
467
+ <position class="chosen_actuator" name="right_ankle" joint="right_ankle"/>
468
+ </actuator>
469
+ <equality/>
470
+ </mujoco>
variants.js CHANGED
@@ -168,6 +168,13 @@ export function meshMaterialsFor(v) {
168
  "ankle_right.stl": v.feet,
169
  "sole_left.stl": v.soles,
170
  "sole_right.stl": v.soles,
 
 
 
 
 
 
 
171
  // Dark mechanics. leg.stl is the printed shin that wraps the ankle
172
  // motor and its bearing ring: in gray they read as two gray motors,
173
  // so both go dark with the motor they hold.
 
168
  "ankle_right.stl": v.feet,
169
  "sole_left.stl": v.soles,
170
  "sole_right.stl": v.soles,
171
+ // Roller variant: the blade + ankle bracket take the shoe-upper slot,
172
+ // the rims take the sole accent, the tires are rubber-dark.
173
+ "ankle_l_v1.stl": v.feet,
174
+ "ankle_r_v1.stl": v.feet,
175
+ "roller_blade.stl": v.feet,
176
+ "rim.stl": v.soles,
177
+ "tire.stl": v.mechDark,
178
  // Dark mechanics. leg.stl is the printed shin that wraps the ankle
179
  // motor and its bearing ring: in gray they read as two gray motors,
180
  // so both go dark with the motor they hold.