diff --git a/.gitignore b/.gitignore
index 91927a356..60e79a853 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,10 +82,6 @@ src/unilab/assets/motions/x2/*.csv
src/unilab/assets/robots/x2/meshes/*.STL
src/unilab/assets/robots/microduck/assets/*
!src/unilab/assets/robots/microduck/assets/.gitkeep
-src/unilab/assets/robots/t800/assets/*
-!src/unilab/assets/robots/t800/assets/.gitkeep
-src/unilab/assets/robots/t800/textures/*
-!src/unilab/assets/robots/t800/textures/.gitkeep
src/unilab/assets/robots/g1/assets/
src/unilab/assets/robots/g1/textures/
src/unilab/assets/robots/go2/assets/
diff --git a/docs/sphinx/source/en/4-developer_guide/7-motion_assets.md b/docs/sphinx/source/en/4-developer_guide/7-motion_assets.md
index 4a8ea9ff5..7c36b71e1 100644
--- a/docs/sphinx/source/en/4-developer_guide/7-motion_assets.md
+++ b/docs/sphinx/source/en/4-developer_guide/7-motion_assets.md
@@ -88,7 +88,7 @@ Robot binary meshes and textures (for example `.STL`, `.obj`, and `.png`) are
externalized the same way, on the Hugging Face dataset repo
[unilabsim/unilab-robots](https://huggingface.co/datasets/unilabsim/unilab-robots).
The registered robots are a2, allegro_hand, g1, go2, go2_arm, microduck,
-sharpa_wave, t800, and x2 (`ROBOT_ASSET_SPECS` in `src/unilab/assets/hub.py`).
+sharpa_wave, and x2 (`ROBOT_ASSET_SPECS` in `src/unilab/assets/hub.py`).
Their mesh/texture directories download lazily on first use and land under
their original paths (for example `src/unilab/assets/robots/g1/assets/` and
`robots/g1/textures/` for G1), so the original relative XML paths remain
@@ -107,30 +107,29 @@ To add a new robot's binary assets:
1. Upload each directory to the HF repo while keeping the directory layout
identical. A robot with multiple asset directories requires one upload per
- directory. For example, T800 uses:
+ directory. For example, G1 uses:
```bash
uv run hf upload unilabsim/unilab-robots \
- src/unilab/assets/robots/t800/assets robots/t800/assets \
+ src/unilab/assets/robots/g1/assets robots/g1/assets \
--repo-type dataset
uv run hf upload unilabsim/unilab-robots \
- src/unilab/assets/robots/t800/textures robots/t800/textures \
+ src/unilab/assets/robots/g1/textures robots/g1/textures \
--repo-type dataset
```
2. Ignore the downloaded directory contents in `.gitignore` (robots whose
entire directory is HF-hosted ignore the whole directory; the older
- microduck/t800 entries keep a `.gitkeep`), exclude the directory in
+ microduck entry keeps a `.gitkeep`), exclude the directory in
`tool.uv.build-backend.source-exclude`, and register it in
`ROBOT_ASSET_SPECS`.
3. Scenes built through `create_backend` are then covered automatically:
`ensure_robot_assets_for_paths` resolves the registered directories on a
cold path before any backend parses the XML. Entry points that bypass
- `create_backend` resolve explicitly, e.g. a T800 task factory calls:
+ `create_backend` resolve explicitly, e.g. the MicroDuck task factory calls:
```python
- resolve_robot_asset_dir("robots/t800/assets", marker="LINK_BASE.obj")
- resolve_robot_asset_dir("robots/t800/textures", marker="LINK_BASE.png")
+ resolve_robot_asset_dir("robots/microduck/assets", marker="trunk_base.stl")
```
## Architecture Notes
@@ -149,6 +148,6 @@ To add a new robot's binary assets:
original local path exactly.
- Robot binary assets use the same directory resolver
(`resolve_robot_asset_dir`). The
- thin X2, MicroDuck, and T800 task factories resolve their directories once
+ thin X2 and MicroDuck task factories resolve their directories once
before delegating to the shared manager environment factory. The resolver is
also exposed through the `unilab-pull-assets` CLI.
diff --git a/docs/sphinx/source/en/5-reference/5-support_matrix.md b/docs/sphinx/source/en/5-reference/5-support_matrix.md
index d3d9014a0..9283e39fe 100644
--- a/docs/sphinx/source/en/5-reference/5-support_matrix.md
+++ b/docs/sphinx/source/en/5-reference/5-support_matrix.md
@@ -13,9 +13,9 @@ in English. Do not infer support beyond the evidence grade shown below.
headless physics in an external Python 3.11 worker; its owner scope is
currently limited to the configured G1 walk-flat PPO/SAC paths.
- `--sim mjwarp` has completed training validation only on the `g1_walk_flat`
- host adapter, where PPO (torch) and SAC (torch) are Tested; the SAC
- `t800_walk_flat` mjwarp owner is Configured only, other entrypoints follow
- the matrix below, and using it requires installing the `mjwarp` extra.
+ host adapter, where PPO (torch) and SAC (torch) are Tested; other
+ entrypoints follow the matrix below, and using it requires installing the
+ `mjwarp` extra.
- `--algo`, `--task`, and `--sim` jointly select the owner YAML.
- Do not treat `training.sim_backend` as a standalone backend switch.
@@ -53,9 +53,7 @@ owner may only cover training smoke and an explicitly enabled DR subset.
`mjwarp` has completed training validation only on the `g1_walk_flat` host
adapter: the PPO (torch) and SAC (torch) owners have completed training
validation and have backend, contract, and playback automated coverage, so
-they are marked `Tested`. The SAC `t800_walk_flat` mjwarp owner only has an
-owner YAML and compose coverage, so it is marked `Configured`, which does not
-imply training validation. mjwarp playback
+they are marked `Tested`. mjwarp playback
supports explicit, finite-step `record` and reuses the MuJoCo offline
renderer; `uv run eval --sim mjwarp --render-mode interactive` routes to the
MuJoCo interactive viewer (mjwarp physics, MuJoCo rendering of env[0], forced
@@ -110,7 +108,6 @@ rendering/playback paths remain unsupported.
| PPO (torch) | `go2w_joystick_flat` (go2w joystick flat) | Tested | - | Tested | - | - |
| PPO (torch) | `go2w_joystick_rough` (go2w joystick rough) | Tested | - | Tested | - | - |
| PPO (torch) | `stewart_balance` (stewart balance) | Tested | - | Tested | - | - |
-| PPO (torch) | `t800_walk_flat` (t800 walk flat) | Tested | Registered | - | - | - |
| APPO (torch) | `go1_joystick_flat` (Go1 joystick) | Tested | - | Tested | - | - |
| APPO (torch) | `go2_joystick_flat` (Go2 joystick) | Tested | - | Tested | - | - |
| APPO (torch) | `g1_walk_flat` (G1 walk flat) | Tested | Registered | Registered | Registered | Registered |
@@ -137,7 +134,6 @@ rendering/playback paths remain unsupported.
| SAC (torch) | `g1_23dof_wall_flip_tracking` (g1 23dof wall flip tracking) | Tested | - | Registered | - | - |
| SAC (torch) | `g1_23dof_wbt_obs` (g1 23dof wbt obs) | Tested | - | Registered | - | - |
| SAC (torch) | `g1_wbt_obs` (g1 wbt obs) | Tested | - | Registered | - | - |
-| SAC (torch) | `t800_walk_flat` (t800 walk flat) | Tested | Configured | - | - | - |
| TD3 (torch) | `go1_joystick_flat` (Go1 joystick) | Registered | - | Tested | - | - |
| TD3 (torch) | `go2_joystick_flat` (Go2 joystick) | Registered | - | Tested | - | - |
| TD3 (torch) | `g1_walk_flat` (G1 walk flat) | Tested | Registered | Registered | Registered | Registered |
diff --git a/docs/sphinx/source/zh_CN/4-developer_guide/7-motion_assets.md b/docs/sphinx/source/zh_CN/4-developer_guide/7-motion_assets.md
index d4b957ef6..f69c5d374 100644
--- a/docs/sphinx/source/zh_CN/4-developer_guide/7-motion_assets.md
+++ b/docs/sphinx/source/zh_CN/4-developer_guide/7-motion_assets.md
@@ -81,7 +81,7 @@ env:
托管在 Hugging Face 数据集仓库
[unilabsim/unilab-robots](https://huggingface.co/datasets/unilabsim/unilab-robots)。
已注册的机器人为 a2、allegro_hand、g1、go2、go2_arm、microduck、sharpa_wave、
-t800、x2(见 `src/unilab/assets/hub.py` 的 `ROBOT_ASSET_SPECS`)。它们的
+x2(见 `src/unilab/assets/hub.py` 的 `ROBOT_ASSET_SPECS`)。它们的
mesh/纹理目录在首次使用时按需下载,落盘到原始路径(例如 G1 的
`src/unilab/assets/robots/g1/assets/` 与 `robots/g1/textures/`),因此 XML 中的
原始相对路径保持有效。这些目录通过 `pyproject.toml` 的
@@ -97,28 +97,27 @@ uv run unilab-pull-assets --robot all # 所有已注册机器人
新增某个机器人的二进制资产:
1. 按目录上传到 HF 仓库,保持目录结构一致。一个机器人有多个资产目录时,
- 每个目录分别上传。例如 T800:
+ 每个目录分别上传。例如 G1:
```bash
uv run hf upload unilabsim/unilab-robots \
- src/unilab/assets/robots/t800/assets robots/t800/assets \
+ src/unilab/assets/robots/g1/assets robots/g1/assets \
--repo-type dataset
uv run hf upload unilabsim/unilab-robots \
- src/unilab/assets/robots/t800/textures robots/t800/textures \
+ src/unilab/assets/robots/g1/textures robots/g1/textures \
--repo-type dataset
```
2. 在 `.gitignore` 中忽略下载目录(整个目录托管到 HF 的机器人直接忽略整个目录;
- 较早的 microduck/t800 条目保留 `.gitkeep`),在
+ 较早的 microduck 条目保留 `.gitkeep`),在
`tool.uv.build-backend.source-exclude` 中排除该目录,并在 `ROBOT_ASSET_SPECS`
中注册。
3. 经由 `create_backend` 构建的 scene 会被自动覆盖:
`ensure_robot_assets_for_paths` 在 backend 解析 XML 之前的冷路径解析已注册目录。
- 绕过 `create_backend` 的入口需要显式解析,例如 T800 task factory 调用两次:
+ 绕过 `create_backend` 的入口需要显式解析,例如 MicroDuck task factory:
```python
- resolve_robot_asset_dir("robots/t800/assets", marker="LINK_BASE.obj")
- resolve_robot_asset_dir("robots/t800/textures", marker="LINK_BASE.png")
+ resolve_robot_asset_dir("robots/microduck/assets", marker="trunk_base.stl")
```
## 架构说明
@@ -131,6 +130,6 @@ uv run unilab-pull-assets --robot all # 所有已注册机器人
`ensure_robot_assets_for_paths`,然后再分发给具体 backend。
- 热路径(`step` / `reset`)**不会**触发任何文件下载或解析。
- `ASSETS_ROOT_PATH` 定义不变,下载落盘位置与原始本地路径完全一致。
-- 机器人二进制资产使用同一目录 resolver(`resolve_robot_asset_dir`)。X2、
- MicroDuck 和 T800 的薄 task factory 都会先在冷路径解析一次,再委托给共享
+- 机器人二进制资产使用同一目录 resolver(`resolve_robot_asset_dir`)。X2 和
+ MicroDuck 的薄 task factory 都会先在冷路径解析一次,再委托给共享
manager env factory;同一 resolver 也通过 `unilab-pull-assets` CLI 暴露。
diff --git a/docs/sphinx/source/zh_CN/5-reference/5-support_matrix.md b/docs/sphinx/source/zh_CN/5-reference/5-support_matrix.md
index f4b929f5c..cc1d4c142 100644
--- a/docs/sphinx/source/zh_CN/5-reference/5-support_matrix.md
+++ b/docs/sphinx/source/zh_CN/5-reference/5-support_matrix.md
@@ -46,7 +46,7 @@ uv run scripts/generate_support_matrix.py --write
`Tested` 只描述仓库中已有自动化覆盖或显式 maintainer 训练验证,不代表该组合具备同名 MuJoCo owner 的全部 backend capability;例如 phase-1 Motrix owner 可能只覆盖训练 smoke 和明确启用的 DR 子集。
-`mjwarp` 完成训练验证的只有 `g1_walk_flat` host adapter:PPO (torch) 与 SAC (torch) owner 已完成训练验证,并有 backend、contract 与 playback 自动化覆盖,因此标记为 `Tested`。SAC `t800_walk_flat` 的 mjwarp owner 只有 owner YAML 与 compose 覆盖,标记为 `Configured`,不代表训练验证。mjwarp playback 默认仅支持显式、有限步数的 `record` 并复用 MuJoCo 离线 renderer;`uv run eval --sim mjwarp --render-mode interactive` 路由到 MuJoCo 交互 viewer(mjwarp 跑物理、MuJoCo 渲染 env[0],强制单 env);不支持 `auto` 或 native playback。其他 entrypoint 中出现的 `Registered` 只表示 env/backend registry identity,不代表对应算法、terrain、完整 DR 或 production training 支持。
+`mjwarp` 完成训练验证的只有 `g1_walk_flat` host adapter:PPO (torch) 与 SAC (torch) owner 已完成训练验证,并有 backend、contract 与 playback 自动化覆盖,因此标记为 `Tested`。mjwarp playback 默认仅支持显式、有限步数的 `record` 并复用 MuJoCo 离线 renderer;`uv run eval --sim mjwarp --render-mode interactive` 路由到 MuJoCo 交互 viewer(mjwarp 跑物理、MuJoCo 渲染 env[0],强制单 env);不支持 `auto` 或 native playback。其他 entrypoint 中出现的 `Registered` 只表示 env/backend registry identity,不代表对应算法、terrain、完整 DR 或 production training 支持。
`isaacgym` 是 Python 3.8 子进程后端,当前只接入 `g1_walk_flat`。SAC (torch) owner 已在真机(external Python 3.8 worker runtime,不在仓库 CI 覆盖)完成训练与 record playback 验证,标记为 `Tested`;其余 isaacgym cell 最高只到 `Configured`(registry + owner YAML + compose/contract 覆盖),不代表任何训练或 play 验证。playback 走 IsaacGym 原生渲染(viewer + camera sensor 离屏录制),有显示器时 `play_render_mode=auto` 打开交互 viewer,无显示器时自动降级为离屏录制。
@@ -95,7 +95,6 @@ uv run scripts/generate_support_matrix.py --write
| PPO (torch) | `microduck_velocity_bam_flat` (microduck velocity bam flat) | Tested | - | - | - | - | - |
| PPO (torch) | `microduck_velocity_flat` (microduck velocity flat) | Tested | Configured | - | - | - | - |
| PPO (torch) | `stewart_balance` (stewart balance) | Tested | - | Tested | - | - | - |
-| PPO (torch) | `t800_walk_flat` (t800 walk flat) | Tested | Registered | - | - | - | - |
| APPO (torch) | `go1_joystick_flat` (Go1 joystick) | Tested | - | Tested | - | - | - |
| APPO (torch) | `go2_joystick_flat` (Go2 joystick) | Tested | - | Tested | - | - | - |
| APPO (torch) | `g1_walk_flat` (G1 walk flat) | Tested | Registered | Registered | Registered | Registered | Registered |
@@ -123,7 +122,6 @@ uv run scripts/generate_support_matrix.py --write
| SAC (torch) | `g1_23dof_wbt_obs` (g1 23dof wbt obs) | Tested | - | Registered | - | - | - |
| SAC (torch) | `g1_wbt_obs` (g1 wbt obs) | Tested | - | Registered | - | - | - |
| SAC (torch) | `microduck_velocity_flat` (microduck velocity flat) | Tested | Configured | - | - | - | - |
-| SAC (torch) | `t800_walk_flat` (t800 walk flat) | Tested | Configured | - | - | - | - |
| TD3 (torch) | `go1_joystick_flat` (Go1 joystick) | Registered | - | Tested | - | - | - |
| TD3 (torch) | `go2_joystick_flat` (Go2 joystick) | Registered | - | Tested | - | - | - |
| TD3 (torch) | `g1_walk_flat` (G1 walk flat) | Tested | Registered | Registered | Registered | Registered | Registered |
diff --git a/pyproject.toml b/pyproject.toml
index 993016312..cccd79d1d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,8 +17,6 @@ source-exclude = [
"/src/unilab/assets/robots/go2_arm/assets",
"/src/unilab/assets/robots/microduck/assets",
"/src/unilab/assets/robots/sharpa_wave/meshes",
- "/src/unilab/assets/robots/t800/assets",
- "/src/unilab/assets/robots/t800/textures",
"/src/unilab/assets/robots/x2/meshes",
]
diff --git a/scripts/tools/support_matrix.py b/scripts/tools/support_matrix.py
index ccf61f35c..f06858b4f 100644
--- a/scripts/tools/support_matrix.py
+++ b/scripts/tools/support_matrix.py
@@ -338,8 +338,6 @@ def render_support_matrix(root: Path | None = None) -> str:
"",
"`mjwarp` 完成训练验证的只有 `g1_walk_flat` host adapter:PPO (torch) 与 SAC (torch) owner "
"已完成训练验证,并有 backend、contract 与 playback 自动化覆盖,因此标记为 `Tested`。"
- "SAC `t800_walk_flat` 的 mjwarp owner 只有 owner YAML 与 compose 覆盖,标记为 `Configured`,"
- "不代表训练验证。"
"mjwarp playback 默认仅支持显式、有限步数的 `record` 并复用 MuJoCo 离线 renderer;"
"`uv run eval --sim mjwarp --render-mode interactive` 路由到 MuJoCo 交互 viewer"
"(mjwarp 跑物理、MuJoCo 渲染 env[0],强制单 env);不支持 `auto` 或 native playback。"
diff --git a/src/unilab/assets/hub.py b/src/unilab/assets/hub.py
index a6c1af77b..52d8ed1f2 100644
--- a/src/unilab/assets/hub.py
+++ b/src/unilab/assets/hub.py
@@ -54,10 +54,6 @@
"go2w": (("robots/go2/assets", "base_0.obj", "**/*", "asset"),),
"microduck": (("robots/microduck/assets", "trunk_base.stl", "*.stl", "STL"),),
"sharpa_wave": (("robots/sharpa_wave/meshes", "DP_HB1_4F.STL", "*.STL", "STL"),),
- "t800": (
- ("robots/t800/assets", "LINK_BASE.obj", "*.obj", "OBJ"),
- ("robots/t800/textures", "LINK_BASE.png", "*.png", "PNG"),
- ),
"x2": (("robots/x2/meshes", "pelvis.STL", "*.STL", "STL"),),
}
diff --git a/src/unilab/assets/robots/t800/LICENSE.engineai.txt b/src/unilab/assets/robots/t800/LICENSE.engineai.txt
deleted file mode 100644
index bda48c827..000000000
--- a/src/unilab/assets/robots/t800/LICENSE.engineai.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-BSD 3-Clause License
-
-Copyright (c) 2025- Shenzhen Zhongqing Robot Technology Co., Ltd. ("EngineAI")
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-* Neither the name of the copyright holder nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/src/unilab/assets/robots/t800/assets/.gitkeep b/src/unilab/assets/robots/t800/assets/.gitkeep
deleted file mode 100644
index 8b1378917..000000000
--- a/src/unilab/assets/robots/t800/assets/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/unilab/assets/robots/t800/scene_flat.xml b/src/unilab/assets/robots/t800/scene_flat.xml
deleted file mode 100644
index a9b65a910..000000000
--- a/src/unilab/assets/robots/t800/scene_flat.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/unilab/assets/robots/t800/t800.xml b/src/unilab/assets/robots/t800/t800.xml
deleted file mode 100644
index a01164189..000000000
--- a/src/unilab/assets/robots/t800/t800.xml
+++ /dev/null
@@ -1,398 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/unilab/assets/robots/t800/textures/.gitkeep b/src/unilab/assets/robots/t800/textures/.gitkeep
deleted file mode 100644
index 8b1378917..000000000
--- a/src/unilab/assets/robots/t800/textures/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/unilab/conf/ppo/task/t800_walk_flat/base.yaml b/src/unilab/conf/ppo/task/t800_walk_flat/base.yaml
deleted file mode 100644
index f6a8bff38..000000000
--- a/src/unilab/conf/ppo/task/t800_walk_flat/base.yaml
+++ /dev/null
@@ -1,290 +0,0 @@
-# @package _global_
-# Canonical EngineAI T800 25-DoF walk-flat Manager-Based task declaration.
-env:
- scene:
- model_file: src/unilab/assets/robots/t800/scene_flat.xml
- default_keyframe_name: stand
- entities:
- robot:
- root_body_name: LINK_BASE
- joint_names:
- - J00_HIP_PITCH_L
- - J01_HIP_ROLL_L
- - J02_HIP_YAW_L
- - J03_KNEE_PITCH_L
- - J04_ANKLE_PITCH_L
- - J05_ANKLE_ROLL_L
- - J06_HIP_PITCH_R
- - J07_HIP_ROLL_R
- - J08_HIP_YAW_R
- - J09_KNEE_PITCH_R
- - J10_ANKLE_PITCH_R
- - J11_ANKLE_ROLL_R
- - J12_TORSO_YAW
- - J13_SHOULDER_PITCH_L
- - J14_SHOULDER_ROLL_L
- - J15_SHOULDER_YAW_L
- - J16_ELBOW_PITCH_L
- - J17_ELBOW_YAW_L
- - J18_SHOULDER_PITCH_R
- - J19_SHOULDER_ROLL_R
- - J20_SHOULDER_YAW_R
- - J21_ELBOW_PITCH_R
- - J22_ELBOW_YAW_R
- - J23_HEAD_PITCH
- - J24_HEAD_YAW
- actuator_names:
- - J00_HIP_PITCH_L
- - J01_HIP_ROLL_L
- - J02_HIP_YAW_L
- - J03_KNEE_PITCH_L
- - J04_ANKLE_PITCH_L
- - J05_ANKLE_ROLL_L
- - J06_HIP_PITCH_R
- - J07_HIP_ROLL_R
- - J08_HIP_YAW_R
- - J09_KNEE_PITCH_R
- - J10_ANKLE_PITCH_R
- - J11_ANKLE_ROLL_R
- - J12_TORSO_YAW
- - J13_SHOULDER_PITCH_L
- - J14_SHOULDER_ROLL_L
- - J15_SHOULDER_YAW_L
- - J16_ELBOW_PITCH_L
- - J17_ELBOW_YAW_L
- - J18_SHOULDER_PITCH_R
- - J19_SHOULDER_ROLL_R
- - J20_SHOULDER_YAW_R
- - J21_ELBOW_PITCH_R
- - J22_ELBOW_YAW_R
- - J23_HEAD_PITCH
- - J24_HEAD_YAW
- body_names: [LINK_BASE]
- sim_dt: 0.002
- ctrl_dt: 0.01
- max_episode_seconds: 20.0
- observations:
- policy:
- enable_corruption: true
- terms:
- base_ang_vel:
- func: unilab.envs.mdp.builtin_sensor
- params: {sensor_name: torso_gyro}
- noise:
- _target_: unilab.managers._noise.UniformNoiseCfg
- n_min: -0.2
- n_max: 0.2
- operation: add
- projected_gravity:
- func: unilab.envs.mdp.projected_gravity_from_sensor
- params: {sensor_name: torso_upvector}
- noise:
- _target_: unilab.managers._noise.UniformNoiseCfg
- n_min: -0.05
- n_max: 0.05
- operation: add
- joint_pos:
- func: unilab.envs.mdp.joint_pos_rel
- params:
- asset_cfg:
- _target_: unilab.managers.SceneEntityCfg
- name: robot
- joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]
- noise:
- _target_: unilab.managers._noise.UniformNoiseCfg
- n_min: -0.01
- n_max: 0.01
- operation: add
- joint_vel:
- func: unilab.envs.mdp.joint_vel_rel
- params:
- asset_cfg:
- _target_: unilab.managers.SceneEntityCfg
- name: robot
- joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]
- noise:
- _target_: unilab.managers._noise.UniformNoiseCfg
- n_min: -1.5
- n_max: 1.5
- operation: add
- actions:
- func: unilab.envs.mdp.last_action
- params: {action_name: joint_pos}
- command:
- func: unilab.envs.mdp.generated_commands
- params: {command_name: twist}
- gait_phase:
- func: unilab.tasks.locomotion.g1.manager_terms.G1GaitPhase
- params: {frequency: 1.5, init_mode: offset_phase}
- critic:
- terms:
- base_ang_vel:
- func: unilab.envs.mdp.builtin_sensor
- params: {sensor_name: torso_gyro}
- projected_gravity:
- func: unilab.envs.mdp.projected_gravity_from_sensor
- params: {sensor_name: torso_upvector}
- joint_pos:
- func: unilab.envs.mdp.joint_pos_rel
- params:
- asset_cfg:
- _target_: unilab.managers.SceneEntityCfg
- name: robot
- joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]
- joint_vel:
- func: unilab.envs.mdp.joint_vel_rel
- params:
- asset_cfg:
- _target_: unilab.managers.SceneEntityCfg
- name: robot
- joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]
- actions:
- func: unilab.envs.mdp.last_action
- params: {action_name: joint_pos}
- command:
- func: unilab.envs.mdp.generated_commands
- params: {command_name: twist}
- gait_phase:
- func: unilab.tasks.locomotion.g1.manager_terms.G1GaitPhase
- params: {frequency: 1.5, init_mode: offset_phase}
- base_lin_vel:
- func: unilab.envs.mdp.builtin_sensor
- params: {sensor_name: pelvis_local_linvel}
- policy_observation_group: policy
- critic_observation_group: critic
- actions:
- joint_pos:
- _target_: unilab.tasks.locomotion.t800.manager_terms.T800JointPositionActionCfg
- entity_name: robot
- actuator_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]
- held_actuator_names: [J12_TORSO_YAW, J23_HEAD_PITCH, J24_HEAD_YAW]
- scale:
- J00_HIP_PITCH_L: 0.5
- J01_HIP_ROLL_L: 0.2
- J02_HIP_YAW_L: 0.2
- J03_KNEE_PITCH_L: 0.5
- J04_ANKLE_PITCH_L: 0.5
- J05_ANKLE_ROLL_L: 0.2
- J06_HIP_PITCH_R: 0.5
- J07_HIP_ROLL_R: 0.2
- J08_HIP_YAW_R: 0.2
- J09_KNEE_PITCH_R: 0.5
- J10_ANKLE_PITCH_R: 0.5
- J11_ANKLE_ROLL_R: 0.2
- J13_SHOULDER_PITCH_L: 0.2
- J14_SHOULDER_ROLL_L: 0.2
- J15_SHOULDER_YAW_L: 0.05
- J16_ELBOW_PITCH_L: 0.2
- J17_ELBOW_YAW_L: 0.05
- J18_SHOULDER_PITCH_R: 0.2
- J19_SHOULDER_ROLL_R: 0.2
- J20_SHOULDER_YAW_R: 0.05
- J21_ELBOW_PITCH_R: 0.2
- J22_ELBOW_YAW_R: 0.05
- use_default_offset: true
- commands:
- twist:
- _target_: unilab.tasks.locomotion.g1.manager_terms.G1VelocityCommandCfg
- entity_name: robot
- resampling_time_range: [20.0, 20.0]
- heading_command: false
- heading_control_stiffness: 0.5
- rel_standing_envs: 0.0
- rel_heading_envs: 0.0
- rel_world_envs: 0.0
- rel_forward_envs: 0.0
- init_velocity_prob: 0.0
- planar_dead_zone: 0.2
- ranges:
- lin_vel_x: [-0.6, 1.0]
- lin_vel_y: [-0.4, 0.4]
- ang_vel_z: [-0.8, 0.8]
- events:
- reset_scene_to_default:
- func: unilab.envs.mdp.reset_scene_to_default
- mode: reset
- reset_root_state_uniform:
- func: unilab.envs.mdp.reset_root_state_uniform
- mode: reset
- params:
- pose_range:
- x: [-0.5, 0.5]
- y: [-0.5, 0.5]
- z: [0.0, 0.0]
- roll: [0.0, 0.0]
- pitch: [0.0, 0.0]
- yaw: [-3.141592653589793, 3.141592653589793]
- velocity_range:
- x: [-0.5, 0.5]
- y: [-0.5, 0.5]
- z: [-0.5, 0.5]
- roll: [-0.5, 0.5]
- pitch: [-0.5, 0.5]
- yaw: [-0.5, 0.5]
- pd_gains:
- func: unilab.envs.mdp.pd_gains
- mode: reset
- params:
- kp_range: [0.9, 1.1]
- kd_range: [0.9, 1.1]
- operation: scale
- terminations:
- time_out:
- func: unilab.envs.mdp.time_out
- time_out: true
- tilt:
- func: unilab.tasks.locomotion.g1.manager_terms.g1_tilt_exceeded
- params: {max_tilt_deg: 25.0}
- base_height:
- func: unilab.envs.mdp.root_height_below_minimum
- params: {minimum_height: 0.7165}
-
-reward:
- tracking_lin_vel:
- func: unilab.tasks.locomotion.common.sensor_reward_terms.track_lin_vel
- weight: 2.0
- params: {tracking_sigma: 0.25, command_name: twist, sensor_name: pelvis_local_linvel}
- tracking_ang_vel:
- func: unilab.tasks.locomotion.common.sensor_reward_terms.track_ang_vel
- weight: 0.2
- params: {tracking_sigma: 0.25, command_name: twist, sensor_name: torso_gyro}
- feet_phase:
- func: unilab.tasks.locomotion.g1.manager_terms.feet_phase
- weight: 1.5
- params:
- frequency: 1.5
- swing_height: 0.13
- tracking_sigma: 0.014
- min_forward_speed: 0.0
- command_name: twist
- lin_vel_z:
- func: unilab.tasks.locomotion.common.sensor_reward_terms.lin_vel_z
- weight: -1.0
- params: {sensor_name: pelvis_local_linvel}
- ang_vel_xy:
- func: unilab.tasks.locomotion.common.sensor_reward_terms.ang_vel_xy
- weight: -0.25
- params: {sensor_name: torso_gyro}
- base_height:
- func: unilab.tasks.locomotion.common.manager_terms.base_height_l2
- weight: -500.0
- params: {target_height: 1.0165}
- orientation:
- func: unilab.tasks.locomotion.common.sensor_reward_terms.orientation
- weight: -5.0
- params: {sensor_name: torso_upvector}
- penalty_feet_ori:
- func: unilab.tasks.locomotion.g1.manager_terms.penalty_feet_ori
- weight: -10.0
- action_rate:
- func: unilab.envs.mdp.action_rate_l2
- weight: -0.01
- pose:
- func: unilab.tasks.locomotion.g1.manager_terms.weighted_pose
- weight: -0.1
- params:
- asset_cfg:
- _target_: unilab.managers.SceneEntityCfg
- name: robot
- joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]
- pose_weights: [0.01, 1.0, 5.0, 0.01, 5.0, 5.0, 0.01, 1.0, 5.0, 0.01, 5.0, 5.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0]
diff --git a/src/unilab/conf/ppo/task/t800_walk_flat/mujoco.yaml b/src/unilab/conf/ppo/task/t800_walk_flat/mujoco.yaml
deleted file mode 100644
index b58966d5b..000000000
--- a/src/unilab/conf/ppo/task/t800_walk_flat/mujoco.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-# @package _global_
-# MuJoCo owner for the standalone T800 Manager-Based PPO task.
-defaults:
- - /task/t800_walk_flat/base
- - _self_
-
-training:
- task_name: T800WalkFlat
- sim_backend: mujoco
- play_steps: 2000
-
-algo:
- num_envs: 2048
- max_iterations: 5000
- empirical_normalization: false
- obs_groups:
- actor:
- - policy
- critic:
- - critic
- policy:
- actor_hidden_dims: [512, 256, 128]
- critic_hidden_dims: [512, 256, 128]
-
-play_profile:
- enabled: true
- env:
- render_spacing: 2.0
diff --git a/src/unilab/conf/sac/task/t800_walk_flat/base.yaml b/src/unilab/conf/sac/task/t800_walk_flat/base.yaml
deleted file mode 100644
index 1b3cb6a35..000000000
--- a/src/unilab/conf/sac/task/t800_walk_flat/base.yaml
+++ /dev/null
@@ -1,111 +0,0 @@
-# @package _global_
-# Standalone EngineAI T800 25-DoF Manager-Based SAC walk-flat owner.
-env:
- scene:
- model_file: src/unilab/assets/robots/t800/scene_flat.xml
- default_keyframe_name: stand
- entities:
- robot:
- root_body_name: LINK_BASE
- body_names: [LINK_BASE]
- joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J12_TORSO_YAW, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R, J23_HEAD_PITCH, J24_HEAD_YAW]
- actuator_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J12_TORSO_YAW, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R, J23_HEAD_PITCH, J24_HEAD_YAW]
- sim_dt: 0.002
- ctrl_dt: 0.01
- max_episode_seconds: 20.0
- observations:
- policy:
- enable_corruption: true
- terms:
- base_ang_vel: {func: unilab.envs.mdp.builtin_sensor, params: {sensor_name: torso_gyro}, scale: 0.25}
- projected_gravity: {func: unilab.envs.mdp.projected_gravity_from_sensor, params: {sensor_name: torso_upvector}}
- joint_pos:
- func: unilab.envs.mdp.joint_pos_rel
- params:
- asset_cfg: {_target_: unilab.managers.SceneEntityCfg, name: robot, joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]}
- noise: {_target_: unilab.managers._noise.UniformNoiseCfg, n_min: -0.01, n_max: 0.01, operation: add}
- joint_vel:
- func: unilab.envs.mdp.joint_vel_rel
- scale: 0.05
- params:
- asset_cfg: {_target_: unilab.managers.SceneEntityCfg, name: robot, joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]}
- noise: {_target_: unilab.managers._noise.UniformNoiseCfg, n_min: -0.1, n_max: 0.1, operation: add}
- actions: {func: unilab.envs.mdp.last_action, params: {action_name: joint_pos}}
- command: {func: unilab.envs.mdp.generated_commands, params: {command_name: twist}}
- gait_phase: {func: unilab.tasks.locomotion.g1.manager_terms.G1GaitPhase, params: {frequency: 1.5, init_mode: offset_phase}}
- critic:
- enable_corruption: false
- terms:
- base_ang_vel: {func: unilab.envs.mdp.builtin_sensor, params: {sensor_name: torso_gyro}, scale: 0.25}
- projected_gravity: {func: unilab.envs.mdp.projected_gravity_from_sensor, params: {sensor_name: torso_upvector}}
- joint_pos:
- func: unilab.envs.mdp.joint_pos_rel
- params:
- asset_cfg: {_target_: unilab.managers.SceneEntityCfg, name: robot, joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]}
- joint_vel:
- func: unilab.envs.mdp.joint_vel_rel
- scale: 0.05
- params:
- asset_cfg: {_target_: unilab.managers.SceneEntityCfg, name: robot, joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]}
- actions: {func: unilab.envs.mdp.last_action, params: {action_name: joint_pos}}
- command: {func: unilab.envs.mdp.generated_commands, params: {command_name: twist}}
- gait_phase: {func: unilab.tasks.locomotion.g1.manager_terms.G1GaitPhase, params: {frequency: 1.5, init_mode: offset_phase}}
- base_lin_vel: {func: unilab.envs.mdp.builtin_sensor, params: {sensor_name: pelvis_local_linvel}, scale: 2.0}
- policy_observation_group: policy
- critic_observation_group: critic
- actions:
- joint_pos:
- _target_: unilab.tasks.locomotion.t800.manager_terms.T800JointPositionActionCfg
- entity_name: robot
- actuator_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]
- held_actuator_names: [J12_TORSO_YAW, J23_HEAD_PITCH, J24_HEAD_YAW]
- scale: {J00_HIP_PITCH_L: 1.0, J01_HIP_ROLL_L: 1.0, J02_HIP_YAW_L: 1.0, J03_KNEE_PITCH_L: 1.0, J04_ANKLE_PITCH_L: 1.0, J05_ANKLE_ROLL_L: 1.0, J06_HIP_PITCH_R: 1.0, J07_HIP_ROLL_R: 1.0, J08_HIP_YAW_R: 1.0, J09_KNEE_PITCH_R: 1.0, J10_ANKLE_PITCH_R: 1.0, J11_ANKLE_ROLL_R: 1.0, J13_SHOULDER_PITCH_L: 0.2, J14_SHOULDER_ROLL_L: 0.2, J15_SHOULDER_YAW_L: 0.05, J16_ELBOW_PITCH_L: 0.2, J17_ELBOW_YAW_L: 0.05, J18_SHOULDER_PITCH_R: 0.2, J19_SHOULDER_ROLL_R: 0.2, J20_SHOULDER_YAW_R: 0.05, J21_ELBOW_PITCH_R: 0.2, J22_ELBOW_YAW_R: 0.05}
- use_default_offset: true
- commands:
- twist:
- _target_: unilab.tasks.locomotion.g1.manager_terms.G1VelocityCommandCfg
- entity_name: robot
- resampling_time_range: [20.0, 20.0]
- heading_command: false
- heading_control_stiffness: 0.5
- rel_standing_envs: 0.0
- rel_heading_envs: 0.0
- rel_world_envs: 0.0
- rel_forward_envs: 0.0
- init_velocity_prob: 0.0
- planar_dead_zone: 0.2
- ranges: {lin_vel_x: [-0.6, 1.0], lin_vel_y: [-0.4, 0.4], ang_vel_z: [-0.8, 0.8]}
- events:
- reset_scene_to_default: {func: unilab.envs.mdp.reset_scene_to_default, mode: reset}
- reset_root_state_uniform:
- func: unilab.envs.mdp.reset_root_state_uniform
- mode: reset
- params:
- pose_range: {x: [-0.5, 0.5], y: [-0.5, 0.5], z: [0.0, 0.0], roll: [0.0, 0.0], pitch: [0.0, 0.0], yaw: [-3.141592653589793, 3.141592653589793]}
- velocity_range: {x: [-0.5, 0.5], y: [-0.5, 0.5], z: [-0.5, 0.5], roll: [-0.5, 0.5], pitch: [-0.5, 0.5], yaw: [-0.5, 0.5]}
- pd_gains: {func: unilab.envs.mdp.pd_gains, mode: reset, params: {kp_range: [0.9, 1.1], kd_range: [0.9, 1.1], operation: scale}}
- terminations:
- time_out: {func: unilab.envs.mdp.time_out, time_out: true}
- tilt: {func: unilab.tasks.locomotion.g1.manager_terms.g1_tilt_exceeded, params: {max_tilt_deg: 65.0}}
- base_height: {func: unilab.envs.mdp.root_height_below_minimum, params: {minimum_height: 0.3}}
- curriculum:
- penalty_scaling:
- func: unilab.tasks.locomotion.g1.manager_terms.G1PenaltyCurriculum
- params: {initial_scale: 0.125, min_scale: 0.125, max_scale: 0.25, level_down_threshold: 150.0, level_up_threshold: 750.0, degree: 0.001}
-
-reward:
- tracking_lin_vel: {func: unilab.tasks.locomotion.common.sensor_reward_terms.track_lin_vel, weight: 2.0, params: {tracking_sigma: 0.25, command_name: twist, sensor_name: pelvis_local_linvel}}
- tracking_ang_vel: {func: unilab.tasks.locomotion.common.sensor_reward_terms.track_ang_vel, weight: 1.5, params: {tracking_sigma: 0.25, command_name: twist, sensor_name: torso_gyro}}
- penalty_ang_vel_xy: {func: unilab.tasks.locomotion.common.sensor_reward_terms.ang_vel_xy, weight: -1.0, params: {sensor_name: torso_gyro}}
- penalty_orientation: {func: unilab.tasks.locomotion.common.sensor_reward_terms.orientation, weight: -10.0, params: {sensor_name: torso_upvector}}
- penalty_action_rate: {func: unilab.envs.mdp.action_rate_l2, weight: -4.0}
- pose:
- func: unilab.tasks.locomotion.g1.manager_terms.weighted_pose
- weight: -0.5
- params:
- asset_cfg: {_target_: unilab.managers.SceneEntityCfg, name: robot, joint_names: [J00_HIP_PITCH_L, J01_HIP_ROLL_L, J02_HIP_YAW_L, J03_KNEE_PITCH_L, J04_ANKLE_PITCH_L, J05_ANKLE_ROLL_L, J06_HIP_PITCH_R, J07_HIP_ROLL_R, J08_HIP_YAW_R, J09_KNEE_PITCH_R, J10_ANKLE_PITCH_R, J11_ANKLE_ROLL_R, J13_SHOULDER_PITCH_L, J14_SHOULDER_ROLL_L, J15_SHOULDER_YAW_L, J16_ELBOW_PITCH_L, J17_ELBOW_YAW_L, J18_SHOULDER_PITCH_R, J19_SHOULDER_ROLL_R, J20_SHOULDER_YAW_R, J21_ELBOW_PITCH_R, J22_ELBOW_YAW_R]}
- pose_weights: [0.01, 2.0, 5.0, 0.01, 5.0, 5.0, 0.01, 2.0, 5.0, 0.01, 5.0, 5.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0]
- penalty_feet_ori: {func: unilab.tasks.locomotion.g1.manager_terms.penalty_feet_ori, weight: -20.0}
- penalty_close_feet_lateral: {func: unilab.tasks.locomotion.t800.manager_terms.penalty_close_feet_lateral, weight: -5.0, params: {min_width: 0.18, sigma: 0.04}}
- feet_phase: {func: unilab.tasks.locomotion.g1.manager_terms.feet_phase, weight: 5.0, params: {frequency: 1.5, swing_height: 0.11, tracking_sigma: 0.014, min_forward_speed: 0.0, command_name: twist}}
- alive: {func: unilab.tasks.locomotion.common.manager_terms.alive, weight: 10.0}
diff --git a/src/unilab/conf/sac/task/t800_walk_flat/mjwarp.yaml b/src/unilab/conf/sac/task/t800_walk_flat/mjwarp.yaml
deleted file mode 100644
index b30906415..000000000
--- a/src/unilab/conf/sac/task/t800_walk_flat/mjwarp.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-# @package _global_
-# Configured-only SAC mjwarp owner for the T800 Manager-Based task. Keeps
-# DENYLIST parity with the MuJoCo owner plus the mjwarp capacity knobs; kp/kd
-# randomization is disabled because the mjwarp backend does not advertise gain
-# DR support. Offline record reuses MuJoCo rendering; interactive eval routes
-# through the MuJoCo viewer; native playback and device-resident runtime are
-# intentionally absent.
-defaults:
- - /task/t800_walk_flat/base
- - _self_
-
-training:
- task_name: T800WalkFlat
- sim_backend: mjwarp
- play_render_mode: record
-algo:
- num_envs: 2048
- max_iterations: 10000
- save_interval: 1000
- learning_starts: 10
- updates_per_step: 8
- gamma: 0.98488578
- algo_params:
- alpha_init: 0.001
- target_entropy_ratio: 0.0
-env:
- mjwarp_nconmax: 128
- mjwarp_njmax: 256
- render_spacing: 2.0
- events:
- # mjwarp does not advertise kp/kd gain DR support.
- pd_gains: null
diff --git a/src/unilab/conf/sac/task/t800_walk_flat/mujoco.yaml b/src/unilab/conf/sac/task/t800_walk_flat/mujoco.yaml
deleted file mode 100644
index 84a26f846..000000000
--- a/src/unilab/conf/sac/task/t800_walk_flat/mujoco.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-# @package _global_
-# MuJoCo owner for the standalone T800 Manager-Based SAC task.
-defaults:
- - /task/t800_walk_flat/base
- - _self_
-
-training:
- task_name: T800WalkFlat
- sim_backend: mujoco
-
-algo:
- num_envs: 2048
- max_iterations: 100000
- save_interval: 1000
- learning_starts: 10
- updates_per_step: 8
- gamma: 0.98488578
- algo_params:
- alpha_init: 0.001
- target_entropy_ratio: 0.0
diff --git a/src/unilab/tasks/__init__.py b/src/unilab/tasks/__init__.py
index 5d1f94783..f6bc3beb1 100644
--- a/src/unilab/tasks/__init__.py
+++ b/src/unilab/tasks/__init__.py
@@ -13,7 +13,6 @@
"unilab.tasks.locomotion.go2_arm",
"unilab.tasks.locomotion.a2",
"unilab.tasks.locomotion.microduck",
- "unilab.tasks.locomotion.t800",
"unilab.tasks.manipulation.allegro_inhand",
"unilab.tasks.manipulation.sharpa_inhand",
"unilab.tasks.manipulation.stewart",
diff --git a/src/unilab/tasks/locomotion/t800/__init__.py b/src/unilab/tasks/locomotion/t800/__init__.py
deleted file mode 100644
index 5262d9a2e..000000000
--- a/src/unilab/tasks/locomotion/t800/__init__.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""EngineAI T800 walk-flat task on the shared Manager-Based runtime."""
-
-from unilab.assets.hub import resolve_robot_asset_dir
-from unilab.base import registry
-from unilab.envs import ManagerBasedRlEnv, ManagerBasedRlEnvCfg, make_manager_based_rl_env
-
-
-def make_t800_walk_env(
- cfg: ManagerBasedRlEnvCfg,
- num_envs: int = 1,
- backend_type: str = "mujoco",
-) -> ManagerBasedRlEnv:
- """Resolve T800 binary assets before materializing the generic runtime."""
- resolve_robot_asset_dir("robots/t800/assets", marker="LINK_BASE.obj")
- resolve_robot_asset_dir("robots/t800/textures", marker="LINK_BASE.png")
- return make_manager_based_rl_env(cfg, num_envs=num_envs, backend_type=backend_type)
-
-
-registry.register_env_config("T800WalkFlat", ManagerBasedRlEnvCfg)
-registry.register_env("T800WalkFlat", make_t800_walk_env, sim_backend="mujoco")
-registry.register_env("T800WalkFlat", make_t800_walk_env, sim_backend="mjwarp")
-
-
-__all__ = ["make_t800_walk_env"]
diff --git a/src/unilab/tasks/locomotion/t800/manager_terms.py b/src/unilab/tasks/locomotion/t800/manager_terms.py
deleted file mode 100644
index 8092abcb6..000000000
--- a/src/unilab/tasks/locomotion/t800/manager_terms.py
+++ /dev/null
@@ -1,169 +0,0 @@
-"""T800-specific terms for the Manager-Based locomotion environment."""
-
-from __future__ import annotations
-
-import math
-from dataclasses import dataclass
-from numbers import Real
-from typing import TYPE_CHECKING, Any
-
-import numpy as np
-
-from unilab.envs.mdp import JointPositionAction, JointPositionActionCfg
-from unilab.managers.manager_base import ManagerTermBaseCfg
-from unilab.tasks.locomotion.common.manager_terms import SensorTermBase
-from unilab.utils.rotation import np_quat_apply_inverse, np_yaw_quat
-
-if TYPE_CHECKING:
- from collections.abc import Sequence
-
- from unilab.managers._types import ManagerBasedRlEnv
-
-
-_LATERAL_FOOT_SENSORS = (
- "left_foot_pos",
- "right_foot_pos",
- "base_link_quaternion",
-)
-
-
-def _finite_real(value: Any, *, label: str, strict_positive: bool = False) -> float:
- if isinstance(value, (bool, np.bool_)) or not isinstance(value, Real):
- raise TypeError(f"{label} must be a real number")
- result = float(value)
- if not math.isfinite(result):
- raise ValueError(f"{label} must be finite")
- if result < 0.0 or (strict_positive and result == 0.0):
- relation = "greater than zero" if strict_positive else "non-negative"
- raise ValueError(f"{label} must be {relation}")
- return result
-
-
-def compute_lateral_feet_penalty(
- left_foot: np.ndarray,
- right_foot: np.ndarray,
- base_quat: np.ndarray,
- min_width: float = 0.20,
- sigma: float = 0.04,
-) -> np.ndarray:
- """Penalize signed foot separation below a minimum heading-frame width."""
- separation_w = left_foot - right_foot
- separation_heading = np_quat_apply_inverse(np_yaw_quat(base_quat), separation_w)
- signed_width = separation_heading[:, 1]
- deficit = np.maximum(min_width - signed_width, 0.0)
- return 1.0 - np.exp(-np.square(deficit / sigma))
-
-
-def _validate_unique_selectors(label: str, selectors: Sequence[str]) -> None:
- if len(selectors) != len(set(selectors)):
- raise ValueError(f"T800JointPositionAction {label} selectors contain duplicates")
-
-
-@dataclass(kw_only=True)
-class T800JointPositionActionCfg(JointPositionActionCfg):
- """Control selected joints while holding the remaining T800 joints."""
-
- held_actuator_names: tuple[str, ...] | list[str]
-
- def build(self, env: ManagerBasedRlEnv) -> "T800JointPositionAction":
- return T800JointPositionAction(self, env)
-
-
-class T800JointPositionAction(JointPositionAction):
- """Joint-position action with cold-path-resolved non-policy hold targets."""
-
- def __init__(self, cfg: T800JointPositionActionCfg, env: ManagerBasedRlEnv):
- _validate_unique_selectors("active", cfg.actuator_names)
- _validate_unique_selectors("held", cfg.held_actuator_names)
-
- super().__init__(cfg, env)
- active_ids, _ = self._entity.find_joints_by_actuator_names(cfg.actuator_names)
- held_ids, _ = self._entity.find_joints_by_actuator_names(cfg.held_actuator_names)
- if len(set(active_ids)) != len(active_ids) or len(set(held_ids)) != len(held_ids):
- raise ValueError("T800JointPositionAction resolved joint IDs must be unique")
- if set(active_ids) & set(held_ids):
- raise ValueError("T800JointPositionAction active and held joints overlap")
- if set(active_ids) | set(held_ids) != set(range(self._entity.num_joints)):
- raise ValueError(
- "T800JointPositionAction active and held joints must form a complete partition"
- )
-
- self._held_ids = np.asarray(held_ids, dtype=np.intp)
- self._held_ids.setflags(write=False)
- held_shape = (self.num_envs, self._held_ids.size)
- self._held_default_targets = np.empty(
- held_shape, dtype=self._entity.data.default_joint_pos.dtype
- )
- self._held_encoder_bias = np.empty(held_shape, dtype=self._entity.data.encoder_bias.dtype)
-
- def apply_actions(self) -> None:
- super().apply_actions()
- np.take(
- self._entity.data.default_joint_pos,
- self._held_ids,
- axis=1,
- out=self._held_default_targets,
- )
- np.take(
- self._entity.data.encoder_bias,
- self._held_ids,
- axis=1,
- out=self._held_encoder_bias,
- )
- np.subtract(
- self._held_default_targets,
- self._held_encoder_bias,
- out=self._held_default_targets,
- )
- self._entity.set_joint_position_target(
- self._held_default_targets,
- joint_ids=self._held_ids,
- )
-
-
-class penalty_close_feet_lateral(SensorTermBase):
- """Penalty for insufficient signed lateral foot separation."""
-
- _allowed_params = frozenset({"min_width", "sigma"})
-
- def __init__(self, cfg: ManagerTermBaseCfg, env: ManagerBasedRlEnv):
- super().__init__(cfg, env)
- self._min_width = _finite_real(
- cfg.params.get("min_width", 0.20), label=f"{self.name} min_width"
- )
- self._sigma = _finite_real(
- cfg.params.get("sigma", 0.04),
- label=f"{self.name} sigma",
- strict_positive=True,
- )
- self._sensor_view = self._bind(_LATERAL_FOOT_SENSORS)
- if self._sensor_view.dimensions != (3, 3, 4):
- raise ValueError(
- f"{self.name} sensor dimensions must be (3, 3, 4); received "
- f"{self._sensor_view.dimensions} on backend "
- f"'{self._sensor_view.backend_type}'"
- )
-
- def __call__(self, env: ManagerBasedRlEnv, **params: Any) -> np.ndarray:
- del params
- values = self._read(self._sensor_view, self.name)
- expected_shape = (env.num_envs, 10)
- if values.shape != expected_shape:
- raise ValueError(
- f"{self.name} sensor view must have shape {expected_shape}, got {values.shape}"
- )
- return compute_lateral_feet_penalty(
- values[:, 0:3],
- values[:, 3:6],
- values[:, 6:10],
- min_width=self._min_width,
- sigma=self._sigma,
- )
-
-
-__all__ = [
- "T800JointPositionAction",
- "T800JointPositionActionCfg",
- "compute_lateral_feet_penalty",
- "penalty_close_feet_lateral",
-]
diff --git a/src/unilab/tasks/migration_matrix.py b/src/unilab/tasks/migration_matrix.py
index 2c8ae4303..8c61bc0fe 100644
--- a/src/unilab/tasks/migration_matrix.py
+++ b/src/unilab/tasks/migration_matrix.py
@@ -58,7 +58,6 @@ class TaskMigrationRecord:
{
"MicroduckVelocityBamFlat",
"MicroduckVelocityFlat",
- "T800WalkFlat",
}
)
@@ -172,7 +171,6 @@ def migration_record(task_name: str) -> TaskMigrationRecord:
"manager terms and owner configuration.",
)
if task_name in _EXTERNAL_ASSET_LOCOMOTION_TASKS:
- family = "t800_locomotion" if task_name == "T800WalkFlat" else "microduck_locomotion"
scope = (
"Hydra owner YAML materializes the canonical NumPy Manager-Based runtime."
if task_name == "MicroduckVelocityFlat"
@@ -187,7 +185,7 @@ def migration_record(task_name: str) -> TaskMigrationRecord:
)
return TaskMigrationRecord(
task_name,
- family,
+ "microduck_locomotion",
"Compatible",
"complete",
scope,
diff --git a/tests/assets/test_hub.py b/tests/assets/test_hub.py
index 7a0754c6c..6f86d9d1e 100644
--- a/tests/assets/test_hub.py
+++ b/tests/assets/test_hub.py
@@ -353,7 +353,6 @@ def test_robot_asset_specs_cover_hf_hosted_robots():
"go2w",
"microduck",
"sharpa_wave",
- "t800",
"x2",
}
assert set(ROBOT_ASSET_SPECS) == expected
diff --git a/tests/envs/locomotion/t800/test_t800_asset_contract.py b/tests/envs/locomotion/t800/test_t800_asset_contract.py
deleted file mode 100644
index a66155d6a..000000000
--- a/tests/envs/locomotion/t800/test_t800_asset_contract.py
+++ /dev/null
@@ -1,374 +0,0 @@
-"""Asset-level contracts for the EngineAI T800 walk-flat model."""
-
-from __future__ import annotations
-
-import hashlib
-import xml.etree.ElementTree as ET
-from pathlib import Path
-
-import numpy as np
-import pytest
-from unisim.backend.mujoco.xml import create_discardvisual_xml
-
-from unilab.assets import ASSETS_ROOT_PATH
-from unilab.assets.hub import resolve_robot_asset_dir
-
-ROBOT_XML = ASSETS_ROOT_PATH / "robots" / "t800" / "t800.xml"
-SCENE_XML = ASSETS_ROOT_PATH / "robots" / "t800" / "scene_flat.xml"
-G1_ROBOT_XML = ASSETS_ROOT_PATH / "robots" / "g1" / "g1.xml"
-G1_SCENE_XML = ASSETS_ROOT_PATH / "robots" / "g1" / "scene_flat.xml"
-TEXTURES_DIR = ROBOT_XML.parent / "textures"
-ENGINEAI_LICENSE = ROBOT_XML.parent / "LICENSE.engineai.txt"
-
-EXPECTED_TEXTURES = {
- "LINK_BASE": (
- "LINK_BASE.png",
- "a298400671d0868d8a404801e280d2f9bd01b33ec76660354192baba7a9b12e0",
- ),
- "LINK_HIP_PITCH": (
- "LINK_HIP_PITCH_L.png",
- "192c9753ad475eb1cd631c2de474e3f644dd2f1189a40ed25759299321265b08",
- ),
- "LINK_HIP_ROLL": (
- "LINK_HIP_ROLL_L.png",
- "f123c3a3fed080c94c0296618e8a6712f7da7234a9a8611be3e08a7972fb58ab",
- ),
- "LINK_HIP_YAW": (
- "LINK_HIP_YAW_L.png",
- "97b5f5c52a05f8315ed07c531f5a8834e92fbae0f89cd21853428ae858eacc9a",
- ),
- "LINK_KNEE_PITCH": (
- "LINK_KNEE_PITCH_L.png",
- "ce66f28fc5dac742c73e9b4dfe735e20d2b89641afb4d6e033292a7e41a2de85",
- ),
- "LINK_ANKLE_ROLL": (
- "LINK_ANKLE_ROLL_L.png",
- "c69f48f543389e72954d8ede6d93a9d4e96441576f3e35639454138d70b476b2",
- ),
- "LINK_TORSO_YAW": (
- "LINK_TORSO_YAW.png",
- "b7f88ec8e9276ac9c62e19d52037ee30cbc4eefee44e36ca46974711cd8430f4",
- ),
- "LINK_SHOULDER_PITCH": (
- "LINK_SHOULDER_PITCH_L.png",
- "6bb410836ee6431783ee0f69429f9853c14e884f069b6466b9d4b8bda0a95fea",
- ),
- "LINK_SHOULDER_ROLL": (
- "LINK_SHOULDER_ROLL_L.png",
- "fb76b91c8155478cf6d92088d89914a6eb1b907f78a1dcf18128fffee8618311",
- ),
- "LINK_SHOULDER_YAW": (
- "LINK_SHOULDER_YAW_L.png",
- "e7778698452bedb342bbaf198e5c782e17f1d2a9e10f94e2721b8a01fb815737",
- ),
- "LINK_ELBOW_PITCH_L": (
- "LINK_ELBOW_PITCH_L.png",
- "cb9c31e56644890ba5e3c356bcc1ad266a3cf459104a82505aedd62efb66f260",
- ),
- "LINK_ELBOW_PITCH_R": (
- "LINK_ELBOW_PITCH_R.png",
- "8aa0572b3ef8e819f69937d4be0536e0854dc6ef39bd821668dea9b73b0f270c",
- ),
- "LINK_ELBOW_YAW_L": (
- "LINK_ELBOW_YAW_L.png",
- "672b2be4bcdce1863424685a1f681f0d7b23d143fd910311d833268e1246c05c",
- ),
- "LINK_ELBOW_YAW_R": (
- "LINK_ELBOW_YAW_R.png",
- "6283738a1be141cdb676ad3b93cfc2145bcf1f4c7ae39ce8f886fcdcaa3beee6",
- ),
- "LINK_HEAD_YAW": (
- "LINK_HEAD_YAW.png",
- "65fc0f09e4bd0bfc5c910e2e7267ff598a1cf13f85845b56d51055150d53765f",
- ),
-}
-
-JOINT_NAMES = (
- "J00_HIP_PITCH_L",
- "J01_HIP_ROLL_L",
- "J02_HIP_YAW_L",
- "J03_KNEE_PITCH_L",
- "J04_ANKLE_PITCH_L",
- "J05_ANKLE_ROLL_L",
- "J06_HIP_PITCH_R",
- "J07_HIP_ROLL_R",
- "J08_HIP_YAW_R",
- "J09_KNEE_PITCH_R",
- "J10_ANKLE_PITCH_R",
- "J11_ANKLE_ROLL_R",
- "J12_TORSO_YAW",
- "J13_SHOULDER_PITCH_L",
- "J14_SHOULDER_ROLL_L",
- "J15_SHOULDER_YAW_L",
- "J16_ELBOW_PITCH_L",
- "J17_ELBOW_YAW_L",
- "J18_SHOULDER_PITCH_R",
- "J19_SHOULDER_ROLL_R",
- "J20_SHOULDER_YAW_R",
- "J21_ELBOW_PITCH_R",
- "J22_ELBOW_YAW_R",
- "J23_HEAD_PITCH",
- "J24_HEAD_YAW",
-)
-EXPECTED_KP = np.asarray(
- [
- 180,
- 100,
- 100,
- 180,
- 40,
- 40,
- 180,
- 100,
- 100,
- 180,
- 40,
- 40,
- 100,
- 60,
- 50,
- 50,
- 60,
- 50,
- 60,
- 50,
- 50,
- 60,
- 50,
- 100,
- 100,
- ],
- dtype=np.float64,
-)
-EXPECTED_KD = np.asarray(
- [
- 5,
- 3,
- 3,
- 5,
- 0.3,
- 0.3,
- 5,
- 3,
- 3,
- 5,
- 0.3,
- 0.3,
- 5,
- 0.3,
- 0.3,
- 0.3,
- 0.3,
- 0.3,
- 0.3,
- 0.3,
- 0.3,
- 0.3,
- 0.3,
- 1,
- 1,
- ],
- dtype=np.float64,
-)
-EXPECTED_FORCE_LIMITS = np.asarray(
- [
- 415,
- 370,
- 222,
- 415,
- 160,
- 160,
- 415,
- 370,
- 222,
- 415,
- 160,
- 160,
- 222,
- 160,
- 160,
- 160,
- 160,
- 52,
- 160,
- 160,
- 160,
- 160,
- 52,
- 52,
- 52,
- ],
- dtype=np.float64,
-)
-
-
-@pytest.fixture(scope="module", autouse=True)
-def _resolve_t800_binary_assets() -> None:
- resolve_robot_asset_dir("robots/t800/assets", marker="LINK_BASE.obj")
- resolve_robot_asset_dir("robots/t800/textures", marker="LINK_BASE.png")
-
-
-def _sha256(path: Path) -> str:
- return hashlib.sha256(path.read_bytes()).hexdigest()
-
-
-def _required_attrib(root: ET.Element, xpath: str) -> dict[str, str]:
- element = root.find(xpath)
- assert element is not None
- return element.attrib
-
-
-def test_t800_robot_materials_bind_original_textures():
- mujoco = pytest.importorskip("mujoco")
- root = ET.parse(ROBOT_XML).getroot()
- expected_files = {
- material_name: f"textures/{file_name}"
- for material_name, (file_name, _) in EXPECTED_TEXTURES.items()
- }
- material_textures = {
- element.get("name"): element.get("texture")
- for element in root.findall("./asset/material")
- if element.get("name") in EXPECTED_TEXTURES
- }
- texture_files = {
- element.get("name"): element.get("file")
- for element in root.findall("./asset/texture")
- if element.get("name") in EXPECTED_TEXTURES
- }
-
- assert material_textures == {name: name for name in EXPECTED_TEXTURES}
- assert texture_files == expected_files
- assert {path.name for path in TEXTURES_DIR.glob("*.png")} == {
- file_name for file_name, _ in EXPECTED_TEXTURES.values()
- }
- for file_name, expected_digest in EXPECTED_TEXTURES.values():
- assert _sha256(TEXTURES_DIR / file_name) == expected_digest
-
- model = mujoco.MjModel.from_xml_path(str(ROBOT_XML))
- rgb_role = int(mujoco.mjtTextureRole.mjTEXROLE_RGB)
- for material_name in EXPECTED_TEXTURES:
- material_id = mujoco.mj_name2id(
- model,
- mujoco.mjtObj.mjOBJ_MATERIAL,
- material_name,
- )
- assert material_id >= 0
- assert model.mat_texid[material_id, rgb_role] >= 0
-
-
-def test_t800_vendored_assets_include_engineai_license():
- assert ENGINEAI_LICENSE.is_file()
- assert _sha256(ENGINEAI_LICENSE) == (
- "a3e5f08bf7ae0983cc7a9f602e9a555f5956a8dade26cf4cf9cc95c441fe0b6c"
- )
-
-
-def test_t800_flat_scene_matches_g1_ground_visual_contract():
- t800_scene = ET.parse(SCENE_XML).getroot()
- g1_scene = ET.parse(G1_SCENE_XML).getroot()
-
- for xpath in (
- "./visual/headlight",
- "./visual/rgba",
- "./visual/global",
- "./asset/texture[@type='skybox']",
- "./asset/texture[@name='groundplane']",
- "./asset/material[@name='groundplane']",
- "./worldbody/geom[@name='floor']",
- ):
- assert _required_attrib(t800_scene, xpath) == _required_attrib(g1_scene, xpath)
-
- g1_robot = ET.parse(G1_ROBOT_XML).getroot()
- expected_light = _required_attrib(g1_robot, "./worldbody/light")
- t800_lights = t800_scene.findall("./worldbody/light")
- assert [light.attrib for light in t800_lights] == [expected_light]
- assert ET.parse(ROBOT_XML).getroot().find("./worldbody/light") is None
-
-
-def test_t800_robot_xml_is_robot_only_and_uses_25_position_actuators():
- mujoco = pytest.importorskip("mujoco")
- root = ET.parse(ROBOT_XML).getroot()
- assert root.find(".//keyframe") is None
-
- model = mujoco.MjModel.from_xml_path(str(ROBOT_XML))
- assert (model.nq, model.nv, model.nu) == (32, 31, 25)
- names = tuple(
- mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_ACTUATOR, actuator_id)
- for actuator_id in range(model.nu)
- )
- assert names == JOINT_NAMES
-
- affine = int(mujoco.mjtBias.mjBIAS_AFFINE)
- assert np.all(model.actuator_biastype == affine)
- np.testing.assert_allclose(model.actuator_gainprm[:, 0], EXPECTED_KP)
- np.testing.assert_allclose(model.actuator_biasprm[:, 2], -EXPECTED_KD)
- np.testing.assert_allclose(model.actuator_forcerange[:, 0], -EXPECTED_FORCE_LIMITS)
- np.testing.assert_allclose(model.actuator_forcerange[:, 1], EXPECTED_FORCE_LIMITS)
- np.testing.assert_allclose(model.actuator_ctrlrange, model.jnt_range[1:])
-
-
-def test_t800_flat_scene_has_walk_sensors_and_stand_keyframe():
- mujoco = pytest.importorskip("mujoco")
- model = mujoco.MjModel.from_xml_path(str(SCENE_XML))
-
- sensor_names = {
- mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_SENSOR, sensor_id)
- for sensor_id in range(model.nsensor)
- }
- required_sensors = {
- "pelvis_local_linvel",
- "torso_gyro",
- "torso_upvector",
- "left_foot_pos",
- "left_foot_quat",
- "right_foot_pos",
- "right_foot_quat",
- *(f"left_foot_contact_{index}" for index in range(4)),
- *(f"right_foot_contact_{index}" for index in range(4)),
- }
- assert required_sensors <= sensor_names
-
- key_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_KEY, "stand")
- assert key_id >= 0
- assert model.key_qpos[key_id].shape == (32,)
- assert model.key_ctrl[key_id].shape == (25,)
-
-
-def test_t800_training_compile_discards_visual_meshes():
- mujoco = pytest.importorskip("mujoco")
- training_xml = Path(create_discardvisual_xml(str(SCENE_XML)))
- try:
- model = mujoco.MjModel.from_xml_path(str(training_xml))
- finally:
- training_xml.unlink(missing_ok=True)
-
- assert model.nmesh == 0
- assert model.ntex == 0
- assert np.all(model.geom_type != int(mujoco.mjtGeom.mjGEOM_MESH))
-
-
-def test_t800_stand_foot_sites_track_the_collision_sole_height():
- mujoco = pytest.importorskip("mujoco")
- model = mujoco.MjModel.from_xml_path(str(SCENE_XML))
- data = mujoco.MjData(model)
- key_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_KEY, "stand")
- mujoco.mj_resetDataKeyframe(model, data, key_id)
- mujoco.mj_forward(model, data)
-
- for side in ("left", "right"):
- site_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_SITE, f"{side}_foot")
- sole_heights = []
- for index in range(4):
- geom_id = mujoco.mj_name2id(
- model,
- mujoco.mjtObj.mjOBJ_GEOM,
- f"{side}_foot_contact_{index}_geom",
- )
- rotation = data.geom_xmat[geom_id].reshape(3, 3)
- vertical_extent = np.abs(rotation[2]) @ model.geom_size[geom_id]
- sole_heights.append(data.geom_xpos[geom_id, 2] - vertical_extent)
-
- site_height = data.site_xpos[site_id, 2]
- lowest_collision = min(sole_heights)
- assert site_height >= lowest_collision - 1.0e-3
- assert site_height - lowest_collision < 5.0e-3
diff --git a/tests/envs/locomotion/t800/test_t800_manager_terms.py b/tests/envs/locomotion/t800/test_t800_manager_terms.py
deleted file mode 100644
index d147766bb..000000000
--- a/tests/envs/locomotion/t800/test_t800_manager_terms.py
+++ /dev/null
@@ -1,305 +0,0 @@
-"""Unit contracts for T800-specific Manager-Based terms."""
-
-from __future__ import annotations
-
-from types import SimpleNamespace
-from typing import Any, cast
-
-import numpy as np
-import pytest
-from unisim.backend.base import SimBackend
-
-from unilab.base.entity import EntityCfg, EntityScene
-from unilab.managers import RewardTermCfg
-from unilab.managers._types import ManagerBasedRlEnv
-from unilab.tasks.locomotion.t800.manager_terms import (
- T800JointPositionActionCfg,
- compute_lateral_feet_penalty,
- penalty_close_feet_lateral,
-)
-
-
-class _ActionBackend:
- backend_type = "fake"
- num_envs = 2
- num_actuators = 3
-
- def __init__(self) -> None:
- self.actuator_names = ("knee_motor", "hip_motor", "head_motor")
- self.target_joint_names = ("knee", "hip", "head")
- self.joint_index = {"hip": 0, "knee": 1, "head": 2}
- self.dof_pos = np.zeros((self.num_envs, 3), dtype=np.float32)
-
- def get_actuator_names(self) -> tuple[str, ...]:
- return self.actuator_names
-
- def get_actuator_joint_names(self) -> tuple[str, ...]:
- return self.target_joint_names
-
- def get_actuator_ctrl_range(self) -> np.ndarray:
- return np.tile(np.asarray([[-10.0, 10.0]], dtype=np.float32), (3, 1))
-
- def get_joint_dof_pos_indices(self, names: list[str]) -> np.ndarray:
- return np.asarray([self.joint_index[name] for name in names], dtype=np.int32)
-
- def get_joint_dof_vel_indices(self, names: list[str]) -> np.ndarray:
- return self.get_joint_dof_pos_indices(names)
-
- def get_dof_pos(self) -> np.ndarray:
- return self.dof_pos
-
- def get_dof_vel(self) -> np.ndarray:
- return np.zeros_like(self.dof_pos)
-
- def get_default_dof_pos(self) -> np.ndarray:
- return np.asarray([0.1, 0.2, 0.3], dtype=np.float32)
-
- def get_joint_range(self) -> np.ndarray:
- return np.tile(np.asarray([[-1.0, 1.0]], dtype=np.float32), (3, 1))
-
-
-def _action(
- *,
- active: tuple[str, ...] = ("hip", "knee"),
- held: tuple[str, ...] = ("head",),
- clip: dict[str, tuple[float, float]] | None = None,
-) -> tuple[Any, np.ndarray, EntityScene]:
- backend = _ActionBackend()
- control = np.zeros((backend.num_envs, backend.num_actuators), dtype=np.float32)
- scene = EntityScene(
- {
- "robot": EntityCfg(
- joint_names=("hip", "knee", "head"),
- actuator_names=backend.actuator_names,
- )
- },
- cast(SimBackend, backend),
- control,
- )
- env = cast(
- ManagerBasedRlEnv,
- SimpleNamespace(num_envs=backend.num_envs, scene=scene),
- )
- scale = {name: {"hip": 2.0, "knee": 3.0}.get(name, 1.0) for name in active}
- cfg = T800JointPositionActionCfg(
- entity_name="robot",
- actuator_names=active,
- held_actuator_names=held,
- scale=scale,
- use_default_offset=True,
- clip=clip,
- )
- return cfg.build(env), control, scene
-
-
-def test_t800_action_writes_active_targets_and_holds_non_policy_defaults() -> None:
- action, control, scene = _action()
- scene["robot"].data.encoder_bias[:] = np.asarray([[0.01, 0.02, 0.03]])
- raw = np.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32)
-
- action.process_actions(raw)
- action.apply_actions()
-
- assert action.action_dim == 2
- assert action.target_names == ["hip", "knee"]
- np.testing.assert_allclose(action.processed_action, raw * [2.0, 3.0] + [0.1, 0.2])
- np.testing.assert_allclose(control[:, 1], action.processed_action[:, 0] - 0.01)
- np.testing.assert_allclose(control[:, 0], action.processed_action[:, 1] - 0.02)
- np.testing.assert_allclose(control[:, 2], 0.3 - 0.03)
-
-
-def test_t800_action_clips_active_targets_before_mapping_and_keeps_held_target() -> None:
- action, control, scene = _action(clip={"hip": (-1.0, 1.0), "knee": (-2.0, 2.0)})
- scene["robot"].data.encoder_bias[:] = np.asarray(
- [[0.01, 0.02, 0.03], [0.04, 0.05, 0.06]], dtype=np.float32
- )
- raw = np.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32)
-
- action.process_actions(raw)
- action.apply_actions()
-
- np.testing.assert_allclose(action.processed_action, [[1.0, 2.0], [1.0, 2.0]])
- np.testing.assert_allclose(control[:, 1], [1.0 - 0.01, 1.0 - 0.04])
- np.testing.assert_allclose(control[:, 0], [2.0 - 0.02, 2.0 - 0.05])
- np.testing.assert_allclose(control[:, 2], [0.3 - 0.03, 0.3 - 0.06])
-
-
-@pytest.mark.parametrize(
- ("active", "held", "message"),
- [
- (("hip", "head"), ("head",), "overlap"),
- (("hip",), ("head",), "partition"),
- (("hip", "hip"), ("knee", "head"), "duplicate"),
- (("hip", "knee"), ("head", "head"), "duplicate"),
- (("hip", "knee"), ("missing",), "match"),
- ],
-)
-def test_t800_action_partition_fails_closed(
- active: tuple[str, ...],
- held: tuple[str, ...],
- message: str,
-) -> None:
- with pytest.raises(ValueError, match=message):
- _action(active=active, held=held)
-
-
-class _LateralScene:
- def __init__(self, sensor_data: dict[str, np.ndarray]) -> None:
- self.sensor_data = sensor_data
- self.bound_names: tuple[str, ...] | None = None
-
- def bind_sensor_data(self, names: tuple[str, ...]):
- self.bound_names = tuple(names)
- arrays = [self.sensor_data[name] for name in names]
- return SimpleNamespace(
- dimensions=tuple(array.shape[1] for array in arrays),
- backend_type="fake",
- read=lambda: np.concatenate(arrays, axis=1),
- )
-
-
-def _lateral_env(
- *,
- left: np.ndarray | None = None,
- right: np.ndarray | None = None,
- base_quat: np.ndarray | None = None,
-) -> tuple[ManagerBasedRlEnv, _LateralScene]:
- left = (
- np.asarray([[0.0, 0.05, 0.0], [0.0, -0.10, 0.0]], dtype=np.float32)
- if left is None
- else left
- )
- right = (
- np.asarray([[0.0, -0.05, 0.0], [0.0, 0.10, 0.0]], dtype=np.float32)
- if right is None
- else right
- )
- base_quat = (
- np.asarray([[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], dtype=np.float32)
- if base_quat is None
- else base_quat
- )
- scene = _LateralScene(
- {
- "left_foot_pos": left,
- "right_foot_pos": right,
- "base_link_quaternion": base_quat,
- }
- )
- env = cast(ManagerBasedRlEnv, SimpleNamespace(num_envs=left.shape[0], scene=scene))
- return env, scene
-
-
-def _lateral_term(env: ManagerBasedRlEnv, **params: Any) -> penalty_close_feet_lateral:
- return penalty_close_feet_lateral(
- RewardTermCfg(
- func=penalty_close_feet_lateral,
- weight=-1.0,
- params={"min_width": 0.20, "sigma": 0.04, **params},
- ),
- env,
- )
-
-
-def test_lateral_penalty_is_invariant_to_fore_aft_foot_offsets() -> None:
- left = np.asarray([[0.0, 0.05, 0.0], [3.0, 0.05, 1.0]], dtype=np.float32)
- right = np.asarray([[0.0, -0.05, 0.0], [-4.0, -0.05, -2.0]], dtype=np.float32)
- quat = np.asarray([[1.0, 0.0, 0.0, 0.0]] * 2, dtype=np.float32)
-
- penalty = compute_lateral_feet_penalty(left, right, quat, min_width=0.20, sigma=0.04)
-
- assert penalty[0] == pytest.approx(penalty[1])
-
-
-def test_lateral_penalty_is_invariant_to_world_yaw() -> None:
- local_left = np.asarray([[1.0, 0.05, 0.0]], dtype=np.float32)
- local_right = np.asarray([[-2.0, -0.05, 0.0]], dtype=np.float32)
- yaw_90_left = np.asarray([[-0.05, 1.0, 0.0]], dtype=np.float32)
- yaw_90_right = np.asarray([[0.05, -2.0, 0.0]], dtype=np.float32)
- identity = np.asarray([[1.0, 0.0, 0.0, 0.0]], dtype=np.float32)
- yaw_90 = np.asarray([[np.sqrt(0.5), 0.0, 0.0, np.sqrt(0.5)]], dtype=np.float32)
-
- local_penalty = compute_lateral_feet_penalty(
- local_left, local_right, identity, min_width=0.20, sigma=0.04
- )
- yawed_penalty = compute_lateral_feet_penalty(
- yaw_90_left, yaw_90_right, yaw_90, min_width=0.20, sigma=0.04
- )
-
- np.testing.assert_allclose(yawed_penalty, local_penalty, atol=1.0e-6)
-
-
-def test_lateral_penalty_is_zero_at_or_above_minimum_width() -> None:
- left = np.asarray([[0.0, 0.10, 0.0], [0.0, 0.20, 0.0]], dtype=np.float32)
- right = np.asarray([[0.0, -0.10, 0.0], [0.0, -0.10, 0.0]], dtype=np.float32)
- quat = np.asarray([[1.0, 0.0, 0.0, 0.0]] * 2, dtype=np.float32)
-
- penalty = compute_lateral_feet_penalty(left, right, quat, min_width=0.20, sigma=0.04)
-
- np.testing.assert_array_equal(penalty, 0.0)
-
-
-def test_lateral_penalty_is_near_one_for_crossed_feet() -> None:
- left = np.asarray([[0.0, -0.10, 0.0]], dtype=np.float32)
- right = np.asarray([[0.0, 0.10, 0.0]], dtype=np.float32)
- quat = np.asarray([[1.0, 0.0, 0.0, 0.0]], dtype=np.float32)
-
- penalty = compute_lateral_feet_penalty(left, right, quat, min_width=0.20, sigma=0.04)
-
- assert penalty[0] > 0.999
-
-
-def test_lateral_term_binds_named_sensors_and_matches_helper() -> None:
- env, scene = _lateral_env()
- term = _lateral_term(env)
-
- result = term(env)
-
- assert scene.bound_names == (
- "left_foot_pos",
- "right_foot_pos",
- "base_link_quaternion",
- )
- expected = compute_lateral_feet_penalty(
- scene.sensor_data["left_foot_pos"],
- scene.sensor_data["right_foot_pos"],
- scene.sensor_data["base_link_quaternion"],
- min_width=0.20,
- sigma=0.04,
- )
- np.testing.assert_allclose(result, expected)
-
-
-def test_lateral_term_fails_closed_when_a_named_sensor_is_missing() -> None:
- env, scene = _lateral_env()
- del scene.sensor_data["right_foot_pos"]
-
- with pytest.raises(KeyError, match="right_foot_pos"):
- _lateral_term(env)
-
-
-def test_lateral_term_rejects_wrong_sensor_dimensions_at_construction() -> None:
- env, _ = _lateral_env(base_quat=np.zeros((2, 3), dtype=np.float32))
-
- with pytest.raises(ValueError, match=r"dimensions.*\(3, 3, 3\)"):
- _lateral_term(env)
-
-
-@pytest.mark.parametrize(
- ("params", "error", "message"),
- [
- ({"min_width": -0.01}, ValueError, "min_width"),
- ({"sigma": 0.0}, ValueError, "sigma"),
- ({"sigma": -0.01}, ValueError, "sigma"),
- ({"min_width": float("nan")}, ValueError, "finite"),
- ({"sigma": float("inf")}, ValueError, "finite"),
- ({"min_width": True}, TypeError, "real number"),
- ],
-)
-def test_lateral_term_rejects_invalid_parameters_at_construction(
- params: dict[str, Any], error: type[Exception], message: str
-) -> None:
- env, _ = _lateral_env()
-
- with pytest.raises(error, match=message):
- _lateral_term(env, **params)
diff --git a/tests/envs/locomotion/t800/test_t800_owner_contract.py b/tests/envs/locomotion/t800/test_t800_owner_contract.py
deleted file mode 100644
index 953131beb..000000000
--- a/tests/envs/locomotion/t800/test_t800_owner_contract.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""Registry and cold-path owner contracts for the T800 walk-flat task."""
-
-from __future__ import annotations
-
-import importlib
-import importlib.util
-
-from unilab.base import registry
-from unilab.envs import ManagerBasedRlEnvCfg
-from unilab.tasks import __unilab_registry_modules__
-
-
-def test_t800_registry_metadata_is_manager_based_and_bootstrapped_explicitly() -> None:
- """The production bootstrap exposes the Manager-Based MuJoCo and mjwarp owners."""
- registry.ensure_registries()
-
- assert "unilab.tasks.locomotion.t800" in __unilab_registry_modules__
- metadata = registry.list_registered_envs()
- assert metadata["T800WalkFlat"] == {
- "config_factory": "ManagerBasedRlEnvCfg",
- "available_backends": ["mujoco", "mjwarp"],
- }
-
- t800 = importlib.import_module("unilab.tasks.locomotion.t800")
- for backend in ("mujoco", "mjwarp"):
- factory = registry._envs["T800WalkFlat"].env_factory_dict[backend]
- assert factory is t800.make_t800_walk_env
- assert callable(factory)
- assert factory.__name__ == "make_t800_walk_env"
- assert factory.__module__ == "unilab.tasks.locomotion.t800"
- assert registry._envs["T800WalkFlat"].env_cfg_factory is ManagerBasedRlEnvCfg
-
- try:
- legacy_spec = importlib.util.find_spec("unilab.envs.locomotion.t800")
- except ModuleNotFoundError:
- legacy_spec = None
- assert legacy_spec is None
-
-
-def test_t800_factory_resolves_assets_before_generic_runtime(monkeypatch) -> None:
- """Asset markers resolve before the exact generic runtime invocation."""
- t800 = importlib.import_module("unilab.tasks.locomotion.t800")
- calls: list[tuple[str, object, object]] = []
- sentinel = object()
-
- def fake_resolver(directory: str, *, marker: str):
- calls.append(("resolve", directory, marker))
- return sentinel
-
- def fake_builder(cfg, *, num_envs: int, backend_type: str):
- calls.append(("build", num_envs, backend_type))
- assert cfg is config
- return sentinel
-
- config = ManagerBasedRlEnvCfg()
- monkeypatch.setattr(t800, "resolve_robot_asset_dir", fake_resolver)
- monkeypatch.setattr(t800, "make_manager_based_rl_env", fake_builder)
-
- result = t800.make_t800_walk_env(config, num_envs=4, backend_type="mujoco")
-
- assert result is sentinel
- assert calls == [
- ("resolve", "robots/t800/assets", "LINK_BASE.obj"),
- ("resolve", "robots/t800/textures", "LINK_BASE.png"),
- ("build", 4, "mujoco"),
- ]
diff --git a/tests/envs/locomotion/t800/test_t800_ppo_owner.py b/tests/envs/locomotion/t800/test_t800_ppo_owner.py
deleted file mode 100644
index 74ed9b8fb..000000000
--- a/tests/envs/locomotion/t800/test_t800_ppo_owner.py
+++ /dev/null
@@ -1,234 +0,0 @@
-"""Hydra and cold-path contracts for the T800 Manager-Based PPO owner."""
-
-from __future__ import annotations
-
-from pathlib import Path
-from typing import Any, cast
-
-import numpy as np
-import pytest
-from hydra import compose, initialize_config_dir
-from hydra.core.global_hydra import GlobalHydra
-
-from unilab.base import registry
-from unilab.base.config_adapter import BackendAdapter
-from unilab.base.config_materialization import apply_cfg_overrides
-from unilab.envs import ManagerBasedRlEnvCfg
-from unilab.tasks.locomotion.t800.manager_terms import T800JointPositionActionCfg
-
-ROOT_DIR = Path(__file__).parents[4]
-CONF_DIR = ROOT_DIR / "src" / "unilab" / "conf" / "ppo"
-
-_JOINT_NAMES = (
- "J00_HIP_PITCH_L",
- "J01_HIP_ROLL_L",
- "J02_HIP_YAW_L",
- "J03_KNEE_PITCH_L",
- "J04_ANKLE_PITCH_L",
- "J05_ANKLE_ROLL_L",
- "J06_HIP_PITCH_R",
- "J07_HIP_ROLL_R",
- "J08_HIP_YAW_R",
- "J09_KNEE_PITCH_R",
- "J10_ANKLE_PITCH_R",
- "J11_ANKLE_ROLL_R",
- "J12_TORSO_YAW",
- "J13_SHOULDER_PITCH_L",
- "J14_SHOULDER_ROLL_L",
- "J15_SHOULDER_YAW_L",
- "J16_ELBOW_PITCH_L",
- "J17_ELBOW_YAW_L",
- "J18_SHOULDER_PITCH_R",
- "J19_SHOULDER_ROLL_R",
- "J20_SHOULDER_YAW_R",
- "J21_ELBOW_PITCH_R",
- "J22_ELBOW_YAW_R",
- "J23_HEAD_PITCH",
- "J24_HEAD_YAW",
-)
-_ACTIVE_JOINT_NAMES = _JOINT_NAMES[:12] + _JOINT_NAMES[13:23]
-_HELD_JOINT_NAMES = (_JOINT_NAMES[12], _JOINT_NAMES[23], _JOINT_NAMES[24])
-_ACTION_SCALES = (
- 0.5,
- 0.2,
- 0.2,
- 0.5,
- 0.5,
- 0.2,
- 0.5,
- 0.2,
- 0.2,
- 0.5,
- 0.5,
- 0.2,
- 0.2,
- 0.2,
- 0.05,
- 0.2,
- 0.05,
- 0.2,
- 0.2,
- 0.05,
- 0.2,
- 0.05,
-)
-_POSE_WEIGHTS = (
- 0.01,
- 1.0,
- 5.0,
- 0.01,
- 5.0,
- 5.0,
- 0.01,
- 1.0,
- 5.0,
- 0.01,
- 5.0,
- 5.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
-)
-
-
-def _compose_owner():
- GlobalHydra.instance().clear()
- with initialize_config_dir(config_dir=str(CONF_DIR), version_base="1.3"):
- return compose("config", overrides=["task=t800_walk_flat/mujoco"])
-
-
-def _materialize_owner() -> tuple[Any, ManagerBasedRlEnvCfg]:
- cfg = _compose_owner()
- registry.ensure_registries()
- env_override = BackendAdapter(
- cfg, root_dir=ROOT_DIR, algo_name="ppo"
- ).build_task_env_cfg_override()
- env_cfg = registry.materialize_env_config("T800WalkFlat")
- assert isinstance(env_cfg, ManagerBasedRlEnvCfg)
- apply_cfg_overrides(env_cfg, env_override)
- env_cfg.validate()
- return cfg, env_cfg
-
-
-def test_t800_ppo_owner_materializes_the_complete_manager_contract() -> None:
- cfg, env_cfg = _materialize_owner()
- env_cfg = cast(Any, env_cfg)
-
- assert cfg.training.task_name == "T800WalkFlat"
- assert cfg.training.sim_backend == "mujoco"
- assert cfg.training.play_steps == 2000
- assert env_cfg.sim_dt == pytest.approx(0.002)
- assert env_cfg.ctrl_dt == pytest.approx(0.01)
- assert env_cfg.max_episode_seconds == pytest.approx(20.0)
- assert env_cfg.policy_observation_group == "policy"
- assert env_cfg.critic_observation_group == "critic"
-
- assert env_cfg.scene is not None
- assert env_cfg.scene.model_file.endswith("robots/t800/scene_flat.xml")
- assert env_cfg.scene.default_keyframe_name == "stand"
- robot = env_cfg.scene.entities["robot"]
- assert robot.root_body_name == "LINK_BASE"
- assert robot.body_names == ["LINK_BASE"]
- assert tuple(robot.joint_names or ()) == _JOINT_NAMES
- assert tuple(robot.actuator_names or ()) == _JOINT_NAMES
-
- action = env_cfg.actions["joint_pos"]
- assert isinstance(action, T800JointPositionActionCfg)
- assert tuple(action.actuator_names) == _ACTIVE_JOINT_NAMES
- assert tuple(action.held_actuator_names) == _HELD_JOINT_NAMES
- action_scale = cast(Any, action.scale)
- assert list(action_scale) == list(_ACTIVE_JOINT_NAMES)
- assert tuple(action_scale.values()) == pytest.approx(_ACTION_SCALES)
- assert action.use_default_offset is True
-
- policy_terms = env_cfg.observations["policy"].terms
- critic_terms = env_cfg.observations["critic"].terms
- expected_terms = (
- "base_ang_vel",
- "projected_gravity",
- "joint_pos",
- "joint_vel",
- "actions",
- "command",
- "gait_phase",
- )
- assert tuple(policy_terms) == expected_terms
- assert tuple(critic_terms) == (*expected_terms, "base_lin_vel")
- assert sum((3, 3, 22, 22, 22, 3, 2)) == 77
- assert sum((3, 3, 22, 22, 22, 3, 2, 3)) == 80
- assert policy_terms["base_ang_vel"].params["sensor_name"] == "torso_gyro"
- assert policy_terms["projected_gravity"].params["sensor_name"] == "torso_upvector"
- assert critic_terms["base_lin_vel"].params["sensor_name"] == "pelvis_local_linvel"
-
- assert tuple(env_cfg.rewards) == (
- "tracking_lin_vel",
- "tracking_ang_vel",
- "feet_phase",
- "lin_vel_z",
- "ang_vel_xy",
- "base_height",
- "orientation",
- "penalty_feet_ori",
- "action_rate",
- "pose",
- )
- assert env_cfg.rewards["feet_phase"].weight == pytest.approx(1.5)
- assert env_cfg.rewards["feet_phase"].params["swing_height"] == pytest.approx(0.13)
- assert env_cfg.rewards["feet_phase"].params["tracking_sigma"] == pytest.approx(0.014)
- assert env_cfg.rewards["base_height"].params["target_height"] == pytest.approx(1.0165)
- assert env_cfg.terminations["tilt"].params["max_tilt_deg"] == pytest.approx(25.0)
- assert env_cfg.terminations["base_height"].params["minimum_height"] == pytest.approx(0.7165)
- assert tuple(env_cfg.rewards["pose"].params["pose_weights"]) == pytest.approx(_POSE_WEIGHTS)
-
- for name, bound in {
- "base_ang_vel": 0.2,
- "projected_gravity": 0.05,
- "joint_pos": 0.01,
- "joint_vel": 1.5,
- }.items():
- noise = policy_terms[name].noise
- assert noise is not None
- assert noise.n_min == pytest.approx(-bound)
- assert noise.n_max == pytest.approx(bound)
- assert all(term.noise is None for term in critic_terms.values())
-
-
-@pytest.mark.slow
-def test_t800_ppo_owner_builds_and_steps_real_mujoco_env() -> None:
- pytest.importorskip("mujoco")
- cfg, _ = _materialize_owner()
- env = cast(
- Any,
- registry.make(
- "T800WalkFlat",
- sim_backend=str(cfg.training.sim_backend),
- num_envs=1,
- env_cfg_override=BackendAdapter(
- cfg, root_dir=ROOT_DIR, algo_name="ppo"
- ).build_task_env_cfg_override(),
- ),
- )
- try:
- reset_obs, reset_info = env.reset()
- assert isinstance(reset_info, dict)
- assert env.action_space.shape == (22,)
- assert reset_obs["obs"].shape == (1, 77)
- assert reset_obs["critic"].shape == (1, 80)
-
- action = np.zeros((1, 22), dtype=np.float32)
- state = env.step(action)
- assert np.isfinite(state.obs["obs"]).all()
- assert np.isfinite(state.obs["critic"]).all()
- assert np.isfinite(state.reward).all()
- controls = env._control
- assert controls.shape == (1, 25)
- assert np.isfinite(controls).all()
- finally:
- env.close()
diff --git a/tests/envs/locomotion/t800/test_t800_sac_owner.py b/tests/envs/locomotion/t800/test_t800_sac_owner.py
deleted file mode 100644
index a05e47def..000000000
--- a/tests/envs/locomotion/t800/test_t800_sac_owner.py
+++ /dev/null
@@ -1,216 +0,0 @@
-"""Hydra and cold-path contracts for the T800 Manager-Based SAC owner."""
-
-from __future__ import annotations
-
-from pathlib import Path
-from typing import Any, cast
-
-import numpy as np
-import pytest
-from hydra import compose, initialize_config_dir
-from hydra.core.global_hydra import GlobalHydra
-
-from unilab.base import registry
-from unilab.base.config_adapter import BackendAdapter
-from unilab.base.config_materialization import apply_cfg_overrides
-from unilab.envs import ManagerBasedRlEnvCfg
-from unilab.tasks.locomotion.t800.manager_terms import (
- T800JointPositionActionCfg,
- penalty_close_feet_lateral,
-)
-
-ROOT_DIR = Path(__file__).parents[4]
-CONF_DIR = ROOT_DIR / "src" / "unilab" / "conf" / "sac"
-
-_JOINT_NAMES = (
- "J00_HIP_PITCH_L",
- "J01_HIP_ROLL_L",
- "J02_HIP_YAW_L",
- "J03_KNEE_PITCH_L",
- "J04_ANKLE_PITCH_L",
- "J05_ANKLE_ROLL_L",
- "J06_HIP_PITCH_R",
- "J07_HIP_ROLL_R",
- "J08_HIP_YAW_R",
- "J09_KNEE_PITCH_R",
- "J10_ANKLE_PITCH_R",
- "J11_ANKLE_ROLL_R",
- "J12_TORSO_YAW",
- "J13_SHOULDER_PITCH_L",
- "J14_SHOULDER_ROLL_L",
- "J15_SHOULDER_YAW_L",
- "J16_ELBOW_PITCH_L",
- "J17_ELBOW_YAW_L",
- "J18_SHOULDER_PITCH_R",
- "J19_SHOULDER_ROLL_R",
- "J20_SHOULDER_YAW_R",
- "J21_ELBOW_PITCH_R",
- "J22_ELBOW_YAW_R",
- "J23_HEAD_PITCH",
- "J24_HEAD_YAW",
-)
-_ACTIVE = _JOINT_NAMES[:12] + _JOINT_NAMES[13:23]
-_HELD = (_JOINT_NAMES[12], _JOINT_NAMES[23], _JOINT_NAMES[24])
-_SCALES = (1.0,) * 12 + (0.2, 0.2, 0.05, 0.2, 0.05, 0.2, 0.2, 0.05, 0.2, 0.05)
-_POSE = (0.01, 2.0, 5.0, 0.01, 5.0, 5.0, 0.01, 2.0, 5.0, 0.01, 5.0, 5.0) + (50.0,) * 10
-
-
-def _compose_owner():
- GlobalHydra.instance().clear()
- with initialize_config_dir(config_dir=str(CONF_DIR), version_base="1.3"):
- return compose("config", overrides=["task=t800_walk_flat/mujoco"])
-
-
-def _materialize_owner() -> tuple[Any, ManagerBasedRlEnvCfg]:
- cfg = _compose_owner()
- registry.ensure_registries()
- env_override = BackendAdapter(
- cfg, root_dir=ROOT_DIR, algo_name="sac"
- ).build_task_env_cfg_override()
- env_cfg = registry.materialize_env_config("T800WalkFlat")
- assert isinstance(env_cfg, ManagerBasedRlEnvCfg)
- apply_cfg_overrides(env_cfg, env_override)
- env_cfg.validate()
- return cfg, env_cfg
-
-
-def test_t800_sac_owner_materializes_complete_contract() -> None:
- cfg, env_cfg = _materialize_owner()
- env_cfg = cast(Any, env_cfg)
-
- assert CONF_DIR.name == "sac"
- assert cfg.training.task_name == "T800WalkFlat"
- assert cfg.training.sim_backend == "mujoco"
- assert cfg.algo.gamma == pytest.approx(0.98488578)
- assert cfg.algo.learning_starts == 10
- assert cfg.algo.updates_per_step == 8
- assert cfg.algo.algo_params.alpha_init == pytest.approx(0.001)
- assert cfg.algo.algo_params.target_entropy_ratio == pytest.approx(0.0)
- assert cfg.algo.max_iterations == 100000
- assert cfg.algo.save_interval == 1000
- assert env_cfg.sim_dt == pytest.approx(0.002)
- assert env_cfg.ctrl_dt == pytest.approx(0.01)
- assert env_cfg.max_episode_seconds == pytest.approx(20.0)
-
- robot = env_cfg.scene.entities["robot"]
- assert env_cfg.scene.model_file.endswith("robots/t800/scene_flat.xml")
- assert env_cfg.scene.default_keyframe_name == "stand"
- assert robot.root_body_name == "LINK_BASE"
- assert robot.body_names == ["LINK_BASE"]
- assert tuple(robot.joint_names) == _JOINT_NAMES
- assert tuple(robot.actuator_names) == _JOINT_NAMES
-
- action = env_cfg.actions["joint_pos"]
- assert isinstance(action, T800JointPositionActionCfg)
- assert tuple(action.actuator_names) == _ACTIVE
- assert tuple(action.held_actuator_names) == _HELD
- assert list(cast(Any, action.scale)) == list(_ACTIVE)
- assert tuple(cast(Any, action.scale).values()) == pytest.approx(_SCALES)
- assert action.use_default_offset is True
-
- policy = env_cfg.observations["policy"].terms
- critic = env_cfg.observations["critic"].terms
- assert tuple(policy) == (
- "base_ang_vel",
- "projected_gravity",
- "joint_pos",
- "joint_vel",
- "actions",
- "command",
- "gait_phase",
- )
- assert tuple(critic) == (*tuple(policy), "base_lin_vel")
- assert env_cfg.observations["policy"].enable_corruption is True
- assert env_cfg.observations["critic"].enable_corruption is False
- assert policy["base_ang_vel"].params["sensor_name"] == "torso_gyro"
- assert policy["projected_gravity"].params["sensor_name"] == "torso_upvector"
- assert policy["base_ang_vel"].scale == pytest.approx(0.25)
- assert policy["joint_vel"].scale == pytest.approx(0.05)
- assert critic["projected_gravity"].params["sensor_name"] == "torso_upvector"
- assert critic["base_lin_vel"].params["sensor_name"] == "pelvis_local_linvel"
- assert critic["base_lin_vel"].scale == pytest.approx(2.0)
- assert policy["joint_pos"].params["asset_cfg"].joint_names == list(_ACTIVE)
- assert policy["joint_vel"].params["asset_cfg"].joint_names == list(_ACTIVE)
- assert critic["joint_pos"].params["asset_cfg"].joint_names == list(_ACTIVE)
- assert critic["joint_vel"].params["asset_cfg"].joint_names == list(_ACTIVE)
- assert policy["joint_pos"].noise.n_max == pytest.approx(0.01)
- assert policy["joint_vel"].noise.n_max == pytest.approx(0.1)
- assert all(term.noise is None for term in critic.values())
-
- assert sum((3, 3, 22, 22, 22, 3, 2)) == 77
- assert sum((3, 3, 22, 22, 22, 3, 2, 3)) == 80
- assert tuple(env_cfg.rewards) == (
- "tracking_lin_vel",
- "tracking_ang_vel",
- "penalty_ang_vel_xy",
- "penalty_orientation",
- "penalty_action_rate",
- "pose",
- "penalty_feet_ori",
- "penalty_close_feet_lateral",
- "feet_phase",
- "alive",
- )
- expected_weights = {
- "tracking_lin_vel": 2.0,
- "tracking_ang_vel": 1.5,
- "penalty_ang_vel_xy": -1.0,
- "penalty_orientation": -10.0,
- "penalty_action_rate": -4.0,
- "pose": -0.5,
- "penalty_feet_ori": -20.0,
- "penalty_close_feet_lateral": -5.0,
- "feet_phase": 5.0,
- "alive": 10.0,
- }
- for name, weight in expected_weights.items():
- assert env_cfg.rewards[name].weight == pytest.approx(weight)
- assert tuple(env_cfg.rewards["pose"].params["pose_weights"]) == pytest.approx(_POSE)
- lateral = env_cfg.rewards["penalty_close_feet_lateral"]
- assert lateral.func is penalty_close_feet_lateral
- assert lateral.params["min_width"] == pytest.approx(0.18)
- assert lateral.params["sigma"] == pytest.approx(0.04)
- phase = env_cfg.rewards["feet_phase"]
- assert phase.params["swing_height"] == pytest.approx(0.11)
- assert phase.params["tracking_sigma"] == pytest.approx(0.014)
-
- curriculum = env_cfg.curriculum["penalty_scaling"]
- assert curriculum.params == {
- "initial_scale": 0.125,
- "min_scale": 0.125,
- "max_scale": 0.25,
- "level_down_threshold": 150.0,
- "level_up_threshold": 750.0,
- "degree": 0.001,
- }
-
-
-@pytest.mark.slow
-def test_t800_sac_owner_builds_and_steps_real_mujoco_env() -> None:
- pytest.importorskip("mujoco")
- cfg, _ = _materialize_owner()
- env = cast(
- Any,
- registry.make(
- "T800WalkFlat",
- sim_backend="mujoco",
- num_envs=1,
- env_cfg_override=BackendAdapter(
- cfg, root_dir=ROOT_DIR, algo_name="sac"
- ).build_task_env_cfg_override(),
- ),
- )
- try:
- obs, info = env.reset()
- assert isinstance(info, dict)
- assert env.action_space.shape == (22,)
- assert obs["obs"].shape == (1, 77)
- assert obs["critic"].shape == (1, 80)
- state = env.step(np.zeros((1, 22), dtype=np.float32))
- assert np.isfinite(state.obs["obs"]).all()
- assert np.isfinite(state.obs["critic"]).all()
- assert np.isfinite(state.reward).all()
- assert env._control.shape == (1, 25)
- assert np.isfinite(env._control).all()
- finally:
- env.close()
diff --git a/tests/scripts/test_support_matrix.py b/tests/scripts/test_support_matrix.py
index ca0dd03a7..6e38150cb 100644
--- a/tests/scripts/test_support_matrix.py
+++ b/tests/scripts/test_support_matrix.py
@@ -21,19 +21,6 @@ def test_support_matrix_marks_go2_ppo_backends_as_tested():
assert row.cells["motrix"].level == EvidenceLevel.TESTED
-def test_support_matrix_marks_t800_mujoco_tested_and_sac_mjwarp_configured_only():
- """T800 evidence stays on MuJoCo; the SAC mjwarp owner is configured but unvalidated."""
- ppo_row = _row("PPO (torch)", "t800_walk_flat")
- sac_row = _row("SAC (torch)", "t800_walk_flat")
-
- for row in (ppo_row, sac_row):
- assert row.cells["mujoco"].level == EvidenceLevel.TESTED
- assert row.cells["motrix"].level == EvidenceLevel.MISSING
-
- assert ppo_row.cells["mjwarp"].level == EvidenceLevel.REGISTERED
- assert sac_row.cells["mjwarp"].level == EvidenceLevel.CONFIGURED
-
-
def test_support_matrix_marks_validated_g1_mjwarp_entrypoints_as_tested():
torch_row = _row("PPO (torch)", "g1_walk_flat")
sac_row = _row("SAC (torch)", "g1_walk_flat")
diff --git a/tests/tasks/test_migration_matrix.py b/tests/tasks/test_migration_matrix.py
index 11a0d11b0..ea2c85947 100644
--- a/tests/tasks/test_migration_matrix.py
+++ b/tests/tasks/test_migration_matrix.py
@@ -31,7 +31,6 @@ def test_registered_tasks_have_explicit_migration_records() -> None:
("MicroduckVelocityFlat", "microduck_locomotion", "complete", "Compatible"),
("MicroduckGroundPickFlat", "microduck_locomotion", "mba", "Adapted"),
("MicroduckSitStandFlat", "microduck_locomotion", "mba", "Adapted"),
- ("T800WalkFlat", "t800_locomotion", "complete", "Compatible"),
("Go2JoystickRough", "quadruped_rough", "complete", "Compatible"),
],
)
diff --git a/tests/tasks/test_package_boundary.py b/tests/tasks/test_package_boundary.py
index e324d56d5..4f3476cca 100644
--- a/tests/tasks/test_package_boundary.py
+++ b/tests/tasks/test_package_boundary.py
@@ -20,7 +20,6 @@
"unilab.tasks.locomotion.go2_arm",
"unilab.tasks.locomotion.a2",
"unilab.tasks.locomotion.microduck",
- "unilab.tasks.locomotion.t800",
"unilab.tasks.manipulation.allegro_inhand",
"unilab.tasks.manipulation.sharpa_inhand",
"unilab.tasks.manipulation.stewart",
diff --git a/tests/tasks/test_production_registry_closeout.py b/tests/tasks/test_production_registry_closeout.py
index e9e12b078..de1771d88 100644
--- a/tests/tasks/test_production_registry_closeout.py
+++ b/tests/tasks/test_production_registry_closeout.py
@@ -40,9 +40,6 @@
# Approved wrapper: cold-path MicroDuck STL resolution before delegating
# to the generic factory.
("unilab.tasks.locomotion.microduck", "make_microduck_velocity_env"),
- # Approved wrapper: cold-path T800 OBJ/texture resolution before
- # delegating to the generic factory.
- ("unilab.tasks.locomotion.t800", "make_t800_walk_env"),
)
_SNAPSHOT_CODE = textwrap.dedent(
diff --git a/tests/test_pull_assets.py b/tests/test_pull_assets.py
index 82336fea3..a96f61744 100644
--- a/tests/test_pull_assets.py
+++ b/tests/test_pull_assets.py
@@ -16,37 +16,6 @@ def _populate(directory: Path, *, suffix: str, count: int) -> Path:
return directory
-def test_pull_assets_t800_resolves_both_asset_directories(
- monkeypatch: pytest.MonkeyPatch,
- tmp_path: Path,
- capsys: pytest.CaptureFixture[str],
-):
- targets = {
- "robots/t800/assets": _populate(tmp_path / "assets", suffix=".obj", count=26),
- "robots/t800/textures": _populate(tmp_path / "textures", suffix=".png", count=15),
- }
- calls: list[tuple[str, str, bool]] = []
-
- def fake_resolver(directory: str, *, marker: str, show_progress: bool) -> Path:
- calls.append((directory, marker, show_progress))
- return targets[directory]
-
- monkeypatch.setattr(pull_assets, "resolve_robot_asset_dir", fake_resolver)
-
- assert pull_assets.main(["--robot", "t800"]) == 0
- assert calls == [
- ("robots/t800/assets", "LINK_BASE.obj", False),
- ("robots/t800/textures", "LINK_BASE.png", False),
- ]
- output = capsys.readouterr().out
- assert "26 OBJ files" in output
- assert "15 PNG files" in output
- lines = output.strip().splitlines()
- assert len(lines) == 2
- assert lines[0] == "Downloading t800 assets ..."
- assert lines[-1].startswith("Robot assets ready: 1 robots, 2 directories, 41 files")
-
-
def test_pull_assets_microduck_keeps_single_stl_directory(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,