Skip to content

docs(contract): write down the spatial conventions; state the import callback rule the code keeps - #242

Merged
facontidavide merged 1 commit into
mainfrom
docs/spatial-conventions-and-import-timing
Nov 14, 2026
Merged

facontidavide merged 1 commit into
mainfrom
docs/spatial-conventions-and-import-timing

Conversation

@facontidavide

Copy link
Copy Markdown
Contributor

Two contract texts that 1.0.0 would otherwise freeze missing or wrong. Both came out of the Codex readiness review of main. Comments, Markdown and the changelog only: no code token changes.

1. Spatial conventions

Nothing in the schemas said which unit positions use, which way a quaternion rotates, or how a FrameTransform maps a point. Changing any of these after 1.0 would reinterpret recorded data, so they need to be stated before the tag.

Geometry.proto now states them once, and docs/builtin_type.md repeats them for readers of the C++ types:

Convention Statement
Units Positions and lengths are in metres unless a field says otherwise (pixels for image annotations, encoding for depth, screen pixels under scale_invariant).
Handedness Frames are right-handed. The meaning of the axes belongs to the frame named by frame_id.
Quaternion (x, y, z, w), w scalar, Hamilton: v' = q * v * conjugate(q). Unit length. All zeros is read as the identity.
Pose p_frame = R(orientation) * p_local + position
FrameTransform p_parent = R(rotation) * p_child + translation

These describe existing behaviour, checked in two places:

  • the SDK decoder reads an all-zero quaternion as the identity (geometry_codec.hpp);
  • PlotJuggler's pj_tf::Transform applies t + q * p and its buffer normalizes on write.

Metres and right-handed frames are the ROS and Foxglove convention the types mirror; I did not find code that would contradict them, but they are a statement of intent rather than something a test pins.

The worked example was wrong. FrameTransforms.proto said that with translation (1, 2, 0) and a 90° rotation about z, the point (1, 0, 0) maps to (-1, 3, 0). The rotation gives (0, 1, 0), and adding the translation gives (1, 3, 0). I computed it to confirm. The same sentence is in the upstream Foxglove schema this file was copied from.

2. Descriptor import: callback timing

descriptor_import_protocol.h promised that no job callback may occur before start_import returns true. No provider can keep that: the worker is released as the last statement before return true, and a thread cannot be ordered against the return itself. provider_job.hpp already said this; the C header did not.

The rule is now what ProviderJob does:

  • no callback before out_job is populated;
  • none at all when start_import returns false;
  • a callback may run before start_import has returned, so the host has its callback state ready before it calls.

Three comments that quoted the old rule (in provider_job.cpp and the extension test) are reworded to match.

For the host: PlotJuggler should already prepare its callback context before calling start_import. I did not check that in the app.

Verification

Pre-commit passes. Nothing was built locally: the diff touches comments and Markdown only, and CI compiles the headers and the test file.

🤖 Generated with Claude Code

…callback rule the code keeps

Two contract texts that 1.0 would otherwise freeze wrong or missing.

Spatial conventions. Nothing said which unit positions are in, which way a
quaternion rotates, or how a FrameTransform maps a point. Geometry.proto now
states them once: metres, right-handed frames, quaternions as (x, y, z, w)
with v' = q * v * conjugate(q), all-zero read as the identity, and
p_parent = R(rotation) * p_child + translation. This is what the decoders
and PlotJuggler's transform code already do. The worked example in
FrameTransforms.proto, copied from the upstream schema, gave (-1, 3, 0)
where the result is (1, 3, 0).

Descriptor import. The header promised that no job callback occurs before
start_import returns true. A provider cannot keep that: a worker thread
cannot be ordered against the return itself, and provider_job.hpp already
said so. The rule is now what ProviderJob does: no callback before out_job
is populated, none when start_import returns false, and the host has its
callback state ready before it calls.

Comments, docs and the changelog only.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@facontidavide
facontidavide merged commit 7921b50 into main Nov 14, 2026
4 checks passed
@facontidavide
facontidavide deleted the docs/spatial-conventions-and-import-timing branch November 14, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant