| name | domain |
| description | Sailing instrument tribal knowledge that is NOT directly grep-recoverable from the code — common-mistake guards, B&G/instrument-vendor quirks, J/105 polar reference targets, and calibration miscalibration symptoms. Auto-triggers when working on sk_reader.py, can_reader.py, nmea2000.py, polar.py, boat_settings.py, synthesize.py, maneuver_detector.py, export.py, or calibration-related code. Skill content is intentionally narrow — for paths, PGN byte layouts, conversion constants, and table schemas already encoded in the source, read sk_reader.py / nmea2000.py / storage.py directly. |
Sailing Instrument Domain — Tribal Knowledge
This skill encodes only what the codebase does NOT make obvious on its own:
common mistakes the model has made before, vendor-specific quirks, polar
target speeds for our hull, and miscalibration symptoms. For Signal K paths,
PGN byte layouts, conversion constants, table schemas, and unit conversions,
read sk_reader.py, nmea2000.py, and storage.py directly — those files
are the source of truth and are always more current than this skill.
1. Common mistakes (priming guards)
These are errors the model has historically made. Read these before writing
instrument-handling code.
- Polar calculations always use BSP (
navigation.speedThroughWater), never
SOG (navigation.speedOverGround). SOG includes current — a 1-knot
favorable current makes the boat look 1 knot faster at every TWA/TWS bin
and contaminates the polar.
- TWA is derived FROM apparent wind, not the reverse. AWA + AWS + BSP →
TWA + TWS. The masthead wand measures AWA directly; TWA is computed by the
instrument processor (B&G Zeus). Code that "derives AWA from TWA" is
upside-down.
- TWA is 0–180 in HelmLog, not 0–360. Port/starboard symmetry is assumed
for polars. If you find yourself wrapping a TWA value through 360, you are
probably handling TWD (true wind direction), not TWA.
- Wind reference field matters. PGN 130306's reference field decides
what
wind_angle_deg actually means. Mishandling this is a recurring bug.
Codes (also in nmea2000.py):
0 — true, boat-referenced — value IS TWA
2 — apparent — filter out for polars
4 — true, north-referenced — value is TWD;
TWA = (TWD - HDG + 360) % 360 then fold to 0–180
- B&G systems typically emit reference 4 (TWD), not reference 0 (TWA).
So
_compute_twa() in polar.py is on the hot path; do not bypass it.
- Reference=4 needs a contemporaneous heading. If
HeadingRecord is
missing or stale, drop the wind sample — do not guess.
- AIS PGNs (129038–129810) are never ingested. Data licensing
requirement (#208), not a technical limitation. If you find yourself
adding an AIS decoder, stop and check
docs/data-licensing.md.
2. J/105 reference polars (used for synthetic test data)
These are the optimal upwind/downwind targets for our hull, used by
synthesize.py to generate fixture data. They are not in the SQL schema; if
you need numeric ground truth for a test, this is it:
| TWS (kts) | Upwind TWA | Upwind BSP | Downwind TWA | Downwind BSP |
|---|
| 6 | 44° | 5.2 kts | 150° | 4.8 kts |
| 8 | 43° | 6.0 kts | 145° | 5.8 kts |
| 10 | 42° | 6.5 kts | 140° | 6.5 kts |
| 12 | 41° | 6.8 kts | 135° | 7.0 kts |
| 16 | 39° | 7.3 kts | 130° | 7.6 kts |
If observed BSP is consistently >10% above or below these for the same
(TWS, TWA), suspect BSP calibration drift before assuming the model is fast
or slow.
3. Polar binning conventions
Not obvious from code at a glance:
- TWS bins: floor of TWS in knots (0, 1, 2, … 30+).
- TWA bins: floor to nearest 5° (0, 5, 10, … 175, 180).
- Per bin: mean BSP, P90 BSP, session count, sample count.
- Minimum sessions: 3 races before the baseline is published.
VMG bands (for VMG-per-sail analysis): 0–6, 6–10, 10–15, 15–20, 20+ kts.
4. Calibration miscalibration symptoms
Crew-entered tuning parameters live in boat_settings.py. They are stored
for debrief correlation; most do not yet feed calculations. The non-obvious
part is what miscalibration looks like in the data:
| Parameter | What it controls | Miscalibration symptom |
|---|
| BSP calibration | Paddlewheel scale factor | Polars consistently above/below J/105 targets; VMG unreliable |
| AWA offset | Wind vane zero point | Upwind performance differs port vs. starboard tack |
| Compass deviation | Heading correction table | TWA wrong when derived from TWD (ref=4); tack/gybe misclassified |
| Depth offset | Transducer-to-keel distance | offset_m in DepthRecord; shallow-water alarms fire at wrong depth |
| GPS antenna position | SOG/COG | Usually accurate; offset matters only for match-racing precision |
If a debrief shows asymmetric upwind speed across tacks, the first
hypothesis should be AWA offset, not crew technique.
5. Maneuver classification rule
Heading change alone does NOT classify a tack vs. gybe — TWA before and
after determines it. Source of truth is maneuver_detector.py, but the
rule in plain English:
| TWA before | TWA after | Heading change | Classification |
|---|
| <90° (upwind) | <90° (upwind) | ≥60° | Tack |
| >90° (downwind) | >90° (downwind) | ≥60° | Gybe |
| crosses 90° boundary | — | ≥60° | Mark rounding |
Without true wind data the detector falls back to a generic "maneuver"
type — heading change with no TWA context is unclassified, not assumed.