Fixes stale sensor data after environment reset#5782
Conversation
The shared sensor lazy-eval kernel was marking envs as outdated on reset, which caused the next data access to refetch from a physics buffer that had not been stepped since the reset and overwrite the zeros that each sensor's reset() override had just written. Inside ManagerBasedRLEnv._reset_idx this surfaced as observations reflecting the pre-reset state (contact force on a cube that had been teleported back up, joint wrench on a freshly reset articulation, spurious finite-diff acceleration on IMU/PVA, etc.). Clear the outdated flag in reset_envs_kernel instead of setting it. The next InteractiveScene.update re-arms the flag at the configured update period, so post-reset reads return the freshly-zeroed buffers and the following step's read pulls fresh physics data as before. Adds per-backend, per-sensor regression tests (PhysX: ContactSensor, JointWrenchSensor, PVA, IMU; Newton: same set) that exercise the scene.reset(env_ids) path through the public data accessor. Fixes isaac-sim#4970
There was a problem hiding this comment.
Review Summary
This is a well-targeted fix for #4970 — the stale sensor data issue after environment reset. The approach is minimal and correct.
The Bug (Recap)
When scene.reset(env_ids) runs inside ManagerBasedRLEnv._reset_idx:
- Each sensor's
reset()zeros its_databuffers ✓ reset_envs_kernelthen marked the env as outdated → ❌ Problem- Next
sensor.dataaccess triggered a lazy refetch from a physics buffer that hadn't been stepped - The zeros got overwritten with stale pre-reset values
The Fix
# Before: is_outdated[env] = True (triggering unwanted refetch)
# After: is_outdated[env] = False (preserving the zeroed data)This is the correct single-point fix. The flag is re-armed by InteractiveScene.update after the next physics step, restoring normal lazy-fetch behavior.
✅ Strengths
-
Minimal diff: One line change in
reset_envs_kernelcovers all affected sensors (ContactSensor, IMU, PVA, JointWrenchSensor) across both PhysX and Newton backends. -
Comprehensive test coverage: 8 new regression tests (4 sensors × 2 backends) that specifically reproduce the manager-based RL flow. Each test was verified RED→GREEN.
-
Well-documented: The changelog fragment and updated docstring in
reset_envs_kernelclearly explain the rationale. -
No API changes: Purely internal behavior fix — no breaking changes for downstream users.
⚠️ Considerations
1. Sensors Without Zeroing reset() Override
The PR description notes:
Sensors that don't zero data (e.g. ray-caster, frame transformer) still see refetches at the next update-period boundary.
This is correct because those sensors don't override reset() to zero their data — they rely on the fetch. But it's worth confirming this behavior is still exercised by existing tests.
Question for author: Were the existing ray-caster and frame-transformer tests confirmed to still pass with this change? The PR description mentions existing ContactSensor (20) and IMU (8) tests passed, but doesn't explicitly mention ray-caster/frame-transformer.
2. Partial Reset Edge Case
With scene.reset(env_ids) where env_ids is a subset:
- Only the specified envs get
is_outdated[env] = False - Other envs retain their
is_outdatedstate
This looks correct, but the new tests only cover single-env scenarios. It would strengthen confidence to see test_reset_with_env_ids_only_zeros_selected_envs patterns also check that non-reset envs retain their normal lazy-fetch behavior (i.e., their is_outdated flag isn't accidentally cleared).
3. Missing Import in Newton IMU Test
Looking at test_imu.py (Newton), the test uses RigidObject but I see it's correctly imported:
from isaaclab.assets import RigidObject, RigidObjectCfgGood — this was updated.
🔍 Regression Test Robustness
The tests correctly mirror the _reset_idx flow:
- Run simulation until sensor has non-zero data
- Write new asset state
- Call
scene.reset(env_ids)without a subsequent physics step - Assert
sensor.datareturns zeros, not stale values
This is the exact scenario from issue #4970. The assertion style is clear:
assert post_reset_force_mag == 0.0, (
"Contact sensor returned stale pre-reset data after scene.reset(): "
f"got {post_reset_force_mag}, expected 0.0"
)Verdict
Looks good to merge pending:
- Confirmation that ray-caster/frame-transformer tests still pass (should be covered by CI)
- (Optional) Consider adding a multi-env partial-reset test to verify non-reset envs retain outdated flag
The fix is correct, minimal, and well-tested. Nice work @AntoineRichard!
📋 Update (2026-05-27)
New commits pushed: 153a1e5c → ce490ebe
Reviewed the diff between previously-reviewed commit and new head. The changes appear to be unrelated to this PR's fix (stale sensor data after reset). They include:
- CI: Added
ovphysxpip package to rendering workflow - Docs: LEAPP export updates, Windows tab-sets, ecosystem link fixes
- New: PPISP camera QA demos (
ppisp_camera.py,ppisp_camera_ovrtx.py) - Cleanup: Removed legacy OVRTX 0.2.x code paths
- Fix: Tutorial CPU tensor device mismatch (
add_new_robot.py) - Test: Docstring updates, golden images for ovphysx rendering
These look like upstream base-branch changes that merged in, not modifications to the sensor reset fix itself. The core fix in reset_envs_kernel (flipping is_outdated[env] = False) remains unchanged.
Original review assessment stands: ✅ Ready to merge.
The kernel-level fix (reset_envs_kernel setting is_outdated=False) broke FrameTransformer's refetch-on-reset behavior that PR isaac-sim#1276 relies on, causing test_outdated_sensor.py to fail in CI. Move the fix into each step-dependent sensor's reset() so it only affects the four sensors that genuinely have a staleness problem (contact / IMU / PVA / joint-wrench across PhysX and Newton); FrameTransformer / RayCaster / Camera keep their original refetch-on-reset semantics. * Revert reset_envs_kernel back to is_outdated=True (PR isaac-sim#1276 contract). * Add clear_outdated_envs_kernel + SensorBase._mark_envs_up_to_date() helper for sensors to opt out of the refetch. * Each affected sensor's reset() now calls _mark_envs_up_to_date(mask) after zeroing _data, so subsequent data access returns those zeros rather than refetching the unstepped physics buffer. * Simplify Newton ContactSensor.reset() to use the shared _resolve_indices_and_mask helper instead of its custom resolution. * Add per-package changelog fragments (isaaclab / isaaclab_physx / isaaclab_newton).
Replace the SensorBase opt-out scaffolding from the previous attempt with
a kernel-level check: each step-dependent sensor's update kernel now
takes the sensor timestamp and early-returns for envs where it is still
0 (the existing reset_envs_kernel signal that no physics step has
occurred since the last reset). Reading the physics buffer for those
envs would inject pre-reset values; the kernel skip keeps the data that
the sensor's reset() populated.
This is a single added argument and a single conditional per affected
kernel — no shared kernel, helper method, or class attribute. Touches:
PhysX: update_net_forces_kernel, imu_update_kernel,
pva_update_kernel, joint_wrench_split_kernel.
Newton: copy_from_newton_kernel, imu_copy_kernel,
pva_update_kernel, joint_wrench_to_incoming_joint_frame_kernel.
FrameTransformer / RayCaster / Camera kernels are untouched so they
continue to refetch right after reset (the path PR isaac-sim#1276 set up).
… locomanip quaternion, deformable demo, IK, benchmark scripts (#5816) # Description Cherry pick following PRs from develop: - #5779 - #5760 - #5791 - #5810 - #5811 - #5812 - #5754 - #5802 - #5803 - #5782 - #5804 - #5744 - #5775 - #5784 --------- Signed-off-by: Kelly Guo <kellyg@nvidia.com> Co-authored-by: John <jaybdub@users.noreply.github.com> Co-authored-by: myurasov-nv <168484206+myurasov-nv@users.noreply.github.com> Co-authored-by: Antoine RICHARD <antoiner@nvidia.com> Co-authored-by: Mike Yan Michelis <46975745+mmichelis@users.noreply.github.com> Co-authored-by: hujc <jichuanh@nvidia.com>
Description
ContactSensor(and other lazy-eval sensors —IMU,PVA,JointWrenchSensor) surfaced pre-reset values whenscene.reset(env_ids)ran insideManagerBasedRLEnv._reset_idx. The reset call zeroed each sensor's_databuffers correctly, but the sharedreset_envs_kernelthen marked the env as outdated — so the nextsensor.dataaccess (duringobservation_manager.compute()) triggered a lazy refetch from a physics buffer that had not been stepped since the reset, overwriting the zeros with stale data (contact forces, joint wrenches, velocities → spurious finite-diff accelerations).The fix clears the outdated flag in
reset_envs_kernelinstead of setting it. The nextInteractiveScene.update(dt)re-arms the flag at the sensor's configured update period, so:dataread afterscene.reset(env_ids)returns the freshly-zeroed buffers.This is a single-point fix in the shared sensor layer — it covers both Newton and PhysX backends for every sensor that follows the "reset() zeros _data + data is lazy" pattern.
Fixes #4970
Type of change
Screenshots
N/A — behavior change is observable in the sensor data values, covered by the regression tests below.
Checklist
pre-commitchecks with./isaaclab.sh --formatsource/<pkg>/changelog.d/for every touched package (do not editCHANGELOG.rstor bumpextension.toml— CI handles that)CONTRIBUTORS.mdor my name already exists thereTests added
Per-backend, per-sensor regression tests that mirror the manager-based env path (write new asset state, then
scene.reset(env_ids), then readsensor.data). Each was verified RED with the kernel change temporarily reverted and GREEN with it restored:test_contact_sensor.py::test_contact_sensor_no_stale_data_after_reset(cuda + cpu)test_joint_wrench_sensor.py::test_no_stale_data_after_scene_resettest_pva.py::test_no_stale_data_after_scene_resettest_imu.py::test_no_stale_data_after_scene_resettest_contact_sensor.py::test_no_stale_data_after_scene_reset(cuda + cpu)test_joint_wrench_sensor.py::test_no_stale_data_after_scene_resettest_pva.py::test_no_stale_data_after_scene_resettest_imu.py::test_no_stale_data_after_scene_resetExisting
ContactSensor(PhysX, 20 tests) andIMU(PhysX, 8 tests) suites continue to pass.Note
The Newton IMU's native
SensorIMU(accelerometer/gyroscope) does not carry data between physics steps the way PhysX'sget_velocities()does, so its test passes both before and after the fix. The test stays in as a guard against future regressions in Newton IMU's design.Notes for reviewers
reset()to zero their_data(contact, IMU, PVA, joint wrench, both backends) now keep those zeros until the next physics step. Sensors that don't zero data (e.g. ray-caster, frame transformer) still see refetches at the next update-period boundary, which preserves current behavior for them.