RPO Toolkit is a paper-traceable implementation of spacecraft rendezvous and proximity-operations mission design in Rust. Every algorithm β quasi-nonsingular ROE, J2 and DMF-drag analytical STMs, e/i-separation passive safety, null-space-projected formation design, Brent-refined closest-approach β cites its source equation (Koenig 2017, D'Amico 2010, Meeus) and is validated against nyx-space full-physics propagation to the meter.
The workspace is split into two engines: a microsecond, browser-deployable analytical core (MIT/Apache) and a full-physics numerical engine (AGPL, via nyx-space) for drag extraction, validation, and Monte Carlo. The same mission designer that plans a formation in a browser tab runs Monte Carlo on a server.
- Formation-flying and proximity-ops researchers β paper-traceable implementations of quasi-nonsingular ROE methods (Koenig 2017, D'Amico 2010) with equation-level provenance and full-physics validation.
- Rust and frontend engineers building interactive mission-design tools β microsecond analytical planning in the browser via WASM and full-physics Monte Carlo on the server, both from the same code.
This is a ground-based mission-design assistant for human analysts β interactive, advisory, dual-plan β not an autonomous flight-software stack. See Formation Design below for the distinction.
The crate boundary enforces at compile time that the WASM binary never links AGPL code.
rpo-core (MIT/Apache-2.0) <-- rpo-wasm (MIT/Apache-2.0)
^
rpo-nyx (AGPL-3.0) <-- rpo-cli (AGPL-3.0)
<-- rpo-api (AGPL-3.0)
| Analytical Engine (rpo-core) | Numerical Engine (rpo-nyx) | |
|---|---|---|
| Crates | rpo-core, rpo-wasm | rpo-nyx, rpo-cli, rpo-api |
| License | MIT OR Apache-2.0 | AGPL-3.0-or-later (nyx-space) |
| WASM | Yes (wasm32-unknown-unknown) |
No (requires nyx/anise/rayon) |
| Speed | Microseconds | Seconds to minutes |
| Perturbations | J2 + differential drag (DMF) | Full: gravity field, drag, SRP, 3rd-body |
| Use cases | Lambert transfers, formation design, targeting, covariance, interactive UI | Drag extraction, validation, Monte Carlo |
| Valid regime | ROE-linear (delta-r/r < 0.5%) | Any separation |
Prerequisites: Rust 1.88.0 (pinned via rust-toolchain.toml, edition 2024). For the WASM crate, install wasm-pack with cargo install wasm-pack.
cargo build # build workspace
cargo test # run all testsRun an example mission (CLI):
cargo run -p rpo-cli -- mission --input examples/mission.json
cargo run -p rpo-cli -- validate --input examples/validate.json --auto-drag # full-physics validation
cargo run -p rpo-cli -- mc --input examples/mc.json --auto-drag # Monte Carlo ensembleBuild the WASM module with TypeScript definitions:
wasm-pack build rpo-wasm --target webSee CLI Reference for all commands and flags.
flowchart TD
A["Configure spacecraft\n(chief + deputy)"] --> B["Upload ECI\nstate vectors"]
B --> C{"Auto-classify\n(microseconds)"}
C -- "Far-field\n(delta-r/r >= 0.005)" --> D["Lambert transfer\n+ perch handoff\n(microseconds)"]
C -- "Proximity\n(delta-r/r < 0.005)" --> E2["Drag estimation\nfrom current states\n(~3 s, async)"]
D -- "iterate" --> D
D -- "lock in" --> E1["Drag estimation\nfrom perch states\n(~3 s, async)"]
E1 --> S["Formation safety\nrequirements\n(optional)"]
E2 --> S
S --> F["Waypoint planning\nformation design . safety . POCA . COLA . free-drift . covariance + mahalanobis . eclipse\n(microseconds)"]
F -- "iterate" --> F
F --> G["Validate\nnyx full-physics\n(seconds)"]
G -- "adjust" --> F
G --> H["Monte Carlo\nensemble analysis\n(minutes)"]
| Step | Function | Engine | Speed |
|---|---|---|---|
| Classify separation | classify_separation() |
Analytical | microseconds |
| Lambert transfer | solve_lambert() |
Analytical | microseconds |
| Drag estimation | extract_dmf_rates() |
nyx-space | ~3 s |
| Waypoint targeting + eclipse | plan_waypoint_mission() |
Analytical | microseconds |
| Formation design | suggest_enrichment_from_parts(), enrich_waypoint(), accept_waypoint_enrichment() |
Analytical | microseconds |
| Safety analysis | assess_safety() |
Analytical | microseconds |
| Free-drift abort analysis | compute_free_drift_analysis() |
Analytical | microseconds |
| Closest approach (POCA) | compute_poca_analysis() |
Analytical | microseconds |
| Collision avoidance | assess_cola() |
Analytical | microseconds |
| Covariance + Mahalanobis | propagate_mission_covariance() |
Analytical | microseconds |
| Full-physics validation | validate_mission_nyx() |
nyx-space | seconds |
| Monte Carlo ensemble | run_monte_carlo() |
nyx-space | minutes |
What this tool is, in one contrast:
PRISMA FSW : sensor β classify β compute correction β execute maneuver
(autonomous, closed-loop, onboard)
RPO Toolkit : compute baseline + enriched plans β present both β
analyst decides β replan
(advisory, human-in-the-loop, ground)
The toolkit implements the quasi-nonsingular ROE formation-design vocabulary from D'Amico 2010 as an advisory layer with accept/dismiss affordance. Short maneuver legs naturally produce poor intermediate e/i geometry even when 3D keep-out is fully satisfied, so the tool enforces what must not be violated (keep-out distance, checked at every trajectory sample) and evaluates what requires analyst judgment (passive safety, advisory cards with explicit opt-in).
See docs/formation-design.md for the primitives (e/i separation, null-space waypoint enrichment, perch enrichment, transit monitoring, free-drift abort) and their equation-to-function mapping.
Analytical engine benchmarks (Apple M-series, single core, cargo bench -p rpo-core):
| Operation | Time | Notes |
|---|---|---|
roe_to_ric |
7.5 ns | ROE -> RIC mapping |
analyze_safety |
12.8 ns | passive safety analysis |
propagate_j2_drag_stm |
87.3 ns | J2+drag STM propagation (1 orbit) |
classify_separation |
133 ns | ECI -> Keplerian -> ROE -> classify |
find_closest_approaches |
4.0 us | Brent-refined POCA (1 leg) |
solve_leg |
14.6 us | Newton-Raphson dv targeting (1 leg) |
assess_cola |
39.0 us | COLA assessment (2-leg mission) |
plan_waypoint_mission |
198 us | full 2-waypoint mission plan (incl. eclipse) |
Full benchmark suite (component conversions, intermediate eclipse stages, free-drift, etc.) runs via cargo bench -p rpo-core; Criterion HTML reports land in target/criterion/.
Validated against nyx-space full-physics propagation β J2 harmonics, US Std Atm 1976 drag, SRP with conical eclipses, Sun/Moon third-body perturbations β for LEO orbits (~400 km altitude, ~51.6Β° inclination, ISS-class) with ~300 m formation separations.
The table below shows actual observed errors from running the bundled validation example β reproducible with one command:
cargo run -p rpo-cli -- validate --input examples/validate.json # J2 STM
cargo run -p rpo-cli -- validate --input examples/validate.json --auto-drag # J2 + DMF drag| Scenario | Max | Mean | RMS |
|---|---|---|---|
| J2 STM, 3 legs, ~4.5 orbits (no drag) | 58 m | 32 m | 36 m |
| J2 + DMF-drag STM, 3 legs (auto-drag) | 152 m | 68 m | 79 m |
| Eclipse Sun direction (Meeus vs ANISE DE440s) | 0.005Β° | 0.005Β° | β |
| Eclipse entry/exit timing | 83 s | 32 s | β |
Velocity error stays under 40 mm/s (no-drag) and 55 mm/s (auto-drag) across the same run. Per-leg growth under drag: position RMS grows roughly 3Γ from leg 1 to leg 3 (41 m β 92 m β 138 m) as DMF linearization accumulates β a known linear-regime characteristic, not a bug.
Regression tests enforce conservative pass/fail gates with ~10Γ margin over observed errors, and the J2 STM is independently validated against the per-component ROE bounds in Koenig et al. (2017), Table 4 Case 1. See docs/validation.md for the exact constants and test names.
Outside this regime: GEO, HEO, highly eccentric orbits, and formations with Ξ΄r/r > 0.5% are not currently validated against full physics. See Status & Roadmap.
For the full pipeline (classify -> Lambert -> waypoints -> covariance -> eclipse), use rpo_nyx::pipeline::execute_mission(). For WASM/browser contexts, the analytical Lambert solver lives in rpo_core::propagation::lambert and rpo_core::pipeline::compute_transfer_with_enrichment(); pair it with rpo_core::pipeline::execute_mission_from_transfer() for end-to-end browser-side planning. The sketch below shows the lower-level waypoint planning API (analytical only, no nyx dependency); examples/mission.json is a complete runnable scenario driven via rpo-cli.
use rpo_core::prelude::*;
use rpo_core::elements::{state_to_keplerian, compute_roe};
use rpo_core::mission::ProximityConfig;
use nalgebra::Vector3;
// chief, deputy: ECI StateVector values from your scenario
// (see examples/mission.json for a ~300 m formation at ISS altitude)
let phase = classify_separation(&chief, &deputy, &ProximityConfig::default())?;
let chief_elements = state_to_keplerian(&chief)?;
let departure = DepartureState {
roe: compute_roe(&chief_elements, &state_to_keplerian(&deputy)?)?,
chief: chief_elements,
epoch: chief.epoch,
};
let waypoints = vec![Waypoint {
position_ric_km: Vector3::new(0.0, 0.5, 0.0),
velocity_ric_km_s: Some(Vector3::zeros()),
tof_s: Some(4200.0),
}];
let mission = plan_waypoint_mission(
&departure, &waypoints, &MissionConfig::default(), &PropagationModel::J2Stm,
)?;
println!("Phase: {phase:?} dv: {:.3} m/s legs: {}",
mission.total_dv_km_s * 1000.0, mission.legs.len());The rpo-wasm crate compiles to WebAssembly with auto-generated TypeScript definitions via tsify-next. Build with wasm-pack build rpo-wasm --target web, then import:
import init, {
classify_separation,
compute_transfer_with_enrichment,
plan_waypoint_mission,
compute_safety_analysis,
} from "rpo-wasm";
await init();
// Classify β far-field or proximity?
const phase = classify_separation(chief, deputy, { roe_threshold: 0.005 });
if (!("proximity" in phase)) {
// Far-field: solve Lambert locally (microseconds, no server round-trip),
// then resume from the perch state.
const { transfer } = compute_transfer_with_enrichment(transferInput, null);
// ... use `transfer` as the departure for the proximity-ops loop below.
}
// Plan β a 2-waypoint approach in microseconds
const mission = plan_waypoint_mission(
departure,
[
{ position_ric_km: [0, 0.5, 0], velocity_ric_km_s: null, tof_s: 4200 },
{ position_ric_km: [0, 0.1, 0], velocity_ric_km_s: [0, 0, 0], tof_s: 4200 },
],
{}, // MissionConfig β all fields optional
"j2", // PropagatorChoice
);
// Safety β e/i passive safety + keep-out
const safety = compute_safety_analysis(
mission,
{ min_distance_3d_km: 0.05, min_ei_separation_km: 0.2 },
null,
"j2",
);
// Everything above runs in ~1 ms in the browser. See docs/WASM.md for POCA,
// COLA, covariance, eclipse, formation enrichment, and the full 18-function API.All input/output types have full TypeScript definitions. See docs/WASM.md for the complete API reference.
- CLI Reference -- all commands, flags, input formats
- API Reference -- WebSocket protocol, message types, error codes
- WASM Reference -- WASM bindings, TypeScript API, browser usage
- Formation Design -- D'Amico primitive-to-equation mapping
- Validation -- test-suite gates, per-component Koenig accuracy
- Input Schema -- shared JSON schema for
PipelineInput
The CLI provides batch execution and shell-composable plumbing for scripting. The WebSocket API is a stateless backend for the 3 nyx-dependent operations (drag extraction, validation, Monte Carlo) with progress streaming. The WASM crate exposes the full analytical engine to the browser with auto-generated TypeScript definitions.
689 tests across 5 crates (455 rpo-core, 98 rpo-nyx, 77 rpo-cli, 48 rpo-wasm, 11 rpo-api). 34 full-physics tests are #[ignore] by default β running validate, mc, or cargo test -- --ignored downloads ~50 MB of ANISE kernels (DE440s, PCK) on first use and caches them. Analytical-only operations (mission without --auto-drag, all WASM functions) have no external dependencies.
cargo test # full suite (5 crates)
cargo test -p rpo-core # analytical engine only
cargo bench -p rpo-core # criterion benchmarks
cargo clippy --workspace -- -D warnings # lint (pedantic)-
Koenig, Guffanti, D'Amico -- "New State Transition Matrices for Spacecraft Relative Motion in Perturbed Orbits" (PDF), JGCD 2017. J2/drag STMs, ROE definitions, perturbation parameters.
-
D'Amico -- "Autonomous Formation Flying in Low Earth Orbit" (PDF), PhD thesis, TU Delft 2010. QNS ROE, e/i separation, formation design, collision avoidance.
-
Izzo -- "Revisiting Lambert's Problem" (PDF), Celestial Mechanics and Dynamical Astronomy 121.1:1-15, 2015. In-tree Izzo Lambert solver: Lancaster-Blanchard time-of-flight, Householder iteration, multi-revolution branches.
-
Meeus -- Astronomical Algorithms, 2nd ed. Sun/Moon ephemeris, eclipse geometry.
-
Brent -- Algorithms for Minimization without Derivatives, 1973. Root-bracketing for closest-approach refinement.
Every module traces to specific equations in these papers; see inline doc-comments for mappings.
Solo-authored, actively developed, research-grade Rust. Not on crates.io β build from source at the workspace root. See Validated Accuracy above for the regime in which numerical results are backed by full-physics tests; GEO, HEO, and larger separations are on the roadmap below.
In progress / next:
- React Three Fiber frontend β interactive 3D mission designer running in the browser via the WASM analytical engine, WebSocket to
rpo-apifor nyx-dependent operations (drag extraction, validation, Monte Carlo). Analytical ops stay sub-frame; numerical ops stream progress. - Drag-aware formation design β DMF-rate feedback into waypoint null-space enrichment so drift compensation happens upstream of the analyst advisory rather than as a post-hoc warning.
- Extended orbit regimes β GEO and HEO validation; appropriate STM extensions; finite-burn modeling for maneuvers that cannot be treated as impulsive.
Bug reports and questions: GitHub issues.
If you use RPO Toolkit in research, please cite:
@software{melkonian_rpo_toolkit,
author = {Melkonian, Sarkis},
title = {RPO Toolkit: A Paper-Traceable Implementation of Spacecraft
Rendezvous and Proximity-Operations Mission Design in Rust},
year = {2026},
url = {https://github.com/sakobu/rpo-toolkit}
}- rpo-core, rpo-wasm -- MIT OR Apache-2.0
- rpo-nyx, rpo-cli, rpo-api -- AGPL-3.0-or-later (required by nyx-space)
Sarkis Melkonian