Claude
Cursor
Skill
test-assets
Choose and manage reproducible worlds, models, datasets, and recordings for robotics tests.
Virus-scanned
Reviewed automatically before listing.
Download
robium-ai-robium-skills_test-assets-498ea4e.zip · 10 KB
Install
skills CLI
npx skills add https://github.com/robium-ai/robium/tree/main/skills/test-assets
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install robium-ai-robium@llmmart
Git
git clone https://github.com/robium-ai/robium.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole robium-ai/robium collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Test assets
A fixture earns its place by being a pinned input with a known consumer, not by being realistic or popular.
Choose what the test needs
- Start from the behavior under test, then select the smallest representative world, model, recording, dataset slice, checkpoint, or golden.
- Prefer a recognizable public asset when it matches the behavior. It gives reviewers context and avoids maintaining an equivalent private fixture.
- Add an asset only when an active test consumes it. Record the upstream revision, license, verification method, entrypoint, and derivation.
- Reuse outputs from verified scenarios when useful: a generated map or bag can become the pinned input to the next layer.
Choose how it is owned
- Use a pinned pointer for large or independently maintained assets. Verify an immutable revision and checksum, and cache the fetched bytes.
- Vendor only when the test must survive upstream availability or inspect the exact bytes, redistribution is allowed, and the project has a stated size budget.
- Generate project-specific bags and goldens from a committed, seeded producer when no canonical source exists.
- Compare re-simulated behavior with tolerance bands. Reserve byte equality for pure replay or deterministic transforms.
Go deeper only when needed
- To select a known world, robot model, dataset, or recording, read references/canonical-assets.md and verify its dated evidence against the current source before adoption.
- To define a project catalog, manifest, slice, or golden, read references/test-assets-layout.md.
- Use examples/catalog.yaml only as a proven catalog shape, not as a universal asset set.
- Use scripts/fetch_assets.py only for checksum-pinned pointer archives that match its supported manifest shape.
- Testing owns the test layers and pass bars. Data owns training-data sourcing; the simulator and Hub skills own loading and transfer mechanics.
Done
- Every fixture has an active consumer, reproducible source or generator, license evidence, and an assertion whose tolerance matches how the fixture is produced.
Files (robium)
-
examples
-
worlds
-
aws-small-house
-
asset.yaml 944 B
schema_version: "1" id: world.aws-small-house kind: world name: AWS RoboMaker Small House revision: "1" storage: pointer license: id: MIT file: LICENSE url: https://github.com/aws-robotics/aws-robomaker-small-house-world/blob/ff9631ca6d1db9c1ba656498151464b5ab74aafe/LICENSE verification: date: "2026-08-27" method: Clean-cache archive fetch, SHA-256 verification, entrypoint check, and pinned-license byte comparison. source: type: git-archive repository: https://github.com/aws-robotics/aws-robomaker-small-house-world revision: ff9631ca6d1db9c1ba656498151464b5ab74aafe url: https://github.com/aws-robotics/aws-robomaker-small-house-world/archive/ff9631ca6d1db9c1ba656498151464b5ab74aafe.tar.gz sha256: e459bd9d7bdabdfc40f8afc6770ceb1d774316e5da94a1048f036baa7388b2d9 archive: tar.gz strip_prefix: aws-robomaker-small-house-world-ff9631ca6d1db9c1ba656498151464b5ab74aafe entrypoints: world: worlds/small_house.world -
LICENSE 931 B · in bundle
-
-
tugbot-warehouse
-
asset.yaml 778 B
schema_version: "1" id: world.tugbot-warehouse kind: world name: Tugbot in Warehouse revision: "1" storage: pointer license: id: CC-BY-NC-ND-4.0 file: LICENSE url: https://creativecommons.org/licenses/by-nc-nd/4.0/ verification: date: "2026-08-27" method: Clean-cache Fuel archive fetch, SHA-256 verification, entrypoint check, and Fuel world-details license check. source: type: fuel-world-zip repository: https://fuel.gazebosim.org/1.0/OpenRobotics/worlds/Tugbot%20in%20Warehouse revision: "2" url: https://fuel.gazebosim.org/1.0/OpenRobotics/worlds/Tugbot%20in%20Warehouse/2/Tugbot%20in%20Warehouse.zip sha256: 22af262814fe01326723b4e21457869470d1d3aaa10db7abc47e3536d13adfbb archive: zip strip_prefix: null entrypoints: world: tugbot_warehouse.sdf -
LICENSE 499 B · in bundle
-
-
-
catalog.yaml 412 B
# status: verified 2026-08-27 against the active robium-apps Robot Navigation catalog schema_version: "1" assets: - id: world.aws-small-house kind: world name: AWS RoboMaker Small House storage: pointer manifest: worlds/aws-small-house/asset.yaml - id: world.tugbot-warehouse kind: world name: Tugbot in Warehouse storage: pointer manifest: worlds/tugbot-warehouse/asset.yaml
-
-
references
-
canonical-assets.md 5.8 KB
# Canonical test assets: the verified catalog Well-known public assets to test robotics apps against: worlds, robot models, datasets, and recordings. Every entry carries a citation line saying how and when it was verified; keep that discipline when adding entries (see the end of this file). Licenses shown were read from the upstream API/card on the verification date; re-confirm at adoption time before vendoring. ## Worlds | Asset | Upstream | License | Canonical for | Verified | |---|---|---|---|---| | TurtleBot3 House | [ROBOTIS-GIT/turtlebot3_simulations](https://github.com/ROBOTIS-GIT/turtlebot3_simulations) | Apache-2.0 | Indoor/house nav testing; the Nav2-tutorial-canonical world | GitHub API 2026-07-18: 515★, pushed 2025-07-14, SPDX Apache-2.0 | | Tugbot in Warehouse (OpenRobotics) | [Gazebo Fuel](https://app.gazebosim.org/OpenRobotics/fuel/worlds/Tugbot%20in%20Warehouse) | CC-BY-NC-ND-4.0; pointer only | Industrial/warehouse scenes in modern gz | Fuel world-details API + clean-cache version 2 ZIP SHA/entrypoint check, 2026-08-27 | | AWS RoboMaker Small House | [aws-robotics/aws-robomaker-small-house-world](https://github.com/aws-robotics/aws-robomaker-small-house-world) | MIT at pinned commit | Richer furnished multi-room house; **Gazebo-Classic era: verify modern-gz load before adopting** | Commit `ff9631ca6d1db9c1ba656498151464b5ab74aafe`; pinned license byte comparison + clean-cache archive SHA/entrypoint check, 2026-08-27 | | AWS RoboMaker Small Warehouse | [aws-robotics/aws-robomaker-small-warehouse-world](https://github.com/aws-robotics/aws-robomaker-small-warehouse-world) | check repo at adoption | The most-recognized open warehouse world; **same Gazebo-Classic caveat** | GitHub API 2026-07-18: 487★, last pushed 2024-07-26 | ## Robot models | Asset | Upstream | License | Canonical for | Verified | |---|---|---|---|---| | TurtleBot3 burger/waffle | [ROBOTIS-GIT/turtlebot3_simulations](https://github.com/ROBOTIS-GIT/turtlebot3_simulations) | Apache-2.0 | Mobile-base/Nav2 testing | GitHub API 2026-07-18 (same repo as house world) | | Unitree Go2 (MJCF) | [google-deepmind/mujoco_menagerie](https://github.com/google-deepmind/mujoco_menagerie), dir `unitree_go2` | per-model (repo SPDX: NOASSERTION; check the model's own license file) | Quadruped testing in MuJoCo | GitHub API 2026-07-18: dir listing confirms unitree_go2 | | Unitree Go2/G1 (URDF, ROS) | [unitreerobotics/unitree_ros](https://github.com/unitreerobotics/unitree_ros) | BSD-3-Clause | Official Unitree descriptions for ROS/Gazebo use | GitHub API 2026-07-18: 1,477★, pushed 2026-07-08, SPDX BSD-3-Clause | | Unitree G1 (MJCF) | mujoco_menagerie dir `unitree_g1`; also [unitreerobotics/unitree_mujoco](https://github.com/unitreerobotics/unitree_mujoco) (BSD-3-Clause) | per-model (Menagerie) | Humanoid testing in MuJoCo | GitHub API 2026-07-18: dir listing confirms unitree_g1; unitree_mujoco 1,089★ | | SO-101 arm (MJCF) | [google-deepmind/mujoco_menagerie](https://github.com/google-deepmind/mujoco_menagerie), dir `robotstudio_so101`; derived from [TheRobotStudio/SO-ARM100](https://github.com/TheRobotStudio/SO-ARM100), `Simulation/SO101/` | Apache-2.0 | The LeRobot-ecosystem arm in MuJoCo; Menagerie adds manipulation collision geometry, a camera mount, and ready scenes | GitHub contents API and both upstream READMEs on 2026-09-07 at Menagerie commit `8161bba264d7fa7c99ca301e91e7fb44737676ad`; model requires MuJoCo 3.1.3+ | ## Datasets | Asset | Upstream | License | Canonical for | Verified | |---|---|---|---|---| | svla_so101_pickplace | [lerobot/svla_so101_pickplace](https://huggingface.co/datasets/lerobot/svla_so101_pickplace) | Apache-2.0 (card tag) | The canonical SO-101 sample: official LeRobot org, SmolVLA-tutorial dataset | HF API 2026-07-18: 42 likes (most-liked SO-101 dataset); every higher-download alternative is a 0-like community upload. Runner-up (sim-MuJoCo shape, inspect before trust): szk1ck/so101-pickplace-sim-mujoco | | pusht | [lerobot/pusht](https://huggingface.co/datasets/lerobot/pusht) | MIT (card tag) | The CI-sized LeRobot standard for train-smoke tests | HF API 2026-07-18: 18,181 downloads, 54 likes | ## Recordings (replay fixtures) | Asset | Upstream | License | Canonical for | Verified | |---|---|---|---|---| | EuRoC MAV sequences | [ETH ASL dataset page](https://projects.asl.ethz.ch/datasets/doku.php?id=kmavvisualinertialdatasets) | check page at adoption | Canonical VIO/drone rosbags that "just replay" | HTTP 200 on 2026-07-18; re-verify format/license at adoption | | TUM RGB-D sequences | [TUM CVG dataset page](https://cvg.cit.tum.de/data/datasets/rgbd-dataset) | check page at adoption | The RGB-D SLAM classic, rosbag downloads | HTTP 200 on 2026-07-18; re-verify at adoption | | Foxglove sample MCAPs | [foxglove.dev/examples](https://foxglove.dev/examples) | check page at adoption | Viz-tooling scenarios (foxglove/rerun skills) | HTTP 200 on 2026-07-18; re-verify at adoption | ## Known gaps - **Nav2/TB3 rosbag search was inconclusive:** a 2026-07-18 public-source/API search did not identify a canonical bag suitable for Robium's navigation regression. Self-recording from a seeded scenario was the practical fallback, not proof that no suitable public bag exists. Search again before generating a new fixture. - **Drone assets unpicked**: no vertical yet (px4 is future work); the matrix row is an honest gap, not an oversight. - **Legged robots have models here but no robium skill coverage**: Go2/G1 are test assets awaiting a future legged vertical. ## Adding an entry 1. Verify the asset against its live source (API call, direct fetch), never from memory, and write the citation line with method + date. 2. Read the license from the upstream repo/card at adoption time; an asset whose license forbids redistribution stays pointer-mode only. 3. State what the asset is canonical *for*; an entry without a clear testing role doesn't belong in the catalog. -
test-assets-layout.md 2.7 KB
# The standard test-assets layout Use one small catalog plus one manifest directory per asset. Pointer mode is the default for large worlds, models, and datasets: Git owns metadata and license evidence, while an ignored cache holds downloaded bytes. ## Layout test-assets/ README.md catalog.yaml scripts/fetch_assets.py worlds/ example-world/ asset.yaml LICENSE cache/ # ignored in pointer mode datasets/ # per-asset manifests follow the same pattern bags/ # seeded project recordings, not arbitrary downloads goldens/ # tolerance-band outputs from verified scenarios ## Catalog schema schema_version: "1" assets: - id: world.example kind: world name: Example World storage: pointer manifest: worlds/example-world/asset.yaml IDs are stable and names are human-facing. The resolver verifies that the catalog and manifest agree on `id`, `kind`, `name`, and `storage`. ## Per-asset manifest schema schema_version: "1" id: world.example kind: world name: Example World revision: "1" # local schema revision storage: pointer license: id: SPDX-ID file: LICENSE # checked-in evidence beside asset.yaml url: https://upstream.example/license verification: date: "2026-08-27" method: Clean-cache archive fetch, SHA-256, entrypoint, and license check. source: type: git-archive repository: https://upstream.example/project revision: immutable-upstream-revision url: https://upstream.example/archive.tar.gz sha256: 64-lowercase-hex-characters archive: tar.gz # tar.gz | zip strip_prefix: project-revision entrypoints: world: worlds/example.world Rules: - Pin an immutable upstream revision and the exact archive SHA-256. A branch, `latest`, or download URL without a checksum is not a pointer lock. - Keep a license record next to every manifest. Compare it with the pinned upstream source at adoption time; restrictive assets remain pointer-only. - Record the verification date and concrete method. A clean-cache fetch must validate safe extraction and every declared entrypoint. - Do not add a fixture without an active application or test consumer. - Vendored mode may commit bytes only under a documented size budget and with redistribution terms that permit it. ## Dataset slices and goldens - Take deterministic slices, such as the first N episodes, and record N and the source revision. Confirm the reduced dataset still loads. - Compare simulated goldens with explicit tolerances and seeds. Exact byte checks apply only to pure replay, not re-run physics.
-
-
scripts
-
fetch_assets.py 7.5 KB
#!/usr/bin/env python3 # /// script # requires-python = ">=3.12" # dependencies = ["pyyaml>=6,<7"] # /// """Fetch checksum-pinned pointer assets from a test-assets catalog.""" from __future__ import annotations import argparse import hashlib import re import shutil import stat import sys import tarfile import tempfile import urllib.request import zipfile from pathlib import Path, PurePosixPath import yaml def fail(message: str) -> ValueError: return ValueError(message) def mapping(value: object, label: str) -> dict: if not isinstance(value, dict): raise fail(f"{label} must be a mapping") return value def relative_path(value: object, label: str) -> Path: if not isinstance(value, str) or not value: raise fail(f"{label} must be a non-empty relative path") path = PurePosixPath(value) if path.is_absolute() or ".." in path.parts: raise fail(f"{label} must be a safe relative path") return Path(*path.parts) def load_catalog(path: Path) -> list[dict]: catalog = mapping(yaml.safe_load(path.read_text()), "catalog") if catalog.get("schema_version") != "1": raise fail("unsupported catalog schema_version") entries = catalog.get("assets") if not isinstance(entries, list) or not entries: raise fail("catalog assets must be a non-empty list") assets: list[dict] = [] seen: set[str] = set() for entry_value in entries: entry = mapping(entry_value, "catalog asset") asset_id = str(entry.get("id", "")) if not asset_id or asset_id in seen: raise fail(f"missing or duplicate asset id: {asset_id!r}") seen.add(asset_id) manifest_path = path.parent / relative_path(entry.get("manifest"), "manifest") manifest = mapping(yaml.safe_load(manifest_path.read_text()), asset_id) for field in ("id", "kind", "name", "storage"): if manifest.get(field) != entry.get(field): raise fail(f"{asset_id}: catalog/manifest {field} mismatch") if manifest.get("storage") != "pointer": raise fail(f"{asset_id}: resolver accepts pointer assets only") if not asset_id.startswith(f"{entry.get('kind')}."): raise fail(f"{asset_id}: id prefix must match kind") license_data = mapping(manifest.get("license"), f"{asset_id}.license") if not license_data.get("id") or not license_data.get("url"): raise fail(f"{asset_id}: license id and upstream URL are required") license_path = manifest_path.parent / relative_path( license_data.get("file"), f"{asset_id}.license.file" ) if not license_path.is_file(): raise fail(f"{asset_id}: missing license evidence {license_path}") verification = mapping(manifest.get("verification"), f"{asset_id}.verification") if not verification.get("date") or not verification.get("method"): raise fail(f"{asset_id}: dated verification method is required") if re.fullmatch(r"\d{4}-\d{2}-\d{2}", str(verification["date"])) is None: raise fail(f"{asset_id}: verification date must use YYYY-MM-DD") source = mapping(manifest.get("source"), f"{asset_id}.source") if not all(source.get(key) for key in ("repository", "revision", "url", "sha256", "archive")): raise fail(f"{asset_id}: source provenance is incomplete") digest = str(source["sha256"]) if len(digest) != 64 or any(c not in "0123456789abcdef" for c in digest): raise fail(f"{asset_id}: sha256 must be 64 lowercase hex characters") entrypoints = mapping(manifest.get("entrypoints"), f"{asset_id}.entrypoints") if not entrypoints: raise fail(f"{asset_id}: at least one entrypoint is required") manifest["_manifest_path"] = manifest_path assets.append(manifest) return assets def safe_members(names: list[str]) -> None: for name in names: path = PurePosixPath(name) if path.is_absolute() or ".." in path.parts: raise fail(f"unsafe archive member: {name}") def fetch(asset: dict, destination: Path) -> None: asset_id = asset["id"] source = asset["source"] target = destination / asset_id with tempfile.TemporaryDirectory(prefix="robium-asset-") as temp_name: temp = Path(temp_name) archive_path = temp / "asset.archive" request = urllib.request.Request(source["url"], headers={"User-Agent": "robium-assets/1"}) digest = hashlib.sha256() with urllib.request.urlopen(request) as response, archive_path.open("wb") as output: while chunk := response.read(1024 * 1024): digest.update(chunk) output.write(chunk) actual = digest.hexdigest() if actual != source["sha256"]: raise fail(f"{asset_id}: checksum mismatch: expected {source['sha256']}, got {actual}") extracted = temp / "extracted" extracted.mkdir() if source["archive"] == "tar.gz": with tarfile.open(archive_path, "r:gz") as archive: members = archive.getmembers() safe_members([member.name for member in members]) if any(member.issym() or member.islnk() for member in members): raise fail(f"{asset_id}: archive contains links") archive.extractall(extracted, members=members, filter="data") elif source["archive"] == "zip": with zipfile.ZipFile(archive_path) as archive: members = archive.infolist() safe_members([member.filename for member in members]) if any(stat.S_ISLNK(member.external_attr >> 16) for member in members): raise fail(f"{asset_id}: archive contains links") archive.extractall(extracted) else: raise fail(f"{asset_id}: unsupported archive {source['archive']!r}") root = extracted if source.get("strip_prefix"): root = extracted / relative_path(source["strip_prefix"], "strip_prefix") if not root.is_dir(): raise fail(f"{asset_id}: strip_prefix not found") for label, value in asset["entrypoints"].items(): if not (root / relative_path(value, f"entrypoint {label}")).is_file(): raise fail(f"{asset_id}: missing entrypoint {label}: {value}") if target.exists(): shutil.rmtree(target) target.parent.mkdir(parents=True, exist_ok=True) shutil.copytree(root, target) print(f"{asset_id}: verified {source['sha256']} -> {target}") def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("asset_ids", nargs="*") parser.add_argument("--catalog", type=Path, default=Path("test-assets/catalog.yaml")) parser.add_argument("--destination", type=Path, default=Path("test-assets/cache")) parser.add_argument("--list", action="store_true") args = parser.parse_args() try: assets = load_catalog(args.catalog) if args.list: print("\n".join(asset["id"] for asset in assets)) return 0 selected = set(args.asset_ids) unknown = selected - {asset["id"] for asset in assets} if unknown: raise fail(f"unknown asset ids: {', '.join(sorted(unknown))}") for asset in assets: if not selected or asset["id"] in selected: fetch(asset, args.destination) return 0 except (OSError, ValueError, yaml.YAMLError) as error: print(f"error: {error}", file=sys.stderr) return 1 if __name__ == "__main__": raise SystemExit(main())
-
-
SKILL.md 2.3 KB
--- name: test-assets description: Choose and manage reproducible worlds, models, datasets, and recordings for robotics tests. --- # Test assets A fixture earns its place by being a pinned input with a known consumer, not by being realistic or popular. ## Choose what the test needs - Start from the behavior under test, then select the smallest representative world, model, recording, dataset slice, checkpoint, or golden. - Prefer a recognizable public asset when it matches the behavior. It gives reviewers context and avoids maintaining an equivalent private fixture. - Add an asset only when an active test consumes it. Record the upstream revision, license, verification method, entrypoint, and derivation. - Reuse outputs from verified scenarios when useful: a generated map or bag can become the pinned input to the next layer. ## Choose how it is owned - Use a pinned pointer for large or independently maintained assets. Verify an immutable revision and checksum, and cache the fetched bytes. - Vendor only when the test must survive upstream availability or inspect the exact bytes, redistribution is allowed, and the project has a stated size budget. - Generate project-specific bags and goldens from a committed, seeded producer when no canonical source exists. - Compare re-simulated behavior with tolerance bands. Reserve byte equality for pure replay or deterministic transforms. ## Go deeper only when needed - To select a known world, robot model, dataset, or recording, read [references/canonical-assets.md](references/canonical-assets.md) and verify its dated evidence against the current source before adoption. - To define a project catalog, manifest, slice, or golden, read [references/test-assets-layout.md](references/test-assets-layout.md). - Use [examples/catalog.yaml](examples/catalog.yaml) only as a proven catalog shape, not as a universal asset set. - Use [scripts/fetch_assets.py](scripts/fetch_assets.py) only for checksum-pinned pointer archives that match its supported manifest shape. - Testing owns the test layers and pass bars. Data owns training-data sourcing; the simulator and Hub skills own loading and transfer mechanics. ## Done - Every fixture has an active consumer, reproducible source or generator, license evidence, and an assertion whose tolerance matches how the fixture is produced.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.