@ksp-gonogo/kerbcast 1.11.0 → 1.12.0-rc.1
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 +174 -14
- package/dist/__generated__/types.d.ts +570 -14
- package/dist/__generated__/types.js +82 -1
- package/dist/anchors.d.ts +61 -0
- package/dist/anchors.js +71 -0
- package/dist/captureClip.d.ts +116 -0
- package/dist/captureClip.js +208 -0
- package/dist/client.d.ts +313 -22
- package/dist/client.js +856 -42
- package/dist/commandTrack.d.ts +66 -6
- package/dist/commandTrack.js +137 -13
- package/dist/frameUt.d.ts +44 -10
- package/dist/frameUt.js +34 -4
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/mp4.d.ts +103 -0
- package/dist/mp4.js +481 -0
- package/dist/panZoom.d.ts +1 -2
- package/dist/panZoom.js +1 -2
- package/dist/recording.d.ts +200 -16
- package/dist/recording.js +532 -48
- package/dist/testing/index.d.ts +36 -2
- package/dist/testing/index.js +86 -5
- package/package.json +1 -1
- package/src/__generated__/types.ts +569 -13
- package/src/anchors.test.ts +68 -0
- package/src/anchors.ts +110 -0
- package/src/captureClip.test.ts +422 -0
- package/src/captureClip.ts +263 -0
- package/src/client.test.ts +1324 -3
- package/src/client.ts +988 -57
- package/src/commandTrack.test.ts +206 -10
- package/src/commandTrack.ts +160 -14
- package/src/framePipe.test.ts +79 -0
- package/src/frameUt.test.ts +15 -0
- package/src/frameUt.ts +72 -9
- package/src/index.ts +25 -0
- package/src/mp4.ts +627 -0
- package/src/panZoom.ts +1 -2
- package/src/recording.capture.test.ts +349 -0
- package/src/recording.test.ts +338 -7
- package/src/recording.ts +703 -63
- package/src/testing/index.ts +92 -7
- package/src/testing.test.ts +1 -1
package/README.md
CHANGED
|
@@ -199,7 +199,11 @@ and watches a craft from there, the way a tracking station would. The
|
|
|
199
199
|
game's settings give each scene one, the Launchpad camera, and a client can
|
|
200
200
|
create more at any vantage. Each appears in `client.cameras` with
|
|
201
201
|
`kind: "command"` and a `commandCentre` describing where it stands and what
|
|
202
|
-
it watches.
|
|
202
|
+
it watches. `commandCentre.siteKey` names the place the lens stands and
|
|
203
|
+
`siteKind` says what it is (`station`, `launchSite`, `surface` or `vessel`);
|
|
204
|
+
the key stays the same when the craft a `vessel` site rides docks, while
|
|
205
|
+
`vantagePersistentId` follows the craft it rides now. `stationId` is
|
|
206
|
+
deprecated.
|
|
203
207
|
|
|
204
208
|
A created camera lasts for the rest of the game scene (a flight, or the
|
|
205
209
|
Space Center while the game's command camera setting is on); leaving the
|
|
@@ -247,13 +251,26 @@ at the Space Center. `createCommandCamera` takes the same `aim` as an option.
|
|
|
247
251
|
The aim is one of:
|
|
248
252
|
|
|
249
253
|
- `vessel`: follows the craft with that `persistentId` (any entry of
|
|
250
|
-
`commandCentre.targets`, whatever its eligibility)
|
|
254
|
+
`commandCentre.targets`, whatever its eligibility). Where a world model
|
|
255
|
+
governs the feed, a craft whose position it cannot narrow enough for the
|
|
256
|
+
view to frame is refused as `tooUncertain`; add `allowUncertain: true` to
|
|
257
|
+
aim at it anyway, as a deliberate search of the region it may be in. Under
|
|
258
|
+
the game's own knowledge every known craft is accepted, and while a command
|
|
259
|
+
lock is held browser aims are refused whatever they allow
|
|
251
260
|
- `orbit`: follows a conic (`body`, `inclination`, `eccentricity`,
|
|
252
261
|
`semiMajorAxis`, `lan`, `argumentOfPeriapsis` in degrees and metres,
|
|
253
262
|
`meanAnomalyAtEpoch` in radians, `epoch`)
|
|
254
|
-
- `positionAt`: follows a
|
|
255
|
-
`velocity` as `[x, y, z]` relative to `body`
|
|
256
|
-
|
|
263
|
+
- `positionAt`: follows a craft coasting from a state you give: `position`
|
|
264
|
+
and `velocity` as `[x, y, z]` relative to `body` at `ut`, z up, in KSP's
|
|
265
|
+
fixed inertial frame, carried on ballistically under the body's gravity
|
|
266
|
+
with no thrust. From a KSP `Orbit` that frame is always
|
|
267
|
+
`Planetarium.Zup.LocalToWorld(orbit.getRelativePositionAtUT(ut))` (and the
|
|
268
|
+
same for `getOrbitalVelocityAtUT`, `orbit.pos`, `orbit.vel`); the raw
|
|
269
|
+
values are not, since KSP leaves Zup turned after a low flight. A browser
|
|
270
|
+
client has no `Orbit`, so it takes these vectors from whatever mod or model
|
|
271
|
+
hands it craft states in that frame. A velocity of zero no longer holds a
|
|
272
|
+
point still (it falls); use `surface` for a fixed point. Before 1.12 this
|
|
273
|
+
was Zup's own frame, carried on linearly
|
|
257
274
|
- `surface`: holds a point on a `body` (`latitude`, `longitude`, `altitude`
|
|
258
275
|
in metres above sea level), turning with the planet
|
|
259
276
|
- `direction`: holds a direction from the camera's local horizon,
|
|
@@ -377,9 +394,9 @@ before or during the lock are not restored.
|
|
|
377
394
|
|
|
378
395
|
The lock holds until its holder releases it (or the scene changes); it never
|
|
379
396
|
times out. A holder that stops calling for 5 s blanks every command feed
|
|
380
|
-
("
|
|
381
|
-
still refused. This is deliberate: a holder that delays commands (
|
|
382
|
-
applies signal delay) can not be bypassed while it is unresponsive.
|
|
397
|
+
("Authority not responding") but keeps the lock, so aims from anyone else are
|
|
398
|
+
still refused. This is deliberate: a holder that delays commands (one that
|
|
399
|
+
applies signal delay, say) can not be bypassed while it is unresponsive.
|
|
383
400
|
|
|
384
401
|
### In game (C#)
|
|
385
402
|
|
|
@@ -406,15 +423,15 @@ Holding the lock:
|
|
|
406
423
|
|
|
407
424
|
```csharp
|
|
408
425
|
// Re-take it on every scene load; a different owner is refused.
|
|
409
|
-
KerbcastControl.SetCommandAuthority("
|
|
426
|
+
KerbcastControl.SetCommandAuthority("mission-control");
|
|
410
427
|
|
|
411
428
|
// Only calls passing the owner are accepted now, and each is a heartbeat.
|
|
412
429
|
KerbcastControl.SetCommandAim(pad, CommandAim.PositionAt("Kerbin", ut, pos, vel,
|
|
413
|
-
subject: craft.persistentId, framingMarginM: uncertaintyM), "
|
|
430
|
+
subject: craft.persistentId, framingMarginM: uncertaintyM), "mission-control");
|
|
414
431
|
KerbcastControl.SetCommandAim(pad, CommandAim.Vessel(other)); // false: locked
|
|
415
432
|
|
|
416
433
|
// Every command camera returns to its default.
|
|
417
|
-
KerbcastControl.ClearCommandAuthority("
|
|
434
|
+
KerbcastControl.ClearCommandAuthority("mission-control");
|
|
418
435
|
```
|
|
419
436
|
|
|
420
437
|
- `CommandVantage.LaunchSite(site, distance, bearing, height)`,
|
|
@@ -438,16 +455,159 @@ KerbcastControl.ClearCommandAuthority("gonogo");
|
|
|
438
455
|
- The lock holds until `ClearCommandAuthority` or a scene change, and fails
|
|
439
456
|
closed: a holder that makes no call (`SetCommandAuthority`, `SetCommandAim`
|
|
440
457
|
with its owner, `SetCommandBlank`) for 5 s of real time blanks every
|
|
441
|
-
command feed "
|
|
458
|
+
command feed "Authority not responding" and keeps the lock, so everyone else
|
|
442
459
|
stays refused. `SetCommandBlank(flightId, reason)` blanks one feed
|
|
443
460
|
- `DestroyCommandCamera(flightId)` returns false for the settings camera or
|
|
444
461
|
an unknown id
|
|
445
|
-
- `CommandFeeds()` lists every built command camera, with its `
|
|
446
|
-
`
|
|
462
|
+
- `CommandFeeds()` lists every built command camera, with its `SiteKey`,
|
|
463
|
+
`SiteKind`, `VantagePersistentId`, `Aim`, `SubjectPersistentId`, and
|
|
464
|
+
`Created` set on the ones made at runtime (`StationId` is obsolete);
|
|
447
465
|
`CommandTargets(flightId)` lists the craft it could follow (a craft docked
|
|
448
466
|
into another drops out; the combined craft keeps the survivor's
|
|
449
467
|
`persistentId`)
|
|
450
468
|
|
|
469
|
+
## Anchored markers
|
|
470
|
+
|
|
471
|
+
Anchors mark things in the game on a camera's picture. The game projects
|
|
472
|
+
each one through the camera that rendered every frame (its pose and field
|
|
473
|
+
of view at that moment, so zoom and pan are exact), and the result rides
|
|
474
|
+
that frame's stamp. Markers drawn from it line up with the picture under
|
|
475
|
+
any stream delay. There is no live position: everything is tied to frames.
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
import { AnchorDirection, AnchorKind } from "@ksp-gonogo/kerbcast";
|
|
479
|
+
|
|
480
|
+
await client.setAnchors(flightId, [
|
|
481
|
+
{ id: "port", kind: AnchorKind.Part, vessel: targetPid, part: portPid, orientation: true },
|
|
482
|
+
{ id: "ship", kind: AnchorKind.Vessel, vessel: targetPid, occlusion: true },
|
|
483
|
+
{ id: "site", kind: AnchorKind.Surface, body: "Mun", latitude: 2.4, longitude: 81.5 },
|
|
484
|
+
{ id: "pro", kind: AnchorKind.Direction, direction: AnchorDirection.TargetRelativeVelocity },
|
|
485
|
+
]);
|
|
486
|
+
await client.setAnchors(flightId, null); // clear
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
- Kinds: `part` (craft and part persistentId, optional `offset` in the
|
|
490
|
+
part's axes; a docking port is marked at its docking face; follows the
|
|
491
|
+
part through docking), `vessel` (centre of mass; follows a craft merged
|
|
492
|
+
into another), `surface` (body, latitude, longitude, altitude) and
|
|
493
|
+
`direction` (a named direction of a craft, the active one by default, or
|
|
494
|
+
a fixed inertial `vector`), drawn at infinity, and `kinematic` (a point
|
|
495
|
+
you describe with `kinematics`: a `conic` from KSP Orbit elements, a
|
|
496
|
+
`stateVector` coasting ballistically in the same fixed frame as a
|
|
497
|
+
`positionAt` aim, or a `surface` point, each about a named `body`), for a
|
|
498
|
+
last-known ghost, a hypothesis or a predicted point
|
|
499
|
+
- `orientation: true` on a part or vessel adds its `forward` and `up` axes
|
|
500
|
+
in camera space (x right, y up, z forward) to every result.
|
|
501
|
+
`occlusion: true` adds `occluded`, at the cost of a ray cast per frame.
|
|
502
|
+
`marginM` pads the radius `apparentRadiusPx` is worked out from
|
|
503
|
+
- Anchors belong to the client that set them: other clients never see them,
|
|
504
|
+
the command lock does not apply, and the sidecar drops them when the
|
|
505
|
+
client disconnects. The client keeps them until cleared and sends them
|
|
506
|
+
again on reconnect; anchors on a camera the sidecar does not list wait
|
|
507
|
+
until it appears. At most `client.maxAnchorsPerCamera` per camera, which
|
|
508
|
+
the sidecar announces in `hello` (16 when it does not say,
|
|
509
|
+
`MAX_ANCHORS_PER_CAMERA`)
|
|
510
|
+
- Needs a 1.12 sidecar and plugin; against an older sidecar they are kept
|
|
511
|
+
but never sent (`client.anchorsSupported`)
|
|
512
|
+
|
|
513
|
+
Each result lands on the displayed frame through `watchDisplayedFrames`:
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
import { anchorPoint, fitFrame, frameToScreen, frameLengthToScreen, findAnchor } from "@ksp-gonogo/kerbcast";
|
|
517
|
+
|
|
518
|
+
client.watchDisplayedFrames(flightId, video, (frame) => {
|
|
519
|
+
const port = findAnchor(frame.anchors, "port");
|
|
520
|
+
const point = port && anchorPoint(port); // null when the result carries no position
|
|
521
|
+
if (!point || !frame.frameWidth || !frame.frameHeight) return;
|
|
522
|
+
const fitted = fitFrame(video.clientWidth, video.clientHeight,
|
|
523
|
+
frame.frameWidth, frame.frameHeight, "contain"); // or "cover"
|
|
524
|
+
const { x, y } = frameToScreen(point, frame.frameWidth, frame.frameHeight, fitted);
|
|
525
|
+
const r = frameLengthToScreen(port.apparentRadiusPx ?? 0, frame.frameHeight, fitted);
|
|
526
|
+
draw(x, y, r, port.inFrame, port.behind);
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Each result has `x`, `y` (capture pixels from the top left), `inFrame`,
|
|
531
|
+
`behind` (then `x`, `y` sit off the frame on the side it is nearest),
|
|
532
|
+
`distanceM` (absent for directions), `apparentRadiusPx`, `angleOffAxis`
|
|
533
|
+
(degrees), and `forward`, `up`, `occluded` when asked for. Anchors set in
|
|
534
|
+
game carry their `owner`; this client's own have none. Every stamp also
|
|
535
|
+
gives the frame's `fov` and size, for overlays that do their own maths.
|
|
536
|
+
|
|
537
|
+
An `observed: true` result carries only what the picture shows: `x`, `y`,
|
|
538
|
+
`apparentRadiusPx` (and `forward`, `up`) only while the anchor is in frame and
|
|
539
|
+
not occluded, and never `distanceM`, `angleOffAxis` or `behind`. Out of view it
|
|
540
|
+
is just `inFrame` and `occluded`. Use `anchorPoint(result)`, which is `null`
|
|
541
|
+
when there is no position.
|
|
542
|
+
|
|
543
|
+
A command camera adds its own anchor: `id: "subject"`, owner `"kerbcast"`
|
|
544
|
+
(`COMMAND_SUBJECT_ANCHOR_ID`, `KERBCAST_ANCHOR_OWNER`), a `vessel` anchor on
|
|
545
|
+
the craft it watches with occlusion on. It is always observed, so it never
|
|
546
|
+
gives away a craft the picture does not show. The React brackets follow it.
|
|
547
|
+
A second, `id: "aim"` (`COMMAND_AIM_ANCHOR_ID`), is where the aim point lands
|
|
548
|
+
in the frame: the centre, except while the camera turns onto a moved aim.
|
|
549
|
+
|
|
550
|
+
While a command authority holds the lock, every browser client's anchors
|
|
551
|
+
are observed-only by default. The holder can relax that
|
|
552
|
+
(`KerbcastControl.SetAnchorPolicy(AnchorPolicy.Full, owner)`, for every
|
|
553
|
+
camera or one) and can withhold the subject anchor
|
|
554
|
+
(`KerbcastControl.SetSubjectAnchor(false, owner)`). Both return to the
|
|
555
|
+
unrestricted behaviour when the lock clears. A `kinematic` anchor is never
|
|
556
|
+
observed: it marks what you supplied, not what the game shows. Where it would
|
|
557
|
+
be observed, though, its result still withholds the lens's pose: `x`, `y` and
|
|
558
|
+
`apparentRadiusPx` only while `inFrame`, and never `distanceM`, `behind` or
|
|
559
|
+
`angleOffAxis`. Under the lock
|
|
560
|
+
`commandTrack.trueOffsetPx` is likewise sent only while the true craft is in
|
|
561
|
+
frame and not occluded.
|
|
562
|
+
|
|
563
|
+
### State that rides the frame
|
|
564
|
+
|
|
565
|
+
From 1.12 every frame's stamp carries what that frame shows, so a view that
|
|
566
|
+
delays the picture never learns it sooner:
|
|
567
|
+
|
|
568
|
+
- `liveLightTimeS` on every frame of every camera: the light time covering
|
|
569
|
+
what of the lens's own craft and site the frame draws at its `ut`. It is
|
|
570
|
+
0 with no light speed. A delayed view releases the frame no sooner than
|
|
571
|
+
its `ut` plus this plus its own delay from the lens. Absent means unknown
|
|
572
|
+
(the game could not read it, or it is older than 1.12): under a signal
|
|
573
|
+
speed, hold the frame. Other loaded craft near the lens are still drawn at
|
|
574
|
+
`ut` and are not covered yet; once craft beyond the boundary are hidden,
|
|
575
|
+
the bound covers everything drawn at `ut`
|
|
576
|
+
- `boundaryM`: how far light travels in one 60 fps frame at the signal
|
|
577
|
+
speed, the picture's boundary distance. Absent with no light speed
|
|
578
|
+
- `command` on a command camera's frames: its fix, range, bracket,
|
|
579
|
+
occlusion, sunlight, blank reason, lock owner, subject, the subject's
|
|
580
|
+
refusal (`unknown`, `notPositioned`, `refused`, `stale`) and which world
|
|
581
|
+
model snapshot it read
|
|
582
|
+
|
|
583
|
+
All three arrive on `DisplayedFrameUt` through `watchDisplayedFrames`.
|
|
584
|
+
`effectiveCommandTrack(track, displayUt, frame.command)` uses a frame's own
|
|
585
|
+
state whole; the React feed does this for its brackets and readout.
|
|
586
|
+
|
|
587
|
+
A host with its own delayed playout keeps the `DisplayedFrameUt` it got for
|
|
588
|
+
each frame and hands the one it is showing to the React feed as
|
|
589
|
+
`displayFrame` (beside, and winning over, `displayUt`): its UT picks the
|
|
590
|
+
transition in force and its `command` draws the brackets and readout. A UT
|
|
591
|
+
alone carries no frame state, so under a lock `displayUt` by itself shows no
|
|
592
|
+
fix. Without either, the feed uses the frame the browser presents, or the
|
|
593
|
+
newest stamped frame before it, which lags the picture and so is never early.
|
|
594
|
+
|
|
595
|
+
While a command lock is held, the camera list's `commandTrack` keeps only its
|
|
596
|
+
`ut`, `authority` and `events` (each replayed at its own UT); the other fields
|
|
597
|
+
read as their defaults, which a 1.11 client shows as no fix. The targets list
|
|
598
|
+
in `commandCentre` carries the `ut` it describes:
|
|
599
|
+
`client.commandCentreAt(flightId, displayUt)` gives the list as it stood at
|
|
600
|
+
the displayed frame, and `null` while every list sent is newer. With no
|
|
601
|
+
displayed UT it gives the newest, except under a lock, where it gives `null`.
|
|
602
|
+
Lists are kept back to two hours of UT before the newest, at most 600 per
|
|
603
|
+
camera (a UT older than every list kept finds none), and are forgotten when
|
|
604
|
+
the camera leaves the list or the client reconnects.
|
|
605
|
+
|
|
606
|
+
In game, `KerbcastControl.SetAnchors(flightId, anchors, owner)` does the same
|
|
607
|
+
with `AnchorSpec.ForPart`, `ForVessel`, `ForSurface`, `ForDirection` and
|
|
608
|
+
`ForVector`. Those results go to every client that asked for anchors, tagged
|
|
609
|
+
with `owner`, until replaced, `ClearAnchors(owner)` or a scene change.
|
|
610
|
+
|
|
451
611
|
## Pan/zoom input helper
|
|
452
612
|
|
|
453
613
|
Driving pan and zoom from a gamepad stick, nudge buttons, or an FoV
|