Skip to content

RFC: signed athwartships quantities should be positive to starboard #685

Description

@dirkwa

Summary

Two keys in the specification declare positive to port. Every other signed lateral or rotational quantity declares positive to starboard. This RFC proposes a single principle:

Signed athwartships quantities in Signal K are positive to starboard, negative to port, matching the right-handed body frame that navigation.attitude.yaw and navigation.rateOfTurn already use.

The two outliers are sensors.*.fromCenter and steering.autopilot.target.windAngle*. They are separable — one is a breaking change, the other is a free clarification — and are proposed as separate commits so they can be accepted independently.

This came out of review on SignalK/signalk-server#2399, where @keesverruijt asked why fromCenter is port-positive: "Just seems backwards to me. If you draw a boat on xy 2d frame with nose up positive is SB."

Part 1 — sensors.*.fromCenter (breaking, MODEL bump)

schemas/groups/sensors.json defines:

"The perpendicular distance from the longitudinal centerline of the vessel to the sensor location, -ve to starboard, +ve to port."

It is the only port-positive key of its kind

Key File Sign
sensors.*.fromCenter sensors.json:36 +port ⚠️
steering.rudderAngle steering.json:9 +stbd
steering.rudderAngleTarget steering.json:15 +stbd
navigation.attitude.roll navigation.json:520 +stbd
navigation.attitude.yaw navigation.json:530 +stbd
navigation.rateOfTurn navigation.json:600 +stbd
propulsion.*.thrustAngle propulsion.json:159 +stbd
design.keel.angle design.json:528 +stbd
environment.wind.angleApparent environment.json:296 +stbd

Where it came from

Commit fa162f7, 23 Aug 2014, "Added sensors, design data, and anchor data". The clause has survived 12 years with only cosmetic edits (units dropped, indentation, punctuation). There is no issue, RFC, or discussion anywhere in the repository history justifying it — it appears to be an initial-commit choice that was never revisited.

Why it now matters

It used to be inert metadata. It no longer is. Server-side GNSS lever-arm correction (SignalK/signalk-server#2399, #680) multiplies fromCenter by a heading taken from the same body frame as attitude.yaw. Mixing a port-positive offset with a starboard-positive rotation forces a non-standard rotation matrix — the implementation documents its own deviation:

* (Note: this differs from the standard NED rotation matrix because
*  +y body is port here, not starboard.)

Under a starboard-positive convention this collapses to the standard NED rotation and the note disappears.

Implementations already disagree with the spec, and with each other

  • n2k-signalk/aisFromStarboard.js computes beam/2 - fromStarboard, i.e. positive = starboard, feeding sensors.ais.fromCenter from five PGNs. Pinned by a real-data test: beam 32 m, reference point 8 m from the starboard rail, asserts +8.
  • signalk-to-nmea2000/conversions/ais.js encodes beam/2 + fromCenter, i.e. positive = port — spec-compliant.

Because the decoder and encoder disagree, the AIS round trip is broken today:

posRefFromStarboard = 8   (8 m from the starboard rail, 32 m beam)
  → n2k-signalk decodes  → fromCenter = 8
  → signalk-to-nmea2000  → posRefFromStarboard = 24

The reference point is mirrored to the opposite side of the hull, off by 16 m. A live bug regardless of how this RFC resolves — but only fixable once the two ends agree.

There is also a third position: signalk-server's @signalk/path-metadata has served "positive towards starboard" for /vessels/*/sensors/*/fromCenter since v2.28.0, contradicting the same server's own OpenAPI description and admin UI.

NMEA 2000 does not settle it

Worth stating explicitly, since "match N2K" would look like the obvious tiebreak. Per canboat, positionReferenceFromStarboard is:

"Signed": false, "RangeMin": 0, "Unit": "m"

An unsigned distance from the starboard side (AIS dimension C/D per ITU-R M.1371). NMEA 2000 has no sign here at all — the signed centreline-relative form is entirely a Signal K invention. There is no external convention to preserve, only which one Signal K standardises on.

Proposal

Change to "+ve to starboard, -ve to port".

Under mdbook/src/versioning.md this is a MODEL bump: it inverts the meaning of existing data with no structural change, so historical values silently become wrong. Suggest tracking it as a v2 item — v2-food-for-thought.md exists for exactly this and has no axis-convention entry yet.

Mitigating the blast radius: fromCenter is not widely consumed. Freeboard-SK ignores the reference-point offsets entirely. The known sign-sensitive consumers are n2k-signalk, signalk-to-nmea2000 and hoekens-anchor-alarm. On the server side the multi-GNSS feature that uses it most heavily has shipped only in v2.31.0-beta.1/beta.2 — never in a stable release — so there is an unusually clean window.

Part 2 — steering.autopilot.target.windAngle* (non-breaking, ADDITION)

Separable from Part 1 and much cheaper. The spec currently contradicts itself about the same physical quantity:

  • environment.wind.angleApparent / angleTrueGround / angleTrueWater — "negative to port" → +stbd
  • steering.autopilot.target.windAngleApparent / windAngleTrue — "+port -starboard" → +port

An autopilot comparing measured apparent wind angle against its target must silently negate one, or steer to the mirror image of the requested angle.

Nearly free to fix:

  • No implementation pins the autopilot keys. No consumer of steering.autopilot.target.windAngle* found in signalk-server, signalk-autopilot, signalk-to-nmea2000 or Freeboard-SK.
  • The measured side is already starboard-positive in practice. n2k-signalk/pgns/130306.js maps the N2K wind angle (0…2π clockwise from bow) into (-π, π], so 0…π is starboard — matching environment.wind, not the autopilot keys.
  • The only spec fixture (test/data/vessel-valid/steering-sample.json) holds 1.0472 and 1.39626, both positive, so no test data encodes a side.

A two-line description change, no code migration, no data reinterpretation — ADDITION-level, not a MODEL bump.

Prepared changes

Both parts are implemented, and the ecosystem consequences worked through, so the full picture is visible rather than just the argument. None should merge before this RFC is settled — if it resolves the other way they are discarded, except the two that are independent of it.

PR Scope Depends on this RFC?
#680 Spec: both parts, as separate commits yes
SignalK/signalk-server#2943 Lever-arm math, OpenAPI, admin UI, tests yes
SignalK/signalk-to-nmea2000#162 Encoder sign; repairs the AIS round trip yes
SignalK/n2k-signalk#341 AtoN NaN bug (PGN 129041) no — independent bug
#684 Schema id versions stuck at 1.5.1 no — unrelated, found while auditing

Questions

  1. Does the principle hold — should all signed athwartships quantities be starboard-positive?
  2. For Part 1, is a v2/MODEL bump the right vehicle, or is there appetite for a 1.x REVISION with a documented migration note, given how few consumers exist and that the largest is beta-only?
  3. Should Part 2 be split out and landed immediately, independent of Part 1?
  4. fromBow has no sign statement at all — implicitly "toward the stern". Worth stating explicitly while the frame is being documented?

/cc @keesverruijt @tkurki

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions