Skip to content

Verify navigation with diagnostics

Use diagnostics to verify the facts your integration reports, the poses it applies and the corrections it sends. Camera movement alone can hide incorrect picks or bounds because it also reflects the server’s navigation policy.

This walkthrough follows logging and overlays in the runnable demo applications. Complete that quickstart first so your demo is running and connected to Rotatrix. The examples below show how the integration collects evidence and how the application displays it.

Native integrations can configure local session logs once to capture SDK events and host messages with automatic rotation and retention. Browser demos use the same logging interface with a console sink.

  1. Run the demo with Rotatrix connected, then press D to show diagnostics.
  2. Start a gesture over a cube. Compare the reported pick point and bounds with the cube, then repeat over empty space and with a selection. Check which query alternatives were actually evaluated.
  3. Use mouse navigation during the gesture to see correction status. Press D again to hide the overlay; logging continues in the terminal or browser console.

Normal logs include query results, gesture changes, corrections and readback status changes. Gesture performance summaries are collected automatically; see performance summary fields. To include correction details, enable debug logging:

Terminal window
python examples/python_demo_3d_app/main.py --debug

C++ demo diagnostic overlay showing query facts, timings, a returned pick.cursor hit on a cube, and a skipped viewport-center pick.

In this capture, the yellow pick.cursor row reports a hit and identifies it as the returned candidate. The 2D crosshair marks the screen position picked; the 3D crosshair marks the resulting surface position. The selection-only picks missed, while pick.viewport_center was skipped after an earlier candidate supplied a result. The header identifies the gesture and query, and the timings show how long the query and individual facts took. “Returned candidate” confirms what the client sent, not which pivot the server ultimately chose.

ResultMeaning
Unknown readbackThe write succeeded, but the application did not return its resulting pose.
Equivalent / differsThe resulting pose matches / differs under the NavigationSession’s comparison policy. Application constraints can legitimately change a pose.
AcknowledgedThe server acknowledged a delta correction sent by the integration.
SkippedAn earlier alternative supplied the query result, so this alternative was not evaluated.
Returned candidateThe first available result returned to the server. The client does not know whether the server ultimately used it.

The collector uses actual query and write results, without extra picks or camera reads. Retained correction status expires according to the collector configuration.

Three components connect navigation to the display:

  • The SDK’s NavigationSession executes queries and pose updates through the application’s adapter, then reports their results.
  • The SDK’s NavigationDiagnostics collector turns those results into log messages and a presentation snapshot containing text and geometry.
  • The application’s logger and renderer send messages to the console and draw the snapshot in the viewport.

The integration creates and connects these components. Its pick resolver also returns a local marker position, so the collector can show where each test ran.

At startup, the integration creates the collector and supplies it to NavigationSession. That connects the collector to actual navigation results and the same comparison policy used to detect camera or object changes.

MyOpenAxisIntegration.start() extends the quickstart’s setup here. app, client, adapter and scheduler are already created. Passing the collector to the NavigationSession lets it observe real navigation; the application hooks provide the toggle and rendering callbacks:

configure_logging("python-demo",
level="debug" if logging.getLogger().isEnabledFor(logging.DEBUG) else "info",
sinks=[lambda level, message: getattr(logging.getLogger("demo"), level)(message)])
diagnostics = NavigationDiagnostics(log_level="debug",
context_key=lambda context: app)
object_adapter = MyObjectAdapter(app)
session = NavigationSession(client, adapter, scheduler, observation=adapter.read, diagnostics=diagnostics,
object_adapter=object_adapter, object_observation=object_adapter.read,
comparison=compare_camera_pose, object_comparison=compare_object_pose)
app.toggle_diagnostics = lambda: diagnostics.set_enabled(not diagnostics.enabled)
app.diagnostic_frame = lambda: diagnostics.presentation()
app.diagnostics_enabled = lambda: diagnostics.enabled
app.diagnostic_colors = COLORS

The SDK writes a session file and mirrors messages through Python’s logging module, configured for INFO or DEBUG with --debug. The application hooks connect D to capture visibility and provide the renderer with presentation data and the shared palette.

A pick crosshair shows where the application tested the scene, including tests that missed. When NavigationSession requests a pick fact, the demo’s resolver calls the application’s picker and returns markerPosition alongside the hit point and bounds. A miss returns only the marker position. A skipped test, such as a selection pick with no selection, returns the unavailable value.

The collector reads that position from the resolver result. The SDK removes it before choosing an available candidate and sending the response, so a miss still allows the next first alternative to run.

MyQueryCapture.resolve() chooses the saved cursor or viewport center and checks that a test can run. app is the application captured for this query:

pixel = self.cursor if name.startswith('pick.cursor') else (self.width/2, self.height/2)
if (pixel is None or not (0 <= pixel[0] < self.width and 0 <= pixel[1] < self.height)
or (name.endswith('.selection') and self.app.selected is None)):
return UNAVAILABLE
hit = self.app.pick(pixel, selection_only=name.endswith('.selection'))
if hit is not None:
_, point, box = hit
return {'point': point, 'bounds': bounds_value((box.minimum, box.maximum)), 'markerPosition': pixel}
return {'markerPosition': pixel}

The demo renderers interpret markerPosition in their own viewport coordinates. This local position is distinct from the protocol’s cursor coordinates; other applications can use a different convention, such as NDC. Samples at identical positions with the same tone share a crosshair with separate label lines. The pick evidence reference describes the return contract.

A presentation snapshot contains text rows, world-space segments and screen markers. Its context identifies the view that produced the evidence; its expiry deadline tells the application when retained status needs refreshing. This lets the renderer display the collected results without re-running queries.

The application reads the snapshot on the collector’s owning thread, draws it in the matching view, and refreshes after expiry even if no new event arrives.

Panda3D’s frame callback calls MyApplication._draw_diagnostics(). It checks context, revision, expiry and viewport size before rebuilding:

def _draw_diagnostics(self):
# Only the renderer is application code; content/styles come from SDK.
frame = self.diagnostic_frame()
if frame is None:
for node in self._diagnostic_nodes:
node.remove_node()
self._diagnostic_nodes.clear()
self._diagnostic_world_points = ()
self._diagnostic_key = None
return
key = (frame.revision,frame.expires_at,self.size)
if key == self._diagnostic_key:
return
self._diagnostic_key = key
for node in self._diagnostic_nodes:
node.remove_node()
self._diagnostic_nodes.clear()
self._diagnostic_world_points = ()
if frame.context is not None and frame.context is not self:
return
self._diagnostic_world_points = tuple(
point for segment in frame.segments for point in (segment.start,segment.end))

The renderer uses the presentation’s text, tones, segments and markers. Its drawing implementation is available in the full source.

Use the Diagnostics API for evidence fields, context mapping, thread ownership and refresh scheduling. Follow the rendering contract when mapping that evidence to native graphics. Use local session logs to route output, or observer events for a custom consumer.

Check reported facts against known application state before judging navigation. Cover each supported fact, including unavailable cases:

  • Picking: cursor and viewport-center samples, with and without a selection; hits, misses and a cursor outside the viewport.
  • Geometry and camera: bounds, orientation and camera pose in both projection types, with wide and tall viewports.
  • Pose application: requested versus observed poses, including constraints and concurrent native input; verify the resulting corrections.
  • Context: switch documents, views and object targets; evidence must remain associated with the context that produced it.

A skipped alternative has not been tested: exercise a query that requests it or test the resolver directly. Do not count a plausible server-chosen pivot as verification of all pick alternatives. Compare diagnostics enabled and disabled: camera behavior and query results should match. Check expiry and shutdown cleanup. If you add a graphical overlay, verify its markers, bounds and context filtering too.