fix: harden mode state machine + per-duck randomized chirps
Browse filesState machine:
- sit/stand hand-over timers are now cancellable and cleared by every
transition entry point (setMode, triggerRoll, resetSim); a stale 2 s
stand-up timer could previously flip a sitting or rolling duck to the
walking policy mid-motion
- resetSim cancels any roll in flight and pending timers, always lands
on walk (R mid-roll no longer replays the roll on the reset duck)
- rolls only launch from a standing walk, not from a sit
- RT quack uses a Schmitt trigger (fire at 0.35, re-arm below 0.2):
analog jitter around a single threshold caused multiple quacks
Sound:
- ship the runtime's four chirp voice banks (12 takes each); each
colourway has its own duck voice and every quack picks a random take
Co-authored-by: Cursor <cursoragent@cursor.com>
- assets/{quack.wav → voices/duck1/chirp_a.wav} +2 -2
- assets/voices/duck1/chirp_b.wav +3 -0
- assets/voices/duck1/chirp_c.wav +3 -0
- assets/voices/duck1/chirp_d.wav +3 -0
- assets/voices/duck1/chirp_e.wav +3 -0
- assets/voices/duck1/chirp_f.wav +3 -0
- assets/voices/duck1/chirp_g.wav +3 -0
- assets/voices/duck1/chirp_h.wav +3 -0
- assets/voices/duck1/chirp_i.wav +3 -0
- assets/voices/duck1/chirp_j.wav +3 -0
- assets/voices/duck1/chirp_k.wav +3 -0
- assets/voices/duck1/chirp_l.wav +3 -0
- assets/voices/duck2/chirp_a.wav +3 -0
- assets/voices/duck2/chirp_b.wav +3 -0
- assets/voices/duck2/chirp_c.wav +3 -0
- assets/voices/duck2/chirp_d.wav +3 -0
- assets/voices/duck2/chirp_e.wav +3 -0
- assets/voices/duck2/chirp_f.wav +3 -0
- assets/voices/duck2/chirp_g.wav +3 -0
- assets/voices/duck2/chirp_h.wav +3 -0
- assets/voices/duck2/chirp_i.wav +3 -0
- assets/voices/duck2/chirp_j.wav +3 -0
- assets/voices/duck2/chirp_k.wav +3 -0
- assets/voices/duck2/chirp_l.wav +3 -0
- assets/voices/duck3/chirp_a.wav +3 -0
- assets/voices/duck3/chirp_b.wav +3 -0
- assets/voices/duck3/chirp_c.wav +3 -0
- assets/voices/duck3/chirp_d.wav +3 -0
- assets/voices/duck3/chirp_e.wav +3 -0
- assets/voices/duck3/chirp_f.wav +3 -0
- assets/voices/duck3/chirp_g.wav +3 -0
- assets/voices/duck3/chirp_h.wav +3 -0
- assets/voices/duck3/chirp_i.wav +3 -0
- assets/voices/duck3/chirp_j.wav +3 -0
- assets/voices/duck3/chirp_k.wav +3 -0
- assets/voices/duck3/chirp_l.wav +3 -0
- assets/voices/duck4/chirp_a.wav +3 -0
- assets/voices/duck4/chirp_b.wav +3 -0
- assets/voices/duck4/chirp_c.wav +3 -0
- assets/voices/duck4/chirp_d.wav +3 -0
- assets/voices/duck4/chirp_e.wav +3 -0
- assets/voices/duck4/chirp_f.wav +3 -0
- assets/voices/duck4/chirp_g.wav +3 -0
- assets/voices/duck4/chirp_h.wav +3 -0
- assets/voices/duck4/chirp_i.wav +3 -0
- assets/voices/duck4/chirp_j.wav +3 -0
- assets/voices/duck4/chirp_k.wav +3 -0
- assets/voices/duck4/chirp_l.wav +3 -0
- index.html +1 -1
- rl.js +57 -14
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:450d807c1e6f535f30a1e50491df9b57567b8b8be357148de731de8b7582c8c2
|
| 3 |
+
size 5336
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:660fa601e43eb7f05e67903446ccb11b9205bec31fdf4695a824c885e1da621e
|
| 3 |
+
size 5510
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2451a1286f28eaa5f54875c54486e03dd234c425c70c62ecc35c37f1bc745fc8
|
| 3 |
+
size 8658
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:070253cd8fd2323226677935a6a7f6bb664e556a2292493e9269c8bc50cda188
|
| 3 |
+
size 11002
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9f0225bf39edae00420250f26610b923ca369eacaae97d775ca6e0d68d0789f2
|
| 3 |
+
size 7214
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:41bb6b9105669e0d297ab4e396491ff4798d0092c9f67911858cc970bb6e2fee
|
| 3 |
+
size 6460
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c33fe26fc9491564c12a2ab4d49da7d5502d02e097067e1ca7b5e48078a5f2c8
|
| 3 |
+
size 11068
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:47a7a3b9d6e06b601daaa47c3426c5dab963c9a05bd9878ca39155fbe7a55f8b
|
| 3 |
+
size 10512
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0114c825dec129ae02fbd2aacf0595497d919108865e25e57b6610dd2f41e270
|
| 3 |
+
size 6578
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3a7a18a47c5a18d9fd21a78ea25dcb657feb7460680171357c5f123fc05ec454
|
| 3 |
+
size 7278
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bd64c51c26fe8f311e41ef9e949ca5a47718b529ce973d32d9490b2a350c89db
|
| 3 |
+
size 9542
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cecbff6accd2f0d525d302db9c6582d695e31e907bcee14bb82ede4db1d204e0
|
| 3 |
+
size 8632
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9f7aa1decf578eab8c05b1fc2ed5205439d19541bf270d26762440d0a8ca0aa9
|
| 3 |
+
size 8702
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2fa61541951c240fa9e94cfb3582bf61f6a57254b1233e6c7342b852474bd33e
|
| 3 |
+
size 7032
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4ae92893fe916f96629c26ded93e71855fd0cee490c1690027ce0a19b3a29284
|
| 3 |
+
size 16544
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:971282d4fa0ab372713bc42bc510d03d6f033e8760e7592658cfec8c4ceace71
|
| 3 |
+
size 10252
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a1d662e8b23203d5622984e698e9ed67a749133416fee6ddc59a3552675259b9
|
| 3 |
+
size 8972
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d340696b480ae7cc2cc5b85de00741e86bd29ce766ad8eaa5f305726a079ae35
|
| 3 |
+
size 8576
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d79c3369e113a4323ec8afe3a4ca22b7a47e3867266af233974ff423d8fc134a
|
| 3 |
+
size 13824
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b04e48823c5b41f61c60633644bb3ffbf20611de9dcdc9bcdaff3c02ed5b2b5b
|
| 3 |
+
size 13046
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d34507b77bbab2a1ca9a467c19089e4605c6836194bc4a56b2dbd1890ad0f487
|
| 3 |
+
size 5716
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e7c217684a7fb0f27acb3a06052c784f3a66f775122bb368c2be4c27a50ac3f2
|
| 3 |
+
size 9098
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:00d2a8938313ea05addc67743d36dd21ba466601817c7ad891bd0ea15dfc1ec2
|
| 3 |
+
size 11676
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a1fc77cdb7c00e5ed7ebe618452c11c517b7f3f647dfaa70566896ec2bfae084
|
| 3 |
+
size 13124
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4ed6496a3208db4057f1547e2cc5c18a078d4200abdd1b564d7a502640448854
|
| 3 |
+
size 5896
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b701381ae6aaa98a46114ad96847fcd4b087a4b3f1f02b5f1f2bbda7219dd183
|
| 3 |
+
size 8448
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f4e678fe44d371931ce323495a2e9bf9957fda5a382526acc58c1a82cc3e9eef
|
| 3 |
+
size 13430
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6f52cab48cb6eb9566e40da17023d8365064848746be2b444786431e8d8bd092
|
| 3 |
+
size 12864
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:95f839d2c444acfa5bf823d9b8c65e84774f82650f4d8e3b5c7aa80cd4f242a0
|
| 3 |
+
size 5496
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f6e581b10f2b4e81f13ffa186f9761285e581dda161614008a922ed59f46079a
|
| 3 |
+
size 6528
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f78916cbdc2065d914f20c509ef0157500d14d386c1d8e2be00c018a99a8e942
|
| 3 |
+
size 17304
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c9a4c5a0b36cc2e684b7e00e95f9e6ec02f6dc2799337a2cddb1e791f84278e2
|
| 3 |
+
size 12524
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4dc2ef2ce703297da12fbe5f9722236df4a5faa907142cfc00e56970c75bc445
|
| 3 |
+
size 8418
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:97931d92be70e1448d6bed56515b8ffb1a439a6e44ae6c35b8c5e3ae7b400c00
|
| 3 |
+
size 5182
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5d68b91b7b6ae44a24b693183587dd27f1c8d8e9c6fd94e0f5c7c0931d9f98f0
|
| 3 |
+
size 17800
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0f874aacabad93a0f706695874260dc421b3624e716ba4bfbc3e83e5ba840af6
|
| 3 |
+
size 13884
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:356abe1da47758ab6df17954975123c53be79a9bcc919b5adf06d7ac8a602fb2
|
| 3 |
+
size 7350
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:63c6dba54db0a001e7ed9376cdccb9bd47a9e255fa7fd909e2505dc9fd332193
|
| 3 |
+
size 9116
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:22804c3f59ff7eccef58aacbfd855fe9808f35cbe21ff4cd72264c2e30fd156a
|
| 3 |
+
size 13554
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8158e8e9d118037bd03bed4d564dd9e70b07ade5acf8cb56b7d52cbe77dc53cd
|
| 3 |
+
size 11580
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:836664d06509dbe43daddef6bd3415372774158145d950139b0e30f4057be41a
|
| 3 |
+
size 8552
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2839e0865a5e42aff830b2d1ccf113fba60c28e403c42886ba1b1e5bd470ce52
|
| 3 |
+
size 7752
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3ba26ccde31aafa4d43df34058da9a7b53aacdeaa847b2a9f12660b31a022a1e
|
| 3 |
+
size 12176
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:28231a6ec54e9fabf53c6add53987b8b946ded9b384e7d0af5a9bca5fe0c0f57
|
| 3 |
+
size 13742
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d028418fb90dcf2b91efb8bc034ac6a934a302e50243271cc9cd272f956d3810
|
| 3 |
+
size 6604
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e768b952f37f22c2852e06ae1c88ef84d662721b7b518204b79292fbd794b543
|
| 3 |
+
size 6472
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:80ccda91a3632145bfa6a750f8f1230ab59e94e541b82fb81c74810a56802c24
|
| 3 |
+
size 17340
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5a683cdb89ec94c655e900275a5afbd0a618e7523888d6bc681de4bbffe30f3d
|
| 3 |
+
size 11280
|
|
@@ -468,7 +468,7 @@
|
|
| 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 = "
|
| 472 |
const sign = new URLSearchParams(location.search).get("__sign");
|
| 473 |
const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
|
| 474 |
import(`./rl.js?v=${V}${suffix}`);
|
|
|
|
| 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 = "24"; // 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}`);
|
|
@@ -197,7 +197,7 @@ const velCmd = new Float32Array(3); // twist command, driven by held keys
|
|
| 197 |
// the control loop reads it before the input section below has evaluated.
|
| 198 |
const padCmd = new Float32Array(3);
|
| 199 |
let padActive = false;
|
| 200 |
-
const padPrev = { x: false, dpadDown: false, dpadUp: false,
|
| 201 |
// Declared before the control loop starts: buildObs reads it via
|
| 202 |
// effectiveCmd on the very first control step.
|
| 203 |
const held = new Set();
|
|
@@ -220,7 +220,24 @@ function effectiveCmd() {
|
|
| 220 |
let rollRun = null;
|
| 221 |
let rollSource = "kb"; // which hint lights up: keyboard Space or pad X
|
| 222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
function resetSim() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
mujoco.mj_resetDataKeyframe(model, data, standKeyId);
|
| 225 |
mujoco.mj_forward(model, data);
|
| 226 |
lastAction.fill(0);
|
|
@@ -461,14 +478,28 @@ function syncRig() {
|
|
| 461 |
const QUACK_MS = 480;
|
| 462 |
let quackAt = -Infinity;
|
| 463 |
let padJaw = 0;
|
| 464 |
-
//
|
| 465 |
-
//
|
| 466 |
-
//
|
| 467 |
-
//
|
| 468 |
// Browsers block audio until the first user gesture; the rejected play()
|
| 469 |
// is swallowed and sound simply starts working after the first click/key.
|
| 470 |
-
const
|
| 471 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 = () => {
|
|
@@ -476,8 +507,7 @@ const quack = () => {
|
|
| 476 |
};
|
| 477 |
const quackLoud = () => {
|
| 478 |
quack();
|
| 479 |
-
|
| 480 |
-
quackSound.play().catch(() => {});
|
| 481 |
};
|
| 482 |
function jawOpenNow() {
|
| 483 |
const t = (performance.now() - quackAt) / QUACK_MS;
|
|
@@ -642,11 +672,14 @@ pollPad = function pollGamepad() {
|
|
| 642 |
|
| 643 |
// Triggers drive the mouth like the runtime (max of both), and the right
|
| 644 |
// one quacks on its rising edge — same as the robot's chirp.
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 649 |
-
padPrev.rt =
|
| 650 |
};
|
| 651 |
|
| 652 |
// Read-only state label (bottom-left): reflects the active policy,
|
|
@@ -657,13 +690,19 @@ 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") {
|
| 663 |
// Leaving a sit: let the sitstand policy stand the duck back up first.
|
| 664 |
if (mode === "sitstand" && sitFlag === 1) {
|
| 665 |
sitFlag = 0;
|
| 666 |
-
setTimeout(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
syncButtons();
|
| 668 |
return;
|
| 669 |
}
|
|
@@ -676,7 +715,8 @@ function setMode(next) {
|
|
| 676 |
mode = "sitstand";
|
| 677 |
sitFlag = 0;
|
| 678 |
lastAction.fill(0);
|
| 679 |
-
setTimeout(() => {
|
|
|
|
| 680 |
if (mode === "sitstand") { sitFlag = 1; syncButtons(); }
|
| 681 |
}, 800);
|
| 682 |
}
|
|
@@ -688,7 +728,10 @@ function setMode(next) {
|
|
| 688 |
// continuous action history across policy switches, and the roll initiates
|
| 689 |
// more reliably mid-gait with the true last actions in the obs.
|
| 690 |
function triggerRoll(source = "kb") {
|
| 691 |
-
|
|
|
|
|
|
|
|
|
|
| 692 |
rollSource = source;
|
| 693 |
quack();
|
| 694 |
mode = "roll";
|
|
|
|
| 197 |
// the control loop reads it before the input section below has evaluated.
|
| 198 |
const padCmd = new Float32Array(3);
|
| 199 |
let padActive = false;
|
| 200 |
+
const padPrev = { x: false, dpadDown: false, dpadUp: false, rtArmed: true };
|
| 201 |
// Declared before the control loop starts: buildObs reads it via
|
| 202 |
// effectiveCmd on the very first control step.
|
| 203 |
const held = new Set();
|
|
|
|
| 220 |
let rollRun = null;
|
| 221 |
let rollSource = "kb"; // which hint lights up: keyboard Space or pad X
|
| 222 |
|
| 223 |
+
// Pending mode-transition timers (sit hand-over, stand-up hand-back).
|
| 224 |
+
// Every transition entry point clears them: a stale timer firing after
|
| 225 |
+
// the state has moved on is exactly how a sitting or rolling duck ends
|
| 226 |
+
// up handed to the walking policy mid-motion.
|
| 227 |
+
let sitTimer = null;
|
| 228 |
+
let standTimer = null;
|
| 229 |
+
function clearModeTimers() {
|
| 230 |
+
clearTimeout(sitTimer); sitTimer = null;
|
| 231 |
+
clearTimeout(standTimer); standTimer = null;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
function resetSim() {
|
| 235 |
+
// A reset is a full transition to the walking stand: cancel any roll in
|
| 236 |
+
// flight and any pending sit/stand hand-over, or they'd replay on the
|
| 237 |
+
// freshly reset duck.
|
| 238 |
+
clearModeTimers();
|
| 239 |
+
rollRun = null;
|
| 240 |
+
mode = "walk";
|
| 241 |
mujoco.mj_resetDataKeyframe(model, data, standKeyId);
|
| 242 |
mujoco.mj_forward(model, data);
|
| 243 |
lastAction.fill(0);
|
|
|
|
| 478 |
const QUACK_MS = 480;
|
| 479 |
let quackAt = -Infinity;
|
| 480 |
let padJaw = 0;
|
| 481 |
+
// Voice banks from the robot runtime: each printed duck ships a different
|
| 482 |
+
// voice, so each colourway gets its own bank and every quack draws a
|
| 483 |
+
// random chirp take from it - same as the real ducks all sounding
|
| 484 |
+
// slightly different. Audio elements are created lazily and cached.
|
| 485 |
// Browsers block audio until the first user gesture; the rejected play()
|
| 486 |
// is swallowed and sound simply starts working after the first click/key.
|
| 487 |
+
const CHIRP_TAKES = "abcdefghijkl";
|
| 488 |
+
const VOICE_BANK = { classic: "duck1", charcoal: "duck2", purple: "duck3", blue: "duck4" };
|
| 489 |
+
const chirpCache = new Map();
|
| 490 |
+
function playChirp() {
|
| 491 |
+
const bank = VOICE_BANK[currentVariant] ?? "duck1";
|
| 492 |
+
const take = CHIRP_TAKES[(Math.random() * CHIRP_TAKES.length) | 0];
|
| 493 |
+
const url = signed(`./assets/voices/${bank}/chirp_${take}.wav`);
|
| 494 |
+
let a = chirpCache.get(url);
|
| 495 |
+
if (!a) {
|
| 496 |
+
a = new Audio(url);
|
| 497 |
+
a.volume = 0.7;
|
| 498 |
+
chirpCache.set(url, a);
|
| 499 |
+
}
|
| 500 |
+
a.currentTime = 0;
|
| 501 |
+
a.play().catch(() => {});
|
| 502 |
+
}
|
| 503 |
// Silent jaw flap for mode/colour changes; the actual quack sound only
|
| 504 |
// plays on the gamepad right trigger (anything more gets noisy fast).
|
| 505 |
const quack = () => {
|
|
|
|
| 507 |
};
|
| 508 |
const quackLoud = () => {
|
| 509 |
quack();
|
| 510 |
+
playChirp();
|
|
|
|
| 511 |
};
|
| 512 |
function jawOpenNow() {
|
| 513 |
const t = (performance.now() - quackAt) / QUACK_MS;
|
|
|
|
| 672 |
|
| 673 |
// Triggers drive the mouth like the runtime (max of both), and the right
|
| 674 |
// one quacks on its rising edge — same as the robot's chirp.
|
| 675 |
+
// Schmitt trigger on the analog RT value: fire at 0.35, re-arm only once
|
| 676 |
+
// it drops below 0.2. A single threshold re-fires on every jitter around
|
| 677 |
+
// it, which is how one squeeze used to quack several times.
|
| 678 |
const rt = gp.buttons[7]?.value ?? 0;
|
| 679 |
const lt = gp.buttons[6]?.value ?? 0;
|
| 680 |
padJaw = Math.max(rt, lt);
|
| 681 |
+
if (padPrev.rtArmed && rt >= 0.35) { quackLoud(); padPrev.rtArmed = false; }
|
| 682 |
+
else if (!padPrev.rtArmed && rt < 0.2) padPrev.rtArmed = true;
|
| 683 |
};
|
| 684 |
|
| 685 |
// Read-only state label (bottom-left): reflects the active policy,
|
|
|
|
| 690 |
// No policy switching mid-roll: the roll ends on its own (upright again
|
| 691 |
// or expired) and returns to walk - switching now would floor the duck.
|
| 692 |
if (mode === "roll" && rollRun) return;
|
| 693 |
+
clearModeTimers();
|
| 694 |
quack();
|
| 695 |
rollRun = null;
|
| 696 |
if (next !== "sit") {
|
| 697 |
// Leaving a sit: let the sitstand policy stand the duck back up first.
|
| 698 |
if (mode === "sitstand" && sitFlag === 1) {
|
| 699 |
sitFlag = 0;
|
| 700 |
+
standTimer = setTimeout(() => {
|
| 701 |
+
standTimer = null;
|
| 702 |
+
mode = next;
|
| 703 |
+
lastAction.fill(0);
|
| 704 |
+
syncButtons();
|
| 705 |
+
}, 2000);
|
| 706 |
syncButtons();
|
| 707 |
return;
|
| 708 |
}
|
|
|
|
| 715 |
mode = "sitstand";
|
| 716 |
sitFlag = 0;
|
| 717 |
lastAction.fill(0);
|
| 718 |
+
sitTimer = setTimeout(() => {
|
| 719 |
+
sitTimer = null;
|
| 720 |
if (mode === "sitstand") { sitFlag = 1; syncButtons(); }
|
| 721 |
}, 800);
|
| 722 |
}
|
|
|
|
| 728 |
// continuous action history across policy switches, and the roll initiates
|
| 729 |
// more reliably mid-gait with the true last actions in the obs.
|
| 730 |
function triggerRoll(source = "kb") {
|
| 731 |
+
// Rolls only launch from a standing walk: from a sit (or mid sit/stand
|
| 732 |
+
// hand-over) the roll policy just faceplants the duck.
|
| 733 |
+
if (mode !== "walk" || standTimer) return;
|
| 734 |
+
clearModeTimers();
|
| 735 |
rollSource = source;
|
| 736 |
quack();
|
| 737 |
mode = "roll";
|