Skip to content

Commit 70fe31d

Browse files
sebcrozetclaude
andcommitted
fix(python): make vehicle CI tests architecture-robust + fix typos
The `test` job failed on ubuntu/windows (x86_64) while passing on macOS (arm64): rapier is not bit-reproducible across architectures, and two vehicle tests encoded arm64-specific results. - test_vehicle_brakes_decelerate: measure `current_vehicle_speed` (chassis forward axis) instead of `linvel.x`. The vehicle's heading drifts and the drift differs per arch, so the world-x component is not forward speed. - test_example_runs[vehicle/drive.py]: assert output shape + direction via regex instead of an exact speed snapshot. - typos: reword `mis-decode`/`rapierNd`/"PNGs"; allowlist the `ba` and `typ` identifiers in .typos.toml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1c3771e commit 70fe31d

6 files changed

Lines changed: 33 additions & 12 deletions

File tree

.typos.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ iit_softfoot = "iit_softfoot"
2222
FoV = "FoV"
2323
# Shepperd's method: named after S. W. Shepperd (quaternion extraction).
2424
Shepperd = "Shepperd"
25+
# Short local identifiers in the Python bindings/tests: `ba` (body a, paired
26+
# with `bb`) and `typ` (a "type" value — `type` is reserved in Rust/Python).
27+
ba = "ba"
28+
typ = "typ"
2529

2630
# Case insensitive, matches inside word.
2731
[default.extend-words]

python/docs/api/serde.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ Flavor-tagging caveat
3333

3434
Snapshots are **not** tagged with the ``(dim, scalar)`` flavor that
3535
produced them. Restoring an f32 snapshot through
36-
``rapier3d_f64.PhysicsWorld.restore(...)`` will silently mis-decode
36+
``rapier3d_f64.PhysicsWorld.restore(...)`` will silently misinterpret
3737
the floats. See :doc:`../limitations` for the long version.

python/docs/limitations.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Snapshot blobs are not flavor-tagged
2121
:meth:`rapier3d.PhysicsWorld.snapshot` emits a bincode blob with a
2222
``b"RPYS"`` magic prefix and a u32 version, but **not** a
2323
``(dim, scalar)`` tag. Restoring an f32 snapshot through
24-
``rapier3d_f64.PhysicsWorld.restore(...)`` will silently mis-decode
24+
``rapier3d_f64.PhysicsWorld.restore(...)`` will silently misinterpret
2525
the floats. Always restore through the same flavor that snapshotted.
2626

2727
``rapier-py-3d-f64`` lacks URDF / mesh loaders

python/docs/repackaging/00-target-layout.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Documented in Phase 5; no separate `-determinism` dist for now.
7878
| `from rapier import dim2` | `import rapier2d` |
7979
| `from rapier.dim3 import f64` | `import rapier3d_f64` |
8080
| `from rapier.dim2 import f64` | `import rapier2d_f64` |
81-
| `rapier.RapierError` (cross-flavor) | per-package `rapierNd.RapierError` |
81+
| `rapier.RapierError` (cross-flavor) | per-package `rapier{2,3}d.RapierError` |
8282

8383
There is no longer a single exception base shared across flavors; each package
8484
exposes its own `RapierError` tree. Note this prominently in the changelog.

python/tests/test_controllers.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,12 @@ def test_vehicle_brakes_decelerate(ns):
418418
w.step()
419419
w.update_query_pipeline()
420420
veh.update_vehicle(1.0 / 60.0, w.rigid_bodies, w.colliders, w.query_pipeline)
421-
forward_before = abs(w.rigid_bodies[h].linvel.x)
421+
# Use the controller's forward speed (along the chassis axis), not the
422+
# world-space `linvel.x`: the vehicle's heading drifts during the run, and
423+
# how much it drifts is not bit-reproducible across architectures (x86_64
424+
# vs aarch64). `current_vehicle_speed` is the heading-independent measure of
425+
# "how fast is it going forward", so the brake check is robust everywhere.
426+
forward_before = abs(veh.current_vehicle_speed)
422427
assert forward_before > 0.1, f"vehicle never accelerated ({forward_before})"
423428

424429
# Brake.
@@ -430,7 +435,7 @@ def test_vehicle_brakes_decelerate(ns):
430435
w.step()
431436
w.update_query_pipeline()
432437
veh.update_vehicle(1.0 / 60.0, w.rigid_bodies, w.colliders, w.query_pipeline)
433-
forward_after = abs(w.rigid_bodies[h].linvel.x)
438+
forward_after = abs(veh.current_vehicle_speed)
434439
assert forward_after < forward_before, (
435440
f"brakes did not slow the vehicle (before={forward_before}, after={forward_after})"
436441
)

python/tests/test_examples.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from __future__ import annotations
1010

1111
import os
12+
import re
1213
import subprocess
1314
import sys
1415
from pathlib import Path
@@ -18,13 +19,19 @@
1819

1920
EXAMPLES_DIR = Path(__file__).resolve().parent.parent / "examples"
2021

21-
# Map example script -> final stdout line we expect.
22-
EXPECTED: dict[str, str] = {
22+
# Map example script -> expected final stdout line. A plain string is matched
23+
# exactly; a compiled regex is matched with `.search()` for outputs that are
24+
# not bit-reproducible across architectures (the ray-cast vehicle sim drifts
25+
# between x86_64 and aarch64), where we assert the shape/direction instead.
26+
EXPECTED: dict[str, str | re.Pattern[str]] = {
2327
"hello_world.py": "final: y=0.60 (rest height ~0.6)",
2428
"joints/pendulum.py": "tip: x=+0.00 y=+3.50",
2529
"joints/six_dof_motor.py": "motor: lin.x=3.52 ang.z=0.49",
2630
"character/stairs.py": "climbed: x=13.50 y=0.28",
27-
"vehicle/drive.py": "vehicle: speed=-30.3 km/h vx=-2.18",
31+
# The vehicle controller's exact speed depends on per-architecture floating
32+
# point (rapier is not bit-reproducible across arches without
33+
# enhanced-determinism). Assert it drove backward at a real speed instead.
34+
"vehicle/drive.py": re.compile(r"^vehicle: speed=-\d+\.\d+ km/h vx=[-+]\d+\.\d+$"),
2835
"urdf/load_simple.py": "urdf: name=two_link links=2 joints=1",
2936
"render/matplotlib_animation.py": "matplotlib: segments=9720 frames=120",
3037
"serde/snapshot_restore.py": "snapshot: snap.y=0.58 later.y=0.60 bytes=1767",
@@ -42,8 +49,8 @@
4249
def test_example_runs(relpath: str, tmp_path: Path) -> None:
4350
script = EXAMPLES_DIR / relpath
4451
assert script.exists(), f"example missing: {script}"
45-
# Use tmp_path as cwd so example artifacts (e.g. matplotlib PNGs) don't
46-
# pollute the repo.
52+
# Use tmp_path as cwd so example artifacts (e.g. matplotlib image output)
53+
# don't pollute the repo.
4754
env = os.environ.copy()
4855
res = subprocess.run(
4956
[sys.executable, str(script)],
@@ -60,9 +67,14 @@ def test_example_runs(relpath: str, tmp_path: Path) -> None:
6067
# The final non-empty line of stdout must match the expected snapshot.
6168
lines = [ln for ln in res.stdout.splitlines() if ln.strip()]
6269
assert lines, f"example {relpath} produced no stdout output"
63-
assert lines[-1] == EXPECTED[relpath], (
70+
expected = EXPECTED[relpath]
71+
if isinstance(expected, re.Pattern):
72+
matched = expected.search(lines[-1]) is not None
73+
else:
74+
matched = lines[-1] == expected
75+
assert matched, (
6476
f"example {relpath} drifted:\n"
65-
f" expected: {EXPECTED[relpath]!r}\n"
77+
f" expected: {expected!r}\n"
6678
f" actual: {lines[-1]!r}\n"
6779
f" full stdout:\n{res.stdout}"
6880
)

0 commit comments

Comments
 (0)