tfrere HF Staff Cursor commited on
Commit
ee81e70
Β·
1 Parent(s): 5937cda

feat: one-shot roulade on Space with auto-return to walk, drop reset button

Browse files
Files changed (2) hide show
  1. index.html +1 -2
  2. rl.js +41 -2
index.html CHANGED
@@ -159,13 +159,12 @@
159
  <button id="btn-walk" class="on">Run</button>
160
  <button id="btn-sit">Sit</button>
161
  <button id="btn-roulade">Roulade</button>
162
- <button id="btn-reset">Reset</button>
163
  </div>
164
  <div class="row" id="swatches"></div>
165
  </div>
166
  <div class="keys">
167
  <b>&uarr;/&darr;</b> forward / back &nbsp; <b>&larr;/&rarr;</b> turn &nbsp; <b>A/E</b> strafe<br />
168
- <b>R</b> reset &nbsp; drag to orbit &nbsp; scroll to zoom
169
  </div>
170
  </div>
171
  <div id="loading">Compiling physics&hellip;</div>
 
159
  <button id="btn-walk" class="on">Run</button>
160
  <button id="btn-sit">Sit</button>
161
  <button id="btn-roulade">Roulade</button>
 
162
  </div>
163
  <div class="row" id="swatches"></div>
164
  </div>
165
  <div class="keys">
166
  <b>&uarr;/&darr;</b> forward / back &nbsp; <b>&larr;/&rarr;</b> turn &nbsp; <b>A/E</b> strafe<br />
167
+ <b>Space</b> roulade &nbsp; <b>R</b> reset &nbsp; drag to orbit &nbsp; scroll to zoom
168
  </div>
169
  </div>
170
  <div id="loading">Compiling physics&hellip;</div>
rl.js CHANGED
@@ -195,6 +195,9 @@ const held = new Set();
195
 
196
  let mode = "walk"; // "walk" | "sitstand" | "roulade"
197
  let sitFlag = 0;
 
 
 
198
 
199
  function resetSim() {
200
  mujoco.mj_resetDataKeyframe(model, data, standKeyId);
@@ -268,6 +271,28 @@ async function controlStep() {
268
  } else {
269
  fallenSince = null;
270
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  }
272
 
273
  let running = true;
@@ -464,6 +489,7 @@ const KEYMAP = {
464
  window.addEventListener("keydown", (e) => {
465
  if (e.repeat) return;
466
  if (e.code === "KeyR") { resetSim(); return; }
 
467
  const act = KEYMAP[e.code];
468
  if (!act) return;
469
  e.preventDefault();
@@ -481,10 +507,10 @@ window.addEventListener("blur", () => { held.clear(); refreshVelCmd(); });
481
  const btnWalk = document.getElementById("btn-walk");
482
  const btnSit = document.getElementById("btn-sit");
483
  const btnRoulade = document.getElementById("btn-roulade");
484
- document.getElementById("btn-reset").addEventListener("click", resetSim);
485
 
486
  function setMode(next) {
487
  quack();
 
488
  if (next !== "sit") {
489
  // Leaving a sit: let the sitstand policy stand the duck back up first.
490
  if (mode === "sitstand" && sitFlag === 1) {
@@ -508,6 +534,19 @@ function setMode(next) {
508
  }
509
  syncButtons();
510
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  function syncButtons() {
512
  const sitting = mode === "sitstand" && sitFlag === 1;
513
  btnWalk.classList.toggle("on", mode === "walk" || (mode === "sitstand" && !sitting));
@@ -516,7 +555,7 @@ function syncButtons() {
516
  }
517
  btnWalk.addEventListener("click", () => setMode("walk"));
518
  btnSit.addEventListener("click", () => setMode("sit"));
519
- btnRoulade.addEventListener("click", () => setMode("roulade"));
520
 
521
  // ── Colour swatches: re-skin the rig live, with a quack ─────────────────
522
  // One representative colour per variant so the dots read at a glance.
 
195
 
196
  let mode = "walk"; // "walk" | "sitstand" | "roulade"
197
  let sitFlag = 0;
198
+ // One-shot roulade tracking: trigger time + whether the trunk actually
199
+ // tipped over yet. Set by triggerRoulade, cleared when we hand back to walk.
200
+ let rouladeRun = null;
201
 
202
  function resetSim() {
203
  mujoco.mj_resetDataKeyframe(model, data, standKeyId);
 
271
  } else {
272
  fallenSince = null;
273
  }
274
+
275
+ // One-shot roulade, step-counted like the robot runtime (a single roll is
276
+ // ~1 s = 50 control steps there): hand back to walking once the trunk has
277
+ // tipped over and is upright again, or after a hard 2 s window if the roll
278
+ // never initiated. Counting steps instead of wall time keeps the logic
279
+ // correct when the sim is fast-forwarded or the tab is throttled.
280
+ if (mode === "roulade" && rouladeRun) {
281
+ rouladeRun.steps++;
282
+ if (obs[5] > -0.3) rouladeRun.tipped = true;
283
+ const upright = obs[5] < -0.85;
284
+ const done = rouladeRun.tipped && upright && rouladeRun.steps >= 40;
285
+ const expired = rouladeRun.steps >= 150; // 3 s, roll should long be over
286
+ if (done || expired) {
287
+ rouladeRun = null;
288
+ mode = "walk";
289
+ lastAction.fill(0);
290
+ // Timed out mid-roll: don't hand a tipped duck to the walking policy
291
+ // (it has no get-up skill), restart from the keyframe instead.
292
+ if (!upright) resetSim();
293
+ if (uiReady) syncButtons();
294
+ }
295
+ }
296
  }
297
 
298
  let running = true;
 
489
  window.addEventListener("keydown", (e) => {
490
  if (e.repeat) return;
491
  if (e.code === "KeyR") { resetSim(); return; }
492
+ if (e.code === "Space") { e.preventDefault(); triggerRoulade(); return; }
493
  const act = KEYMAP[e.code];
494
  if (!act) return;
495
  e.preventDefault();
 
507
  const btnWalk = document.getElementById("btn-walk");
508
  const btnSit = document.getElementById("btn-sit");
509
  const btnRoulade = document.getElementById("btn-roulade");
 
510
 
511
  function setMode(next) {
512
  quack();
513
+ rouladeRun = null;
514
  if (next !== "sit") {
515
  // Leaving a sit: let the sitstand policy stand the duck back up first.
516
  if (mode === "sitstand" && sitFlag === 1) {
 
534
  }
535
  syncButtons();
536
  }
537
+
538
+ // One roulade, then straight back to running (Space key or the button).
539
+ // lastAction is deliberately NOT zeroed here: the robot runtime keeps one
540
+ // continuous action history across policy switches, and the roll initiates
541
+ // more reliably mid-gait with the true last actions in the obs.
542
+ function triggerRoulade() {
543
+ if (mode === "roulade") return;
544
+ quack();
545
+ mode = "roulade";
546
+ sitFlag = 0;
547
+ rouladeRun = { steps: 0, tipped: false };
548
+ syncButtons();
549
+ }
550
  function syncButtons() {
551
  const sitting = mode === "sitstand" && sitFlag === 1;
552
  btnWalk.classList.toggle("on", mode === "walk" || (mode === "sitstand" && !sitting));
 
555
  }
556
  btnWalk.addEventListener("click", () => setMode("walk"));
557
  btnSit.addEventListener("click", () => setMode("sit"));
558
+ btnRoulade.addEventListener("click", triggerRoulade);
559
 
560
  // ── Colour swatches: re-skin the rig live, with a quack ─────────────────
561
  // One representative colour per variant so the dots read at a glance.