tfrere HF Staff Cursor commited on
Commit
53d6d58
·
1 Parent(s): 0df9a77

feat: duck-head modal logo, input lockout mid-roll, quieter quack

Browse files

- welcome modal uses the site's duck-head mark (open variant on hover)
instead of the wordmark sticker
- all movement/mode inputs are ignored while a roll is in flight; the
existing deterministic completion check (tipped then upright, 3 s cap)
releases the lock, no arbitrary timer
- quack.wav only plays on the gamepad right trigger; mode/colour changes
keep the silent jaw flap

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

.gitattributes CHANGED
@@ -36,4 +36,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
36
  *.stl filter=lfs diff=lfs merge=lfs -text
37
  *.wav filter=lfs diff=lfs merge=lfs -text
38
  *.png filter=lfs diff=lfs merge=lfs -text
 
39
  *.jpg filter=lfs diff=lfs merge=lfs -text
 
36
  *.stl filter=lfs diff=lfs merge=lfs -text
37
  *.wav filter=lfs diff=lfs merge=lfs -text
38
  *.png filter=lfs diff=lfs merge=lfs -text
39
+ *.webp filter=lfs diff=lfs merge=lfs -text
40
  *.jpg filter=lfs diff=lfs merge=lfs -text
assets/{logo-wordmark.png → duck-head-mark-open.webp} RENAMED
File without changes
assets/duck-head-mark.webp ADDED

Git LFS Details

  • SHA256: ad08b60991fa0568409a68001d53868341d55179c757007831ad8000cc1f96bd
  • Pointer size: 130 Bytes
  • Size of remote file: 32.6 kB
index.html CHANGED
@@ -281,12 +281,18 @@
281
  from { transform: translateY(18px) scale(0.94); opacity: 0; }
282
  to { transform: none; opacity: 1; }
283
  }
284
- #welcome .logo-wordmark {
285
- display: block;
286
- width: min(78%, 300px);
 
 
287
  margin: 0.3rem auto 0;
288
  filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.45));
289
  }
 
 
 
 
290
  #welcome .subtitle {
291
  margin-top: 0.55rem;
292
  font-size: 0.62rem;
@@ -400,7 +406,10 @@
400
 
401
  <div id="welcome" hidden>
402
  <div class="card" role="dialog" aria-modal="true" aria-label="Microduck playground">
403
- <img class="logo-wordmark" id="logo-wordmark" alt="Microduck" />
 
 
 
404
  <div class="subtitle">RL playground</div>
405
  <p class="tagline">
406
  A real robot brain in your browser: MuJoCo physics compiled to
@@ -427,7 +436,8 @@
427
  // on the private Space (rl.js hasn't installed its signer yet).
428
  const sign = new URLSearchParams(location.search).get("__sign");
429
  const signedUrl = (u) => (sign ? `${u}?__sign=${encodeURIComponent(sign)}` : u);
430
- document.getElementById("logo-wordmark").src = signedUrl("./assets/logo-wordmark.png");
 
431
  const open = () => {
432
  modal.hidden = false;
433
  modal.classList.remove("closing");
@@ -458,7 +468,7 @@
458
  appended: a plain src attribute can 401 when the browser blocks the
459
  *.static.hf.space auth cookie inside the hub iframe. -->
460
  <script type="module">
461
- const V = "22"; // bump on rl.js changes to defeat heuristic caching
462
  const sign = new URLSearchParams(location.search).get("__sign");
463
  const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
464
  import(`./rl.js?v=${V}${suffix}`);
 
281
  from { transform: translateY(18px) scale(0.94); opacity: 0; }
282
  to { transform: none; opacity: 1; }
283
  }
284
+ /* Duck-head mark from the site header: closed by default, open on
285
+ hover (same swap as the site). */
286
+ #welcome .logo-head {
287
+ position: relative;
288
+ width: 7.2rem;
289
  margin: 0.3rem auto 0;
290
  filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.45));
291
  }
292
+ #welcome .logo-head img { display: block; width: 100%; }
293
+ #welcome .logo-head .open { position: absolute; inset: 0; opacity: 0; }
294
+ #welcome .logo-head:hover .open { opacity: 1; }
295
+ #welcome .logo-head:hover .closed { opacity: 0; }
296
  #welcome .subtitle {
297
  margin-top: 0.55rem;
298
  font-size: 0.62rem;
 
406
 
407
  <div id="welcome" hidden>
408
  <div class="card" role="dialog" aria-modal="true" aria-label="Microduck playground">
409
+ <div class="logo-head">
410
+ <img class="closed" id="logo-head-closed" alt="Microduck" />
411
+ <img class="open" id="logo-head-open" alt="" aria-hidden="true" />
412
+ </div>
413
  <div class="subtitle">RL playground</div>
414
  <p class="tagline">
415
  A real robot brain in your browser: MuJoCo physics compiled to
 
436
  // on the private Space (rl.js hasn't installed its signer yet).
437
  const sign = new URLSearchParams(location.search).get("__sign");
438
  const signedUrl = (u) => (sign ? `${u}?__sign=${encodeURIComponent(sign)}` : u);
439
+ document.getElementById("logo-head-closed").src = signedUrl("./assets/duck-head-mark.webp");
440
+ document.getElementById("logo-head-open").src = signedUrl("./assets/duck-head-mark-open.webp");
441
  const open = () => {
442
  modal.hidden = false;
443
  modal.classList.remove("closing");
 
468
  appended: a plain src attribute can 401 when the browser blocks the
469
  *.static.hf.space auth cookie inside the hub iframe. -->
470
  <script type="module">
471
+ const V = "23"; // bump on rl.js changes to defeat heuristic caching
472
  const sign = new URLSearchParams(location.search).get("__sign");
473
  const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
474
  import(`./rl.js?v=${V}${suffix}`);
rl.js CHANGED
@@ -208,7 +208,11 @@ let sitFlag = 0;
208
  // The twist the policy actually receives: live gamepad sticks win over the
209
  // held keys. No input means zero command - the duck stands in place.
210
  // Shared by buildObs and the HUD mini-sticks so they can never disagree.
 
 
 
211
  function effectiveCmd() {
 
212
  return padActive ? padCmd : velCmd;
213
  }
214
  // One-shot roll tracking: trigger time + whether the trunk actually
@@ -465,8 +469,13 @@ let padJaw = 0;
465
  // is swallowed and sound simply starts working after the first click/key.
466
  const quackSound = new Audio(signed("./assets/quack.wav"));
467
  quackSound.volume = 0.7;
 
 
468
  const quack = () => {
469
  quackAt = performance.now();
 
 
 
470
  quackSound.currentTime = 0;
471
  quackSound.play().catch(() => {});
472
  };
@@ -505,7 +514,7 @@ function renderStats() {
505
 
506
  // Mini sticks: the dot mirrors the effective twist, lit yellow while the
507
  // user is actually driving.
508
- const manual = padActive || held.size > 0;
509
  const yN = vx >= 0 ? vx / VEL_FWD : vx / -VEL_BACK;
510
  dotMove.style.transform = `translate(${(-vy / VEL_LAT) * STICK_R}px, ${-yN * STICK_R}px)`;
511
  dotTurn.style.transform = `translate(${(-wz / VEL_ANG) * STICK_R}px, 0px)`;
@@ -636,7 +645,7 @@ pollPad = function pollGamepad() {
636
  const rt = gp.buttons[7]?.value ?? 0;
637
  const lt = gp.buttons[6]?.value ?? 0;
638
  padJaw = Math.max(rt, lt);
639
- if (padPrev.rt < 0.3 && rt >= 0.3) quack();
640
  padPrev.rt = rt;
641
  };
642
 
@@ -645,6 +654,9 @@ pollPad = function pollGamepad() {
645
  const modeLabel = document.getElementById("mode-label");
646
 
647
  function setMode(next) {
 
 
 
648
  quack();
649
  rollRun = null;
650
  if (next !== "sit") {
 
208
  // The twist the policy actually receives: live gamepad sticks win over the
209
  // held keys. No input means zero command - the duck stands in place.
210
  // Shared by buildObs and the HUD mini-sticks so they can never disagree.
211
+ // Mid-roll every movement input is ignored (zero twist) until the roll
212
+ // hands back to walk on its own - steering would only knock the roll over.
213
+ const ZERO_CMD = new Float32Array(3);
214
  function effectiveCmd() {
215
+ if (mode === "roll") return ZERO_CMD;
216
  return padActive ? padCmd : velCmd;
217
  }
218
  // One-shot roll tracking: trigger time + whether the trunk actually
 
469
  // is swallowed and sound simply starts working after the first click/key.
470
  const quackSound = new Audio(signed("./assets/quack.wav"));
471
  quackSound.volume = 0.7;
472
+ // Silent jaw flap for mode/colour changes; the actual quack sound only
473
+ // plays on the gamepad right trigger (anything more gets noisy fast).
474
  const quack = () => {
475
  quackAt = performance.now();
476
+ };
477
+ const quackLoud = () => {
478
+ quack();
479
  quackSound.currentTime = 0;
480
  quackSound.play().catch(() => {});
481
  };
 
514
 
515
  // Mini sticks: the dot mirrors the effective twist, lit yellow while the
516
  // user is actually driving.
517
+ const manual = (padActive || held.size > 0) && mode !== "roll";
518
  const yN = vx >= 0 ? vx / VEL_FWD : vx / -VEL_BACK;
519
  dotMove.style.transform = `translate(${(-vy / VEL_LAT) * STICK_R}px, ${-yN * STICK_R}px)`;
520
  dotTurn.style.transform = `translate(${(-wz / VEL_ANG) * STICK_R}px, 0px)`;
 
645
  const rt = gp.buttons[7]?.value ?? 0;
646
  const lt = gp.buttons[6]?.value ?? 0;
647
  padJaw = Math.max(rt, lt);
648
+ if (padPrev.rt < 0.3 && rt >= 0.3) quackLoud();
649
  padPrev.rt = rt;
650
  };
651
 
 
654
  const modeLabel = document.getElementById("mode-label");
655
 
656
  function setMode(next) {
657
+ // No policy switching mid-roll: the roll ends on its own (upright again
658
+ // or expired) and returns to walk - switching now would floor the duck.
659
+ if (mode === "roll" && rollRun) return;
660
  quack();
661
  rollRun = null;
662
  if (next !== "sit") {