- ?move=<ref> boot parameter (?policy= alias, ?session=&round= sugar)
- session:<id>[:<round>] refs fetch same-origin /api/play/* endpoints
- episodic manifests mount in the roll slot as a one-shot on R
- perpetual slot sitstand swaps the sit policy; walk unchanged
- script manifests load move_script.json and drive the 13-D command
vector on the walker (linear keyframes, loop), toggled by R
- HUD chip: PLAYING <prompt or name> by <author>
Co-authored-by: Cursor <cursoragent@cursor.com>
fix(build): configurable base prefix and relative mesh_dir for /play/ hosting
Resolve an org/repo Hub id (or a direct .onnx URL) into a validated
onnxruntime session and swap it into the walk slot only, keeping
recovery/sitstand/kicks/roulade on the official set:
- custom-policy.js: manifest schema-2 checks (reject only
present-and-wrong claims, walk-slot compatible kinds only), publish
smoke run mirror (1 input / 1 output, zeros + 2 noisy inferences,
[1,14] finite non-constant output), honors manifest action_scale
- game.js: name-agnostic inference (session inputNames/outputNames
instead of hardcoded obs/actions), per-session action scale,
Number.isFinite guard before writing ctrl, watchdog that auto-reverts
to the official walker on 3 abnormal endings (recovery entries +
solver explosions) in a rolling 20 s window or any non-finite action
- ?policy=<ref> URL param (parsed in App.jsx, loaded after boot, never
blocking it; failures surface in the HUD on the official walker)
- HUD: active-policy chip with unload X, LOADING POLICY OSD line,
toas…
feat: restyle the click-to-walk marker as a tron reticle
Replace the solid pulsing ring with hairline additive lines in the
wireframe-scan art direction: thin ring + rotating radial ticks + center
cross, a one-shot expanding ping on acquire, deterministic hologram
flicker while idle, and a collapse/fade-out on arrival or cancel.
Lives in fx/waypoint-marker.js, driven per-frame from frame(dt).
Co-authored-by: Cursor <cursoragent@cursor.com>
docs: document git-lfs requirement for local clones
A plain clone without LFS serves text pointer files instead of the GLB /
ONNX binaries and boots into a SyntaxError (hub discussion #6). Also
update the ball paragraph: the ball now syncs over the ghost channel.
Co-authored-by: Cursor <cursoragent@cursor.com>
Peers broadcast their ball's free-joint pose in the state payload (new
optional b field, sent only while a ball is in play). Each ghost renders
it as the same translucent shell as its duck - geometry shared with the
local ball, material cloned - interpolated like the trunk and hidden the
moment the peer's ball despawns. debug() now reports peerId and ball.
Co-authored-by: Cursor <cursoragent@cursor.com>
docs: align README keyboard and gamepad mappings with current bindings
Partial pick of hub PR #7 (the landing-overlay part was superseded by
the layout-aware keycaps from PR #2).
Co-authored-by: Cursor <cursoragent@cursor.com>
fix: rate-limit ghost loco rebuilds against flag-flapping peers
Display keyboard tutorial labels for the active layout while keeping the existing physical bindings. Fall back to QWERTY/AZERTY labels when layout data is unavailable.
Tested with `npm test` and `npm run build`.
- Title menu: split layout with live 3D duck (vitrine-style materialize
shader), variant-tinted accents, unified boot spinner (assets + font +
game core), duck hidden on narrow touch screens
- Audio: new WebAudio sound system (sweeps synced to the tron shader,
per-prop pitch scaling) - shipped disabled via SOUND_DISABLED until the
sound design is finished; HUD mute toggle commented out accordingly
- Supply chain: MuJoCo, onnxruntime-web and trystero vendored from npm
instead of jsDelivr/esm.run CDNs (wasm assets bundled locally)
- Ghosts: explicit healthy Nostr relay list; the appId-seeded default
subset had rotted (dead/blocking/rate-limiting relays), silently
breaking peer discovery in prod
Co-authored-by: Cursor <cursoragent@cursor.com>
Receiver: per-ghost adaptive playback delay tracking the measured
snapshot arrival cadence (fast attack / slow release), floored at the
current 150 ms for healthy 15 Hz peers and slew-limited so rate changes
glide without popping; arrival stamps are de-jittered so bursty channel
delivery replays as motion. Sender: broadcast + stale sweep moved to an
inline Web Worker clock, exempt from the >=1 s hidden-tab timer clamp, so
backgrounded tabs keep sending at the nominal rate. Wire format unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
Pointer-down on the duck (or the live ball) grabs it: a three.js raycast
picks the body and a spring-damper force toward the cursor - projected on
a camera-facing plane, so lifting works - is written to xfrc_applied
every physics substep. Formula and gains mirror the native viewer's
mjv_applyPerturbForce (stiffness 100, damping sqrt(stiffness), scaled by
subtree mass). Orbit is disabled for the drag and restored on release;
grabbing detaches the chase cam; touch overlay untouched.
Co-authored-by: Cursor <cursoragent@cursor.com>
LT pressure now picks the wheee note on a major pentatonic octave anchored
below the sample's natural pitch (playbackRate glide, constant gain); the
ride plays the loop segment directly so pitch responds from the first
frame. Release cuts the ride like the robot runtime (no end-segment
retrigger). Both triggers open the beak analogically (visual only). Also
fixes LT/RT trigger cross-talk on pads that mirror a combined trigger
channel, and prefers standard-mapping gamepads.
Co-authored-by: Cursor <cursoragent@cursor.com>
fix: bust stale edge-cached bundle URL with new content hash
- HUD chrome reworked as comic panels: cream keyline frames with 1px ink
inset, caption cartouches (NAV / SHOP / COLOR / MODE), inner glass
padding and square gapped colour swatches
- drop the T relief toggle and B ball-respawn keyboard bindings; gamepad
Y becomes HEAD mode, A ground pick, LT rides the looping wheee
- decor props row + CRT distortion pass, per-duck wheee voice sets,
ground-pick and stand policies, updated robot meshes, title menu
refresh; README controls updated
Co-authored-by: Cursor <cursoragent@cursor.com>
Cabinets at their real-world 1.73 m height tower over the 0.25 m duck.
The materialization fx now scales with its target: band widths and
stripe wavelength grow linearly with the object's height, the timeline
by sqrt of it (capped), so big props scan as the same effect instead of
a fast flash with hairline bands. Duck and ball sizes are unaffected.
Co-authored-by: Cursor <cursoragent@cursor.com>
feat: relief as gentle slopes baked into the level itself
Replace the plateau boxes with a MuJoCo heightfield over the arena and
vertex displacement of the grid floor shader, both driven by the same
analytic cosine-bump function so physics and visuals are the same
surface. Raising/sinking ramps one scalar (hfield z-size + uTopoScale
uniform). Trigger still uncommitted: T key or rl.setRelief().
Co-authored-by: Cursor <cursoragent@cursor.com>
feat: arcade row moved to the front wall + toggleable relief terraces (prototype)
The three cabinets now face the player from the +X wall. New prototype:
four grid cells (two mirrored terraces) rise into plateaus on demand -
kinematically driven slide joints, contacts with the world excluded so
the buried boxes don't fight the floor, animated rise/sink at 0.25 m/s.
Trigger is deliberately uncommitted for now: T key or rl.setRelief().
Co-authored-by: Cursor <cursoragent@cursor.com>
feat: replace jukebox with a row of 3 arcade cabinets
New arcade.glb (from the user's FBX, texture downscaled to 1024, plus a
500-tri wire stand-in for the hologram pass). Three clones line up
against the back wall facing the arena, staggered in the ceremony, with
one static collision box covering the row.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move assets/, policies/ and robot/ into app/public/ (replacing the dev
symlinks), drop the legacy root sources (rl.js, index.html, modules) now
fully ported to app/src/, and switch the Space to the docker SDK serving
the built bundle on port 8080.
Co-authored-by: Cursor <cursoragent@cursor.com>
(pointer: coarse) misses DevTools device modes and hybrid laptops, so any
real touchstart now arms the thumbs permanently (title page hands the
proof to controls/touch.js via __microduckTouched). ?touch=1 forces the
overlay on any device for desktop testing.
Co-authored-by: Cursor <cursoragent@cursor.com>
feat: floating-stick mobile controls, always-on ball, lighter ball scan
- controls/touch.js: touch input source - floating analog stick (lower-left
zone, base anchors under the finger), A = alternate kick, B = quack with
held-beak jaw; shown on coarse-pointer devices only
- touch mode strips the HUD to Back + thumbs (no quickbar/mode/telemetry)
and the title menu shows a stick/A/B tutorial variant (gamepad still wins)
- a ball is always in play: auto-spawned when the entrance or any respawn
ceremony unlocks input; B now re-pops a fresh one
- ball scan FX reads a coarse geodesic stand-in (fxWireGeometry override)
instead of the dense render sphere, and plays a touch slower
Co-authored-by: Cursor <cursoragent@cursor.com>
feat: staggered reading-order entrance for the title page
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 i…
feat: sci-fi entrance - Tron grid draw-in and duck dissolve on entry
- The jaw no longer flaps on mode changes, rolls, kicks or colour
swaps: it is driven only by the analog gamepad triggers and the
RT-edge quack (which keeps its flap). Ghost jaw sync is untouched
since it broadcasts the same jawOpenNow value.
- The soccer ball gains a bump map derived from the same Voronoi panel
construction (seams as soft recessed grooves plus a fine leather
grain) and moves to MeshPhysicalMaterial with a touch of clearcoat
for a waxed vintage look.
Co-authored-by: Cursor <cursoragent@cursor.com>
feat: right-stick camera orbit with inertia, subtle Tron glow on grid
- Gamepad right stick orbits the camera around the duck through a
smoothed angular velocity (exponential ramp in, ~0.3 s coast out), so
motion eases instead of stopping dead. Vertical is flight-style
inverted. Any deflection detaches the chase cam; R3 toggles it back,
mirroring the C key. New RS/R3 HUD hints, README gamepad section
updated.
- Grid lines (floor and arena walls) get a thicker antialiased core and
a faint wide halo for a restrained Tron look, plus slightly brighter
line color and opacity.
Co-authored-by: Cursor <cursoragent@cursor.com>
feat: redesign welcome modal with keycap control grid
Rebuild the welcome card for at-a-glance clarity: typographic identity
under the duck-head mark, a one-line value proposition, six keycap
tiles on an aligned grid (move cluster, kick A/E, roll, ball, camera,
reset), and a compact gamepad/ghosts row. Refined entry animation with
a gentle row stagger (reduced-motion aware), layered depth, and a
compact two-column layout on narrow screens; the card scrolls only as
a last resort on very short viewports.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Remap controls: left stick / A-D turn (strafe removed), Q/E kick
left/right, R rolls, Space resets; HUD, welcome modal and README
updated to match.
- Hold commands at zero for 20 control steps after a kick ends so the
duck finishes the strike cleanly instead of instantly sprinting.
- Rebuild the ball texture per pixel as a spherical Voronoi over the 32
truncated-icosahedron panel centers: large flat-edged black pentagons
with thin seams and no pole special case.
- Enclose the play area in a 3x3 m arena of static box walls rendered
with the floor's grid shader; the respawn watchdog now only fires if
the ball somehow escapes the arena.
Co-authored-by: Cursor <cursoragent@cursor.com>
fix: freeze chase-cam heading during rolls and kicks
The trunk tumbles through a roll (and shakes during kicks), so the yaw
extracted from its quaternion spins wildly and whipped the chase camera
around. The chase now lazily latches the last healthy yaw and holds it
for the whole one-shot; position/target easing still follows the trunk.
No timers: both one-shots deterministically hand back to walk, where
live yaw tracking resumes and the position lerp absorbs the small
heading correction. Measured over a full roll: 180 deg trunk-yaw
excursion, 0.2 deg camera azimuth swing.
Cache-buster bumped to 28.
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the beach-ball CanvasTexture with an equirectangular soccer
pattern: off-white base, a black cap at each texture pole (full-width
strips, dodging the equirect pinch) and two staggered rings of five
pentagons with 1/cos(lat) horizontal stretch compensation. Sphere
segments bumped to 48x32 and max anisotropy enabled so the pole caps
stay round up close.
Also set condim=6 on the ball geom: with the default condim 3 the
rolling/torsional friction coefficients were silently ignored and a
rolling ball never decelerated (verified: now 0.27 -> 0.001 m/s in 6 s).
Cache-buster bumped to 27.
Co-authored-by: Cursor <cursoragent@cursor.com>