@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.
Files changed (44) hide show
  1. package/README.md +174 -14
  2. package/dist/__generated__/types.d.ts +570 -14
  3. package/dist/__generated__/types.js +82 -1
  4. package/dist/anchors.d.ts +61 -0
  5. package/dist/anchors.js +71 -0
  6. package/dist/captureClip.d.ts +116 -0
  7. package/dist/captureClip.js +208 -0
  8. package/dist/client.d.ts +313 -22
  9. package/dist/client.js +856 -42
  10. package/dist/commandTrack.d.ts +66 -6
  11. package/dist/commandTrack.js +137 -13
  12. package/dist/frameUt.d.ts +44 -10
  13. package/dist/frameUt.js +34 -4
  14. package/dist/index.d.ts +5 -3
  15. package/dist/index.js +3 -2
  16. package/dist/mp4.d.ts +103 -0
  17. package/dist/mp4.js +481 -0
  18. package/dist/panZoom.d.ts +1 -2
  19. package/dist/panZoom.js +1 -2
  20. package/dist/recording.d.ts +200 -16
  21. package/dist/recording.js +532 -48
  22. package/dist/testing/index.d.ts +36 -2
  23. package/dist/testing/index.js +86 -5
  24. package/package.json +1 -1
  25. package/src/__generated__/types.ts +569 -13
  26. package/src/anchors.test.ts +68 -0
  27. package/src/anchors.ts +110 -0
  28. package/src/captureClip.test.ts +422 -0
  29. package/src/captureClip.ts +263 -0
  30. package/src/client.test.ts +1324 -3
  31. package/src/client.ts +988 -57
  32. package/src/commandTrack.test.ts +206 -10
  33. package/src/commandTrack.ts +160 -14
  34. package/src/framePipe.test.ts +79 -0
  35. package/src/frameUt.test.ts +15 -0
  36. package/src/frameUt.ts +72 -9
  37. package/src/index.ts +25 -0
  38. package/src/mp4.ts +627 -0
  39. package/src/panZoom.ts +1 -2
  40. package/src/recording.capture.test.ts +349 -0
  41. package/src/recording.test.ts +338 -7
  42. package/src/recording.ts +703 -63
  43. package/src/testing/index.ts +92 -7
  44. 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 position you propagate yourself: `position` and
255
- `velocity` as `[x, y, z]` relative to `body` in its non-rotating frame at
256
- `ut`, carried on linearly
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
- ("gonogo not responding") but keeps the lock, so aims from anyone else are
381
- still refused. This is deliberate: a holder that delays commands (gonogo
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("gonogo");
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), "gonogo");
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("gonogo");
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 "gonogo not responding" and keeps the lock, so everyone else
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 `Aim`,
446
- `SubjectPersistentId`, and `Created` set on the ones made at runtime;
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