# 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.