diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 45d2cddeb..701747193 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -19,13 +19,22 @@ concurrency:
jobs:
integration-test-in-studio-container:
- uses: PickNikRobotics/moveit_pro_ci/.github/workflows/workspace_integration_test.yaml@13d4cdf34697226ac67e34d93dda8b94fa477336 # v0.2.1
+ uses: PickNikRobotics/moveit_pro_ci/.github/workflows/workspace_integration_test.yaml@d490a1dfe91758dd1da3277fbd47b5b1efe5e0c3 # v0.3.1
with:
- image_tag: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }}
+ # Pinned to 9.3.0-rc9 while the main-branch CUDA images are not yet
+ # published; 9.2.1's moveit_pro_test_utils is too old (missing
+ # reset_simulation_before_test that lab_sim's integration test imports).
+ # The dynamic expression below would normally resolve to the PR's base
+ # branch (main) or the pushed ref. Revert once the main-* CUDA tags exist.
+ # image_tag: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.ref_name }}
+ image_tag: "9.3.0-rc9"
colcon_test_args: "--executor sequential"
- runner: "picknik-16-amd64"
- # Coarsen MuJoCo timestep on CI (default 0.002s = 500Hz) so the heavier 3.6.0
- # constraint solver stays at-or-under realtime on CI runners. See
+ runner: "picknik-16-amd64-gpu"
+ enable_gpu: true
+ # Re-assert MuJoCo timestep on CI as a backstop. Scene files in this repo
+ # are standardized to 0.003s, but this override catches any future scene
+ # whose include chain bypasses that standard, keeping the heavier 3.6.0
+ # constraint solver at-or-under realtime on CI runners. See
# PickNikRobotics/moveit_pro#18534 for the underlying flake history.
mujoco_ci_timestep: "0.003"
use_ccache: true
diff --git a/src/april_tag_sim/description/scene.xml b/src/april_tag_sim/description/scene.xml
index 2b42bc047..87a223eb7 100644
--- a/src/april_tag_sim/description/scene.xml
+++ b/src/april_tag_sim/description/scene.xml
@@ -8,6 +8,10 @@
+
+
+
+
@@ -39,7 +43,7 @@
pos="-1.0 -2.0 2.966"
fovy="58"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
euler="0.849 0.000 0.0"
/>
diff --git a/src/dual_arm_sim/description/mujoco/scene.xml b/src/dual_arm_sim/description/mujoco/scene.xml
index 4e6886063..666dab1df 100644
--- a/src/dual_arm_sim/description/mujoco/scene.xml
+++ b/src/dual_arm_sim/description/mujoco/scene.xml
@@ -6,7 +6,13 @@
-
+
@@ -53,7 +59,7 @@
pos="0.7 0.0 0.65"
fovy="58"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
xyaxes="0 -1 0 1 0 0"
/>
-
+
@@ -74,7 +74,7 @@
pos="-1.046 1.252 2.019"
fovy="58"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
xyaxes="-0.607 -0.795 0.000 0.555 -0.424 0.715"
/>
-
+
@@ -136,7 +136,7 @@
pos="0.0 -2.56685 1.62333"
fovy="58"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
quat="0.916616 0.399763 -0.000804 -0.001843"
/>
diff --git a/src/kitchen_sim/description/mujoco/scene.xml b/src/kitchen_sim/description/mujoco/scene.xml
index ffc9c1934..1d3cb728f 100644
--- a/src/kitchen_sim/description/mujoco/scene.xml
+++ b/src/kitchen_sim/description/mujoco/scene.xml
@@ -16,7 +16,7 @@
-
+
@@ -62,7 +62,7 @@
@@ -77,7 +77,7 @@
diff --git a/src/lab_sim/description/scene.xml b/src/lab_sim/description/scene.xml
index 0074434a0..e72097608 100644
--- a/src/lab_sim/description/scene.xml
+++ b/src/lab_sim/description/scene.xml
@@ -84,10 +84,11 @@
/>
-
+
-
+
@@ -124,7 +125,7 @@
pos="1.8 -0.495 1.0"
fovy="58"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
xyaxes="0.586 0.810 0.000 -0.186 0.135 0.973"
user="0"
/>
diff --git a/src/lab_sim/test/conftest.py b/src/lab_sim/test/conftest.py
new file mode 100644
index 000000000..1890fef52
--- /dev/null
+++ b/src/lab_sim/test/conftest.py
@@ -0,0 +1,62 @@
+# Copyright 2026 PickNik Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of the PickNik Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+"""Live per-objective progress for objectives_integration_test.
+
+Pytest's default ``--capture=fd`` redirects fds 1 and 2, so when CTest kills
+the test on timeout (TIMEOUT 600 in CMakeLists.txt) all per-test output is
+lost and the CI log shows nothing past pytest's "collected N items" header.
+
+These hooks write directly to fd 2, bypassing the capture, so the CI log
+always shows which objective was running when the timeout fired and how long
+each completed objective took — the information needed to triage flakes,
+budget overruns, and runner regressions.
+"""
+
+import os
+import time
+
+_started_at: dict[str, float] = {}
+
+
+def pytest_runtest_logstart(nodeid, location):
+ _started_at[nodeid] = time.monotonic()
+ os.write(2, f" START {nodeid}\n".encode())
+
+
+def pytest_runtest_logreport(report):
+ if report.when != "call":
+ return
+ nodeid = report.nodeid
+ elapsed = time.monotonic() - _started_at.get(nodeid, time.monotonic())
+ outcome = report.outcome.upper() # PASSED / FAILED / SKIPPED
+ line = f" {outcome:7s} {nodeid} ({elapsed:.1f}s)"
+ if report.failed and report.longrepr:
+ reason = str(report.longrepr).splitlines()[-1][:200]
+ line += f"\n └─ {reason}"
+ os.write(2, (line + "\n").encode())
diff --git a/src/moveit_pro_kinova_configs/kinova_sim/description/mujoco/gen3_7dof.xml b/src/moveit_pro_kinova_configs/kinova_sim/description/mujoco/gen3_7dof.xml
index c306c66a2..d22ea338f 100644
--- a/src/moveit_pro_kinova_configs/kinova_sim/description/mujoco/gen3_7dof.xml
+++ b/src/moveit_pro_kinova_configs/kinova_sim/description/mujoco/gen3_7dof.xml
@@ -320,7 +320,7 @@
pos="0.0 -0.056 -0.058"
fovy="58"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
quat="0.0 0.0 0.0 1.0"
/>
-
+
@@ -58,7 +58,7 @@
pos="-0.5 0.75 1.5"
fovy="75"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
quat="0.365715 0.232986 -0.484157 -0.759975"
/>
-
+
@@ -64,7 +64,7 @@
pos="0.0 0.3 0.55"
fovy="58"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
quat="0.365715 0.232986 -0.484157 -0.759975"
/>
-
+
@@ -64,7 +64,7 @@
pos="0.0 0.3 0.55"
fovy="58"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
quat="0.365715 0.232986 -0.484157 -0.759975"
/>
-
+
@@ -90,7 +90,7 @@
pos="-0.3 0.3 1.0"
fovy="58"
mode="fixed"
- resolution="640 480"
+ resolution="1280 720"
euler="0 -1.0472 -1.5708"
/>