Add WarpFrontend bridge: run any stable manager-based task on the warp runtime via --manager=warp#5504
Add WarpFrontend bridge: run any stable manager-based task on the warp runtime via --manager=warp#5504hujc7 wants to merge 9 commits intoisaac-sim:developfrom
Conversation
PR isaac-sim#5297 (Decouple Renderer from Camera) replaced `sim.get_setting('/isaaclab/visualizer')` (returned a comma-separated string) with `sim.has_active_visualizers()` plus per-type queries; the warp env path was missed, so it crashes with "AttributeError: 'dict' object has no attribute 'split'" during env construction. Mirror the stable env's pattern: - `has_active_visualizers()` for the gating predicate - `has_kit()` so kitless Newton-only runs (`--viz rerun`) skip ViewportCameraController
Warp's ObservationTermCfg / RewardTermCfg / TerminationTermCfg had no behavioural difference from their stable counterparts — same fields, same base class. The override only carried Warp-first docstrings while keeping the classes as siblings of stable's, which broke `isinstance(stable_term, warp.TermCfg)` checks inside the experimental managers when a stable cfg is fed through the warp runtime. Re-export stable's term cfg classes directly. Term funcs still follow the warp-first `func(env, out, **params)` signature at runtime; the type annotation just lives on stable's class instead of warp's.
Lets a stable manager-based RL env cfg run on the experimental warp runtime without a separate warp task registration. Replaces the duplicate per-robot warp env cfgs by adapting the stable cfg in place. Components: - isaaclab_experimental/envs/warp_frontend.py: 'WarpFrontend' walks the stable cfg, swaps each `term.func` to its same-named warp twin (only accepting candidates whose `__module__` lives under the warp packages — the warp mdp module re-exports stable terms via `from … import *`, so a naive `getattr` would silently keep the stable function), swaps each action `class_type`, picks the `newton` field of any PresetCfg, drops sensors with no warp counterpart (`height_scanner`), and in-place class-promotes `SceneEntityCfg` instances so warp kernels see the `joint_mask` / *\_ids_wp` cached fields. A `WarpAdaptReport` records every term that had no warp twin and surfaces them via the logger; `strict=True` makes the same condition raise `LookupError` instead. - scripts/reinforcement_learning/rsl_rl/train.py: '--manager=warp' flag routes the constructed env through `WarpFrontend.build` instead of `gym.make`. The flag also auto-injects `presets=newton` into Hydra's argv so that PresetCfg wrappers resolve to the newton preset (Hydra's preset resolution runs *before* the adapter). Validated: cartpole and Anymal-D Flat both pass a 3-way comparison — - 'Isaac-Cartpole-v0' (stable manager): trains. - 'Isaac-Cartpole-Warp-v0' (existing direct path): reward 0.06 / ep 76. - 'Isaac-Cartpole-v0 --manager=warp': reward 0.06 / ep 76 (matches direct exactly). - 'Isaac-Velocity-Flat-Anymal-D-v0 presets=newton': -8.45 / 191. - 'Isaac-Velocity-Flat-Anymal-D-Warp-v0': -7.47 / 168. - 'Isaac-Velocity-Flat-Anymal-D-v0 --manager=warp': -7.69 / 174 (within run-to-run variance of the direct warp path). Both stable and warp frontends remain functional for every task; this PR adds a flag-based selector without removing the existing direct warp registrations.
Adds the isaaclab_experimental changelog fragment for the WarpFrontend adapter and the --manager flag, and applies the ruff-format pass that the pre-commit hook produced on warp_frontend.py.
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
This PR introduces a WarpFrontend adapter that allows stable manager-based RL task configs to run on the experimental warp runtime without requiring parallel -Warp-v0 task registrations. The implementation mutates configs in-place to swap stable MDP functions with warp twins, upgrades SceneEntityCfg instances, and drops unsupported sensors. The approach is architecturally sound for reducing task duplication, but there are several correctness issues that need attention before shipping.
Architecture Impact
The changes are relatively self-contained within isaaclab_experimental, with the main integration point being rsl_rl/train.py. The adapter operates at config mutation time before env construction, so it doesn't change the warp runtime itself. However:
- The
manager_term_cfg.pychange from custom classes to re-exports affects any code doingisinstancechecks against the warp term cfg types - The
--manager=warpflag is only added torsl_rl/train.py, not other trainers (skrl, rl_games) — documented as out of scope but creates API inconsistency - The in-place
__class__mutation pattern in_upgrade_scene_entity_cfgsis fragile if the warpSceneEntityCfgever diverges structurally from stable
Implementation Verdict
Minor fixes needed — The core design is solid, but there are edge cases and correctness issues that should be addressed.
Test Coverage
Insufficient. The PR adds no automated tests. The validation is entirely manual (described in PR description). Given the complexity of the adapter logic (module resolution, class swapping, term iteration), this needs at minimum:
- Unit tests for
WarpFrontend.adapt()with mock configs - Regression test for the visualizer fix in
manager_based_env_warp.py - Integration test that the
--manager=warpflag correctly invokes the adapter
CI Status
No CI checks available yet — cannot assess whether existing tests pass.
Findings
🔴 Critical: warp_frontend.py:278-287 — In-place __class__ mutation is unsafe without verifying inheritance
The code does obj.__class__ = _WarpSE after checking isinstance(obj, _StableSE), but if _WarpSE doesn't actually inherit from _StableSE (which the code assumes but doesn't verify), this will corrupt the object. The comment claims "warp inherits stable" but there's no runtime assertion. If someone refactors the warp SceneEntityCfg to not inherit from stable, this silently corrupts configs.
# Should verify at import time:
if not issubclass(_WarpSE, _StableSE):
raise TypeError("WarpFrontend requires warp SceneEntityCfg to inherit from stable")🔴 Critical: warp_frontend.py:95-97 — Iterating over dir(group) while mutating attributes
The apply_to method iterates over dir(group) and calls setattr(group, name, None) to drop terms. While this doesn't crash because the iteration is over a snapshot from dir(), it means subsequent code that relies on attribute presence (like the warp manager's term resolution) will see None values rather than missing attributes. The warp managers need to handle None term entries, or this should use delattr.
🟡 Warning: warp_frontend.py:316-329 — Module resolution swallows all exceptions
try:
import gymnasium as gym
entry = gym.spec(task_id).kwargs.get("env_cfg_entry_point")
except Exception:
entry = NoneThis catches all exceptions including KeyboardInterrupt and SystemExit. Should be except (ImportError, KeyError, AttributeError): or similar to avoid masking real errors.
🟡 Warning: train.py:100-101 — Preset injection doesn't check for conflicting explicit presets
if args_cli.manager == "warp" and not any(a.startswith("presets=") for a in remaining_args):
remaining_args.append("presets=newton")If user passes presets=physx, this silently does nothing (doesn't append newton), but the warp adapter may still try to run and fail confusingly later. Should warn when --manager=warp is used with a non-newton preset.
🟡 Warning: warp_frontend.py:215-217 — Physics preset extraction logic is redundant
The comment says "by the time we run, Hydra's resolve_presets has already collapsed any PresetCfg wrapper" — but then the code checks hasattr(physics, "newton") anyway. If the preset is already resolved, this condition will always be False. This is dead code that adds confusion.
🔵 Improvement: warp_frontend.py:170-177 — DEFAULT_TERM_GROUPS misses commands group
The default term groups don't include ("commands",), which is a manager group in the stable env. If a task has command terms with SceneEntityCfg params, they won't be upgraded. Either add it or document why it's excluded.
🔵 Improvement: manager_term_cfg.py — Re-export change may break type annotations
The change from explicit @configclass definitions to from ... import * means type checkers can no longer see what's exported from this module. Code doing from isaaclab_experimental.managers.manager_term_cfg import RewardTermCfg will work at runtime but may fail type checking. Consider adding explicit __all__ or type stub.
🔵 Improvement: warp_frontend.py:256-261 — build() ignores the adapt report
The build() method calls adapt() but doesn't return or expose the report. If the caller wants to inspect what was dropped/missing, they have to call adapt() separately first. Consider returning a tuple (env, report) or attaching the report to the env.
Greptile SummaryThis PR introduces
Confidence Score: 3/5The bridge logic and the visualizer fix are correct, but the warp path in train.py does not forward render_mode, so combining --video with --manager=warp will fail at the RecordVideo wrapper step. Two related files (warp_frontend.py and train.py) both omit render_mode from the warp env construction, breaking the --video code path that the stable branch handles correctly. The missing-term report is also silently discarded by build(), making it harder to detect incomplete adaptations programmatically. warp_frontend.py (build signature, exception handling in _mdp_modules_for) and train.py (render_mode omission in the warp branch) Important Files Changed
Sequence DiagramsequenceDiagram
participant CLI as rsl_rl/train.py
participant WF as WarpFrontend
participant NS as _NameSwap
participant Env as ManagerBasedRLEnvWarp
CLI->>CLI: parse --manager=warp
CLI->>CLI: inject presets=newton into remaining_args
CLI->>WF: WarpFrontend().build(env_cfg, task_id)
WF->>WF: adapt(cfg, task_id)
WF->>WF: resolve PresetCfg to newton
WF->>WF: drop unsupported sensors
WF->>WF: _upgrade_scene_entity_cfgs(cfg)
WF->>WF: _mdp_modules_for(task_id)
WF->>NS: apply_to(obs/reward/term/event groups)
NS-->>NS: _resolve_twin(name) via __module__ check
NS-->>WF: swapped funcs or drop/raise
WF->>NS: apply_to(actions) class_type swap always strict
NS-->>WF: swapped class_type or LookupError
WF->>WF: log WarpAdaptReport report discarded
WF->>Env: ManagerBasedRLEnvWarp(cfg=cfg) render_mode missing
Env-->>CLI: env
CLI->>CLI: RecordVideo(env) if --video fails without render_mode
Reviews (1): Last reviewed commit: "Add changelog and apply ruff formatting ..." | Re-trigger Greptile |
| def build(self, cfg: Any, task_id: str): | ||
| """Adapt ``cfg`` and return a :class:`ManagerBasedRLEnvWarp` instance. | ||
|
|
||
| Always logs the missing-twin report (if any). With ``strict=True``, | ||
| any missing twin raises before the env is constructed. | ||
| """ | ||
| # Lazy: this is the first warp-lib load. Caller must already be inside | ||
| # the SimulationApp context, i.e. inside ``launch_simulation``. | ||
| from isaaclab_experimental.envs import ManagerBasedRLEnvWarp | ||
|
|
||
| self.adapt(cfg, task_id) | ||
| return ManagerBasedRLEnvWarp(cfg=cfg) |
There was a problem hiding this comment.
WarpFrontend.build always constructs ManagerBasedRLEnvWarp with render_mode=None, but ManagerBasedRLEnvWarp.__init__ accepts render_mode and stores it. When --video is combined with --manager=warp, gym.wrappers.RecordVideo is applied to the returned env, but because render_mode was never set to "rgb_array", gymnasium will raise an error. The warp path silently drops the flag that the stable path (gym.make(..., render_mode="rgb_array" if args_cli.video else None)) correctly sets.
| def build(self, cfg: Any, task_id: str): | |
| """Adapt ``cfg`` and return a :class:`ManagerBasedRLEnvWarp` instance. | |
| Always logs the missing-twin report (if any). With ``strict=True``, | |
| any missing twin raises before the env is constructed. | |
| """ | |
| # Lazy: this is the first warp-lib load. Caller must already be inside | |
| # the SimulationApp context, i.e. inside ``launch_simulation``. | |
| from isaaclab_experimental.envs import ManagerBasedRLEnvWarp | |
| self.adapt(cfg, task_id) | |
| return ManagerBasedRLEnvWarp(cfg=cfg) | |
| def build(self, cfg: Any, task_id: str, render_mode: str | None = None): | |
| """Adapt ``cfg`` and return a :class:`ManagerBasedRLEnvWarp` instance. | |
| Always logs the missing-twin report (if any). With ``strict=True``, | |
| any missing twin raises before the env is constructed. | |
| """ | |
| # Lazy: this is the first warp-lib load. Caller must already be inside | |
| # the SimulationApp context, i.e. inside ``launch_simulation``. | |
| from isaaclab_experimental.envs import ManagerBasedRLEnvWarp | |
| self.adapt(cfg, task_id) | |
| return ManagerBasedRLEnvWarp(cfg=cfg, render_mode=render_mode) |
| # SimulationApp is already alive avoids racing pxr extension init. | ||
| from isaaclab_experimental.envs.warp_frontend import WarpFrontend | ||
|
|
||
| env = WarpFrontend().build(env_cfg, task_id=args_cli.task) |
There was a problem hiding this comment.
The warp env is built without
render_mode, so gym.wrappers.RecordVideo applied on line 222 will fail when --video --manager=warp is used. Pass the same render_mode the stable path uses.
| env = WarpFrontend().build(env_cfg, task_id=args_cli.task) | |
| env = WarpFrontend().build(env_cfg, task_id=args_cli.task, render_mode="rgb_array" if args_cli.video else None) |
| def build(self, cfg: Any, task_id: str): | ||
| """Adapt ``cfg`` and return a :class:`ManagerBasedRLEnvWarp` instance. | ||
|
|
||
| Always logs the missing-twin report (if any). With ``strict=True``, | ||
| any missing twin raises before the env is constructed. | ||
| """ | ||
| # Lazy: this is the first warp-lib load. Caller must already be inside | ||
| # the SimulationApp context, i.e. inside ``launch_simulation``. | ||
| from isaaclab_experimental.envs import ManagerBasedRLEnvWarp | ||
|
|
||
| self.adapt(cfg, task_id) | ||
| return ManagerBasedRLEnvWarp(cfg=cfg) |
There was a problem hiding this comment.
build() discards the WarpAdaptReport returned by adapt(), so callers have no programmatic access to what was dropped. In the default non-strict mode, silently-dropped terms cause the env to train with a smaller effective term set than expected. A caller who wants to enforce a "fail if anything was dropped" policy must call adapt() separately, which then re-mutates the already-mutated cfg when build() calls it again internally.
| modules: list[ModuleType] = [] | ||
| try: | ||
| import gymnasium as gym | ||
|
|
||
| entry = gym.spec(task_id).kwargs.get("env_cfg_entry_point") | ||
| except Exception: | ||
| entry = None |
There was a problem hiding this comment.
The bare
except Exception swallows any error from gymnasium.spec(task_id) — including gymnasium.error.NameNotFound if the task ID is misspelled and real programming errors. When this fires, entry is set to None and only the fallback mdp module is used, so no task-specific warp twins are discovered, causing every term to go missing without any indication of why.
The adapter is now a sequence of CompatRule objects (resolve preset, drop sensors, promote SceneEntityCfg, swap mdp funcs, swap action class). New incompatibilities are added by writing a small rule subclass instead of editing the dispatcher. The CLI flag is renamed --manager → --frontend because the dispatch also covers direct envs: a stable manager-based cfg is adapted onto ManagerBasedRLEnvWarp; a direct task is verified to point at a warp env class and dispatched via gym.make. A stable direct cfg + --frontend=warp raises IncompatibleEnvError with the offending entry_point and a hint at the *-Direct-Warp-v0 alternative. Other fixes: - Forward render_mode through build() so --video keeps working. - Attach the CompatReport on env.unwrapped.warp_compat_report so callers can inspect what was dropped or left unresolved. - Assert the warp SceneEntityCfg subclasses the stable one before doing the in-place __class__ promotion; the rule fails loudly if the hierarchy is ever broken. - Narrow the bare except in mdp-module discovery so real ImportErrors from broken cfgs propagate. - presets=newton is now only auto-injected for stable manager-based tasks; direct warp tasks (which don't carry presets) are left alone. - Warn when the user passes presets=<other> with --frontend=warp. - Add the commands group to the rule that promotes SceneEntityCfg.
The earlier check inspected gym.spec(task).entry_point, which for stable
manager-based tasks is "isaaclab.envs:ManagerBasedRLEnv" — the env class
path, not the cfg path. So the startswith("isaaclab_tasks.manager_based")
test always failed and presets=newton was never injected. Hydra then
resolved every PresetCfg in the cfg tree (physics, contact_forces, etc.)
to its default field, leaving the warp runtime with PhysX class_types it
can't load.
Switch to spec.kwargs["env_cfg_entry_point"], which actually points at the
task cfg module (e.g. "isaaclab_tasks.manager_based.locomotion.velocity.
config.anymal_d.flat_env_cfg:AnymalDFlatEnvCfg"), and the prefix check
selects the right tasks.
The single-file warp_frontend.py grew into a real subsystem worth splitting out, so move it into a frontend/ package with explicit abstractions: - frontend/base.py: Frontend ABC, CompatRule (check + transform via a unified run() method), TaskResolver (centralised gym.spec introspection -> TaskMeta), Workflow / Runtime / Severity enums, Issue / Change / Report record types, register_frontend / get_frontend registry. Helpers walk_attrs / resolve_warp_twin / iter_term_attrs are shared utilities used by rules. - frontend/torch.py: TorchFrontend, the default. Pass-through to gym.make with one rule (WarnIfTaskIsWarpRegistered) for the contradiction case. - frontend/warp.py: WarpFrontend with the full rule pipeline. Includes a new CheckPhysicsIsNewton blocking rule that surfaces the PhysX-with-warp incompatibility (asset class_type strings resolve to isaaclab_physx.* classes that depend on omni.physics.tensors.api, which the warp runtime does not initialise). CLI: rename --frontend stable -> torch since the axis is *runtime*, not *stability tier*. The frontend selector now reads cleanly: --frontend torch -> default gym.make path --frontend warp -> experimental warp runtime via WarpFrontend train.py becomes a thin dispatcher: get_frontend(name) gives a Frontend instance, frontend.preprocess_hydra_args(...) handles preset injection, frontend.build(cfg, task) returns the env. No more inline conditional imports; no more inline preset-injection logic. env.unwrapped.frontend_report is the inspection point - callers and tests can read what changed and what was missing without re-running adapt(). To add a new compatibility check, write a CompatRule subclass and append it to the relevant frontend's `rules` tuple. To add a new runtime, subclass Frontend and call register_frontend(name, cls).
- SwapMdpFunctions: skip terms whose ``func.__module__`` is already under the warp prefixes. Without this, running the bridge against a task already registered under ``isaaclab_tasks_experimental`` (e.g. ``Isaac-Cartpole-Warp-v0 --frontend=warp``) would silently drop terms whose warp twin happens not to live in the resolved fallback module. Also tighten ``_mdp_modules`` to require the trailing dot when matching ``isaaclab_tasks`` so we don't double-replace the prefix and end up importing ``isaaclab_tasks_experimental_experimental.*``. - ResolvePhysicsPreset: scope to MANAGER_BASED via ``applies_to``. Direct cfgs aren't expected to carry ``PresetCfg`` wrappers; running this rule on them was a no-op but the scoping makes the contract explicit. - CheckPhysicsIsNewton: positively accept ``isaaclab_newton.*`` modules, block on ``isaaclab_physx.*``, warn on anything else. The previous rule only rejected PhysX, so a custom or third-party physics cfg in an unrelated module would slip through silently. - PromoteSceneEntityCfg: catch ``TypeError`` from the in-place ``__class__`` reassignment and surface it as a blocking issue. ``issubclass`` does not guarantee Python permits the layout change (slots, layout flags); failing loud at this seam is better than a cryptic crash mid-pipeline. - WarpFrontend.preprocess_hydra_args: normalise leading dashes when inspecting ``presets=``, so ``--presets=foo`` is treated the same as ``presets=foo`` (Hydra accepts both forms). - Frontend.resolve: hard-block when ``gym.spec`` returned no spec. Previously ``meta.runtime`` was ``UNKNOWN`` and ``construct`` would fail later with a less specific error; now the block fires before any rule runs. - train.py: import the frontend lazily and tolerate ``ImportError`` when ``--frontend=torch`` (the default). The experimental package is optional, so a missing install used to break the default path; now it falls back to ``gym.make`` for torch and only fails for ``warp``. - frontend/__init__.py: trim ``__all__`` and the wildcard re-export so helpers (``walk_attrs``, ``iter_term_attrs``, ``resolve_warp_twin``, ``WARP_ROOT_PREFIXES``) are no longer advertised as the public framework surface. They remain importable from ``frontend.base`` for users writing their own rules.
- TaskResolver._classify_runtime: also accept class/callable entry points by inspecting __module__. gym.register accepts both ``"module:Class"`` strings and class objects; the old check only handled strings, so a warp-registered task using the class form classified as Runtime.UNKNOWN and the warn / verify rules silently disengaged. - SwapMdpFunctions._mdp_modules: narrow the exception match from ImportError to ModuleNotFoundError where ``exc.name`` matches the module being looked up. Previously a real ImportError raised inside an existing mdp module (broken import inside the package) would be silently swallowed and the rule would fall through to the fallback module, producing misleading "no warp twin" reports.
Summary
Adds a small adapter (
WarpFrontend) that lets a stable manager-based RLtask config run on the experimental warp runtime
(
ManagerBasedRLEnvWarp) without a parallel-Warp-v0registration. Fordirect envs, where the env class itself encodes the runtime, the same
frontend dispatches by verifying the registered entry-point lives under
isaaclab_experimental/isaaclab_tasks_experimentaland forwarding togym.makeunchanged. Selection is a single CLI flag:--frontend=warpon
rsl_rl/train.py. The warp implementations stay where they are; thebridge just wires the existing pieces together at runtime.
The motivation is to stop forking task registrations — every warp env
that exists today is a near-duplicate of a stable env with
presets=newtonand a swap of mdp callables. With this bridge, a stablecfg +
--frontend=warpis sufficient to drive the warp runtime, and theduplicate
*-Warp-v0registries can be deprecated in a follow-up.Pluggable compatibility framework
Adaptation runs as a sequence of
CompatRuleobjects. Each ruledeclares which workflow it applies to (manager-based / direct / both),
mutates the cfg in place, and appends to a shared
CompatReport. Newincompatibilities are added by writing a small subclass instead of
editing the dispatcher:
Default rules cover physics-preset resolution (
PresetCfg → newton),dropping unsupported sensors, in-place
SceneEntityCfgpromotion to thewarp variant, mdp
term.funcswaps, andActionTermCfg.class_typeswaps. Twins are matched by name with a
__module__prefix check, so aplain
from isaaclab.envs.mdp import *re-export in a warpmdppackage is rejected as not-a-twin.
What
--frontend=warpdoesManagerBasedRLEnvCfgpresets=newton; runs the rule pipeline; constructsManagerBasedRLEnvWarp.DirectRLEnvCfgIncompatibleEnvErrorwith the offending entry-point and a hint to use*-Direct-Warp-v0.DirectRLEnvCfg_verify_direct_warpconfirms entry-point lives under the warp prefixes; then plaingym.make. No mutation._classifyrecognises it; rule pipeline is largely a no-op (preset already resolved); env built normally.The
CompatReportis attached onenv.unwrapped.warp_compat_reportsotests and callers can inspect what was changed and what was missing.
Other small fixes
manager_based_env_warp.py: aligned the visualizer probe with thestable env after the recent
SimulationContext.get_settingAPI change.The old warp path tried to
.splita dict and crashed on init.isaaclab_experimental.managers.manager_term_cfg: re-exports thestable term-cfg classes verbatim instead of defining sibling classes
that broke
isinstance(stable_term, warp.TermCfg). The warp managersalready accept the stable cfg shape — only
funcdiffers at runtime.train.py:render_modeis forwarded so--videokeeps workingunder the warp frontend; warns when the user passes
presets=<other>alongside
--frontend=warp.Validation
All regression runs use 4096 envs, 300 iterations, fixed seed 42, on a
single L40.
Cartpole — manager-based parity
Bridge matches direct registration exactly at 300 iter.
Anymal-D Flat — manager-based parity
Bridge tracks
warp_directwithin run-to-run variance on locomotion(reward 21.36 vs 21.35, ep_len in the same bucket) and recovers the warp
runtime's per-iter time (0.46 vs 0.45 s, vs 0.58 s for stable Newton).
Cartpole — direct-env dispatch
IncompatibleEnvError✓direct_warp_via_frontendmatchesdirect_warp_nativeexactly — thefrontend's direct-env path verifies the entry-point and forwards to
gym.make, no cfg mutation, identical numerics.direct_stableanddirect_warp_nativeare different env classes(separate kernel implementations with their own reward shaping), so
their absolute numbers aren't expected to match — only the
bridge-vs-native pair is a parity check.
stable_via_frontend_xfailcorrectly raisesIncompatibleEnvErrornaming the offending entry-point and pointing at
*-Direct-Warp-v0.Test plan
Isaac-Cartpole-v0 --frontend=warpmatchesIsaac-Cartpole-Warp-v0at 300 iter / 4096 envs.
Isaac-Velocity-Flat-Anymal-D-v0 --frontend=warpmatchesIsaac-Velocity-Flat-Anymal-D-Warp-v0within run-to-run variance.Isaac-Cartpole-Direct-Warp-v0 --frontend=warpis a numeric-exactpass-through to the native direct-warp path.
Isaac-Cartpole-Direct-v0 --frontend=warpraisesIncompatibleEnvErrorwith the offending entry-point.--frontend=stable, default) is unchanged onmanager-based and direct tasks.
Out of scope (follow-up)
*-Warp-v0manager-based registrations once thebridge is the recommended way to opt into warp.
skrl,rl_games) — theadapter itself is trainer-agnostic; only the
--frontendCLI flaglives in the RSL train script today.