Skip to content

ADR-038 — Per-body 2D projection: Mars equirectangular, Moon orthographic dual-disc

Status · Accepted (closes RFC-012 OQ-2) Date · 2026-05-09 (back-filled from v0.4.0 implementation) Closes · RFC-012 OQ-2 ("2D projection for Mars") TA anchor · §components/surface-routes Related ADRs · ADR-037 (shared surface-site type), ADR-039 (cross-link contract)

Context

/moon shipped in v0.1 with a 2D view of two side-by-side orthographic discs (near side + far side) — the natural visual for a tidally-locked body where the always-Earth-facing hemisphere is a meaningful entity.

/mars arrived in v0.4. RFC-012 OQ-2 asked: does the same orthographic-pair pattern transfer? Mars has no permanent far side (rotation period ≈ 24h 37m, not synchronous with Earth) so the "near vs far" semantic doesn't apply. Three projection candidates were considered:

ProjectionProsCons
Equirectangular (cylindrical lat/lon grid)Familiar from NASA Mars maps; lat/lon → pixel is trivial; rover traverses lay flatPolar regions exaggerated
Orthographic dual-disc (Mars equivalent of /moon's near/far)Visual parity with /moon"Near" / "far" arbitrary on Mars; users would need to learn what each disc means
Mollweide / equal-areaPolar regions truthfulLess recognisable; rover traverse rendering more complex

Independent decision per body — RFC-012 explicitly asked us to lock the projection per-body, not globally.

Decision

Mars — equirectangular

/mars 2D mode uses a single equirectangular projection. Latitude on the y-axis (-90° south at top → +90° north at bottom — flipped from raw lat so positive-y maps to north visually), longitude on the x-axis (-180° west to +180° east). Pixel coordinate computation:

px = (lon + 180) / 360 × canvas.width
py = (90 - lat) / 180 × canvas.height

Rover traverses (per RFC-012 OQ-6) render as polylines through the same projection.

Moon — keep orthographic dual-disc

/moon 2D mode keeps its existing orthographic-pair view (near side + far side discs side-by-side). Tidal-lock is a real semantic distinction worth surfacing visually; replacing it with equirectangular for parity with Mars would lose information.

Polar-projection toggle — deferred

A third "polar projection" toggle (north-pole / south-pole stereographic) was considered for Mars to handle the Polar Lander (Mars Polar Lander, Phoenix) sites. Deferred — the equirectangular projection still places these sites correctly, just with exaggerated polar pixel area; the polar projection is a polish-tier feature for a future minor release.

Rationale

  • The equirectangular pattern is what every NASA Mars map ships in — users recognise it instantly; lat/lon labels read directly off the axes.
  • Forcing Mars onto the orthographic dual-disc pattern would require an arbitrary "near" / "far" choice (Mars has neither) — the visual would lie.
  • Keeping /moon on its orthographic-pair view preserves the tidal-lock semantic that distinguishes the Moon from every other body.
  • Per-body projection means the projection helper functions become per-body too — modest additional code, minimal abstraction cost.

Alternatives considered

  • Force a single global projection for both bodies (option: equirectangular for both) — rejected because losing /moon's near/far discs would erase the tidal-lock visual cue.
  • Stereographic-pair for Mars — closer to /moon visually but no payoff (no semantic basis for splitting Mars into "near" and "far").
  • Mollweide equal-area for Mars — accurate but unfamiliar; deferred until users ask for it.

Consequences

Positive:

  • Each body's 2D view matches the conventions readers see in agency maps for that body.
  • Tidal-lock semantic preserved on /moon.
  • Rover traverse rendering on /mars is a one-liner per polyline (project lat/lon → pixel).

Negative:

  • Per-body projection helpers (no shared component for the 2D path — see ADR-037).
  • Polar regions on Mars are visually exaggerated; addressed by future polar-projection toggle if/when needed.

Implementation notes

  • src/routes/mars/+page.svelte — equirectangular draw2d() + lat/lon → pixel helper inline.
  • src/routes/moon/+page.svelte — orthographic-pair draw2d() (unchanged from v0.1).
  • Atmosphere-shell layer (lens-gated, ADR-055) overlays consistently in both 3D modes; the 2D modes don't carry the atmosphere overlay.

Orrery — architecture documentation · MIT · No tracking