# KSP ↔ kRPC integration
The `ksp-bridge` app connects to a running KSP instance via the
[kRPC mod](https://github.com/krpc/krpc) 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)
```bash
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](https://github.com/KSP-CKAN/CKAN).
3. From CKAN, install:
- `kRPC` (the mod itself, by [djungelorm](https://github.com/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:
```bash
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` | Object ids of all bodies |
| `SpaceCenter.GetVessels()` | `list` | 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.