Upstream: https://github.com/pollen-robotics/microduck_rl Upstream-Commit: d424a0c899f6b33cbd3daeb279913134349c0b63 Upstream-Branch: develop
159 lines
8.0 KiB
Markdown
159 lines
8.0 KiB
Markdown
# Design — Roller Crouch-Glide (« s'accroupir en glissant » au bouton)
|
||
|
||
**Date :** 2026-07-17
|
||
**Statut :** conception validée, prêt pour le plan d'implémentation
|
||
|
||
## Contexte
|
||
|
||
Le robot microduck sait patiner (policy roller, tâche `Mjlab-Velocity-Flat-MicroDuck-Rollers`).
|
||
On veut un nouveau geste : sur un appui bouton, il **s'accroupit et continue de glisser
|
||
sur son élan** (comme un patineur en position basse), maintient ~1 s, puis **se relève**
|
||
tout seul et reprend le patinage.
|
||
|
||
Contrainte forte de l'utilisatrice : **ne pas modifier le runtime Rust**
|
||
(`apirrone/microduck_runtime`, installé en binaire). Le geste doit donc réutiliser un
|
||
mécanisme déjà présent dans le runtime.
|
||
|
||
**Découverte clé :** le runtime a déjà un slot « comportement one-shot déclenché au
|
||
bouton » : `--ground-pick`. Il est déclenché par le **bouton A** (front montant),
|
||
joue une policy ONNX pilotée par une **phase** pendant une durée fixe, puis revient
|
||
automatiquement à la policy principale. Surtout, il utilise **exactement le même
|
||
layout d'observation 61D** que la policy roller — les deux sont interchangeables au
|
||
runtime. C'est le véhicule idéal, sans une ligne de Rust.
|
||
|
||
Compromis accepté : le geste est **one-shot** (durée fixe, pas de « bascule maintenue »).
|
||
La durée de l'accroupi est fixée par la période du slot.
|
||
|
||
## Approche retenue (approche B)
|
||
|
||
Créer une **nouvelle tâche mjlab** entraînée sur le robot rollers, qui joue
|
||
descente → glisse accroupi → remontée, piloté par la phase du slot ground-pick.
|
||
L'exporter en ONNX et la charger via `--ground-pick`. Aucune modif Rust.
|
||
|
||
### Fichiers concernés
|
||
|
||
| Fichier | Action |
|
||
|---|---|
|
||
| `src/mjlab_microduck/tasks/microduck_roller_crouch_env_cfg.py` | **Nouveau.** L'env, hybride roller + ground-pick. |
|
||
| `src/mjlab_microduck/tasks/mdp.py` | **Ajout** de la reward `crouch_glide_height_by_phase`. |
|
||
| `src/mjlab_microduck/tasks/__init__.py` | **Ajout** : enregistrer `Mjlab-RollerCrouch-Flat-MicroDuck`. |
|
||
|
||
### Réutilisation (ne rien réinventer)
|
||
|
||
- **Physique / robot roller** ← `microduck_velocity_rollers_env_cfg.py` :
|
||
`MICRODUCK_WALK_ROLLERS_ROBOT_CFG` (14 joints actifs + 4 roues passives),
|
||
capteur de contact sur les `roller_blade`, DR friction des roulements
|
||
(`randomize_wheel_friction` + curriculum), obs 14-dim (roues exclues via
|
||
`SceneEntityCfg("robot", joint_names=(r"^(?!passive_).*",))`), `action.scale=1.0`,
|
||
`kp_fw=200`.
|
||
- **Machinerie phase / one-shot** ← `microduck_ground_pick_env_cfg.py` :
|
||
commande `microduck_mdp.GroundPickPhaseCommand` **réutilisée telle quelle**
|
||
(produit le `[cos(2πφ), sin(2πφ), 0]` que le runtime enverra dans le slot twist),
|
||
padding head/body à zéro (`zero_command_padding`), terminaison `robot_state_is_nan`,
|
||
`reset_action_history`.
|
||
- **DR sim2real** ← repris du roller env sans changement (IMU misalignment obs-level,
|
||
encoder bias, masse/inertie, friction BAM, armature, pushes doux ±0.2).
|
||
|
||
## Le cœur : cible de hauteur « en trapèze » pilotée par la phase
|
||
|
||
Seule vraie nouveauté. Au lieu de descendre la bouche (ground-pick), on pilote la
|
||
**hauteur du tronc** (`com_height` du `trunk_base`) selon la phase, avec un palier bas :
|
||
|
||
```
|
||
hauteur
|
||
haute ┐ ┌── debout (rend la main à la policy roller)
|
||
│ \ /
|
||
basse│ \_______________/ accroupi + glisse (palier 1 s)
|
||
└───────────────────────► phase
|
||
0 0.375 0.625 1
|
||
```
|
||
|
||
- φ ∈ [0, 0.375] : descente vers la hauteur accroupie
|
||
- φ ∈ [0.375, 0.625] : **maintien accroupi** (= 1 s sur une période de 4 s) → glisse
|
||
- φ ∈ [0.625, 1.0] : remontée vers la pose roller debout
|
||
|
||
**Nouvelle reward `crouch_glide_height_by_phase(env, command_name, height_low,
|
||
height_high, hold_lo=0.375, hold_hi=0.625, std=...)`** dans `mdp.py` :
|
||
lit la phase depuis la commande, calcule la hauteur-cible (interpolée haut→bas→haut,
|
||
plate sur le palier), récompense `exp(-((h_mesurée - h_cible)/std)²)`.
|
||
S'inspirer des fonctions `com_height_target` (mdp.py:694) et des
|
||
`interpolated/multistage height target` déjà présentes.
|
||
|
||
Valeurs de départ : `height_high ≈ 0.11` m (hauteur roller debout, cf. bande
|
||
`com_height_target` roller 0.0935–0.1235), `height_low ≈ 0.075` m (accroupi ;
|
||
à affiner en play). La phase est reconstruite depuis `atan2(sin, cos)` de la commande.
|
||
|
||
## Récompenses
|
||
|
||
| Reward | Rôle | Origine |
|
||
|---|---|---|
|
||
| `crouch_glide_height_by_phase` | Cible principale (haut→bas→haut) | **nouveau** |
|
||
| `wheel_speed` (poids réduit ~2–3) | Garder l'élan, ne pas freiner pendant l'accroupi | roller env (`wheel_speed_reward`) |
|
||
| `upright` (≈2), `body_ang_vel` (−0.05), `angular_momentum` (−0.02) | Équilibre / stabilité | roller env |
|
||
| `return_pose` (fin de phase) | Converger vers la pose roller debout pour rendre la main proprement | adapté de `ground_pick_return_pose` |
|
||
| `feet_flat` (−2) | Lames à plat → glisse stable | roller env |
|
||
| `action_rate_l2`, `neck_action_rate_l2`, `joint_torques_l2`, `self_collisions` | Lissage / transfert sim2real | les deux envs |
|
||
|
||
**Explicitement PAS inclus :** `braking` (on ne veut pas s'arrêter), `mouth_ground_proximity`
|
||
/ `mouth_perpendicular_to_ground` (on ne touche pas le sol), `skating_air_time` /
|
||
`single_support` / `glide` (pas de stride pendant le trick — on glisse passivement).
|
||
|
||
## Entraînement
|
||
|
||
- `MicroduckRollerCrouchRlCfg` = copie de `MicroduckRollersRlCfg`
|
||
(MLP 512/256/128, ELU, obs_normalization, PPO, `experiment_name="roller_crouch"`).
|
||
- Enregistrer dans `tasks/__init__.py` :
|
||
`register_mjlab_task(task_id="Mjlab-RollerCrouch-Flat-MicroDuck", ...)`.
|
||
- Lancer :
|
||
```bash
|
||
uv run train Mjlab-RollerCrouch-Flat-MicroDuck \
|
||
--env.scene.num-envs 4096 --agent.max_iterations 8000
|
||
```
|
||
- Épisodes démarrés avec une **vitesse d'entrée réaliste** (le robot arrive en roulant),
|
||
sinon il n'aura pas d'élan à conserver pendant l'accroupi. À câbler via un event de
|
||
reset (vitesse initiale non nulle) ou un push au début d'épisode.
|
||
|
||
## Export + déploiement (flags runtime exacts)
|
||
|
||
Export ONNX (le normaliseur est baké par `export.py`), puis :
|
||
|
||
```bash
|
||
microduck_runtime --variant pre-alpha --new-cmd-obs --roller \
|
||
--model output.onnx \
|
||
--new-dxl-imu --kp 200 --action-scale 0.8 \
|
||
--max-linear-vel 0.6 --max-linear-vel-backward 0.5 --max-angular-vel 0.0 \
|
||
--ground-pick roller_crouch.onnx \
|
||
--ground-pick-period 5.0 \
|
||
--ground-pick-kp-ratio 1.0 \
|
||
--ground-pick-action-scale 0.8
|
||
```
|
||
|
||
Bouton **A** → crouch-glide, puis retour auto à la policy roller.
|
||
|
||
**Pièges de parité entraînement/déploiement (importants pour le sim2real) :**
|
||
- `--ground-pick-kp-ratio 1.0` : le défaut est **0.6** (baisse kp à 120 pendant le trick).
|
||
On entraîne à kp=200 → il faut forcer **1.0** pour que ça corresponde.
|
||
- `--ground-pick-action-scale` doit matcher l'`action_scale` d'entraînement (0.8 ci-dessus).
|
||
- `--ground-pick-period 5.0` doit matcher la période/longueur de mouvement entraînée
|
||
(défaut 4.0, on le garde).
|
||
|
||
## Risques et vérification
|
||
|
||
- **One-shot, durée fixe :** l'accroupi dure `ground-pick-period` puis remonte tout seul.
|
||
Pas de maintien libre — limite acceptée de l'approche B.
|
||
- **Élan pendant le trick :** la phase remplace la commande de vitesse → **pas de poussée
|
||
active** pendant l'accroupi. Si l'élan d'entrée est trop faible, il ralentit. D'où
|
||
l'entraînement avec vitesse d'entrée réaliste.
|
||
- **Vérification :**
|
||
1. En sim (`play`) : il descend, garde les roues qui tournent pendant le palier,
|
||
se relève sans tomber, et la pose finale rejoint proprement la pose roller debout.
|
||
2. Sur le vrai robot : lancer à petite vitesse, appuyer sur A, observer.
|
||
3. Confirmer que la policy roller reprend la main proprement après le retour.
|
||
|
||
## Questions ouvertes / à confirmer pendant l'implémentation
|
||
|
||
- Valeur exacte de `height_low` (accroupi) — à régler en play.
|
||
- Meilleure façon d'injecter la vitesse d'entrée à l'épisode (event reset vs push initial).
|
||
- Poids relatif `wheel_speed` vs `crouch_glide_height_by_phase` (garder l'élan sans
|
||
empêcher de s'accroupir).
|