signalk-racer 1.1.1 → 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.
- package/.claude/RESUME.md +23 -0
- package/.claude/settings.local.json +25 -0
- package/README.md +212 -15
- package/ReleaseNotes.md +24 -0
- package/index.js +352 -68
- package/openapi.json +62 -0
- package/package.json +1 -1
- package/public/index.html +593 -13
- package/racer-webapp.png +0 -0
- package/racer.js +206 -37
- package/tests/racer-maths.test.js +8 -7
- package/tests/racer-ttl.test.js +12 -9
- package/tests/racer-vmg-override.test.js +169 -0
- package/tests/racer-vmg.test.js +227 -114
|
@@ -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.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(npm test:*)",
|
|
5
|
+
"Bash(git -C /home/gregw/src/signalk-racer tag -l)",
|
|
6
|
+
"Bash(git -C /home/gregw/src/signalk-racer log --oneline -5)",
|
|
7
|
+
"Bash(git -C /home/gregw/src/signalk-racer status -sb)",
|
|
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)"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
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
|
|
18
|
-
|
|
19
|
-
| `navigation.racing
|
|
20
|
-
| `navigation.racing
|
|
21
|
-
| `navigation.racing
|
|
22
|
-
| `navigation.racing
|
|
23
|
-
| `navigation.racing
|
|
24
|
-
| `navigation.racing
|
|
25
|
-
| `navigation.racing
|
|
26
|
-
| `navigation.racing
|
|
27
|
-
| `navigation.racing
|
|
28
|
-
| `navigation.racing
|
|
29
|
-
| `navigation.racing
|
|
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
|

|
|
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
|

|
|
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,9 +284,106 @@ 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
|
|
190
293
|
cd ~/.signalk
|
|
191
294
|
npm link signalk-racer
|
|
192
|
-
```
|
|
295
|
+
```
|
|
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
|
+
|
|
354
|
+
---
|
|
355
|
+
## 🚀 Releasing
|
|
356
|
+
|
|
357
|
+
Releases are cut manually from a clean `main` checkout and published to npm.
|
|
358
|
+
|
|
359
|
+
### Pre-flight
|
|
360
|
+
|
|
361
|
+
1. Confirm `main` is up to date and clean: `git checkout main && git pull && git status`.
|
|
362
|
+
2. Run the test suite: `npm test`.
|
|
363
|
+
3. Review commits since the last release: `git log $(git describe --tags --abbrev=0)..HEAD --oneline`.
|
|
364
|
+
4. Decide the version bump (semver): `patch` for bug fixes, `minor` for features, `major` for breaking changes.
|
|
365
|
+
|
|
366
|
+
### Cut the release
|
|
367
|
+
|
|
368
|
+
1. Update `ReleaseNotes.md` — add a new entry at the top with the new version and a short bullet list of changes, then `git add ReleaseNotes.md`.
|
|
369
|
+
2. Bump the version and create the git tag in one step (note the non-default tag prefix so it matches the existing `signalk-racer-X.Y.Z` convention):
|
|
370
|
+
```
|
|
371
|
+
npm version <patch|minor|major> --tag-version-prefix=signalk-racer-
|
|
372
|
+
```
|
|
373
|
+
This rewrites `package.json`, includes any staged files (e.g. `ReleaseNotes.md`) in the bump commit, and creates tag `signalk-racer-X.Y.Z`.
|
|
374
|
+
3. Push the commit and the tag:
|
|
375
|
+
```
|
|
376
|
+
git push origin main
|
|
377
|
+
git push origin signalk-racer-X.Y.Z
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Publish to npm
|
|
381
|
+
|
|
382
|
+
1. Confirm npm auth: `npm whoami` (login with `npm login` if needed).
|
|
383
|
+
2. Dry-run the publish to inspect the tarball contents: `npm publish --dry-run`.
|
|
384
|
+
3. Publish: `npm publish` (the package is public and unscoped; no `--access` flag needed).
|
|
385
|
+
|
|
386
|
+
### After release
|
|
387
|
+
|
|
388
|
+
1. Verify on npm: `npm view signalk-racer version` should report the new version.
|
|
389
|
+
2. (Optional) Create a GitHub release from the new tag with notes copied from `ReleaseNotes.md`.
|
package/ReleaseNotes.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
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
|
+
|
|
21
|
+
## 1.1.2 TTL fixes
|
|
22
|
+
+ use a FIFO queue for VMGs towards the line.
|
|
23
|
+
+ avoid undefined values in deltas
|
|
24
|
+
|
|
1
25
|
## 1.1.1 Unit tests
|
|
2
26
|
+ split into index.js and racing.js for unit testing
|
|
3
27
|
+ unit tested TTL
|