Skip to content

RFC-037 · Physics Kernel — the technical architecture for the Lab subsystem + MCP server

Status: Draft · 2026-08-08 · Closes into: ADR-TBD (physics-kernel package boundary + FigureSpec + MCP hosting) · Related: PRD-033 (vision), UXS-015 (look & feel), ADR-030 (fly-physics pure-function isolation), RFC-034/#412 (launch-ascent engine), RFC-024 + RFC-035 (containerized VPS stack + nginx/subdomain pattern) · Tracking: #458 (epic) · slices #459–#464

Why this is an RFC. PRD-033 sets the vision (liberate the physics into a Lab + an MCP server) and UXS-015 the look & feel; this RFC is the how it's built, and four load-bearing contracts have to be frozen before any code because every downstream slice and both consumption surfaces depend on them: (1) the kernel package boundary — what is "kernel" vs "app", enforced so the pure core can't regain a framework dependency, imported unchanged by both the SvelteKit build and a standalone Node process; (2) the FigureSpec contract — the single declarative figure description that the Lab renderers and the MCP responses both emit, so "draw the result" is defined once; (3) the card / serialization model — the portable atom that lets one card live in Notebook, Focus, and Canvas and persist across sessions; (4) scientific credibility as a public contract — exposing formulas to the world forces choosing one authoritative ephemeris and capping unbounded compute. Getting any of these wrong bakes a shape we can't cheaply walk back into a public API and a persisted document format.


1 · Scope

This RFC owns the technical spine shared by every surface: the kernel, the compute/figure/card contracts, the MCP server, the data strategy, and the slice plan. The product vision lives in PRD-033; the interaction/visual design in UXS-015. Where this RFC says "Notebook / Focus / Canvas," those are the three views defined in UXS-015 over one card model.

2 · Grounding — what already exists (inventory, 2026-08-08)

A two-agent sweep read ~45 modules. Verdict: the physics is overwhelmingly pure already (no three, no Svelte stores, no DOM, deterministic). The kernel is ~80% lift-and-shift.

DomainModulesRepresentative formulas
Ascent / launchorbital/ascent-physics.ts (+constants)2-DOF gravity-turn integrator, Tsiolkovsky Δv, pressure-interpolated thrust/Isp, staging, closed-loop insertion
Descent / EDLorbital/descent-physics.ts (+constants, 11 bodies)1-DOF entry integrator, drag, dynamic pressure, Mach, per-body atmospheres
Transfer / windowslambert.ts, lambert-grid.ts, orbital.ts, orbital/mission-arc.tsLambert (Lagrange-Gauss), porkchop grid, vis-viva, Kepler position, Hohmann + Keplerian transfer ellipse
Cislunarorbital/cislunar/cislunar-geometry.tsECI translunar/trans-Earth coasts, lunar orbit/flyby, spiral burns
Interplanetaryinterplanetary-geometry.tsAnalytic + waypoint-replay heliocentric trajectories
Ephemeris / skyastronomy/{time,planets,moon,horizontal,index}.tsJD/GMST/LST/obliquity, JPL Standish planet positions (Newton Kepler solve), Schlyter Moon, ecliptic→equatorial→horizontal, alt/az sky-pointing
Satellite / TLEsatellite/{tle,propagate,look-angles,index}.tsTLE parse, Kepler + J2 propagator (own, not satellite.js), look-angles, next-pass prediction
Propulsion / enginesorbital/{engine-registry,launcher-engines}.ts~22 engines: thrust/Isp/mass/cycle; launcher↔engine cross-refs
Utilityparse-delta-v, scale, planet-stats, moon-projection, earth-regimes, orbit-regime-matchΔv parse, escape-velocity/gravity tables, light-time, regime bounds

This table is the formula-registry seed (§5): each row becomes one or more registered formulas that the palette, the views, and the MCP tools all derive from.

3 · Contract 1 — the kernel boundary (D1: resolved)

Decision: src/lib/physics/ subtree, not a separate workspace package (revisit only if the MCP build forces it). Rationale: single tsconfig, no monorepo wiring, and the pure modules already live under src/lib — this is a move, not a restructure.

  • Move the PURE modules from §2 under src/lib/physics/{ascent,descent,transfer,cislunar,ephemeris,satellite,propulsion,util}/, keeping their sibling .test.ts.
  • Enforce purity with an ESLint no-restricted-imports boundary on src/lib/physics/**: forbid three, svelte, $app/*, $lib/* (app internals), DOM globals. This is the guard that keeps the kernel re-couplable-proof — the thing ADR-030 did by convention becomes a lint gate.
  • Public index src/lib/physics/index.ts — the only entry both consumers import. Per-export units + citations in doc-comments (public-contract grade).
  • Impure code stays out: satellite/tle-source.ts, the fetch-based loaders, the /science cross-ref lookups (see §7 decoupling).

4 · Contract 2 — FigureSpec (the draw-once contract)

Every formula result may carry a declarative figure description, not a rendered image. One shape, consumed by the Lab's SVG renderers and returned verbatim by the MCP server so an agent can render it anywhere.

ts
type FigureSpec =
  | { kind: 'transfer-ellipse'; frame: 'heliocentric'; bodies: {...}; arc: Vec2[]; marks: Annotation[] }
  | { kind: 'porkchop'; depDays: number[]; arrDays: number[]; grid: number[][]; units: 'km/s' }
  | { kind: 'dv-waterfall'; segments: {label; dv; kind}[] }
  | { kind: 'force-diagram'; vectors: {label; dir; magN}[]; body: 'rocket' }
  | { kind: 'curve'; x: {label;units;vals}; y: {label;units;vals}; marks?: Annotation[] }
  | { kind: 'orbit' | 'ground-track' | 'sky-chart' | 'entry-corridor' | 'cislunar-eci'; ... };
// every FigureSpec carries: provenance: { source: 'computed'; module: string }, and NEVER 'illustration' from the kernel.
  • Renderers draw on the station-blueprint.ts teal-grid substrate (UXS-015 §figure language). The sketchy-but-exact styling lives in the renderer, never in the spec — the spec is pure data.
  • Provenance is intrinsic. Kernel-emitted specs are always computed. The generative/illustration layer (PRD-033 T3) is a separate artifact type the kernel never produces — this is the honesty line made structural.

5 · Contract 3 — the formula registry + card model

The single source that the palette, all three views, and the MCP tool generator derive from.

ts
interface FormulaDef<I> {
  id: string;                       // 'tsiolkovsky', 'lambert-transfer', ...
  schema: JSONSchema;               // typed inputs + UNITS + server-side caps (e.g. steps ≤ N)
  compute(inputs: I): FormulaResult;
}
interface FormulaResult {
  values: Record<string, { value: number; units: string }>;
  figure?: FigureSpec;
  status: { ok: true } | { ok: false; reason: string };   // fail-honest: infeasible carries a reason
}

The card (the UXS-015 atom) is the serializable instance:

ts
interface Card { id: string; formulaId: string; inputs: Record<string, number|string>;
                 wires?: { fromCard: string; output: string; toInput: string }[] }
  • A Notebook = { title, cards: Card[] } (order = narrative; a card may reference an upstream card's output).
  • A Canvas = { cards: Card[], positions, edges } — same cards, plus layout + explicit wiring.
  • Serialization (D-serialize, new): compact form in the URL (shareable, matches IA §state-persistence's URL-encoded exception) for small worksheets; localStorage for the working session; a .orrlab.json export/import for durable documents. One codec, all three views.
  • One registry → MCP tools auto-derived. Each FormulaDef.schema + compute generates one MCP tool. The registry is the single place a new formula is added; every surface picks it up.

6 · Contract 4 — MCP server (hosting, transport, auth)

  • Subdomain / infra. mcp.orrerylearn.com, a new container behind the existing nginx on the VPS. Reuses the RFC-024/035 compose + subdomain-vhost pattern; no new infra class.
  • Transport. MCP Streamable HTTP (current remote-server transport; Claude.ai custom connectors speak it).
  • Auth (D3). "Anybody with credentials logs in" → OAuth 2.1 (the spec's authorization framework) for public multi-user; a shared bearer token / nginx-gated credential for the private beta first. Highest-uncertainty area — verify against the current MCP auth spec before the beta slice.
  • Statelessness = security asset. Pure compute, no per-user state, no mutation, read-only. Nothing to leak; horizontally scalable.
  • Abuse bound. computePorkchopGrid and the integrators are unbounded-cost at large steps — the schema (§5) caps iteration counts server-side; this cap is part of the tool contract, not an afterthought.

7 · Decoupling items (the non-clean six)

  1. Static-JSON couplinglambert-grid.constants, mission-arc, cislunar-events, satellite/stations Vite-import planets.json / small-bodies.json / cislunar-phase-science-map.json / station-tles.json. Resolve per §8.
  2. Fetch/loader boundaryloadLaunchProfile / loadDescentProfile call fetch; pure helpers move to the kernel, loaders stay app-side adapters.
  3. Genuinely impure — excluded: satellite/tle-source.ts (localStorage + fetch + Date.now + memo).
  4. Two ephemerides — §9.
  5. Non-determinism to scrubearth-sidereal.ts (new Date() default); porkchop.ts locale-dependent date/colour helpers move out of the physics namespace (rendering, not physics).
  6. App-domain /science lookupsscienceRefsFor et al. stay app-side.

8 · Data strategy (D2)

  • D2-a inline (bake ephemeris rows into .ts) — portable, but duplicates source-of-truth JSON.
  • D2-b co-bundle (ship the JSON, resolve in both consumers) — single source; build-config cost.
  • D2-c inject (caller passes the data) — purest boundary; ripples to call sites.
  • Decision: D2-b for ephemeris tables — the JSON stays the single source of truth (PA §"data over code") + a codegen drift-check that fails CI if the baked .ts diverges from the JSON; D2-c where the caller already holds the data.

9 · The authoritative ephemeris (public-contract critical)

Two implementations exist: orbital.ts#keplerPos (first-order mean-longitude approximation, /fly's cheap smooth path) and astronomy/planets.ts#heliocentric (JPL Standish + Newton solve, ~arc-minute, 1800–2050). Decision: the Lab and MCP expose the JPL-Standish path as authoritative; the approximation is either unexposed or labelled fast with a documented error bound. /fly internals unchanged. Verified 2026-08-08 by source read: orbital.ts#keplerPos advances mean longitude linearly and uses it directly as the true anomaly (docstring: "no eccentric anomaly solve … accurate to first order"); astronomy/planets.ts#heliocentric uses the JPL Standish ELEMENTS table + an 8-iteration Newton solveKepler (docstring: "~arcminute accuracy, valid 1800–2050"). Authoritative planet positions for the Lab + MCP = astronomy/planets.ts#heliocentric.

10 · Rollout — slices (rollback-safe, Notebook-first per PRD-033)

  1. S1 — kernel boundary. Move pure modules to src/lib/physics/, add the ESLint purity gate, resolve JSON coupling (§8). Prove npm run preflight green + app behaviour unchanged. No new surface.
  2. S2 — registry + FigureSpec + renderers. The formula registry (§5), the FigureSpec type (§4), and the SVG renderers on the teal grid. Public-contract tests + citations.
  3. S3 — Lab: Notebook + Focus (v1 home). The /lab route, card model + serialization codec, Notebook and Focus views over the registry, honesty line + fail-honest states (UXS-015). This is the in-app payoff and it de-risks the kernel in-process.
  4. S4 — MCP server, private beta. Standalone Node process, tools auto-derived from the registry, one domain first (transfer: Lambert + vis-viva + Tsiolkovsky), bearer auth, mcp. subdomain.
  5. S5 — Canvas + wiring + promote (T2). The graph engine (sockets, edges, cycle-check, recompute), Canvas view, promote-to-Notebook. The expensive/novel slice — deliberately after S3 proves the card model.
  6. S6 — MCP OAuth + full tool surface, then the generative/illustration layer (T3) and the agentic ask-box (T4) as later, separately-gated work.

Each slice is inert until wired. S1 has no external exposure and can land independently of auth/hosting.

Tracked as: epic #458 · S1 #459 · S2 #460 · S3 #461 · S4 #462 · S5 #463 · S6 #464. Scheduled v0.9 — parked behind v0.8 completion; do not start S1 until v0.8 closes.

11 · Alternatives considered

  • A — reimplement formulas in the MCP server. Rejected: duplicates the source of truth, guarantees drift, discards the unit tests. The value is one registry.
  • B — expose the whole app over an API. Rejected: drags Three/Svelte/DOM headless, couples the public contract to route internals, can't serve the in-process views.
  • C — publish the kernel to npm. Compatible, natural post-S6 follow-on; out of scope (the ask is a hosted MCP surface).
  • D — per-view bespoke data models. Rejected: the whole subsystem coherence comes from one card model across three views (UXS-015).

12 · Non-goals

  • Changing any physics formula, constant, or its accuracy (extraction is behaviour-preserving; ADR governs constants).
  • Real-time / N-body / higher-precision propagation — the kernel exposes today's fidelity, honestly labelled.
  • Moving impure data layers (TLE live-fetch, profile fetch) into the kernel.
  • The Canvas graph engine in v1 (S5, second workspace).
  • npm publication (§11-C); the generative layer (T3) and agentic ask-box (T4).

13 · Decisions

Resolved:

  • D1 — kernel homesrc/lib/physics/ subtree + ESLint purity gate (§3).

  • D5 — route/views/lab; Notebook / Focus / Canvas (UXS-015).

  • D2 — JSON-data strategy → D2-b co-bundle, JSON stays the source of truth + a CI drift-check (§8).

  • D-serialize — card codec → one codec, three outputs: URL-compact (shareable) + localStorage (session) + .orrlab.json (durable export/import); freeze the URL grammar before S3 (§5).

  • D-verify — ephemeris fidelity → confirmed by source read (§9); authoritative = astronomy/planets.ts#heliocentric.

  • D3 — MCP auth model → shared bearer token (private beta) → OAuth 2.1 (public multi-user); verify vs the current MCP auth spec before S4. (decided 2026-08-08)

  • D4 — new dependency @modelcontextprotocol/sdk (+ an HTTP server dep) → approved, MCP process only, gated to S4; S1–S3 add no new runtime deps (KaTeX already present). (decided 2026-08-08)

All kickoff decisions are resolved; slices S1–S6 are cleared to be tracked as issues.

Orrery — architecture documentation · MIT · No tracking