Files
KSP-MissionControl/ksp/README.md
T
Mavis aebee77843 phase 1c-extract: typed kRPC service client + SpaceCenter extract
Built the missing piece that connects the ksp-bridge to a real KSP
instance via kRPC. This adds a typed service client on top of the
existing KRPCClient, plus the SpaceCenter-specific extraction logic
that pulls the universe state from a running KSP save.

@kerbal-rt/krpc-client
- types.ts — runtime representation of kRPC Type descriptors
  (TypeCode enum, KrpcType interface, decodeKrpcType, typeName)
- decoder.ts — kRPC value codec: primitive decode/encode, class refs,
  enums, collections (LIST/SET/TUPLE/DICTIONARY), system messages
  (STATUS/SERVICES/STREAM/EVENT/PROCEDURE_CALL). 34 unit tests.
- services.ts — ServiceCache built from KRPC.GetServices() response.
  Lookup by (service, procedure), enum value/name resolution. 12 tests.
- service-client.ts — KrpcServices: high-level invoke-by-name client.
  loadServices() helper to connect + load catalog. 9 integration tests
  with a mock kRPC server.
- schema.ts — added Set/Dictionary/Event/Expression types so the
  decoder can handle system messages without external .proto files.
  Also fixed a bug where 'STREAM' was being encoded as 0 due to
  protobufjs's nested-enum string lookup.

ksp-bridge
- extract.ts — the actual SpaceCenter calls. ~280 procedure calls
  per poll for a typical KSP save (UT, bodies, vessels, then per-body
  and per-vessel class methods in parallel). Build the UniverseSnapshot.
- krpc-adapter.ts — rewrote to use KrpcServices (typed) instead of
  the stub extract function. Supports an optional injected services
  for testing.
- bridge.ts — uses the new ExtractedState type and buildSnapshot.
- index.ts — connects to kRPC; falls back to mock mode if no server.
- convert.ts — backward-compat shim, re-exports from extract.ts.

The ksp-bridge can now talk to a real KSP install. We do NOT need
the kRPC mod's .proto files on disk — the server's GetServices()
response is the source of truth for type info. Documented the full
list of procedures we call, the kRPC value encoding, and the new
architecture in ksp/README.md.

Tests: 119 total, all green. Typecheck and build clean across all 11
projects.

Bonus: fixed an integer-overflow bug in the krpc-client connect()
handshake (was passing 'RPC'/'STREAM' strings to protobufjs; its
nested-enum string lookup silently encodes as 0, which made the
stream handshake send the wrong type. Switched to numeric codes.)
2026-06-02 22:02:26 +00:00

7.4 KiB

KSP ↔ kRPC integration

The ksp-bridge app connects to a running KSP instance via the kRPC mod and pushes state to the kerbal-rt API. This README documents what's wired up today and what's still TODO.

Quick start

A. Mock mode (no KSP needed)

cd apps/tools/ksp-bridge
KERBAL_RT_API_URL=http://localhost:4000 \
BRIDGE_POLL_MS=500 \
pnpm start

The bridge starts, tries to connect to 127.0.0.1:50000, fails (no kRPC server running), and falls back to MOCK mode: it emits synthetic state every poll. This is great for verifying the HTTP pipeline and the live-map / hub end-to-end without KSP.

B. With real KSP

  1. Install KSP 1.12.5 (this is the version kRPC 0.5.x targets).

  2. Install CKAN.

  3. From CKAN, install:

    • kRPC (the mod itself, by djungelorm)
    • Any other mods you want
  4. Launch KSP, start a save, and press Alt+F12 to open the kRPC server window. Make sure the RPC server is on 127.0.0.1:50000 and the Stream server is on 127.0.0.1:50001.

  5. Run the bridge:

    cd apps/tools/ksp-bridge
    KERBAL_RT_API_URL=http://localhost:4000 \
    KSP_KRPC_HOST=127.0.0.1 \
    KSP_KRPC_PORT=50000 \
    KSP_KRPC_STREAM_PORT=50001 \
    BRIDGE_POLL_MS=1000 \
    pnpm start
    

    The bridge will log connected to kRPC at 127.0.0.1:50000 — running with real KSP state and start polling.

Architecture

Two layers

  1. @kerbal-rt/krpc-client — the low-level kRPC client.

    • TCP connection (RPC port + stream port)
    • Length-prefixed protobuf framing
    • Connection handshake (ConnectionRequest/ConnectionResponse)
    • Procedure invocation (Request/Response)
    • Stream subscription (AddStream/StreamUpdate)
    • Plus a typed service client built on top:
      • Loads the service catalog via KRPC.GetServices() on connect
      • Encodes procedure arguments based on the cached type info
      • Decodes return values based on the cached type info
      • Knows about the kRPC value encoding (primitives, classes, enums, collections, system messages)
  2. apps/tools/ksp-bridge — the actual KSP bridge.

    • krpc-adapter.ts — owns the KRPCClient + KrpcServices, exposes connect() / readState() / disconnect()
    • extract.ts — calls SpaceCenter methods to read the full universe state and produces a UniverseSnapshot
    • bridge.ts — the polling loop, HTTP POST to API, retry / reconnect
    • index.ts — entrypoint; falls back to MOCK mode if no kRPC server

No .proto files needed

We do not need the kRPC mod's .proto files on disk. The kRPC server provides the full service catalog (procedures, classes, enums, exceptions) via KRPC.GetServices() on connect, and we cache that into a ServiceCache for lookups. The value encoding is implemented in packages/krpc-client/src/decoder.ts.

The original plan (Phase 1c) called for loading the .proto files with protobufjs.loadSync(). We pivoted to the GetServices approach because:

  • The kRPC server is the source of truth (we can't get out of sync)
  • We don't have to ship 30+ .proto files with the bridge
  • The .proto files are mostly for static code generation in other languages; for a dynamic client, GetServices is sufficient

What we read from KSP

Per poll, the bridge makes ~280 procedure calls (for a stock KSP save with 15 bodies and 5 vessels). At BRIDGE_POLL_MS=1000 that's comfortably within what kRPC can handle on loopback. If you need more throughput, the obvious optimization is to batch the calls into a single KRPC.Request with multiple ProcedureCall entries (the server already supports this; we just don't use it yet).

Top-level

Procedure Returns Used for
SpaceCenter.GetUT() double Universal Time (game seconds since epoch)
SpaceCenter.GetBodies() list<CelestialBody> Object ids of all bodies
SpaceCenter.GetVessels() list<Vessel> Object ids of all vessels

Per CelestialBody (8 calls per body)

Procedure Returns Field
CelestialBody.GetName(self) string name
CelestialBody.GetParent(self) CelestialBody (nullable) parentId
CelestialBody.GetRadius(self) double radius (m)
CelestialBody.GetSphereOfInfluence(self) double sphereOfInfluence (m)
CelestialBody.GetGravitationalParameter(self) double μ (m³/s²)
CelestialBody.GetRotationPeriod(self) double rotationPeriod (s)
CelestialBody.GetAxialTilt(self) double axialTilt (rad)
CelestialBody.GetOrbit(self) Orbit (then 8 orbit calls)

Per Orbit (8 calls per orbit)

Procedure Returns Field
Orbit.GetSemiMajorAxis(self) double semiMajorAxis (m)
Orbit.GetEccentricity(self) double eccentricity
Orbit.GetInclination(self) double inclination (rad)
Orbit.GetLongitudeOfAscendingNode(self) double longitudeOfAscendingNode (rad)
Orbit.GetArgumentOfPeriapsis(self) double argumentOfPeriapsis (rad)
Orbit.GetMeanAnomalyAtEpoch(self) double meanAnomalyAtEpoch (rad)
Orbit.GetEpoch(self) double epoch (s)
Orbit.GetReferenceBody(self) CelestialBody (nullable) (for verification only)

Per Vessel (5 calls per vessel)

Procedure Returns Field
Vessel.GetName(self) string name
Vessel.GetType(self) VesselType (enum) type (resolved to name)
Vessel.GetSituation(self) VesselSituation (enum) situation (raw int code)
Vessel.GetOrbit(self) Orbit (then 8 orbit calls)
Vessel.GetReferenceBody(self) CelestialBody referenceBodyId (resolved to name)

What's NOT in scope yet (deferred work)

  • Streams — we don't subscribe to kRPC streams yet. We're polling. For a real-time UI, switching to streams (or hybrid poll+stream) would reduce latency and load. kRPC has AddStream and the stream port is already wired in.
  • Batched calls — every kRPC call is its own request. We could batch multiple ProcedureCall entries in a single Request for ~10x throughput.
  • Ground stations — kRPC doesn't expose ground stations natively. The ksp-bridge accepts them as static config or via mod integration.
  • Comm nets / signal strength — needs the CommNet API. kRPC has it, but we don't use it yet.
  • Crew / science — not in the ksp-bridge scope right now.
  • Maneuver nodes — easy to add (Vessel.GetManeuverNode() etc.) but not needed for the live map / mission clock.

Troubleshooting

no kRPC server at 127.0.0.1:50000

Either KSP isn't running, or the kRPC server isn't enabled. Open the kRPC window in-game (Alt+F12) and make sure "Start server" is checked.

procedure not found in service cache

The kRPC server returned a procedure that we don't know about. This usually means the kRPC version is older or newer than we expect (we target 0.5.x). The ServiceCache will log the procedures it knows about; cross-check with the in-game kRPC window.

wrong number of arguments

The procedure signature in the cache doesn't match what we're sending. This can happen if the kRPC version has a different parameter order or count for a procedure we use. The fix is in apps/tools/ksp-bridge/src/extract.ts — adjust the call site.