Skip to content

Fix ContactSensor first-contact/first-air transitions missed at large simulation times - #7294

Open
aidaodedjl wants to merge 2 commits into
isaac-sim:release/3.0.0-beta2from
aidaodedjl:fix-contact-sensor-first-transition
Open

Fix ContactSensor first-contact/first-air transitions missed at large simulation times#7294
aidaodedjl wants to merge 2 commits into
isaac-sim:release/3.0.0-beta2from
aidaodedjl:fix-contact-sensor-first-transition

Conversation

@aidaodedjl

Copy link
Copy Markdown

Description

Fixes #7283

ContactSensor.compute_first_contact() / compute_first_air() silently miss most touchdown / lift-off transitions once the simulation clock grows.

Root cause. The air/contact timers are computed as differences of float32 timestamps, so their quantization error grows with the clock magnitude (~4.6e-7 s at t ≈ 2–16 s, roughly 400x the default abs_tol = 1e-8). At a touchdown the current contact time equals exactly one update interval, so the t < dt + abs_tol comparison holds only by the tolerance margin — and float32 rounding tips it over, dropping the event. With history_length = 0, ~73% of transitions were lost in the bad clock ranges.

Fix. Latch transitions inside the sensor update kernels instead of inferring them from a timer comparison at query time:

  • each buffer refresh latches the touchdown / lift-off flag and records the age of the ended phase, assigned the interval midpoint (0.5 * elapsed) so the t < dt comparison carries an interval / 2 margin on both sides instead of resting exactly on the boundary;
  • compute_first_transition_kernel now tests the latched flag and event age (latch > 0 and transition_time < dt + abs_tol);
  • compute_first_contact() / compute_first_air() refresh outdated buffers first, so lazy update cadences read fresh latches instead of stale timers;
  • reset kernels clear the latches; data containers allocate the four new buffers when track_air_time is set.

Applied uniformly across all three backends (isaaclab_physx, isaaclab_ovphysx, isaaclab_newton).

Tests

New kernel-level regression test source/isaaclab/test/sensors/test_contact_sensor_first_transition.py drives the sensor update / compute kernels of all three backends directly (no simulation needed), over:

  • eager and lazy refresh cadences (transitions landing exactly on refresh boundaries — the worst case from the issue),
  • fresh and aged clocks (60 s rollout),
  • steady-contact and steady-air phases (no false positives),
  • alternating-contact rollouts (no missed touchdowns or lift-offs).

Verified TDD-style: 12 failed / 6 passed before the fix, 18 passed after; stashing the kernel changes makes the suite fail again (12 failed), restoring it makes it pass — the tests bite.

Checklist

  • I have run this PR's changes on my machine
  • Code is formatted (ruff format/ruff check clean)
  • Changelog fragments added for isaaclab, isaaclab_physx, isaaclab_newton

Notes

isaaclab_ovphysx has no changelog.d (not managed by towncrier), so no fragment was added there.

…ge sim times

The air/contact timers are float32 timestamp differences, so their
quantization error grows with the simulation clock magnitude and soon
exceeds the default abs_tol of compute_first_contact/compute_first_air.
At a touchdown the current contact time equals exactly one update
interval, so the t < dt + abs_tol comparison held only by the tolerance
margin and float32 rounding silently dropped most transitions.

Latch transitions inside the sensor update kernels instead: each buffer
refresh records the transition flag and the age of the ended phase
(assigned the interval midpoint so the t < dt comparison carries an
interval/2 margin on both sides), and the compute kernels test the
latched flag and event age. Also refresh outdated buffers before
computing so lazy update cadences read fresh latches.

Adds kernel-level regression tests driving all three backends
(physx, ovphysx, newton) over eager and lazy refresh cadences, fresh
and aged clocks.
@aidaodedjl
aidaodedjl requested a review from a team August 22, 2026 06:03
@github-actions github-actions Bot added bug Something isn't working isaac-lab Related to Isaac Lab team labels Aug 22, 2026
@greptile-apps

greptile-apps Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR replaces float32 timestamp-boundary inference with latched touchdown and lift-off events across the PhysX, OVPhysX, and Newton contact-sensor backends.

  • Allocates and resets per-sensor transition latches and event-age buffers when air-time tracking is enabled.
  • Refreshes outdated sensor buffers before first-contact and first-air queries.
  • Adds backend-spanning kernel regression tests for eager and lazy cadences, long-running clocks, latch persistence, and steady contact.

Confidence Score: 5/5

The PR appears safe to merge with no concrete blocking or independently actionable non-blocking issue identified.

The three backends consistently allocate, update, query, and reset the new transition state, while the regression suite exercises the timing and persistence behavior targeted by the fix.

Important Files Changed

Filename Overview
source/isaaclab_physx/isaaclab_physx/sensors/contact_sensor/kernels.py Adds persistent touchdown/lift-off latches, midpoint-based event ages, reset handling, and latch-aware transition computation for PhysX.
source/isaaclab_ovphysx/isaaclab_ovphysx/sensors/contact_sensor/kernels.py Applies the same transition-latching behavior to OVPhysX while preserving its pattern-major force indexing.
source/isaaclab_newton/isaaclab_newton/sensors/contact_sensor/contact_sensor_kernels.py Applies equivalent transition latch, age, reset, and query logic to the Newton backend.
source/isaaclab/test/sensors/test_contact_sensor_first_transition.py Adds direct kernel regressions across all three backends for long clocks, eager and lazy refreshes, persistence, and false-positive prevention.
source/isaaclab_physx/isaaclab_physx/sensors/contact_sensor/contact_sensor.py Refreshes stale buffers before transition queries and passes the new latch and age buffers through update and reset launches.
source/isaaclab_ovphysx/isaaclab_ovphysx/sensors/contact_sensor/contact_sensor.py Integrates latch buffers and fresh-query behavior into the OVPhysX sensor lifecycle.
source/isaaclab_newton/isaaclab_newton/sensors/contact_sensor/contact_sensor.py Integrates latch buffers and fresh-query behavior into the Newton sensor lifecycle.

Sequence Diagram

sequenceDiagram
    participant Sim as Simulation step
    participant Sensor as Contact sensor update
    participant Latch as Transition latch and age
    participant Query as compute_first_contact/air
    Sim->>Sensor: Advance timestamp and mark stale
    Sensor->>Sensor: Refresh forces when outdated
    alt Contact state changed
        Sensor->>Latch: Set touchdown/lift-off latch
        Sensor->>Latch: Initialize age to half elapsed interval
    else Same phase
        Sensor->>Latch: Advance event age
    end
    Query->>Sensor: Refresh outdated buffers
    Query->>Latch: "Test latch and age < dt + tolerance"
    Latch-->>Query: First-transition mask
Loading

Reviews (1): Last reviewed commit: "Fix contact sensor missing first-contact..." | Re-trigger Greptile

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isaac Lab Review Bot

The transition-latch implementation is consistent across PhysX, OVPhysX, and Newton, but it changes first-query behavior after reset, leaves several public docstrings describing the removed timer-based logic, and uses a patch-bump changelog fragment for a test-only isaaclab package change.

  • Design and architecture: The private latch-and-age state is wired symmetrically through allocation, reset, update, and query paths in all three backends. However, because transition detection now requires an observed phase change, the first sampled contact or air phase after reset is no longer reported as it was by the previous timer-based implementation; this compatibility decision should be addressed or explicitly documented.
  • API: Method signatures, defaults, and ProxyArray return behavior remain unchanged. The observable post-reset semantics do change, and the PhysX compute_first_air docstring plus both Newton method docstrings still describe comparisons against current phase timers rather than latched transitions, so the public contract is currently inconsistent with the implementation.
  • Implementation: The midpoint age bookkeeping, phase-specific latch clearing, track_air_time-gated allocation, and reset handling are implemented consistently across backends. The source/isaaclab package otherwise contains only a new regression test, so its changelog should use a .skip fragment rather than causing a patch bump and publishing backend implementation details.

Minor fixes needed. Posted 3 actionable findings inline.

Automated review; human maintainers own approval decisions.

# against dt in compute_first_transition_kernel.
fct = first_contact_time[env, sensor]
fat = first_air_time[env, sensor]
if is_first_contact:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning · Api — First update after reset reports no transition

After reset() both timers are zero, so on the first refresh is_first_contact/is_first_detached are false and no latch is set. Previously current_contact_time/current_air_time became one interval (>0, <dt) and the query returned 1.0, so bodies starting an episode in contact (or in air) were reported once. Same in the Newton and OVPhysX kernels. Latch the initial phase entry after reset, or document the change.

@@ -0,0 +1,10 @@
Fixed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Suggestion · Implementation — isaaclab fragment should use .skip suffix

The only source/isaaclab change here is a new test file, yet a patch-tier .rst fragment is added, producing a version bump and a user-facing entry in isaaclab for fixes that live in other packages; it also cites internal paths. Repository rules prescribe <slug>.skip for test-only changes and discourage internal implementation details in entries.

compute_first_transition_kernel,
dim=(self._num_envs, self._num_sensors),
inputs=[float(dt + abs_tol), self._data._current_air_time],
inputs=[

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Suggestion · Api — Stale docstrings describe removed timer comparison

compute_first_contact was reworded for the latch semantics, but compute_first_air here (and both Newton methods) still state the result comes "by comparing the current air time with the given time period". The documented contract no longer matches the implementation, especially for the post-reset case. Update the remaining docstrings the same way.

…p fragment

- The first refresh after reset (both phase timers zero) now latches the
  initial contact/air phase so bodies starting an episode in contact or
  in air are reported once, matching the pre-latch timer behaviour.
- Update the remaining compute_first_contact/compute_first_air
  docstrings (PhysX air, Newton both) to describe latched transitions.
- Replace the isaaclab changelog fragment with an empty .skip marker;
  the package only gains the regression test.
- Add a regression test for the post-reset first-phase report.
@aidaodedjl

Copy link
Copy Markdown
Author

Thanks for the review — all three findings addressed in ce56f0d:

  1. First update after reset — the update kernels now treat a refresh with both phase timers still zero as the first refresh after reset() and latch the initial contact/air phase, so a body starting an episode in contact (or in air) is reported once, exactly like the previous timer-based behaviour. Added a regression test (test_first_phase_after_reset_is_reported) covering both phases at an aged clock, verified to fail when the latch condition is removed.

  2. isaaclab changelog fragment — replaced with an empty .skip marker since the package change is test-only.

  3. Stale docstrings — the PhysX compute_first_air and both Newton method docstrings now describe the latched-transition semantics.

Full suite: 21 passed (was 18).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug Report] compute_first_contact / compute_first_air miss most transitions: default abs_tol is far below the float32 timer error

1 participant