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:
| Projection | Pros | Cons |
|---|---|---|
| Equirectangular (cylindrical lat/lon grid) | Familiar from NASA Mars maps; lat/lon → pixel is trivial; rover traverses lay flat | Polar 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-area | Polar regions truthful | Less 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.heightRover 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
/moonon 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— equirectangulardraw2d()+ lat/lon → pixel helper inline.src/routes/moon/+page.svelte— orthographic-pairdraw2d()(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.