@ksp-gonogo/kerbcast 1.10.2 → 1.11.0-rc.4
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/README.md +255 -2
- package/dist/__generated__/types.d.ts +375 -3
- package/dist/__generated__/types.js +48 -1
- package/dist/client.d.ts +141 -8
- package/dist/client.js +367 -23
- package/dist/commandTrack.d.ts +47 -0
- package/dist/commandTrack.js +78 -0
- package/dist/frameUt.d.ts +86 -0
- package/dist/frameUt.js +75 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/noise.d.ts +41 -0
- package/dist/noise.js +335 -88
- package/dist/testing/index.d.ts +25 -1
- package/dist/testing/index.js +169 -1
- package/package.json +1 -1
- package/src/__generated__/types.ts +376 -2
- package/src/client.test.ts +574 -1
- package/src/client.ts +457 -23
- package/src/commandTrack.test.ts +116 -0
- package/src/commandTrack.ts +117 -0
- package/src/frameUt.test.ts +40 -0
- package/src/frameUt.ts +145 -0
- package/src/index.ts +26 -0
- package/src/noise.test.ts +230 -9
- package/src/noise.ts +403 -93
- package/src/testing/index.ts +182 -2
- package/src/testing.test.ts +62 -2
package/README.md
CHANGED
|
@@ -59,6 +59,10 @@ client.on("adaptive-shed", (event) => {
|
|
|
59
59
|
client.disconnect();
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
Commands can be sent as soon as `connect` resolves. Any sent before the
|
|
63
|
+
control channel has opened wait for it, and reject if it has not opened
|
|
64
|
+
within 10 s or the client disconnects first.
|
|
65
|
+
|
|
62
66
|
### Pre-handshake discovery
|
|
63
67
|
|
|
64
68
|
```ts
|
|
@@ -106,8 +110,9 @@ controls each part actually supports.
|
|
|
106
110
|
skip, producing the macroblocking and stuttering aesthetic of
|
|
107
111
|
in-game CommNet signal loss. Also a real CPU optimisation when
|
|
108
112
|
every consumer wants degraded video.
|
|
109
|
-
- `kind` (`"part"` | `"kerbal"`) and, for a kerbal face
|
|
110
|
-
`crewLocation` (`"seat"` | `"eva"`). Distinguish crew face
|
|
113
|
+
- `kind` (`"part"` | `"kerbal"` | `"command"`) and, for a kerbal face
|
|
114
|
+
camera, `crewLocation` (`"seat"` | `"eva"`). Distinguish crew face
|
|
115
|
+
cameras and command cameras (see [Command cameras](#command-cameras))
|
|
111
116
|
from part cameras.
|
|
112
117
|
- `trackMode` (`"none"` | `"activeVessel"` | `"target"`). Server-authoritative
|
|
113
118
|
auto-track state on a pan+zoom camera, so every consumer reflects the same
|
|
@@ -123,6 +128,254 @@ controls each part actually supports.
|
|
|
123
128
|
the active vessel or its target (`TrackMode`), or `"none"` to stop. The
|
|
124
129
|
chosen mode is published back on `CameraState.trackMode`.
|
|
125
130
|
|
|
131
|
+
## Command cameras
|
|
132
|
+
|
|
133
|
+
A command camera is a camera that stands on the ground (or rides a craft)
|
|
134
|
+
and watches a craft from there, the way a tracking station would. The
|
|
135
|
+
game's settings give each scene one, the Launchpad camera, and a client can
|
|
136
|
+
create more at any vantage. Each appears in `client.cameras` with
|
|
137
|
+
`kind: "command"` and a `commandCentre` describing where it stands and what
|
|
138
|
+
it watches.
|
|
139
|
+
|
|
140
|
+
A created camera lasts for the rest of the game scene (a flight, or the
|
|
141
|
+
Space Center while the game's command camera setting is on); leaving the
|
|
142
|
+
scene removes it. Every connected client sees it, and any of them can aim
|
|
143
|
+
or destroy it.
|
|
144
|
+
|
|
145
|
+
The vantage is one of:
|
|
146
|
+
|
|
147
|
+
- `launchSite`: beside a launch site. `site` names it (`"LaunchPad"`,
|
|
148
|
+
`"Runway"`, a Making History or Kerbal Konstructs site); omit it for the
|
|
149
|
+
active craft's launch site. `distance` (default 500 m), `bearing` from the
|
|
150
|
+
site (default 180, due south) and `height` (default 5 m)
|
|
151
|
+
- `station`: at a CommNet ground station, by its node name (`"KSC"`), with
|
|
152
|
+
`height` above it (default 30 m)
|
|
153
|
+
- `surface`: at any `latitude` and `longitude` on a `body`, `height` metres
|
|
154
|
+
above the ground there (above the sea over water; default 5 m)
|
|
155
|
+
- `vessel`: riding the craft with that `persistentId`. Keep that craft
|
|
156
|
+
beyond physics range of the active one, or the lens sits inside its parts.
|
|
157
|
+
If the craft docks into another (or is grabbed by a claw), the camera rides
|
|
158
|
+
the combined craft. If the craft is destroyed or recovered, the camera is
|
|
159
|
+
removed and its feed ends
|
|
160
|
+
|
|
161
|
+
`createCommandCamera` resolves with the new camera's `CameraState` once the
|
|
162
|
+
game has built it and reported what it is watching, normally within a
|
|
163
|
+
second or two, so `commandCentre.subjectPersistentId` and
|
|
164
|
+
`commandCentre.targets` are already filled in. If that report has not come
|
|
165
|
+
5 s after the camera appeared, it resolves with the state as it stands. The
|
|
166
|
+
request carries a token (random unless you pass `token`) that the camera
|
|
167
|
+
reports back as `commandCentre.token`; that is how the promise finds it,
|
|
168
|
+
and how any other code can. A vantage the game can not find (an unknown
|
|
169
|
+
body, site, station or craft) builds nothing, and the promise rejects after
|
|
170
|
+
`timeoutMs` (default 35 s, since the game waits up to 30 s for a site to be
|
|
171
|
+
placed).
|
|
172
|
+
|
|
173
|
+
Lens reach depends on the vantage: a launch site camera has the tracking
|
|
174
|
+
camera's lens at every Tracking Station level, a station's lens follows the
|
|
175
|
+
Tracking Station's level in career, and surface and vessel vantages zoom
|
|
176
|
+
fully.
|
|
177
|
+
|
|
178
|
+
### Aiming
|
|
179
|
+
|
|
180
|
+
`client.setCommandAim(flightId, aim)` points any command camera, and `null`
|
|
181
|
+
returns it to its default: the active craft in flight, the configured craft
|
|
182
|
+
at the Space Center. `createCommandCamera` takes the same `aim` as an option.
|
|
183
|
+
The aim is one of:
|
|
184
|
+
|
|
185
|
+
- `vessel`: follows the craft with that `persistentId` (any entry of
|
|
186
|
+
`commandCentre.targets`, whatever its eligibility)
|
|
187
|
+
- `orbit`: follows a conic (`body`, `inclination`, `eccentricity`,
|
|
188
|
+
`semiMajorAxis`, `lan`, `argumentOfPeriapsis` in degrees and metres,
|
|
189
|
+
`meanAnomalyAtEpoch` in radians, `epoch`)
|
|
190
|
+
- `positionAt`: follows a position you propagate yourself: `position` and
|
|
191
|
+
`velocity` as `[x, y, z]` relative to `body` in its non-rotating frame at
|
|
192
|
+
`ut`, carried on linearly
|
|
193
|
+
- `surface`: holds a point on a `body` (`latitude`, `longitude`, `altitude`
|
|
194
|
+
in metres above sea level), turning with the planet
|
|
195
|
+
- `direction`: holds a direction from the camera's local horizon,
|
|
196
|
+
`azimuth` degrees clockwise from north and `elevation` degrees above the
|
|
197
|
+
horizon
|
|
198
|
+
|
|
199
|
+
The subject is the craft the camera draws (where it truly is, so an aim off
|
|
200
|
+
the craft shows it off centre), brackets, sizes its zoom by and reads out. A
|
|
201
|
+
`vessel` aim's craft is its subject; `orbit` and `positionAt` take an
|
|
202
|
+
optional `subjectPersistentId`; otherwise the default craft stays the
|
|
203
|
+
subject. A `direction` aim zooms for the subject's range, so the subject is
|
|
204
|
+
framed as it passes through. Every kind takes `framingMarginM`, metres of
|
|
205
|
+
view kept around the subject's own size. `commandCentre.aimKind` (absent for
|
|
206
|
+
the default) and `commandCentre.subjectPersistentId` report what the game
|
|
207
|
+
applied.
|
|
208
|
+
|
|
209
|
+
### A launchpad camera
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import { CommandVantageKind, KerbcastClient } from "@ksp-gonogo/kerbcast";
|
|
213
|
+
|
|
214
|
+
const client = new KerbcastClient({ host: "192.168.1.74", port: 8088 });
|
|
215
|
+
await client.connect([], { slots: 2 });
|
|
216
|
+
|
|
217
|
+
// 500 m south of the active craft's launch site, 5 m up.
|
|
218
|
+
const pad = await client.createCommandCamera(
|
|
219
|
+
{ kind: CommandVantageKind.LaunchSite },
|
|
220
|
+
{ name: "Pad south" },
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const video = document.querySelector("video")!;
|
|
224
|
+
client.camera(pad.flightId).on("stream", (stream) => {
|
|
225
|
+
video.srcObject = stream;
|
|
226
|
+
});
|
|
227
|
+
await client.subscribe(pad.flightId);
|
|
228
|
+
|
|
229
|
+
// Later: the feed ends for every client.
|
|
230
|
+
await client.destroyCommandCamera(pad.flightId);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`{ kind: CommandVantageKind.LaunchSite, site: "Runway", distance: 800,
|
|
234
|
+
bearing: 90, height: 20 }` stands one 800 m east of the runway, 20 m up.
|
|
235
|
+
|
|
236
|
+
### Between two vantages
|
|
237
|
+
|
|
238
|
+
Here a camera stands on a rise west of the KSC and watches the launch pad,
|
|
239
|
+
then a particular craft, then the sky above the pad, then whichever craft
|
|
240
|
+
is active. The coordinates are illustrative.
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import {
|
|
244
|
+
CommandAimKind,
|
|
245
|
+
CommandVantageKind,
|
|
246
|
+
type KerbcastClient,
|
|
247
|
+
} from "@ksp-gonogo/kerbcast";
|
|
248
|
+
|
|
249
|
+
declare const client: KerbcastClient; // connected as above
|
|
250
|
+
|
|
251
|
+
const hill = await client.createCommandCamera(
|
|
252
|
+
{
|
|
253
|
+
kind: CommandVantageKind.Surface,
|
|
254
|
+
body: "Kerbin",
|
|
255
|
+
latitude: -0.08,
|
|
256
|
+
longitude: -74.75,
|
|
257
|
+
height: 10,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "West ridge",
|
|
261
|
+
// The pad: altitude is metres above sea level, and the margin keeps
|
|
262
|
+
// 60 m of view around the subject.
|
|
263
|
+
aim: {
|
|
264
|
+
kind: CommandAimKind.Surface,
|
|
265
|
+
body: "Kerbin",
|
|
266
|
+
latitude: -0.0972,
|
|
267
|
+
longitude: -74.5577,
|
|
268
|
+
altitude: 70,
|
|
269
|
+
framingMarginM: 60,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Follow a particular craft, by Vessel.persistentId.
|
|
275
|
+
await client.setCommandAim(hill.flightId, {
|
|
276
|
+
kind: CommandAimKind.Vessel,
|
|
277
|
+
persistentId: 4000000001,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Hold still, looking east and 30 degrees up, for a launch to climb into.
|
|
281
|
+
await client.setCommandAim(hill.flightId, {
|
|
282
|
+
kind: CommandAimKind.Direction,
|
|
283
|
+
azimuth: 90,
|
|
284
|
+
elevation: 30,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Back to the default: the active craft.
|
|
288
|
+
await client.setCommandAim(hill.flightId, null);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
A station watching a craft is the same with
|
|
292
|
+
`{ kind: CommandVantageKind.Station, station: "KSC" }` and a `vessel` aim.
|
|
293
|
+
|
|
294
|
+
### The command lock
|
|
295
|
+
|
|
296
|
+
A mod in game can take a lock on every command camera's aim
|
|
297
|
+
(`KerbcastControl.SetCommandAuthority(owner)`). While it holds the lock only
|
|
298
|
+
its own aims are accepted: `setCommandAim` rejects with a
|
|
299
|
+
`CommandLockedError` (its `owner` names the holder) whenever the camera's
|
|
300
|
+
`commandTrack.authority` reports one, and a call that races the lock being
|
|
301
|
+
taken comes back as an `error` event with code `ErrorCode.CommandLocked`.
|
|
302
|
+
`createCommandCamera` still creates the camera, at its default aim. When the
|
|
303
|
+
lock is released every command camera returns to its default; aims set
|
|
304
|
+
before or during the lock are not restored.
|
|
305
|
+
|
|
306
|
+
The lock holds until its holder releases it (or the scene changes); it never
|
|
307
|
+
times out. A holder that stops calling for 5 s blanks every command feed
|
|
308
|
+
("gonogo not responding") but keeps the lock, so aims from anyone else are
|
|
309
|
+
still refused. This is deliberate: a holder that delays commands (gonogo
|
|
310
|
+
applies signal delay) can not be bypassed while it is unresponsive.
|
|
311
|
+
|
|
312
|
+
### In game (C#)
|
|
313
|
+
|
|
314
|
+
Another KSP mod can do the same through `Kerbcast.KerbcastControl`, with
|
|
315
|
+
no sidecar round trip:
|
|
316
|
+
|
|
317
|
+
```csharp
|
|
318
|
+
using Kerbcast;
|
|
319
|
+
|
|
320
|
+
// Returns the flightId the feed will have, or 0 when refused.
|
|
321
|
+
uint pad = KerbcastControl.CreateCommandCamera(CommandVantage.LaunchSite());
|
|
322
|
+
|
|
323
|
+
uint ridge = KerbcastControl.CreateCommandCamera(
|
|
324
|
+
CommandVantage.Surface("Kerbin", -0.08, -74.75, height: 10), "West ridge",
|
|
325
|
+
CommandAim.Surface("Kerbin", -0.0972, -74.5577, 70, framingMarginM: 60));
|
|
326
|
+
KerbcastControl.SetCommandAim(ridge, CommandAim.Vessel(vessel.persistentId));
|
|
327
|
+
KerbcastControl.SetCommandAim(ridge, CommandAim.Direction(azimuth: 90, elevation: 30));
|
|
328
|
+
KerbcastControl.SetCommandAim(ridge, null); // back to the default
|
|
329
|
+
|
|
330
|
+
KerbcastControl.DestroyCommandCamera(ridge);
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Holding the lock:
|
|
334
|
+
|
|
335
|
+
```csharp
|
|
336
|
+
// Re-take it on every scene load; a different owner is refused.
|
|
337
|
+
KerbcastControl.SetCommandAuthority("gonogo");
|
|
338
|
+
|
|
339
|
+
// Only calls passing the owner are accepted now, and each is a heartbeat.
|
|
340
|
+
KerbcastControl.SetCommandAim(pad, CommandAim.PositionAt("Kerbin", ut, pos, vel,
|
|
341
|
+
subject: craft.persistentId, framingMarginM: uncertaintyM), "gonogo");
|
|
342
|
+
KerbcastControl.SetCommandAim(pad, CommandAim.Vessel(other)); // false: locked
|
|
343
|
+
|
|
344
|
+
// Every command camera returns to its default.
|
|
345
|
+
KerbcastControl.ClearCommandAuthority("gonogo");
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
- `CommandVantage.LaunchSite(site, distance, bearing, height)`,
|
|
349
|
+
`CommandVantage.Station(nodeName, height)`,
|
|
350
|
+
`CommandVantage.Surface(body, latitude, longitude, height)` and
|
|
351
|
+
`CommandVantage.Vessel(persistentId)` mirror the vantage kinds above, with
|
|
352
|
+
the same defaults
|
|
353
|
+
- `CommandAim.Vessel(persistentId, framingMarginM = 0)`,
|
|
354
|
+
`Orbit(body, inc, e, sma, lan, argPe, meanAnomalyAtEpoch, epoch, subject = 0, framingMarginM = 0)`,
|
|
355
|
+
`PositionAt(body, ut, pos, vel, subject = 0, framingMarginM = 0)`,
|
|
356
|
+
`Surface(body, lat, lon, alt, framingMarginM = 0)` and
|
|
357
|
+
`Direction(azimuth, elevation, framingMarginM = 0)` mirror the aim kinds
|
|
358
|
+
- `CreateCommandCamera(vantage, name = null, aim = null)` returns the
|
|
359
|
+
flightId at once; the feed appears when the vantage resolves, and
|
|
360
|
+
`SetCommandAim` works on it before then too. Under a held lock the aim is
|
|
361
|
+
not applied
|
|
362
|
+
- `SetCommandAim(flightId, aim, owner = null)` returns false when the lock
|
|
363
|
+
refuses it, the feed is not a command feed, or the aim is unusable (a
|
|
364
|
+
craft that no longer exists, such as one docked into another, is
|
|
365
|
+
unusable)
|
|
366
|
+
- The lock holds until `ClearCommandAuthority` or a scene change, and fails
|
|
367
|
+
closed: a holder that makes no call (`SetCommandAuthority`, `SetCommandAim`
|
|
368
|
+
with its owner, `SetCommandBlank`) for 5 s of real time blanks every
|
|
369
|
+
command feed "gonogo not responding" and keeps the lock, so everyone else
|
|
370
|
+
stays refused. `SetCommandBlank(flightId, reason)` blanks one feed
|
|
371
|
+
- `DestroyCommandCamera(flightId)` returns false for the settings camera or
|
|
372
|
+
an unknown id
|
|
373
|
+
- `CommandFeeds()` lists every built command camera, with its `Aim`,
|
|
374
|
+
`SubjectPersistentId`, and `Created` set on the ones made at runtime;
|
|
375
|
+
`CommandTargets(flightId)` lists the craft it could follow (a craft docked
|
|
376
|
+
into another drops out; the combined craft keeps the survivor's
|
|
377
|
+
`persistentId`)
|
|
378
|
+
|
|
126
379
|
## Pan/zoom input helper
|
|
127
380
|
|
|
128
381
|
Driving pan and zoom from a gamepad stick, nudge buttons, or an FoV
|