signalk-racer 1.1.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ # Claude Code — resume checkpoint
2
+
3
+ Session: fe0c247c-615f-438d-9888-bb2d81cfa8b7
4
+ Written: 2026-08-19T08:14:05.997Z
5
+ Trigger: near-limit
6
+ Ref with your in-progress files: refs/claude/checkpoint-fe0c247c
7
+
8
+ ## To resume
9
+
10
+ claude --resume fe0c247c-615f-438d-9888-bb2d81cfa8b7
11
+
12
+ (or open Claude Code in this directory and run /resume)
13
+
14
+ ## Plan (from TodoWrite state)
15
+
16
+ No task list was active; see transcript via the resume command above.
17
+
18
+ ---
19
+
20
+ Don't want these changes? Resume this session (above), then run
21
+ `/rewind` to roll back the turn's tool edits (bash-made changes
22
+ excluded). refs/claude/checkpoint-fe0c247c holds a full snapshot until this session's
23
+ next checkpoint, or for up to ~2 weeks.
@@ -5,7 +5,21 @@
5
5
  "Bash(git -C /home/gregw/src/signalk-racer tag -l)",
6
6
  "Bash(git -C /home/gregw/src/signalk-racer log --oneline -5)",
7
7
  "Bash(git -C /home/gregw/src/signalk-racer status -sb)",
8
- "Bash(git -C /home/gregw/src/signalk-racer remote -v)"
8
+ "Bash(git -C /home/gregw/src/signalk-racer remote -v)",
9
+ "Bash(npx jest *)",
10
+ "Bash(node --check index.js)",
11
+ "Bash(node --check racer.js)",
12
+ "Bash(node *)",
13
+ "Bash(npm root *)",
14
+ "Read(//home/gregw/.signalk/**)",
15
+ "Read(//home/gregw/.signalk/plugin-config-data/**)",
16
+ "Bash(npm view *)",
17
+ "Bash(systemctl list-units *)",
18
+ "Bash(systemctl --user list-units --type=service --all)",
19
+ "Bash(sed *)",
20
+ "Bash(python3 -)",
21
+ "Bash(awk '/^## 1.2.0/,/^## 1.1.2/' ReleaseNotes.md)",
22
+ "Bash(tee /dev/stderr)"
9
23
  ]
10
24
  }
11
25
  }
package/README.md CHANGED
@@ -14,26 +14,33 @@ GitHub repository: [https://github.com/gregw/signalk-racer](https://github.com/g
14
14
 
15
15
  This plugin calculates and publishes the following Signal K paths:
16
16
 
17
- | Path | Description | Units | Std |
18
- |-------------------------------------------------|--------------------------------------------------------|------------------------|-----|
19
- | `navigation.racing.`<br/>`distanceStartline` | Signed minimum distance from the bow to the start line | `m` | Y |
20
- | `navigation.racing.`<br/>`startLineLength` | Total length of the start line | `m` | |
21
- | `navigation.racing.`<br/>`stbLineBias` | Bias of the start line toward the starboard end | `m` | |
22
- | `navigation.racing.`<br/>`startLinePort` | Location of the port (pin) end of the start line | `{latitude,longitude}` | Y |
23
- | `navigation.racing.`<br/>`startLineStb` | Location of the starboard (boat) end of the start line | `{latitude,longitude}` | Y |
24
- | `navigation.racing.`<br/>`nextLegHeading` | True heading for the next leg of the course | `rad` | |
25
- | `navigation.racing.`<br/>`nextLegTrueWindAngle` | True Wind Angle for the next leg of the course | `rad` | |
26
- | `navigation.racing.`<br/>`timeToStart` | Period of time until the race start | `s` | Y |
27
- | `navigation.racing.`<br/>`timeToLine` | Period of time to sail to the line at best VMG | `s` | |
28
- | `navigation.racing.`<br/>`timeToBurn` | Period of time delay before sailing to the at best VMG | `s` | |
29
- | `navigation.racing.`<br/>`startTime` | The start time as an ISO timestamp | `rfc3339` | |
17
+ | Path | Description | Units | Std |
18
+ |--------------------------------------------|------------------------------------------------------------------------|----------------------------------------------------|-----|
19
+ | `navigation.racing.distanceStartline` | Signed minimum distance from the bow to the start line | `m` | Y |
20
+ | `navigation.racing.startLineLength` | Total length of the start line | `m` | |
21
+ | `navigation.racing.startLineBearing` | Bearing of the start line, from the stb end to the port end | `rad` | |
22
+ | `navigation.racing.stbLineBias` | Bias of the start line toward the starboard end | `m` | |
23
+ | `navigation.racing.bestVmg.toCourseSide` | Best VMG across the line towards the course side | `m/s` | |
24
+ | `navigation.racing.bestVmg.fromCourseSide` | Best VMG back across the line from the course side (used when OCS) | `m/s` | |
25
+ | `navigation.racing.bestVmg.toPortEnd` | Best VMG along the line towards the port end (pin) | `m/s` | |
26
+ | `navigation.racing.bestVmg.toStbEnd` | Best VMG along the line towards the stb end (boat) | `m/s` | |
27
+ | `navigation.racing.bestVmg.*.override` | The manual adjustment behind each best VMG, `null` when not adjusted | `m/s` | |
28
+ | `navigation.racing.bestApproach` | The course actually sailed that achieved the best VMG towards the line | `{rad,m/s}` | |
29
+ | `navigation.racing.startLinePort` | Location of the port (pin) end of the start line | `{latitude,longitude}` | Y |
30
+ | `navigation.racing.startLineStb` | Location of the starboard (boat) end of the start line | `{latitude,longitude}` | Y |
31
+ | `navigation.racing.nextLegHeading` | True heading for the next leg of the course | `rad` | |
32
+ | `navigation.racing.nextLegTrueWindAngle` | True Wind Angle for the next leg of the course | `rad` | |
33
+ | `navigation.racing.timeToStart` | Period of time until the race start | `s` | Y |
34
+ | `navigation.racing.timeToLine` | Period of time to sail to the line at best VMG | `s` | |
35
+ | `navigation.racing.timeToBurn` | Period of time delay before sailing to the at best VMG | `s` | |
36
+ | `navigation.racing.startTime` | The start time as an ISO timestamp | `rfc3339` | |
30
37
 
31
38
  These values can be displayed in KIP widgets, Freeboard-SK, or other Signal K clients.
32
39
  There are dedicated widgets for the start timer and line adjustment since 3.5.0 of KIP
33
40
 
34
41
  ![KIP and Freeboard Screenshot](racer-kip.png)
35
42
 
36
- There is also a webapp to adjust the line and timer.
43
+ There is also a webapp to: adjust the line, visualize the approach to the line, adjust VMGs and configure the timer.
37
44
 
38
45
  ![Signalk Racer Webapp](racer-webapp.png)
39
46
 
@@ -93,6 +100,20 @@ If `navigation.headingTrue` is not available, then `navigation.courseOverGroundT
93
100
  - A boat within the start zone has the time calculated by the perpendicular distance to the line divided by their effective VMG to the line.
94
101
  - A boat outside the start zone also has the perpendicular time plus the time calculated by the parallel distance to the zone divided by their effective VMG in that direction.
95
102
  - If the line is changed, then the samples used to calculate the effective VMGs are cleared.
103
+ - Every sample keeps the `cog` and `sog` that produced it, so the course behind the 90th
104
+ percentile VMG towards the line can be recovered and is published as
105
+ `navigation.racing.bestApproach`. The direction is chosen exactly as the time to line
106
+ chooses its legs, and is reported alongside the course:
107
+ - Inside the start zone the line is closed across it, so `toCourseSide` is used, or
108
+ `fromCourseSide` when OCS.
109
+ - Outside the start zone the boat must first run along the line, so the along-line
110
+ samples are used, in the direction *away* from the closest end: beyond the pin it
111
+ must travel towards the starboard end, so `toStbEnd` governs, and vice versa.
112
+ - The four collected VMGs are published under `navigation.racing.bestVmg.*` and may be manually
113
+ adjusted. An adjustment stands in for the 90th percentile of the samples, but the VMG the boat
114
+ is actually sailing still wins if it is better, so an adjustment can never make the estimate
115
+ ignore real progress. Adjustments are cleared when the start timer reaches zero, when the line
116
+ changes, and by the `reset` command.
96
117
  - If the boat is navigating a route and the next waypoint is beyond the second in the route, then the time to line is not calculated.
97
118
 
98
119
 
@@ -114,6 +135,53 @@ If `navigation.headingTrue` is not available, then `navigation.courseOverGroundT
114
135
 
115
136
  ---
116
137
 
138
+ ## 📉 Webapp Start Line Visualization
139
+
140
+ The webapp draws the line "line up": always horizontal, with the **port (pin) end to the left**
141
+ and the **starboard (committee boat) end to the right**. The course side of the line is therefore
142
+ always the **upper** half of the drawing and the pre-start side the lower half, so the arrow beside
143
+ the line length and start heading always points straight up. The drawing is auto-scaled to fit the
144
+ line and the boat, so it zooms as the boat closes. The line length and the heading to sail to cross
145
+ the line are marked on the line itself, and the boat is drawn as a triangle pointing along its
146
+ heading, amber when OCS.
147
+
148
+ Two projections run from the boat. Both start at the boat and are drawn to the same scale as the
149
+ rest of the drawing, so their **tips can be read directly against the line**. Hovering the boat
150
+ reports its SOG and COG and names whichever of the two lines are currently drawn.
151
+
152
+ #### Thick line — "Current cog/sog to start"
153
+
154
+ Runs along the boat's present `navigation.courseOverGroundTrue`. While the start timer is counting
155
+ down, its length is `SOG × timeToStart`: the distance the boat will cover before the gun if nothing
156
+ changes. Its tip is therefore **where the boat will be when the start fires**:
157
+
158
+ - tip short of the line — the boat is late and will cross after the gun
159
+ - tip on the line — a perfect start
160
+ - tip beyond the line — the boat is early and will be OCS
161
+
162
+ With no timer running there is nothing to project against, so it degrades to a short fixed-length
163
+ stub showing course only.
164
+
165
+ #### Thin faint line — "Best VMG to start"
166
+
167
+ Runs along `navigation.racing.bestApproach`, with its length that course's own
168
+ `SOG × timeToStart`. This is **not** a synthetic best case: it is a course the boat has genuinely
169
+ sailed in the last few minutes, being the `cog`/`sog` recorded against the 90th percentile VMG
170
+ sample for the direction that actually matters (across the line, or along it when outside the start
171
+ zone — see [Time To Line](#time-to-line-navigationracingtimetoline)). Because it is a real point of
172
+ sail it runs on its **own bearing**, not the current one, so it will diverge from the thick line
173
+ whenever the boat is not sailing as well as it recently has.
174
+
175
+ Comparing the two tips is the point of the pair: if the thin line reaches the line and the thick one
176
+ falls short, then sailing the boat as well as it has already been sailed would get you there, and
177
+ the difference between the tips is what is being left on the table. If no samples have been
178
+ collected yet, or the timer is not running, this line is not drawn.
179
+
180
+ Over a long countdown both projections run well off the drawing and are simply clipped at its edge;
181
+ the part that matters — where they cross the line — stays visible.
182
+
183
+ ---
184
+
117
185
  ## 🌐 API Access
118
186
 
119
187
  This plugin uses the following WebSocket-based PUT requests to Signal K model paths.
@@ -139,6 +207,38 @@ Used to **set** or **adjust** either end of the start line.
139
207
 
140
208
  ---
141
209
 
210
+ ### `navigation.racing.swapStartLine`
211
+
212
+ Used to **swap** the port (pin) and starboard (boat) ends of the line, reversing its bearing.
213
+ Takes no parameters. Collected VMG samples and any manual adjustments are exchanged along
214
+ with the ends rather than discarded, since reversing the bearing flips the sign of both VMG
215
+ components and so leaves every sample valid, just relabelled.
216
+
217
+ ---
218
+
219
+ ### `navigation.racing.setBestVmg`
220
+
221
+ Used to **adjust** or **reset** the best VMGs used to estimate the time to line.
222
+
223
+ #### Payload:
224
+ ```
225
+ {
226
+ "vmg": "toCourseSide" | "fromCourseSide" | "toPortEnd" | "toStbEnd",
227
+ "value": 5.0,
228
+ "delta": 0.0514,
229
+ "command": "reset"
230
+ }
231
+ ```
232
+
233
+ - `vmg`: which best VMG to adjust. Optional for `reset`, which otherwise clears all four.
234
+ - `value`: absolute best VMG in `m/s`.
235
+ - `delta`: adjustment in `m/s` applied to the current best VMG.
236
+ - `command`: `"reset"` to clear the override and revert to the collected samples.
237
+
238
+ An absolute value may also be put directly to `navigation.racing.bestVmg.<name>`.
239
+
240
+ ---
241
+
142
242
  ### `navigation.racing.setStartTime`
143
243
 
144
244
  Used to **start**, **sync**, **reset**, or **set a fixed start time**.
@@ -184,6 +284,9 @@ Bug reports and suggestions are welcome at
184
284
 
185
285
  ---
186
286
  ## Development
287
+
288
+ ### Linking the plugin into a local server
289
+
187
290
  ```text
188
291
  npm install
189
292
  npm link
@@ -191,6 +294,63 @@ cd ~/.signalk
191
294
  npm link signalk-racer
192
295
  ```
193
296
 
297
+ This replaces `~/.signalk/node_modules/signalk-racer` with a symlink to your checkout, so
298
+ edits take effect on the next server restart with no reinstall. Verify the link with:
299
+
300
+ ```text
301
+ readlink -f ~/.signalk/node_modules/signalk-racer
302
+ ```
303
+
304
+ > ⚠️ **The link is fragile.** `~/.signalk/package.json` still lists `signalk-racer` as a
305
+ > normal dependency, so running `npm install` (or installing/updating *any* plugin from the
306
+ > server's Appstore) will overwrite the symlink with the published release from npm, silently
307
+ > reverting you to the registry version. Re-run `npm link signalk-racer` in `~/.signalk`
308
+ > whenever that happens.
309
+
310
+ ### Checking which build is actually running
311
+
312
+ The plugin stamps its version in three places, so a stale link is easy to spot:
313
+
314
+ - the **Plugin Config** page in the admin UI, as the plugin status `signalk-racer <version> started`
315
+ - the bottom of the **racer webapp**
316
+ - the server log, via `app.debug`, including the directory it was loaded from
317
+
318
+ During development the version carries a `-dev.N` suffix (e.g. `1.2.0-dev.0`), so it is
319
+ immediately distinguishable from a released version installed from npm.
320
+
321
+ A restart is required for any change to plugin code — the server does not hot-reload plugins.
322
+ Changes to files under `public/` are served statically, so those only need a browser reload
323
+ (with cache bypass: `Ctrl+Shift+R`).
324
+
325
+ ### Running the server on this machine
326
+
327
+ The server is installed **globally** (`npm root -g` → `/usr/lib/node_modules`) and runs as a
328
+ system service, using `~/.signalk` as its config directory:
329
+
330
+ ```text
331
+ sudo systemctl status signalk # is it running?
332
+ sudo systemctl restart signalk # pick up plugin changes
333
+ journalctl -u signalk -f # follow the log
334
+ ```
335
+
336
+ ### Updating signalk-server
337
+
338
+ Because it is installed into a system directory, updating needs `sudo`, and the service must be
339
+ restarted afterwards:
340
+
341
+ ```text
342
+ npm view signalk-server version # latest published
343
+ npm ls -g --depth=0 signalk-server # currently installed
344
+ sudo npm install -g signalk-server # update to latest
345
+ sudo systemctl restart signalk
346
+ ```
347
+
348
+ To pin a specific version instead, use `sudo npm install -g signalk-server@2.31.1`.
349
+
350
+ Note that `~/.signalk/package.json` also lists `signalk-server` as a dependency; that entry is
351
+ only bookkeeping for the Appstore and is **not** what the service runs — the globally installed
352
+ copy in `/usr/lib/node_modules/signalk-server` is. Updating the global copy is what counts.
353
+
194
354
  ---
195
355
  ## 🚀 Releasing
196
356
 
package/ReleaseNotes.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## 1.2.0 Visualize line and best VMGs
2
+ + Added `navigation.racing.swapStartLine` to swap the port and starboard ends of the line,
3
+ carrying the collected VMG samples across with the ends
4
+ + Published the four best VMGs behind the time to line as `navigation.racing.bestVmg.`
5
+ `{toCourseSide,fromCourseSide,toPortEnd,toStbEnd}`, each with an `.override` beside it
6
+ reporting the manual adjustment, if any
7
+ + Added `navigation.racing.setBestVmg` to adjust or reset them, plus direct puts to each path.
8
+ An adjustment holds until the gun, but the VMG actually being sailed wins if it is better
9
+ + Samples now keep the `cog` and `sog` that produced them, published as
10
+ `navigation.racing.bestApproach`: the course actually sailed that best closes the line
11
+ + Published the start line bearing as `navigation.racing.startLineBearing`
12
+ + Webapp: swap-ends button, race countdown, and a start line visualisation with the best VMGs
13
+ as a cross below it and projections showing where the boat reaches at the gun
14
+ + Webapp and admin UI now show the running plugin version
15
+ + Fixed VMG samples being rejected below an absolute 1 m/s in each direction, so a boat closing
16
+ the line at an angle only fed the direction it was mostly travelling (now `minVmg`, 0.01 m/s)
17
+ + Fixed the collected VMG samples being discarded whenever any waypoint changed
18
+ + Fixed the start line bias not being cleared when the line is lost
19
+ + Fixed the webapp race countdown never being displayed
20
+
1
21
  ## 1.1.2 TTL fixes
2
22
  + use a FIFO queue for VMGs towards the line.
3
23
  + avoid undefined values in deltas