What this tool does
A contact plan is the time-ordered list of communication
opportunities between spacecraft, landers and ground stations. CPD
computes those opportunities from orbital geometry and link
constraints (range, antenna cone, elevation), then designs an
applicable plan by resolving resource conflicts, and exports it
to the DTN ecosystem (ION / HDTN) and the IPN-V 3D visualizer.
How it works (methods & assumptions)
Propagation. Orbits are advanced with analytic two-body
(Keplerian) propagation. Every body and node is placed onto one fixed
time grid by recursive frame composition into a single Sun-centered
inertial frame, so a moon's orbiter inherits the moon's and planet's
motion automatically.
Contact computation. At each time sample a contact is feasible
when a composite condition holds: range within
[min, max], elevation within limits at each endpoint,
the target inside the antenna boresight cone, and a
line of sight clear of every occluding body sphere. Landers use a
horizon / elevation test instead of self-occlusion (their own body is
excluded as an occluder). Windows are found on the fixed step, then each
boundary is bisection-refined to sub-step (≈1 ms) precision.
Design. The raw topology may oversubscribe a node's interfaces;
the FCP solver resolves this on a time-expanded graph by
per-state max-weight matching, weighting starved links for fairness, to
yield a conflict-free plan.
Design options. A terminal pointed at a peer is busy in both
directions, and it keeps receiving for one OWLT after the transmit
window closes. The second status badge counts the episodes where that
tail overlaps the next booking of the same terminal: a plan can be
conflict-free and still not be light-time clean.
Book reception reserves the receiving terminal for that tail, which
removes those episodes at the price of roughly one OWLT of idle time per
hand-over (very short windows are lost entirely). Slice makes the
solver re-decide at least that often, so competing links alternate within
a long pass instead of one link holding all of it, and Min run
commits a scheduled link to its terminals for at least that long (plus
one OWLT with reception booked), so the solver never leaves hand-over
slivers shorter than a terminal could realistically acquire and lock.
Assumptions to know. Geometric (T1) works from
instantaneous (true) positions and takes
OWLT = range / c. Apparent (T2) solves
the light path for each direction and reports the resulting receive
window. Aberration shifts pointing rather than feasibility and stays out
of scope; relativistic (Shapiro) delays are recorded as a diagnostic and
never applied to a window. Node
inclinations and lander latitudes are expressed in the body's
equatorial frame; body obliquity is carried for visualization but
not applied to contact geometry. Contacts are unidirectional
(ION-style): a full-duplex link emits one contact per direction, each
carrying that transmitter's rate.
Quick start
- Describe the scenario. Click ✎ Build scenario… to
design one with a guided form (no YAML needed), edit or paste YAML in
the left pane, or use Import file…. The example scenario is
pre-loaded.
- Load & Compute (or Ctrl+S / ⌘S from the
editor). Propagates the orbits and determines
the raw contact topology — drawn as the schematic and the timeline.
- Apply design. Pick a method (FCP) and run it to
resolve oversubscription conflicts; the badge flips to
conflict-free.
- Edit the timeline. Drag a contact to move it, drag its
edges to crop, or use Razor, Split @▷,
+ Add contact and Delete selected. Conflicts
re-evaluate live. Hide selected (or H) dims a contact
and removes it from the 2D/3D views without changing the plan.
- Export the result with Export ION / HDTN / TVR / IPN-V, as STK-style access / AER CSVs (STK Access / STK AER), or as an Osmium emulator bundle (topology.yaml + contacts.yaml).
Scenario time is relative: everything is counted in seconds
from t = 0. Add epoch: 2025-01-01T00:00:00Z (or
epoch-s, POSIX seconds) to the scenario to tag that
instant, and the playbars, the timeline playhead tooltip and every
export show real dates. Without it the exports anchor t = 0
at 1970-01-01.
Bundled examples
The Examples menu lists ready-made scenarios: the
earth_moon starter, a solar_system tour, the ESA CCSDS /
DSNS validation reconstructions, the paper2
light-time study set, and the stint_demo pair from the STINT 2026
talk: a Leuven station with one LEO satellite, and DSN Madrid with two
Mars relays and a rover, 20 minutes of light time apart.
The code02_* examples implement the IETF/IRTF
constellation-code I-D (-02):
code02_cislunar embeds a Walker shell with the draft's
link-patterns (in-plane ring + staggered cross-plane ISLs)
and its Charleroi ground station into a cislunar scenario, showing how
the constellation code composes into an interplanetary one.
code02_one_shell is the intermediate, Earth-only demo: one
patterned shell of 120 satellites whose ground-station antenna
conflicts the design stage resolves. code02_two_shell is the
faithful replication of the draft's own two-shell example
(452 satellites, 652 patterned ISLs); at that scale the browser views
get heavy, so prefer the 2D and Statistics views. See
examples/README.md in the repo for the full catalog.
Views
2D Static — a logical schematic (Sun → planets → moons →
nodes) above the editable contact timeline.
Contact timeline — every bar is the transmit window on the
sender's clock. The dashed reception rail under it, capped by
a dot at each end, is the receive window: the first bit arrives one OWLT
after transmission starts, the last one OWLT after it stops. The rail
therefore overhangs the bar by one OWLT in either tier, and the coloured
part of it is reception happening after the window has closed.
2D Dynamic — the same scenario with nodes at their real
orbital phase; press ▶ to animate and watch links light up.
3D View — a CesiumJS globe-free scene of the same scenario,
to scale: bodies and nodes at their real positions from CPD's own
ephemeris, with contact links drawn live from the current plan.
Scheduled links propagate at the speed of light: each contact
lights up at the transmitter, sweeps across to the receiver one OWLT
later, holds while transmitting, then drains transmitter-to-receiver,
so you can see when reception begins and ends.
Each direction of a link rides its own lane, with chevrons pointing
to the receiver (toggle Flow to turn this off).
Statistics — a read-only dashboard summarizing the scenario
and the current contact plan: node/body counts, scheduled vs. dropped
contacts, total contact-time and data volume, duration/rate/range/OWLT
distributions, and per-category and per-node rollups. It tracks every
design and timeline edit.
Tips
In the schematic and dynamic views: wheel = zoom,
drag = pan, click = select (details show under
Selection). In the 3D view: drag = orbit, wheel =
zoom, double-click a body to follow it, and use the
focus buttons (top-left of the scene) to center on a planet, the
Moon, the nodes, or fit the whole system. The ▶ playhead and its
speed are shared across all views, so the schematic, timeline, dynamic
and 3D views all move together.
Transport keys: Space plays forward,
Shift+Space backward (or use ▶ and ◀),
→ / ← jog by one second of playback at the current
speed, and Shift+→ / Shift+← by ten; hold an arrow to
scrub. H hides or shows the selected contacts,
Delete removes them.
Privacy
One essential session cookie; privacy-friendly first-party analytics
(no third-party trackers, no raw IPs stored), with opt-out anytime. See
the privacy policy
to review or change your choice.