Instructions to use langli11/microduck-tricks with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Microduck
How to use langli11/microduck-tricks with Microduck:
# Replace SLOT with the slot specified in the model card (walk, stand, sitstand, ground_pick, kick_left, kick_right, roulade). sudo robotctl policy load SLOT langli11/microduck-tricks
- Notebooks
- Google Colab
- Kaggle
Download docs/roller_glide.md from langli11/microduck-tricks: direct link, hf CLI and curl.
- Browser
- Download file 25.5 kB
-
https://huggingface.co/langli11/microduck-tricks/resolve/main/docs/roller_glide.md
- Command line
-
hf download hf://langli11/microduck-tricks/docs/roller_glide.md
-
curl -L -o roller_glide.md https://huggingface.co/langli11/microduck-tricks/resolve/main/docs/roller_glide.md
Roller glide β riding one blade
Task: Mjlab-RollerGlide-Flat-MicroDuck (+ V2, V3), built on the official roller-skate velocity env.
Goal: a sustained single-blade glide while skating forward (figure-skating style), on a
roller module that has two wheels per blade.
Baseline: nobody glides yet
scripts/eval_single_blade.py classifies support every control step from tireβfloor contacts
(a bout = consecutive single-support steps on the same blade). 10 s straight skating, 5 trials:
| policy | push | single-support time | bouts / 10 s | mean bout | max bout | speed |
|---|---|---|---|---|---|---|
official roller.onnx |
0.3 | 11 % | 52 | 0.022 s | 0.06 s | 0.61 m/s |
official roller.onnx |
0.5 | 17 % | 75 | 0.022 s | 0.08 s | β |
| slalom v2 (ckpt 2000) | 0.3 | 77 % | 107 | 0.072 s | 0.14 s | 0.28 m/s |
| slalom v2 (ckpt 2000) | 0.5 | 80 % | 105 | 0.076 s | 0.16 s | 0.43 m/s |
The official policy never leaves double support for more than 80 ms. This is despite its own
glide reward (weight 4.0: single support + moving + quiet legs) and single_support (3.0).
Two other official terms fight a sustained glide:
gait_symmetry_penalty(β1.0) accumulates each foot's swing time over the episode and penalises |L β R| / (L + R) β a long glide on one blade is maximally lopsided;skating_air_timeonly pays swings between 0.15 s and 0.45 s β past half a second the lifted foot earns nothing.
Method
- Drop
gait_symmetry; widen theskating_air_timewindow to 4 s. Skating stack otherwise unchanged (wheel_speed 10, glide 4, single_support 3, braking, forward_lean). - Streak objective (mdp.py, roller-glide section): a per-env accumulator counts consecutive
control steps of gated single support on the same blade. Gates: exactly one blade in
contact, swing ankle β₯ 2 cm above the stance ankle (a real lift, not a drag), forward speed
β₯ 0.2 m/s (a moving glide, never a one-leg stand), upright. Two payouts:
glide_holdβ per step, min(streak, 1 s)/1 s, weight 2 β 4 β 6 (iterations 0/500/1500), so commitment escalates the pay;glide_frontierβ Ξ(max streak this episode) Γ 20, pays each new record once and nothing for camping. - Reverse-curriculum spawns: 50 % β 35 % β 20 % of episodes are born already gliding β one leg folded (hip β1.0 / knee 1.1 / ankle 0.7 rad, mirrored), trunk leaned 4β10Β° toward the stance blade, 0.25β0.55 m/s forward. "Keep the glide" gets dense data from iteration 0; "enter the glide" is learned on top (the trick that fixed the roulade's last mile).
- Throttle command 0.1β0.6 (forward-biased), heading held straight.
- Training: 4096 envs, 4000 iterations, one A10G on Hugging Face Jobs (β 3 h, β $3). Checkpoints selected by the evaluator above, never by training reward.
Status (2026-09-02): unsolved β three runs, two diagnoses, credits ran out
| run | change | outcome |
|---|---|---|
| v1 (4000 it, cut at ~3500) | official stack + streak objective (hold 2β6, frontier 20), 50 % born-gliding spawns | training glide terms stayed β 0.04/step against a β 1.9/step skating stack; every checkpoint β₯ 500 falls 10/10 from a standing start. Born-gliding start: does not fall (0/6, vs 6/6 for the official policy) but puts the foot down within 0.1 s and resumes 10 Hz mini-stepping. |
| v2 (cancelled at ~1100) | glide pay Γ2.7, wheel_speed 10 β 4, single_support removed, stance-switch tax 0.5, frontier only beyond 0.3 s |
stands still: episode length ~900, wheel_speed β 0.3, glide terms β 0. Cutting the drive plus attempt taxes made "do nothing" the optimum. |
| v3 (cut at 500 by credits) | v2 pay-outs + wheel_speed back to 10 + switch tax 2.0 + moving-double-support tax 0.5 + spawns that roll (wheels spun at v/r) | moving again (wheel_speed rising, penalties correct); glide terms not yet nonzero at 500 iterations β the hold weight was still on its 4 β 10 ramp. Unjudged. |
Two things learned that any next attempt should keep:
- Born-gliding must mean born rolling. The first spawn gave the base 0.4 m/s with the
passive wheels at rest β a braking impulse that trips even the official policy 6/6.
Spinning the wheels at v/r (
wheel_radiusinreset_roller_glide_state,--wheel-radiusin the evaluator) removes the artefact; the official policy still falls 6/6 from a rolling single-blade start, so the balance itself is the hard part. - Reward mass ordering must be glide β« stepping > swizzle > stand, with the drive to move intact. v1 left stepping as good as gliding; v2 removed the drive and got a statue. v3 encodes the full ordering (each non-glide way of moving is taxed) β it is the run to repeat with budget (β $3 on an A10G).
Second round (v4βv6): a commanded sequence, and two more root causes
The requested behaviour is a sequence β skate on two feet, lift one foot and keep gliding,
put it back down β so from v4 on the glide is a command: body-command slot 0 carries a
flag; each episode samples a glide phase [t0, t0 + len) (born-gliding spawns start inside
it); glide rewards and taxes are multiplied by the flag, the official stride reward by
(1 β flag). Deployment writes the same slot (scripts/eval_single_blade.py --glide-cmd t0,t1,
scripts/render_showcase.py glide).
| run | change | outcome |
|---|---|---|
| v4 | commanded phases on v3 | smoke only (v3 was still running) |
| v5 | + spawn from a passively balanced pose (stance hip rolled, partial fold), creep-proof gates (0.05 m/s), forward-progress reward | standing start: no falls but 9 Hz stepping (0.09 s bouts); born-gliding: 6/6 falls; training glide_hold flat at 0.004 |
| v6 | + spawn at the true single-blade height, hold paid from the first counted step, frontier from 0.05 s, dense fold-one-leg shaping | running |
Two more findings, both measured on CPU before spending:
- The spawn height was wrong.
reset_baseplaces the trunk at 0.1335β0.1435 m, but with one leg folded the stance tire touches the floor at 0.127β0.130 m β every born-gliding spawn was dropped 0.5β1.5 cm onto one blade. Worse, the roller keyframe used by the CPU tests has the tires 9 mm inside the floor, so the "passively balanced pose" measured in v5 was not what the env got. At the corrected height a pose with the leg folded 80 % and the stance hip rolled 0.22β0.3 rad stays up 2β3 s with no control (the v1 spawn: 0.4 s). - No reward gradient at short streaks.
glide_holdpaid β streak and the frontier only beyond 0.3 s, so the 2β5-step single-blade fragments an exploring policy actually produces earned nothing β in every run the hold term sat at β 0.004. v6 pays from the first counted step and adds a contact-free "fold one leg" shaping term during the commanded phase.
The evaluator (scripts/eval_single_blade.py, --start glide --wheel-radius 0.015 --spawn-z 0.129 --lift-scale 0.8 --stance-roll 0.25) and the
Mjlab-RollerGlide{,V2,V3,V4,V5,V6}-Flat-MicroDuck envs and tests are in the repo.
Reproduce
uv run --with pytest pytest tests/test_roller_glide_cfg.py
uv run scripts/eval_single_blade.py --policy policies/roller.onnx --push 0.3,0.5 --seconds 10 --trials 5
uv run train Mjlab-RollerGlideV3-Flat-MicroDuck --env.scene.num-envs 4096 --agent.max_iterations 4000 --hf-jobs --flavor a10g-large
uv run scripts/sweep_hf_checkpoints.py --repo <ckpt repo> --task Mjlab-RollerGlideV3-Flat-MicroDuck --outdir out/glidev3_sweep \
--eval "uv run scripts/eval_single_blade.py --policy {onnx} --push 0.3,0.5 --seconds 10 --trials 5 --csv {csv}"
v6 interim (checkpoint 1000 of 4000, held-out protocols, push 0.3)
| protocol | single-support | bouts / run | bout mean / p90 / max | speed | fell |
|---|---|---|---|---|---|
| born gliding (v6 pose, command on 0β6 s) | 62 % | 2.3 | 0.16 / 0.42 / 0.74 s | 0.12β0.14 m/s | 1 / 6 |
| standing start, v6 alone (command 2β6 s) | β | β | β | β | 6 / 6 (lunges, down at 0.36 s) |
| hot-swap: official skater β v6 (glide 2β6 s) | 51 % | 39 | 0.10 / 0.23 / 0.28 s | β 0 | 7 / 10 |
First sub-second single-blade holds from the balanced spawn (v5 never exceeded a few
control steps); the two-feet phases are not learned yet at this checkpoint and the policy
cannot yet take a rolling hand-off from the official skater. Both are expected to move with
the spawn curriculum (born-glide share 0.7 β 0.5 β 0.3 at 1500 / 3000). Evaluator gained
--skate-policy (hot-swap by command phase) for the fallback demo.
v7 β spawn the two-feet phase inside the official skater's manifold
The checkpoint sweep of v6 (250β2000, three protocols, n = 6 each) settled the diagnosis:
| protocol | best checkpoint | falls | bout max |
|---|---|---|---|
| born gliding (v6 pose, command from t = 0) | 2000 | 3 / 6 | 0.80 s |
| hot-swap official β v6 β official (glide 2β6 s) | 750 / 1000 / 2000 | 2β3 / 6 | 0.26β0.30 s |
| standing start, v6 alone (command 2β6 s) | none | 6 / 6 at every checkpoint | 0.02 s |
The single-blade sub-skill emerged (born-gliding spawns), the two-feet phase never did:
standing spawns pitch over within 0.4 s, stride_two_feet stayed β 0.01, and every training
metric stepped down at the 1500 spawn-mix shift (more standing spawns = more immediate
falls β the AGENTS.md "metric drops at a stage boundary" signal). Two causes, both measured
rather than guessed: the non-glide spawns started from rest, so the policy never had
on-policy two-feet skating data to keep; and forward_progress (weight 6) paid a forward
lunge right up to the fall.
v7 (Mjlab-RollerGlideV7-Flat-MicroDuck) applies the Γ©lan-roulade recipe: the non-glide
slice is overlaid with a measured skating state of the released roller policy
(scripts/capture_skate_states.py β data/roller_skate_states.npz, 956 states at
0.52β0.79 m/s: joints, joint velocities, base twist, the four wheel spins, trunk roll/pitch
β 10Β° forward lean), blended U(0.7, 1.0) toward rest so partial hand-offs are covered; 15 % of
that slice still starts from rest. forward_progress is gated on uprightness. Born-glide
share 0.6 β 0.5 β 0.4. The runtime hand-off (official skater β glide policy) is therefore
in-distribution by construction, which is also the deployment story.
Spawn-height check before launch (the AGENTS.md "measure target heights off the actual
robot" rule, again): with the lowest tire on the floor the released skater's crouched pose puts
the trunk at 0.104 m (p5βp95 0.095β0.112) against 0.118 m for HOME, so overlaying its pose at
reset_base's HOME height would have dropped the duck 1.4 cm onto rolling wheels. The bank
records each pose's floor height (z_need) and the overlay keeps reset_base's drop margin
above it. Sanity of the spawn itself (evaluator --start bank): the official policy survives
10/12 bank spawns for 8 s; the two falls happen 0.5β1.3 s in, the actuator-target jolt every
reset has (seeding the last-action slot from the bank changed nothing).
v8 β price the pitch on one blade
How do v6's single-blade holds end? Evaluator fall_dir (body-frame projected gravity at the
fall; x > 0 is a forward lean), born-gliding protocol, n = 12 per checkpoint:
| checkpoint | falls | forward | sideways | fall time | bout max per trial |
|---|---|---|---|---|---|
| 1000 | 5 / 12 | 5 | 0 | 1.06β1.20 s | 0.52β0.74 s |
| 2000 | 5 / 12 | 3 | 2 | 0.74β1.30 s | 0.38β0.82 s |
The bouts cap at 0.6β0.8 s and the duck goes over the front wheel: wheel_speed (10) and
forward_progress (6) keep paying for pushing the mass forward, nothing prices the trunk pitch
on one blade, and the 32Β° upright gate only stops paying once the fall is committed. v8
(Mjlab-RollerGlideV8-Flat-MicroDuck) = v7 + glide_pitch (weight 3, flag-gated Gaussian on
the forward lean around the released skater's own +0.17 β 10Β°, std 0.2) + the progress gate
tightened to 18Β°. v7 and v8 train in parallel; the morning pick is by the hot-swap and
rolling-start protocols, never by training reward.
v6 final sweep (job completed 23:22 PT, 4000 iterations)
| checkpoint | standing start (glide 2β6 s) | born gliding: falls | single support | bouts / 6 s | bout mean | bout max |
|---|---|---|---|---|---|---|
| 1000 | 6 / 6 fell | 1 / 6 | 80 % | 31 | 0.14 s | 0.74 s |
| 2000 | 6 / 6 fell | 3 / 6 | 88 % | 20 | 0.17 s | 0.80 s |
| 3250 | 6 / 6 fell | 0 / 6 | 89 % | 41 | 0.13 s | 0.94 s |
| 3500 | 6 / 6 fell | 0 / 6 | 87 % | 42 | 0.12 s | 0.54 s |
| 3999 | 6 / 6 fell | 1 / 6 | 81 % | 54 | 0.12 s | 0.66 s |
Every checkpoint falls from a standing start. From the born-gliding spawn the late checkpoints stop falling β but by stepping: 40β54 support switches per 6 s with 0.12 s bouts, i.e. the mini-stepping the stance-switch tax was meant to remove, now cheaper than a hold that ends in a pitch-over. v6 is therefore neither the two-feet phase nor a sustained single-blade glide; its role was diagnostic (spawn height, the missing two-feet data, the forward pitch-over), which v7 and v8 act on.
Training-side check of the same spawn (scripts/validate_skate_spawns.py, the v7 env on CPU with
its full DR/noise/delay stack, 24 envs): skate spawns sit 1.4β2.5 cm above the floor (the same
drop margin reset_base gives HOME), lean +0.02β0.18, wheels 27β67 rad/s forward; the released
roller policy survives 15 / 15 skate-or-rest spawns for 2 s and 0 / 9 born-gliding ones (it
never folds a leg). The v7/v8 premise β the two-feet phase starts inside the skater's manifold β
holds in the environment that trains it, not just in the evaluator.
v7 / v8: the two-feet phase is learned in 300 iterations β and lost at the next reward step
Both runs (L4, 4096 envs) on wandb, per-window means:
| run | window | episode length | falls / window | stride_two_feet |
glide_hold (weighted) |
entropy |
|---|---|---|---|---|---|---|
| v7 | 750β1000 | 758 | 2.1 | 0.55 | 0.67 (weight 10) | 7.2 |
| v7 | 1000β1100 | 605 | 5.1 | 0.42 | 0.88 (weight 16) | 8.6 |
| v7 | 1100β1500 | 140β180 | 25β29 | 0.05 | 0.68 | 9.7 |
| v8 | 300β400 | 658 | 3.5 | 0.43 | 0.21 (weight 4) | 8.3 |
| v8 | 400β500 | 613 | 4.5 | 0.42 | 0.48 (weight 10) | 9.6 |
| v8 | 500β800 | 160β210 | 23β26 | 0.06β0.10 | 0.43 | 9.4 |
With the measured skate-state spawns the two-feet phase appears within 300 iterations (episode
length 700+ of a 1000-step episode, 2β4 falls per window against v6's 39) β and both runs collapse
on the same trigger, the glide_hold weight step (v8 at 400: 4 β 10; v7 at 1000: 10 β 16). The
value loss doubles at the step, entropy climbs, and within 100 iterations the stride term dies. The
hold pays from the first counted step (floor 0.5) at 10β16Γ while nothing prices the fall, so
"lift a foot, bank the hold, fall" beats skating on. Pre-collapse v7 checkpoints (500β1000)
survive both hand-off protocols β hot-swap 0β1 / 6 falls, rolling start 0 / 6 β but mini-step
instead of holding a blade (65β108 support switches per 10 s, 0.06 s bouts). Both runs were
cancelled at ~1400 / ~800 iterations; every checkpoint stays in its Hub repo.
v9 (Mjlab-RollerGlideV9-Flat-MicroDuck) = v8 + hold weight 4 β 6 β 8 β 10 (iterations
400 / 1200 / 2400, no step over Γ1.5, capped at v7's best regime) + a fall penalty (mjlab
is_terminated, β100: a doomed 0.5 s attempt banks β 125β250 of hold at weight 10, the fall
must cost more). v9b = v9 with the born-gliding share held at 0.7 for the whole run (v9:
0.6 β 0.5 β 0.4) β the sustained hold has only ever appeared from that spawn. Both launched at
23:37 PT on L4, 4000 iterations each.
Pre-collapse checkpoints on the held-out protocols (push 0.3, glide commanded 2β6 s, n = 6):
| checkpoint | standing start | rolling (bank) start | hot-swap official β policy | born gliding | bouts / 10 s | bout max |
|---|---|---|---|---|---|---|
| v7 750 | 0 / 6 fell | 0 / 6 | 1 / 6 | 0 / 6 | 58β105 | 0.16β0.30 s |
| v7 1000 | 0 / 6 fell | 0 / 6 | 0 / 6 | 0 / 6 | 57β105 | 0.16β0.32 s |
| v8 500 | 0 / 6 fell | 1 / 6 | 0 / 6 | 0 / 6 | 64β114 | 0.14β0.30 s |
These are the first policies in the family that survive every start, including a standing one (v6: 6 / 6 falls at all 16 checkpoints) β they answer a glide command by pumping at ~10 Hz rather than holding a blade. They are the fallback for a two-feet β command β two-feet demo if v9 / v9b do not produce a sustained hold.
v9 at 1500: the collapse is gone, the hold is still missing
v9 checkpoints 250β1500, held-out protocols (push 0.3, glide commanded 2β6 s, n = 6):
| protocol | falls (all six checkpoints) | support switches / run | bout max |
|---|---|---|---|
| standing start | 0 / 6 at every checkpoint | 102β116 | 0.16β0.38 s |
| rolling (bank) start | 0β1 / 6 | 92β113 | 0.16β0.40 s |
| hot-swap official β v9 | 0β3 / 6, all after the switch back to the official at 6 s | 58β82 | 0.24β0.44 s |
| born gliding | 0 / 6 at every checkpoint | 60β63 | 0.20β0.34 s |
The gentle hold ramp and the priced fall removed the collapse and produced the family's first policy that never falls from a standing start β and it answers the glide command by pumping at ~10 Hz, even from the born-gliding spawn where v6 held 0.8 s. Per-step accounting during a commanded glide: a pump earns the hold floor (0.5 Γ 6 on about half its steps β 1.5) at almost no risk; a real hold earns 3β6 but risks β100 plus the lost future; the switch tax (2 per switch β 0.4 per step) barely dents the pump. Risk-averse pumping is the argmax.
v10 (Mjlab-RollerGlideV10-Flat-MicroDuck, on v9b) fixes the order rather than the
magnitudes: switch tax 2 β 6 (a 10 Hz pump now costs 1.2 per step, more than it earns), hold
floor 0.5 β 0.25 (a flicker pays a quarter of a 1 s streak per step), fall β100 β β30 (priced,
not prohibitive). Launched 01:05 PT on L4 as the third concurrent run.
v9b at 1500 (born-gliding share held at 0.7) is indistinguishable from v9: 0 / 6 falls from a standing start at every checkpoint, 57β116 support switches per run, bout max β€ 0.32 s. More practice from the gliding spawn does not by itself produce a hold β the policy puts the foot down immediately and pumps, because that is the safe option under the current prices. The two-feet phase, meanwhile, is solved by every v9 checkpoint from 250 on.
v11 β training wheels for the hold
Every glide run (v6βv10) exits the born-gliding spawn the same way: the lifted foot goes down
within a few control steps and the policy pumps, because a put-down is always available and
always safe, while the hold is a balance skill it has never practised. v11 removes the exit:
a swing-leg lock action term (glide_actions.LockedSwingJointPositionAction) forces the lifted
leg's position targets to the folded pose for the first second of every born-gliding episode
(window 1.0 β 0.5 β 0.25 β 0 s at iterations 1000 / 2000 / 3000), so "balance on one rolling
blade" is on-policy from iteration 0 and the swing_leg_pose reward keeps the policy's own
actions aligned with the lock. Non-born episodes, the head and the play/export config are
untouched β the deployed policy is a plain joint-position policy. Validated in the environment
on CPU (locked envs' swing targets equal the fold, non-born envs untouched, release at the
window end). Everything else is v10. v9b (v9's twin) was stopped at 2500 iterations to free the
job slot.
v10 at 1500 (switch tax 6, hold floor 0.25, fall β30): no change in kind β 0 / 6 falls from a standing start at every checkpoint, 58β115 support switches per run, bout max β€ 0.44 s, born gliding exits into pumping at once. The pump is not a marginal-price decision; the hold is simply not in the policy's repertoire and gradient exploration never finds it. That is the case for v11's lock.
v9 final (4000 iterations, completed 02:54 PT)
| checkpoint | standing start | rolling (bank) start | hot-swap official β v9 | born gliding | bouts / run | bout max |
|---|---|---|---|---|---|---|
| 2000 | 0 / 6 | 1 / 6 | 3 / 6 (after the switch back) | 0 / 6 | 63β114 | 0.16β0.72 s |
| 2750 | 0 / 6 | 0 / 6 | 0 / 6 | 0 / 6 | 71β117 | 0.16β0.26 s |
| 3000β3750 | 0 / 6 | 0 / 6 | 1β5 / 6 (after the switch back) | 0 / 6 | 54β124 | 0.16β0.46 s |
| 3999 | 6 / 6 | 3 / 6 | 3 / 6 | 0 / 6 | 38β79 | 0.16β0.36 s |
v9 is a finished two-feet skater for every start (checkpoint 2750 survives all four protocols),
and never holds a blade. Late checkpoints get flaky (2250 and 3999 fall from a standing start)
and the official policy increasingly fails to take the state back at the 6 s switch β the
hand-off to the official is not something these policies were trained for. Fallback for a
two-feet β command β two-feet demo without the hold: out/glidev9_sweep/onnx/ckpt_2750.onnx.
v11 measured (evaluator --lock emulation, born gliding, n = 8): with the swing leg locked
for 1 s, v11 checkpoints 1000 and 1250 fall sideways toward the lifted leg at 0.40 s in 8 / 8
trials β deterministically. The v6 spawn pose is balanced only for a policy that can move the
folded leg; frozen, it is a falling pendulum, and a v10 checkpoint under the same lock survives
the locked second (falls after release, 5 / 8). The lock manufactures falls, not holds; v11 was
stopped at ~1600 iterations. Hot-swapping the finished two-feet skater (v9 2750) with v6's best
hold checkpoints for the glide phase does not work either: v6 1000 / 2000 / 2250 pitch forward
0.2β0.5 s after the hand-off in 7 / 4 / 3 of 8 trials and hold β€ 0.36 s when they survive.
v12 / v12b β v6's hold magnitude, paced, on the v10 base
The only sustained holds in the family (0.8 s, v6 1000β2000) came from a 16-weight hold with no
fall price; every capped-hold run with a fall price pumps. v12 ramps the hold 4 β 8 β 12 β 16
(iterations 400 / 1200 / 2400, v9's pacing) on the v10 prices (switch tax 6, floor 0.25, fall
β30, born share 0.7). v12b is the same with the fall priced only outside the commanded glide
phase (is_terminated_when_skating), so a failed hold attempt costs only its lost future while
the two-feet phase keeps its price. Both launched at 03:05 PT on L4 next to the still-running v10.
v10 / v12 / v12b outcome (wandb; checkpoint sweeps in out/glide{v10,v12,v12b}_sweep)
| run | hold weight at the end | fall price | episode length (last window) | falls / window | glide_frontier (any window) |
|---|---|---|---|---|---|
| v10 (4000, completed) | 10 | β30 | 859 | 1.1 | 0.008 |
| v12 (3500, 5 h timeout) | 16 | β30 | 781 | 1.9 | 0.009 |
| v12b (3500, 5 h timeout) | 16 | β30 outside the glide only | 168 (collapsed at 2000β2400) | 24 | 0.009 |
Pricing the fall keeps every run stable up to a 16-weight hold (v12 never collapsed), and removing the price inside the glide phase brings the v7 collapse back (v12b, at hold weight 12) β but the frontier term never moves in any of them: with a fall price the policy does not attempt holds, and without one it attempts and dies. The overnight machine outage (DNS / proxy failures on the Mac) crashed every waiter; the runs themselves completed or hit the 5 h timeout with their checkpoints uploaded, and were re-swept the next evening.
Where this leaves the single-blade glide. The family now contains a finished two-feet skater with a working glide command channel (v9 2750, v10 late checkpoints: never falls from a standing start, follows the flag) and one regime that produced sub-second single-blade holds (v6, 0.8 s, 50 % falls, from a balanced spawn only). A sustained hold has not been reached. The physics is unforgiving: no ankle-roll joint and a 5 cm wheelbase mean a one-blade glide is a bicycle-like balance (steer the blade into the lean) with a 5 cm foreβaft margin, and nothing tried here β spawn manifolds, streak rewards, price orders, a locked leg β created the on-policy successes RL needs to learn it. The next serious attempt should change the problem, not the prices: e.g. a slower target speed with a wider foreβaft margin, a swing foot allowed to skim (a "scooter" glide), or a hardware change (ankle roll / longer blade).
v10 full sweep (250β3999, four protocols, n = 6): no falls from a standing or born-gliding start
at any checkpoint but one (3000, 1 / 6), bout max β€ 0.6 s everywhere, 55β117 support switches per
run. Checkpoint 3750 survives all four protocols (0 / 6 each) β a second clean two-feet
fallback next to v9 2750 (out/glidev10_sweep/onnx/ckpt_3750.onnx).
v12 / v12b full sweeps (250β3500, four protocols, n = 6): v12 never falls from a standing or born-gliding start at any checkpoint and never holds (bout max β€ 0.54 s, 58β119 switches per run). v12b matches it up to 2000, then the collapse shows on the held-out protocols too: from 2500 on it falls 6 / 6 from a standing start and 6 / 6 from a rolling start (backward, at 0.6β0.8 s), while still surviving the born-gliding spawn. The held-out numbers agree with the training curves in every version: pricing the fall buys stability and no holds; not pricing it buys attempts and a collapse.