Set up Meta Quest, VIVE Tracker 3.0 (Lighthouse), or VIVE Ultimate tracking as the Mantis pose source.
Mantis data collection needs a pose source: the tracking system that reports where each rig is. (Mantis teleop never tracks; it drives the grippers straight from the rig triggers.) Mantis supports three sources, selected under Mantis settings → Tracking in the control panel (default: Lighthouse). Pick one per installation: the choice, its setup state, and its device bindings are all stored on the host, so every collection run uses the same source without per-run flags.
Source
Hardware
Tracking
Setup contract
Quest
Meta Quest controllers via WebXR
Headset inside-out tracking
Enter VR in the headset browser; handedness identifies left/right. No bridge, no Identify.
Lighthouse
VIVE Tracker 3.0
Outside-in, 1–2 SteamVR base stations
Pinned libsurvive runtime on the host, pair each tracker with its dongle, check base stations, identify sides.
Ultimate
VIVE Ultimate Tracker
Inside-out SLAM, no base stations
One-time pair + room map in VIVE Hub on Windows; then the HTC dongle and pinned pyvut runtime on the host, private shared-map AP config, identify sides.
Keep the product names distinct: VIVE Tracker 3.0 is the Lighthouse device (there is no “Quest 3.0” tracker), Meta Quest is the WebXR source, and VIVE Ultimate Tracker is the inside-out source.
Before starting, finish the Mantis hardware setup: the hub connected, Mantis selected on its tile, the CAN side mapping confirmed, and the grippers-only teleop check passed. Then open Mantis settings → Tracking and choose the source; the selection saves to the host immediately, so runs and the setup actions below always use it.
The Tracking tab walks each source through a step-by-step flow. Each step resolves from what the host actually reports (not from ticking a box), Continue unlocks once it is resolved, a step that regresses later pulls the flow back to it, and the flow stays on a step while its action is running. Setup is complete once the sides are identified (or, for Quest, once the headset’s calibration key is saved). The equivalent CLI commands are listed alongside; see the tracker CLI reference for their flags.
Quest
Lighthouse
Ultimate
Quest uses the headset’s own controller tracking through WebXR, so there is no runtime to install and no tracker to bind; WebXR handedness assigns left and right. The only stored state is the calibration key that identifies the controller generation and pose datum.
1
Source
Choose Quest (the selection saves immediately). The Quest headset and the host must be on the same LAN (or use the wired Quest-over-USB link, managed under General settings → Quest).
2
Headset
Start a Quest bring-up collection run: axol collect-data --mantis true --mantis_allow_uncalibrated true (or Start Collect data with mantis_allow_uncalibrated set under Advanced); teleop never starts the VR server. Then in the Quest browser open axol.almond.bot, enter the host, connect, accept the host’s certificate the first time, and choose Enter VR. Hold both Touch controllers.The step shows each controller’s reported WebXR profile and pose space and, when both match, a copyable common calibration key (for example quest:oculus-touch-v3:grip). Press Use this key, then Save settings. The step resolves once the saved key is a grip-space datum matching the live controllers; a stale report, a target-ray fallback, or two controllers reporting different keys is held rather than accepted.
The client sends gripSpace poses (with targetRaySpace only as an older-runtime fallback), works without body/elbow tracking, and preserves lost optical tracking per side so the server can hold safely. Quest 3 Touch Plus controllers in the standard cradle use a built-in factory tracker→gripper transform; other controllers need a bench-measured transform for both sides under the exact saved key. See Transforms.
Lighthouse tracks two VIVE Tracker 3.0 units from 1–2 SteamVR base stations (1.0 or 2.0) with the pinned libsurvive runtime on the host. Mount the base stations with the whole Mantis workspace in view, and if you use two Base Station 2.0 units, put them on different channels (the button on the back cycles 1–16): two stations on one channel make poses jump or stall as soon as a rig moves.
1
Source
Choose Lighthouse (the selection saves immediately).
2
Runtime
axol provision normally installs the pinned libsurvive build during installation. If the step is red, press Install Lighthouse support (CLI: axol tracker.install). It builds survive-cli at the pinned commit plus Axol’s libsurvive fixes (tracker-clock rollover timestamps, base-station decoding through both trackers, the solver-buffer, scene-solver and solver-stack crash fixes for setups with more base stations, and poses streamed without buffering delay; see tracker.install) with Eigen and the native libusb backend, installs the Vive udev rule, and is safe to rerun: an existing pinned build is re-attested, not rebuilt (a build from before the fixes is rebuilt once).
3
Trackers
Power the base stations. For each tracker, unplug it from USB so it blinks blue, plug its Watchman dongle into the host, and press Pair tracker (CLI: axol tracker.pair). Then, with both trackers powered and in view, press Check base stations (CLI: axol tracker.lighthouse.check): it listens for about 20 seconds (up to 150 seconds while libsurvive calibrates new or moved base stations; keep the trackers still) and resolves only when it sees no shared channel, both base stations, and both trackers reporting. A failure names the channel to change and how.
4
Identify sides
Press Identify trackers (CLI: axol tracker.identify --backend survive). Tracker addresses do not say which rig they are on, so Identify watches which tracker moves for each side: move only the requested rig during each 3-second capture. It refuses to bind while base stations share a channel. Setup is complete once both sides are bound.
libsurvive calibrates the base-station world on first run, so leave the trackers still and visible for a few seconds. The standard flat-back mount uses the built-in factory transform, so nothing needs measuring.
VIVE Ultimate Trackers track themselves (inside-out SLAM) against a room map they share over a private access point, with no base stations or headset. There is no official Linux support; the host uses the community pyvut driver to talk to the wireless dongle over USB HID. Pairing and mapping are Windows-only, one-time operator steps; everything after that runs from the control panel.
1
One time on Windows: pair
On a Windows PC install SteamVR, enable its null/virtual-headset driver, install VIVE Streaming Hub, and install the VIVE Ultimate Tracker service inside VIVE Hub (pinned pyvut setup, HTC pairing prerequisites). Connect the wireless dongle and open Settings → VIVE Ultimate Tracker → Trackers → Pair new. Power on a tracker, wait through its green/blue startup flashes until the LED is solid blue, then hold Power for about two seconds until blue flashes again and pair it. Repeat for the second tracker on the same dongle.
2
One time on Windows: map
Choose Start setup → Create map, establish the center, and scan the entire Mantis operating volume from both low/kneeling and standing heights while facing all four directions. Let the map auto-save (use Refine/Save if offered) and confirm both trackers regain full position + rotation after a power cycle. The pairing and map live on the trackers and move with them to the robot host.
3
Source
Back in the control panel, choose Ultimate (the selection saves immediately).
4
Runtime
Press Install / Repair Ultimate support (CLI: axol tracker.ultimate.install). It installs the pinned pyvut commit, the hid Python binding, system HID libraries, and a persistent udev rule for dongle 0bb4:0350; safe to rerun. Do not replace it with an unpinned pip install or a one-off chmod /dev/hidraw*.
5
Dongle
Plug the same dongle into the host. The step resolves when the dongle is detected, its HID endpoint is accessible, and the operator has USB access. The one host-side step, adding the Axol login to the dialout group, is shown with its command because it needs a re-login.
6
Wi-Fi
Save the trackers’ private shared-map AP configuration: SSID, password, two-letter regulatory country, and Wi-Fi center frequency in MHz. These are not the Windows PC’s network, the robot LAN, or your router. Upstream pyvut requires all four as fallback; with the USB-dongle transport the tracker firmware reports the active SLAM-host SSID/password/frequency and only the saved country is consumed directly. The password is masked and never read back, and the file is written as ~/.almond/tracker/ultimate_wifi.json with mode 0600 (CLI/manual shape in the reference).
7
Identify sides
Power both trackers inside the mapped area and press Identify trackers (CLI: axol tracker.identify --backend ultimate). Move only the requested rig for each side; the addresses are saved separately from Lighthouse bindings. Setup is complete once both sides are bound. axol tracker.ultimate.check is an optional non-invasive host check that reports the same dependency, USB, permission, binding, convention, and redacted AP-config state the Start gate uses.
The dongle protocol is reverse-engineered and firmware-dependent. The pinned runtime and factory transform expect wxyz quaternions and a z-up SLAM world. Bench-check gravity, translations, and rotations before changing those settings, and never use the quaternion/up-axis toggle to compensate for a mount transform: a convention change disables the factory transform and makes saved Ultimate overrides stale, blocking production use until an override is saved under the active convention or the pinned convention is restored.
The standard flat-back mount uses the built-in factory transform (the Tracker 3.0 value with the 11 mm origin difference folded in); nothing needs measuring.
The readiness the flow shows is also enforced by the operation backend: a direct CLI or REST start cannot bypass the runtime, permission, dongle/AP, binding, live-input, or production-transform gates.
Hold both rigs at the agreed start pose, release both controller grips, then press both grips together to align and engage. The controller triggers operate the grippers. In data collection, A starts and confirms a take, exactly as on Axol.If tracking is lost on either side, both sides freeze. Restore both, then release and press both grips again; recovery never resumes motion on its own. Reset returns to rest and needs the same fresh gesture.
A managed run does not move as soon as the trackers appear. Startup waits for both fresh, fully tracked poses and both trigger heartbeats. Then hold both rigs where the session should begin, squeeze both rig triggers together, and release both: on that release the world→robot base transform is fitted at the rigs’ pose and tracking engages. In data collection the same gesture also starts the take (once the teleop core acknowledges the engage); while recording, rapidly fully squeeze and release either trigger three times to save or four times to discard.A tracker-lock loss or stale trigger heartbeat freezes both sides. Restore both, then repeat the both-squeeze→release gesture; nothing re-anchors automatically. Reset returns to rest and requires the gesture again.A Quest may still connect as a view-only passthrough / video / HUD client: its controller frames are ignored and never blend with the managed tracker bridge. Because there is no built-in Quest↔tracker world-registration procedure, the spatial URDF overlay is hidden for these sources by default.
Absolute (Mantis) IK solves the world yaw and translation at engage time, but it does not discover where the gripper TCP sits relative to the tracker. That rigid tracker → TCP transform must come from somewhere:
Source
Transform
Lighthouse (Tracker 3.0)
Built-in factory value [0, 0.035, 0.047, 0, 1, 0, 0] (Ry(180°) flat-back mount rotation; tracker 47 mm behind and 35 mm above the gripper motor face) for both sides, applied automatically after Identify.
Ultimate
Built-in factory value [0, 0.046, 0.047, 0, 1, 0, 0] for both sides (Tracker 3.0’s mount rotation with the 11 mm origin rise), approved for the pinned wxyz / z-up convention.
Quest 3 (Touch Plus)
Built-in factory values for quest:meta-quest-touch-plus:grip, mirrored between the left and right cradles (grip origin 62 mm behind and 45 mm above the gripper motor face), selected automatically when no Quest entry is saved.
Other Quest controllers
No factory value: measure the gripper TCP in each controller’s grip frame and save it under the exact quest:<reported-profile>:grip key for both sides.
Mantis datasets recorded with axol ≤ 0.2.4 used an Rx(+90°) Vive rotation that pitched every recorded gripper ~90° away from the operator’s hold; Axol could not replay them. Repair them with axol migrate-dataset --mantis-tcp-rotation before replay or training.axol 0.2.5–0.2.16 shipped a Vive translation ([0, 0.0355, -0.092] / [0, 0.0465, -0.092]) that put the tracker 92 mm in front of the gripper instead of 47 mm behind it. collect-data refuses to append to a dataset recorded with it; record to a new repo_id.
Overrides and Quest measurements live in ~/.almond/mantis/tcp_transform.json, keyed per side by the identified device (survive:<tracker serial>, ultimate:<mac>, or quest:<profile>:grip). pos is the gripper TCP origin in the tracker’s local frame in metres; quat is the TCP orientation in that frame as [qx, qy, qz, qw]. The file is edited directly (or via PUT /api/tracker/calibration/<source>); the panel does not edit transforms. The full schema, the Ultimate convention rules, and the bring-up route for a missing constant (--mantis_allow_uncalibrated true for a calibration-only capture) are in the tracker CLI reference. Production collect-data refuses to start with a missing, stale, or unapproved transform on either side.
Before recording training data, run a short bring-up collection run (axol collect-data --mantis true --mantis_allow_uncalibrated true, discarding every take) with the workspace clear and check, with the selected live source:
Physical left/right, scale, every translation axis, wrist rotation, and tracker→TCP alignment. With Quest, the headset’s live URDF overlay shows the virtual arms; with Lighthouse/Ultimate, use the control panel’s mirrored view.
Recovery: deliberately occlude or de-localize one side and confirm both freeze; restore tracking and confirm nothing moves until the start gesture is repeated.
tracker.identify binds a tracker to a side by which one you move, not by which rig it is mounted on. If the tracker bound as left sits on the rig held in the right hand, the engage fit faces the robot base backwards and every recorded pose is yawed 180° with the columns swapped. With both sides calibrated, the engage refuses this: the log reports Teleop engage rejected: both grippers face away from the robot's rest heading, the session disengages, and nothing is recorded until both grips are released and squeezed again with the rigs in the correct hands (or re-identified). Hold each rig like the robot holds its rest gripper — fingers down, tracker face up, handle towards you — when engaging; a rig pointed straight up or down is not checked.Recording also counts frames where a hand travelled beyond Axol’s reach (out_of_reach in the episode QA line, plus a warning): the rig has no arm to stop you, but Axol cannot follow those poses on replay.A source that jumps, drifts, swaps sides, or cannot recover cleanly is not ready for collection. Mantis teleop never tracks, and axol teleop --sim --teleop.absolute_mode true is only a generic mapping demo; physical acceptance must use the real source, Mantis selected, in a collection run.
Check the grippers from the rig triggers (no tracking).
Data Collection
Record Mantis episodes to a LeRobot dataset.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Docs","description":"Command-line interface and Python SDK for the Almond Axol dual-arm robot.","colors":{"primary":"#c79760","light":"#eff483","dark":"#c79760"},"logo":{"light":"https://mintcdn.com/almondbot/idfco7KlgFmESEu4/assets/logo.png?fit=max\u0026auto=format\u0026n=idfco7KlgFmESEu4\u0026q=85\u0026s=bc87e5e797ca0a584e6d373c704c28d9","dark":"https://mintcdn.com/almondbot/idfco7KlgFmESEu4/assets/logo.png?fit=max\u0026auto=format\u0026n=idfco7KlgFmESEu4\u0026q=85\u0026s=bc87e5e797ca0a584e6d373c704c28d9"},"favicon":"https://mintcdn.com/almondbot/idfco7KlgFmESEu4/assets/logo.png?fit=max\u0026auto=format\u0026n=idfco7KlgFmESEu4\u0026q=85\u0026s=bc87e5e797ca0a584e6d373c704c28d9","navbar":{"links":[{"href":"https://axol.almond.bot/control","label":"Control Panel"},{"href":"https://axol.almond.bot/vr","label":"VR Teleop"}],"primary":{"type":"button","label":"Get Axol","href":"https://almond.bot"}},"styling":{"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"almondbot","hasAudioNarration":false,"actualSubdomain":"almondbot","customDomains":["docs.almond.bot"],"gitSource":{"type":"github","owner":"almond-bot","repo":"axol","deployBranch":"main","contentDirectory":"docs","isPrivate":false,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6ad7f5c80fccdab1455c2278","collectionId":"6036ca4c-e42e-4d92-9c8d-ca8926145245","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ad7f5c80fccdab1455c2278"},"entitlements":{},"buildId":"6ad7f5c80fccdab1455c2278","clientVersion":"0.0.3908","searchFilterCounts":[{"language":"default","tag":"Advanced","count":64},{"language":"default","tag":"CLI Reference","count":53},{"language":"default","tag":"Operations","count":18},{"language":"default","tag":"Tuning","count":11},{"language":"default","tag":"Python API","count":10},{"language":"default","tag":"Get Started","count":9},{"language":"default","tag":"Motors","count":8},{"language":"default","tag":"Diagnostics","count":6},{"language":"default","tag":"Web Interfaces","count":6},{"language":"default","tag":"Hardware Setup","count":6}]},"assistantConfig":{},"hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-v7-file-tree","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-branches-v2-autocommit","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-branches-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"teamspace","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-nav-generations","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-agent-model-picker","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"client-snapshot-docs-config","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"isAtlas":false,"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"mantis/tracking","slug":"/mantis/tracking","pageMetadata":{"title":"Mantis Tracking","description":"Set up Meta Quest, VIVE Tracker 3.0 (Lighthouse), or VIVE Ultimate tracking as the Mantis pose source.","href":"/mantis/tracking"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Set up Meta Quest, VIVE Tracker 3.0 (Lighthouse), or VIVE Ultimate tracking as the Mantis pose source.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,li:`li`,ol:`ol`,p:`p`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,..._provideComponents(),...props.components},{Card,CardGroup,Heading,Note,Step,Steps,Tab,Table,Tabs,Warning}=_components;if(!Card)_missingMdxReference(`Card`,true);if(!CardGroup)_missingMdxReference(`CardGroup`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Note)_missingMdxReference(`Note`,true);if(!Step)_missingMdxReference(`Step`,true);if(!Steps)_missingMdxReference(`Steps`,true);if(!Tab)_missingMdxReference(`Tab`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Tabs)_missingMdxReference(`Tabs`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`Mantis `,_jsx(_components.strong,{children:`data collection`}),` needs a `,_jsx(_components.strong,{children:`pose source`}),`: the tracking system that reports where each rig is. (Mantis teleop never tracks; it drives the grippers straight from the rig triggers.) Mantis supports three sources, selected under `,_jsx(_components.strong,{children:`Mantis settings → Tracking`}),` in the control panel (default: Lighthouse). Pick one per installation: the choice, its setup state, and its device bindings are all stored on the host, so every collection run uses the same source without per-run flags.`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Source`}),_jsx(_components.th,{children:`Hardware`}),_jsx(_components.th,{children:`Tracking`}),_jsx(_components.th,{children:`Setup contract`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Quest`})}),_jsx(_components.td,{children:`Meta Quest controllers via WebXR`}),_jsx(_components.td,{children:`Headset inside-out tracking`}),_jsx(_components.td,{children:`Enter VR in the headset browser; handedness identifies left/right. No bridge, no Identify.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Lighthouse`})}),_jsx(_components.td,{children:`VIVE Tracker 3.0`}),_jsx(_components.td,{children:`Outside-in, 1–2 SteamVR base stations`}),_jsx(_components.td,{children:`Pinned libsurvive runtime on the host, pair each tracker with its dongle, check base stations, identify sides.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Ultimate`})}),_jsx(_components.td,{children:`VIVE Ultimate Tracker`}),_jsx(_components.td,{children:`Inside-out SLAM, no base stations`}),_jsx(_components.td,{children:`One-time pair + room map in VIVE Hub on Windows; then the HTC dongle and pinned pyvut runtime on the host, private shared-map AP config, identify sides.`})]})]})]}),`\n`,_jsx(Note,{children:_jsxs(_components.p,{children:[`Keep the product names distinct: `,_jsx(_components.strong,{children:`VIVE Tracker 3.0`}),` is the Lighthouse device (there is no “Quest 3.0” tracker), `,_jsx(_components.strong,{children:`Meta Quest`}),` is the WebXR source, and `,_jsx(_components.strong,{children:`VIVE Ultimate Tracker`}),` is the inside-out source.`]})}),`\n`,_jsxs(_components.p,{children:[`Before starting, finish the `,_jsx(_components.a,{href:`/mantis/hardware`,children:`Mantis hardware setup`}),`: the hub connected, `,_jsx(_components.strong,{children:`Mantis`}),` selected on its tile, the CAN side mapping confirmed, and the grippers-only teleop check passed. Then open `,_jsx(_components.strong,{children:`Mantis settings → Tracking`}),` and choose the source; the selection saves to the host immediately, so runs and the setup actions below always use it.`]}),`\n`,_jsx(Heading,{level:`2`,id:`set-up-the-source`,children:`Set up the source`}),`\n`,_jsxs(_components.p,{children:[`The Tracking tab walks each source through a `,_jsx(_components.strong,{children:`step-by-step flow`}),`. Each step resolves from what the host actually reports (not from ticking a box), `,_jsx(_components.strong,{children:`Continue`}),` unlocks once it is resolved, a step that regresses later pulls the flow back to it, and the flow stays on a step while its action is running. Setup is complete once the sides are identified (or, for Quest, once the headset’s calibration key is saved). The equivalent CLI commands are listed alongside; see the `,_jsx(_components.a,{href:`/cli/tracker`,children:`tracker CLI reference`}),` for their flags.`]}),`\n`,_jsxs(Tabs,{children:[_jsxs(Tab,{title:`Quest`,id:`quest`,children:[_jsxs(_components.p,{children:[`Quest uses the headset’s own controller tracking through WebXR, so there is no runtime to install and no tracker to bind; WebXR handedness assigns left and right. The only stored state is the `,_jsx(_components.strong,{children:`calibration key`}),` that identifies the controller generation and pose datum.`]}),_jsxs(Steps,{children:[_jsx(Step,{title:`Source`,children:_jsxs(_components.p,{children:[`Choose `,_jsx(_components.strong,{children:`Quest`}),` (the selection saves immediately). The Quest headset and the host must be on the same LAN (or use the wired `,_jsx(_components.a,{href:`/guides/quest-over-usb`,children:`Quest-over-USB`}),` link, managed under `,_jsx(_components.strong,{children:`General settings → Quest`}),`).`]})}),_jsxs(Step,{title:`Headset`,children:[_jsxs(_components.p,{children:[`Start a Quest `,_jsx(_components.strong,{children:`bring-up collection run`}),`: `,_jsx(_components.code,{children:`axol collect-data --mantis true --mantis_allow_uncalibrated true`}),` (or Start `,_jsx(_components.strong,{children:`Collect data`}),` with `,_jsx(_components.code,{children:`mantis_allow_uncalibrated`}),` set under Advanced); teleop never starts the VR server. Then in the Quest browser open `,_jsx(_components.a,{href:`https://axol.almond.bot`,children:`axol.almond.bot`}),`, enter the host, connect, accept the host’s certificate the first time, and choose `,_jsx(_components.strong,{children:`Enter VR`}),`. Hold both Touch controllers.`]}),_jsxs(_components.p,{children:[`The step shows each controller’s reported WebXR profile and pose space and, when both match, a copyable `,_jsx(_components.strong,{children:`common calibration key`}),` (for example `,_jsx(_components.code,{children:`quest:oculus-touch-v3:grip`}),`). Press `,_jsx(_components.strong,{children:`Use this key`}),`, then `,_jsx(_components.strong,{children:`Save settings`}),`. The step resolves once the saved key is a `,_jsx(_components.strong,{children:`grip-space`}),` datum matching the live controllers; a stale report, a `,_jsx(_components.code,{children:`target-ray`}),` fallback, or two controllers reporting different keys is held rather than accepted.`]})]})]}),_jsxs(_components.p,{children:[`The client sends `,_jsx(_components.code,{children:`gripSpace`}),` poses (with `,_jsx(_components.code,{children:`targetRaySpace`}),` only as an older-runtime fallback), works without body/elbow tracking, and preserves lost optical tracking per side so the server can hold safely. Quest 3 Touch Plus controllers in the standard cradle use a `,_jsx(_components.strong,{children:`built-in factory tracker→gripper transform`}),`; other controllers need a bench-measured transform for both sides under the exact saved key. See `,_jsx(_components.a,{href:`#transforms`,children:`Transforms`}),`.`]})]}),_jsxs(Tab,{title:`Lighthouse`,id:`lighthouse`,children:[_jsxs(_components.p,{children:[`Lighthouse tracks two `,_jsx(_components.strong,{children:`VIVE Tracker 3.0`}),` units from 1–2 SteamVR base stations (1.0 or 2.0) with the pinned `,_jsx(_components.a,{href:`https://github.com/collabora/libsurvive`,children:`libsurvive`}),` runtime on the host. Mount the base stations with the whole Mantis workspace in view, and if you use two `,_jsx(_components.strong,{children:`Base Station 2.0`}),` units, put them on `,_jsx(_components.strong,{children:`different channels`}),` (the button on the back cycles 1–16): two stations on one channel make poses jump or stall as soon as a rig moves.`]}),_jsxs(Steps,{children:[_jsx(Step,{title:`Source`,children:_jsxs(_components.p,{children:[`Choose `,_jsx(_components.strong,{children:`Lighthouse`}),` (the selection saves immediately).`]})}),_jsx(Step,{title:`Runtime`,children:_jsxs(_components.p,{children:[_jsx(_components.code,{children:`axol provision`}),` normally installs the pinned libsurvive build during installation. If the step is red, press `,_jsx(_components.strong,{children:`Install Lighthouse support`}),` (CLI: `,_jsx(_components.a,{href:`/cli/tracker#axol-trackerinstall`,children:_jsx(_components.code,{children:`axol tracker.install`})}),`). It builds `,_jsx(_components.code,{children:`survive-cli`}),` at the pinned commit plus Axol’s libsurvive fixes (tracker-clock rollover timestamps, base-station decoding through both trackers, the solver-buffer, scene-solver and solver-stack crash fixes for setups with more base stations, and poses streamed without buffering delay; see `,_jsx(_components.a,{href:`/cli/tracker#axol-trackerinstall`,children:_jsx(_components.code,{children:`tracker.install`})}),`) with Eigen and the native libusb backend, installs the Vive udev rule, and is safe to rerun: an existing pinned build is re-attested, not rebuilt (a build from before the fixes is rebuilt once).`]})}),_jsx(Step,{title:`Trackers`,children:_jsxs(_components.p,{children:[`Power the base stations. For each tracker, unplug it from USB so it blinks blue, plug its Watchman dongle into the host, and press `,_jsx(_components.strong,{children:`Pair tracker`}),` (CLI: `,_jsx(_components.code,{children:`axol tracker.pair`}),`). Then, with both trackers powered and in view, press `,_jsx(_components.strong,{children:`Check base stations`}),` (CLI: `,_jsx(_components.a,{href:`/cli/tracker#axol-trackerlighthousecheck`,children:_jsx(_components.code,{children:`axol tracker.lighthouse.check`})}),`): it listens for about 20 seconds (up to 150 seconds while libsurvive calibrates new or moved base stations; keep the trackers still) and resolves only when it sees no shared channel, both base stations, and both trackers reporting. A failure names the channel to change and how.`]})}),_jsx(Step,{title:`Identify sides`,children:_jsxs(_components.p,{children:[`Press `,_jsx(_components.strong,{children:`Identify trackers`}),` (CLI: `,_jsx(_components.a,{href:`/cli/tracker#axol-trackeridentify`,children:_jsx(_components.code,{children:`axol tracker.identify --backend survive`})}),`). Tracker addresses do not say which rig they are on, so Identify watches which tracker moves for each side: move `,_jsx(_components.strong,{children:`only the requested rig`}),` during each 3-second capture. It refuses to bind while base stations share a channel. Setup is complete once both sides are bound.`]})})]}),_jsxs(_components.p,{children:[`libsurvive calibrates the base-station world on first run, so leave the trackers still and visible for a few seconds. The standard flat-back mount uses the `,_jsx(_components.strong,{children:`built-in factory transform`}),`, so nothing needs measuring.`]})]}),_jsxs(Tab,{title:`Ultimate`,id:`ultimate`,children:[_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`VIVE Ultimate Trackers`}),` track themselves (inside-out SLAM) against a room map they share over a private access point, with no base stations or headset. There is no official Linux support; the host uses the community `,_jsx(_components.a,{href:`https://github.com/nijkah/pyvut`,children:`pyvut`}),` driver to talk to the `,_jsx(_components.strong,{children:`wireless dongle over USB HID`}),`. Pairing and mapping are Windows-only, one-time operator steps; everything after that runs from the control panel.`]}),_jsxs(Steps,{children:[_jsx(Step,{title:`One time on Windows: pair`,children:_jsxs(_components.p,{children:[`On a Windows PC install SteamVR, enable its `,_jsx(_components.a,{href:`https://github.com/username223/SteamVRNoHeadset`,children:`null/virtual-headset driver`}),`, install VIVE Streaming Hub, and install the `,_jsx(_components.strong,{children:`VIVE Ultimate Tracker service`}),` inside VIVE Hub (`,_jsx(_components.a,{href:`https://github.com/nijkah/pyvut/blob/fcfcd33f4c1f16b0d84f5f741dc1319abdc7942a/README.md#constructing-map-steamvr--vive---only-available-on-windows`,children:`pinned pyvut setup`}),`, `,_jsx(_components.a,{href:`https://business.vive.com/us/support/ultimate-tracker/category_howto/pairing-ultimate-tracker-with-the-dongle.html`,children:`HTC pairing prerequisites`}),`). Connect the wireless dongle and open `,_jsx(_components.strong,{children:`Settings → VIVE Ultimate Tracker → Trackers → Pair new`}),`. Power on a tracker, wait through its green/blue startup flashes until the LED is solid blue, then hold Power for about two seconds until blue flashes again and pair it. Repeat for the second tracker on the same dongle.`]})}),_jsx(Step,{title:`One time on Windows: map`,children:_jsxs(_components.p,{children:[`Choose `,_jsx(_components.strong,{children:`Start setup → Create map`}),`, establish the center, and scan the entire Mantis operating volume from both low/kneeling and standing heights while facing all four directions. Let the map auto-save (use Refine/Save if offered) and confirm both trackers regain full position + rotation after a power cycle. The pairing and map live on the trackers and move with them to the robot host.`]})}),_jsx(Step,{title:`Source`,children:_jsxs(_components.p,{children:[`Back in the control panel, choose `,_jsx(_components.strong,{children:`Ultimate`}),` (the selection saves immediately).`]})}),_jsx(Step,{title:`Runtime`,children:_jsxs(_components.p,{children:[`Press `,_jsx(_components.strong,{children:`Install / Repair Ultimate support`}),` (CLI: `,_jsx(_components.a,{href:`/cli/tracker#axol-trackerultimateinstall`,children:_jsx(_components.code,{children:`axol tracker.ultimate.install`})}),`). It installs the pinned pyvut commit, the `,_jsx(_components.code,{children:`hid`}),` Python binding, system HID libraries, and a persistent udev rule for dongle `,_jsx(_components.code,{children:`0bb4:0350`}),`; safe to rerun. Do not replace it with an unpinned `,_jsx(_components.code,{children:`pip install`}),` or a one-off `,_jsx(_components.code,{children:`chmod /dev/hidraw*`}),`.`]})}),_jsx(Step,{title:`Dongle`,children:_jsxs(_components.p,{children:[`Plug the same dongle into the host. The step resolves when the dongle is detected, its HID endpoint is accessible, and the operator has USB access. The one host-side step, adding the Axol login to the `,_jsx(_components.code,{children:`dialout`}),` group, is shown with its command because it needs a re-login.`]})}),_jsx(Step,{title:`Wi-Fi`,children:_jsxs(_components.p,{children:[`Save the trackers’ `,_jsx(_components.strong,{children:`private shared-map AP`}),` configuration: SSID, password, two-letter regulatory country, and Wi-Fi center frequency in MHz. These are `,_jsx(_components.strong,{children:`not`}),` the Windows PC’s network, the robot LAN, or your router. Upstream pyvut requires all four as fallback; with the USB-dongle transport the tracker firmware reports the active SLAM-host SSID/password/frequency and only the saved country is consumed directly. The password is masked and never read back, and the file is written as `,_jsx(_components.code,{children:`~/.almond/tracker/ultimate_wifi.json`}),` with mode `,_jsx(_components.code,{children:`0600`}),` (CLI/manual shape in the `,_jsx(_components.a,{href:`/cli/tracker#ultimate-wi-fi-file`,children:`reference`}),`).`]})}),_jsx(Step,{title:`Identify sides`,children:_jsxs(_components.p,{children:[`Power both trackers inside the mapped area and press `,_jsx(_components.strong,{children:`Identify trackers`}),` (CLI: `,_jsx(_components.a,{href:`/cli/tracker#axol-trackeridentify`,children:_jsx(_components.code,{children:`axol tracker.identify --backend ultimate`})}),`). Move only the requested rig for each side; the addresses are saved separately from Lighthouse bindings. Setup is complete once both sides are bound. `,_jsx(_components.a,{href:`/cli/tracker#axol-trackerultimatecheck`,children:_jsx(_components.code,{children:`axol tracker.ultimate.check`})}),` is an optional non-invasive host check that reports the same dependency, USB, permission, binding, convention, and redacted AP-config state the Start gate uses.`]})})]}),_jsx(Warning,{children:_jsxs(_components.p,{children:[`The dongle protocol is reverse-engineered and firmware-dependent. The pinned runtime and factory transform expect `,_jsx(_components.code,{children:`wxyz`}),` quaternions and a z-up SLAM world. Bench-check gravity, translations, and rotations before changing those settings, and never use the quaternion/up-axis toggle to compensate for a mount transform: a convention change disables the factory transform and makes saved Ultimate overrides stale, blocking production use until an override is saved under the active convention or the pinned convention is restored.`]})}),_jsxs(_components.p,{children:[`The standard flat-back mount uses the `,_jsx(_components.strong,{children:`built-in factory transform`}),` (the Tracker 3.0 value with the 11 mm origin difference folded in); nothing needs measuring.`]})]})]}),`\n`,_jsx(_components.p,{children:`The readiness the flow shows is also enforced by the operation backend: a direct CLI or REST start cannot bypass the runtime, permission, dongle/AP, binding, live-input, or production-transform gates.`}),`\n`,_jsx(Heading,{level:`2`,id:`start-gesture-and-recovery`,children:`Start gesture and recovery`}),`\n`,_jsxs(Tabs,{children:[_jsxs(Tab,{title:`Quest`,id:`quest-2`,children:[_jsxs(_components.p,{children:[`Hold both rigs at the agreed start pose, `,_jsx(_components.strong,{children:`release both controller grips, then press both grips together`}),` to align and engage. The controller triggers operate the grippers. In data collection, `,_jsx(_components.strong,{children:`A`}),` starts and confirms a take, exactly as on Axol.`]}),_jsxs(_components.p,{children:[`If tracking is lost on either side, both sides freeze. Restore both, then release and press both grips again; recovery never resumes motion on its own. `,_jsx(_components.strong,{children:`Reset`}),` returns to rest and needs the same fresh gesture.`]})]}),_jsxs(Tab,{title:`Lighthouse / Ultimate`,id:`lighthouse-%2F-ultimate`,children:[_jsxs(_components.p,{children:[`A managed run does `,_jsx(_components.strong,{children:`not`}),` move as soon as the trackers appear. Startup waits for `,_jsx(_components.strong,{children:`both fresh, fully tracked poses and both trigger heartbeats`}),`. Then hold both rigs where the session should begin, `,_jsx(_components.strong,{children:`squeeze both rig triggers together, and release both`}),`: on that release the world→robot base transform is fitted at the rigs’ pose and tracking engages. In data collection the same gesture also starts the take (once the teleop core acknowledges the engage); while recording, rapidly fully squeeze and release either trigger `,_jsx(_components.strong,{children:`three times`}),` to save or `,_jsx(_components.strong,{children:`four times`}),` to discard.`]}),_jsxs(_components.p,{children:[`A tracker-lock loss or stale trigger heartbeat freezes both sides. Restore both, then repeat the both-squeeze→release gesture; nothing re-anchors automatically. `,_jsx(_components.strong,{children:`Reset`}),` returns to rest and requires the gesture again.`]}),_jsxs(_components.p,{children:[`A Quest may still connect as a `,_jsx(_components.strong,{children:`view-only`}),` passthrough / video / HUD client: its controller frames are ignored and never blend with the managed tracker bridge. Because there is no built-in Quest↔tracker world-registration procedure, the spatial URDF overlay is hidden for these sources by default.`]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`transforms`,children:`Transforms`}),`\n`,_jsxs(_components.p,{children:[`Absolute (Mantis) IK solves the world yaw and translation at engage time, but it does `,_jsx(_components.strong,{children:`not`}),` discover where the gripper TCP sits relative to the tracker. That rigid `,_jsx(_components.strong,{children:`tracker → TCP transform`}),` must come from somewhere:`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Source`}),_jsx(_components.th,{children:`Transform`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Lighthouse (Tracker 3.0)`}),_jsxs(_components.td,{children:[`Built-in factory value `,_jsx(_components.code,{children:`[0, 0.035, 0.047, 0, 1, 0, 0]`}),` (`,_jsx(_components.code,{children:`Ry(180°)`}),` flat-back mount rotation; tracker 47 mm behind and 35 mm above the gripper motor face) for both sides, applied automatically after Identify.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Ultimate`}),_jsxs(_components.td,{children:[`Built-in factory value `,_jsx(_components.code,{children:`[0, 0.046, 0.047, 0, 1, 0, 0]`}),` for both sides (Tracker 3.0’s mount rotation with the 11 mm origin rise), approved for the pinned `,_jsx(_components.code,{children:`wxyz`}),` / z-up convention.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Quest 3 (Touch Plus)`}),_jsxs(_components.td,{children:[`Built-in factory values for `,_jsx(_components.code,{children:`quest:meta-quest-touch-plus:grip`}),`, mirrored between the left and right cradles (grip origin 62 mm behind and 45 mm above the gripper motor face), selected automatically when no Quest entry is saved.`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Other Quest controllers`}),_jsxs(_components.td,{children:[`No factory value: measure the gripper TCP in each controller’s grip frame and save it under the exact `,_jsx(_components.code,{children:`quest:\u003creported-profile>:grip`}),` key for both sides.`]})]})]})]}),`\n`,_jsxs(Warning,{children:[_jsxs(_components.p,{children:[`Mantis datasets recorded with axol ≤ 0.2.4 used an `,_jsx(_components.code,{children:`Rx(+90°)`}),` Vive rotation that pitched every recorded gripper ~90° away from the operator’s hold; Axol could not replay them. Repair them with `,_jsx(_components.a,{href:`/cli/migrate-dataset#mantis-tracker-gripper-rotation`,children:_jsx(_components.code,{children:`axol migrate-dataset --mantis-tcp-rotation`})}),` before replay or training.`]}),_jsxs(_components.p,{children:[`axol 0.2.5–0.2.16 shipped a Vive translation (`,_jsx(_components.code,{children:`[0, 0.0355, -0.092]`}),` / `,_jsx(_components.code,{children:`[0, 0.0465, -0.092]`}),`) that put the tracker 92 mm in front of the gripper instead of 47 mm behind it. `,_jsx(_components.code,{children:`collect-data`}),` refuses to append to a dataset recorded with it; record to a new `,_jsx(_components.code,{children:`repo_id`}),`.`]})]}),`\n`,_jsxs(_components.p,{children:[`Overrides and Quest measurements live in `,_jsx(_components.code,{children:`~/.almond/mantis/tcp_transform.json`}),`, keyed per side by the identified device (`,_jsx(_components.code,{children:`survive:\u003ctracker serial>`}),`, `,_jsx(_components.code,{children:`ultimate:\u003cmac>`}),`, or `,_jsx(_components.code,{children:`quest:\u003cprofile>:grip`}),`). `,_jsx(_components.code,{children:`pos`}),` is the gripper TCP origin in the tracker’s local frame in `,_jsx(_components.strong,{children:`metres`}),`; `,_jsx(_components.code,{children:`quat`}),` is the TCP orientation in that frame as `,_jsx(_components.strong,{children:_jsx(_components.code,{children:`[qx, qy, qz, qw]`})}),`. The file is edited directly (or via `,_jsx(_components.code,{children:`PUT /api/tracker/calibration/\u003csource>`}),`); the panel does not edit transforms. The full schema, the Ultimate convention rules, and the bring-up route for a missing constant (`,_jsx(_components.code,{children:`--mantis_allow_uncalibrated true`}),` for a calibration-only capture) are in the `,_jsx(_components.a,{href:`/cli/tracker#transform-file`,children:`tracker CLI reference`}),`. Production `,_jsx(_components.code,{children:`collect-data`}),` refuses to start with a missing, stale, or unapproved transform on either side.`]}),`\n`,_jsx(Heading,{level:`2`,id:`acceptance`,children:`Acceptance`}),`\n`,_jsxs(_components.p,{children:[`Before recording training data, run a short `,_jsx(_components.strong,{children:`bring-up collection run`}),` (`,_jsx(_components.code,{children:`axol collect-data --mantis true --mantis_allow_uncalibrated true`}),`, discarding every take) with the workspace clear and check, with the selected live source:`]}),`\n`,_jsxs(_components.ol,{children:[`\n`,_jsxs(_components.li,{children:[`Physical `,_jsx(_components.strong,{children:`left/right`}),`, scale, every translation axis, wrist rotation, and tracker→TCP alignment. With Quest, the headset’s live URDF overlay shows the virtual arms; with Lighthouse/Ultimate, use the control panel’s mirrored view.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Recovery`}),`: deliberately occlude or de-localize one side and confirm both freeze; restore tracking and confirm nothing moves until the start gesture is repeated.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`tracker.identify`}),` binds a tracker to a side by which one you move, not by which rig it is mounted on. If the tracker bound as `,_jsx(_components.em,{children:`left`}),` sits on the rig held in the right hand, the engage fit faces the robot base backwards and every recorded pose is yawed 180° with the columns swapped. With both sides calibrated, the engage refuses this: the log reports `,_jsx(_components.code,{children:`Teleop engage rejected: both grippers face away from the robot's rest heading`}),`, the session disengages, and nothing is recorded until both grips are released and squeezed again with the rigs in the correct hands (or re-identified). Hold each rig like the robot holds its rest gripper — fingers down, tracker face up, handle towards you — when engaging; a rig pointed straight up or down is not checked.`]}),`\n`,_jsxs(_components.p,{children:[`Recording also counts frames where a hand travelled beyond Axol’s reach (`,_jsx(_components.code,{children:`out_of_reach`}),` in the episode QA line, plus a warning): the rig has no arm to stop you, but Axol cannot follow those poses on replay.`]}),`\n`,_jsxs(_components.p,{children:[`A source that jumps, drifts, swaps sides, or cannot recover cleanly is not ready for collection. Mantis teleop never tracks, and `,_jsx(_components.code,{children:`axol teleop --sim --teleop.absolute_mode true`}),` is only a generic mapping demo; physical acceptance must use the real source, Mantis selected, in a collection run.`]}),`\n`,_jsx(Heading,{level:`2`,id:`next-steps`,children:`Next steps`}),`\n`,_jsxs(CardGroup,{cols:2,children:[_jsx(Card,{title:`Teleoperation`,icon:`gamepad`,href:`/operations/teleop`,children:_jsx(_components.p,{children:`Check the grippers from the rig triggers (no tracking).`})}),_jsx(Card,{title:`Data Collection`,icon:`record-vinyl`,href:`/operations/data-collection`,children:_jsx(_components.p,{children:`Record Mantis episodes to a LeRobot dataset.`})})]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Mantis Tracking","description":"Set up Meta Quest, VIVE Tracker 3.0 (Lighthouse), or VIVE Ultimate tracking as the Mantis pose source.","href":"/mantis/tracking"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Set up the source","slug":"set-up-the-source","depth":2,"children":[]},{"title":"Start gesture and recovery","slug":"start-gesture-and-recovery","depth":2,"children":[]},{"title":"Transforms","slug":"transforms","depth":2,"children":[]},{"title":"Acceptance","slug":"acceptance","depth":2,"children":[]},{"title":"Next steps","slug":"next-steps","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"almondbot","siteName":"Docs","title":"Mantis Tracking - Docs","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-20T15:23:17.929Z","originalFileLocation":"mantis/tracking.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Set up Meta Quest, VIVE Tracker 3.0 (Lighthouse), or VIVE Ultimate tracking as the Mantis pose source.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Docs\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Mantis Tracking - Docs\">\u003cmeta data-mint-head property=\"og:description\" content=\"Set up Meta Quest, VIVE Tracker 3.0 (Lighthouse), or VIVE Ultimate tracking as the Mantis pose source.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Docs\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.almond.bot/mantis/tracking\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Mantis Tracking - Docs\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Set up Meta Quest, VIVE Tracker 3.0 (Lighthouse), or VIVE Ultimate tracking as the Mantis pose source.\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/almondbot/HmxSPHVvkmFyL2Le/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Docs\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#c79760\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.almond.bot/mantis/tracking\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/mantis/tracking.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.almond.bot/#organization\",\"name\":\"Docs\",\"url\":\"https://docs.almond.bot\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/almondbot/idfco7KlgFmESEu4/assets/logo.png?fit=max\u0026auto=format\u0026n=idfco7KlgFmESEu4\u0026q=85\u0026s=bc87e5e797ca0a584e6d373c704c28d9\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.almond.bot#website\",\"name\":\"Docs\",\"url\":\"https://docs.almond.bot\",\"publisher\":{\"@id\":\"https://docs.almond.bot/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.almond.bot/mantis/tracking#webpage\",\"url\":\"https://docs.almond.bot/mantis/tracking\",\"name\":\"Mantis Tracking\",\"description\":\"Set up Meta Quest, VIVE Tracker 3.0 (Lighthouse), or VIVE Ultimate tracking as the Mantis pose source.\",\"dateModified\":\"2026-10-20T15:23:17.929Z\",\"isPartOf\":{\"@id\":\"https://docs.almond.bot#website\"},\"breadcrumb\":{\"@id\":\"https://docs.almond.bot/mantis/tracking#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.almond.bot/mantis/tracking#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Mantis\",\"item\":\"https://docs.almond.bot/mantis/hardware\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Mantis Tracking\",\"item\":\"https://docs.almond.bot/mantis/tracking\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.almond.bot/mantis/tracking#article\",\"headline\":\"Mantis Tracking\",\"name\":\"Mantis Tracking\",\"description\":\"Set up Meta Quest, VIVE Tracker 3.0 (Lighthouse), or VIVE Ultimate tracking as the Mantis pose source.\",\"url\":\"https://docs.almond.bot/mantis/tracking\",\"mainEntityOfPage\":{\"@id\":\"https://docs.almond.bot/mantis/tracking#webpage\"},\"dateModified\":\"2026-10-20T15:23:17.929Z\",\"publisher\":{\"@id\":\"https://docs.almond.bot/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.almond.bot#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Docs\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Docs","nav":{"global":null,"groups":[{"group":"Get Started","pages":[{"title":"Almond Axol","href":"/index"},{"title":"Hardware Overview","href":"/hardware"},{"group":"Hardware Setup","root":{"title":"Hardware Setup","href":"/hardware-setup"},"pages":[{"title":"Axol Standalone","href":"/hardware-setup/standalone"},{"title":"Axol + Owl Mount","href":"/hardware-setup/owl-mount"},{"title":"Axol + Ox Cart","href":"/hardware-setup/ox-cart"},{"title":"Axol + Jelly","href":"/hardware-setup/jelly"},{"title":"Axol Cameras","href":"/hardware-setup/cameras"},{"title":"Advantech Thor (MIC-742)","href":"/hardware-setup/thor"}]},{"title":"Installation","href":"/installation"}]},{"group":"Operations","pages":[{"group":"Teleoperation","root":{"title":"Teleoperation","href":"/operations/teleop"},"pages":[{"title":"Teleoperation Config Parameters","sidebarTitle":"Config Parameters","href":"/operations/teleop-config"}]},{"group":"Gravity Compensation","root":{"title":"Gravity Compensation","href":"/operations/gravity-comp"},"pages":[{"title":"Gravity Compensation Config Parameters","sidebarTitle":"Config Parameters","href":"/operations/gravity-comp-config"}]},{"group":"Data Collection","root":{"title":"Data Collection","href":"/operations/data-collection"},"pages":[{"title":"Data Collection Config Parameters","sidebarTitle":"Config Parameters","href":"/operations/data-collection-config"}]},{"group":"Replay Dataset","root":{"title":"Replay Dataset","href":"/operations/replay-dataset"},"pages":[{"title":"Replay Dataset Config Parameters","sidebarTitle":"Config Parameters","href":"/operations/replay-dataset-config"}]},{"title":"Train a Policy","href":"/operations/train-policy"},{"group":"Run Policy","root":{"title":"Run Policy","href":"/operations/run-policy"},"pages":[{"title":"Run Policy Config Parameters","sidebarTitle":"Config Parameters","href":"/operations/run-policy-config"},{"title":"Run Your Own Policy","sidebarTitle":"Custom Policy","href":"/operations/custom-policy"}]},{"group":"DAgger Collection","root":{"title":"DAgger Collection","href":"/operations/dagger"},"pages":[{"title":"DAgger Collection Config Parameters","sidebarTitle":"Config Parameters","href":"/operations/dagger-config"}]}]},{"group":"Mantis","pages":[{"title":"Mantis Hardware","description":"Overview and setup guide for the Mantis handheld data-collection rigs.","href":"/mantis/hardware"},{"title":"Mantis Tracking","href":"/mantis/tracking"}]},{"group":"Remote Teleop","pages":[{"title":"Remote Teleop","description":"Drive Axol over the internet from anywhere — put the Quest and the robot machine on the same Tailscale network.","href":"/guides/remote-teleop"}]},{"group":"Web Interfaces","pages":[{"group":"Web Control Panel","root":{"title":"Web Control Panel","href":"/guides/control-panel"},"pages":[{"title":"The Robot's Settings File","sidebarTitle":"Settings File","href":"/guides/control-panel-settings"}]},{"title":"Diagnostics Dashboard","href":"/guides/diagnostics-dashboard"},{"title":"Calibrating a Robot","href":"/guides/robot-calibration"},{"title":"VR Interface","href":"/guides/vr-interface"},{"title":"Quest over USB","href":"/guides/quest-over-usb"},{"title":"Quest Without Wearing It","href":"/guides/quest-headless"}]},{"group":"Advanced","pages":[{"title":"Development install","href":"/advanced/development-install"},{"group":"CLI Reference","pages":[{"title":"Command configuration","href":"/cli/configuration"},{"group":"Operations","pages":[{"title":"teleop","href":"/cli/teleop"},{"title":"gravity-comp","href":"/cli/gravity-comp"},{"title":"waypoints","href":"/cli/waypoints"},{"title":"collect-data","href":"/cli/collect-data"},{"title":"collect-dagger","href":"/cli/collect-dagger"},{"title":"replay-dataset","href":"/cli/replay-dataset"},{"title":"run-policy","href":"/cli/run-policy"},{"title":"inference-server","href":"/cli/inference-server"},{"title":"policy.check","href":"/cli/policy-check"},{"title":"migrate-dataset","href":"/cli/migrate-dataset"}]},{"group":"Host","pages":[{"title":"serve","href":"/cli/serve"},{"title":"provision","href":"/cli/provision"},{"title":"rt.install","href":"/cli/rt-install"},{"title":"jetson.setup","href":"/cli/jetson-setup"}]},{"group":"CAN","pages":[{"title":"can.setup","href":"/cli/can-setup"},{"title":"can.enable","href":"/cli/can-enable"},{"title":"can.driver","href":"/cli/can-driver"},{"title":"hub.info","href":"/cli/hub-info"},{"title":"hub.flash","href":"/cli/hub-flash"}]},{"group":"Motors","pages":[{"title":"motor.info","href":"/cli/motor-info"},{"title":"motor.health","href":"/cli/motor-health"},{"title":"motor.set-can-id","href":"/cli/motor-set-can-id"},{"title":"motor.set-zero-pos","href":"/cli/motor-set-zero-pos"},{"title":"motor.dump-config","href":"/cli/motor-dump-config"},{"title":"motor.set-config","href":"/cli/motor-set-config"},{"title":"motor.restore-config","href":"/cli/motor-restore-config"},{"title":"motor.flash","href":"/cli/motor-flash"}]},{"group":"Lift","pages":[{"title":"lift.home","href":"/cli/lift-home"},{"title":"lift.goto","href":"/cli/lift-goto"},{"title":"lift.update","href":"/cli/lift-update"}]},{"group":"Cameras","pages":[{"title":"zed.install","href":"/cli/zed-install"},{"title":"zed.driver","href":"/cli/zed-driver"},{"title":"gst.install","href":"/cli/gst-install"},{"title":"gst.build-zed","href":"/cli/gst-build-zed"}]},{"group":"Mantis tracking","pages":[{"title":"tracker.*","href":"/cli/tracker"}]},{"group":"Diagnostics","pages":[{"title":"diag.rom-enable","href":"/cli/diag-rom-enable"},{"title":"diag.rom-disable","href":"/cli/diag-rom-disable"},{"title":"diag.teleop-jitter","href":"/cli/diag-teleop-jitter"},{"title":"diag.offline","href":"/cli/diag-offline"},{"title":"diag.lift-cycle","href":"/cli/diag-lift-cycle"},{"title":"diag.zed-cable","href":"/cli/diag-zed-cable"}]},{"group":"Tuning","pages":[{"title":"tune.pid","href":"/cli/tune-pid"},{"title":"tune.friction","href":"/cli/tune-friction"},{"title":"tune.breakaway","href":"/cli/tune-breakaway"},{"title":"tune.a4","href":"/cli/tune-a4"},{"title":"tune.gravity","href":"/cli/tune-gravity"},{"title":"tune.factory","href":"/cli/tune-factory"},{"title":"tune.motion","href":"/cli/tune-motion"},{"title":"tune.tf / motion.chirp","href":"/cli/tune-tf"},{"title":"tune.filter","href":"/cli/tune-filter"},{"title":"motion.build / motion.list","href":"/cli/motion-build"},{"title":"tune.repeatability","href":"/cli/tune-repeatability"}]}]},{"group":"Python API","pages":[{"title":"Core Concepts","href":"/api/concepts"},{"title":"almond_axol.robot","href":"/api/robot"},{"title":"almond_axol.kinematics","href":"/api/kinematics"},{"title":"almond_axol.teleop","href":"/api/teleop"},{"title":"almond_axol.vr","href":"/api/vr"},{"title":"almond_axol.zed","href":"/api/zed"},{"title":"almond_axol.motor","href":"/api/motor"},{"title":"almond_axol.lerobot","href":"/api/lerobot"},{"title":"almond_axol.policy","href":"/api/policy"},{"title":"Custom policy interface","href":"/api/policy-plan"}]}]}]},"title":"Mantis Tracking - Docs","buildId":"dpl_CUnGQP1HgFUzoxatVC37y4zP83Vx","deploymentId":"dpl_CUnGQP1HgFUzoxatVC37y4zP83Vx","dynamics":[],"chunks":[]}