Terminus: shelves of the sky
Proposal section 3: how high do we build?
Hang a lamp low over a table, and it throws a small, bright circle. Raise it, and the circle grows, but the light spreads thin and arrives from far away. Every satellite constellation is a negotiation with that lamp, and before we commit a single spacecraft we owe the RFP a survey of every height the sky offers. Last time we learned we cannot steer our orbits to follow the twilight band; the fleet will fly fixed rings and hand duty from one to the next. What is still wide open is the altitude of those rings — and altitude, it turns out, is a three-way tug-of-war in which one beloved contestant never even makes it to the rope.

Three things every altitude must buy
A footprint. A satellite does not serve everyone who can technically see it. Near the horizon, its signal grazes through hills, forests, weather, and kilometers of thick atmosphere — and on this planet something worse lives at the horizon: the red star itself, sitting there forever, hissing radio noise exactly where a low satellite would appear. So we count a satellite as usable only when it stands at least 25 degrees above the horizon. That floor is the elevation mask, ε — the lowest we will let a satellite sit in the sky and still count the link — and 25 degrees is a demanding one, bought deliberately to keep the star out of the receiver. The mask carves out the footprint: the patch of ground, directly under the satellite, close enough to see it that high in the sky.
The geometry is one triangle, and it is worth drawing carefully, because every
number in this post falls out of it. Put the planet's center at O, the
satellite at S, and a user at the very edge of coverage at U. Two sides are
known before we launch anything: O → U is the planet's radius R, and O → S is
R + h. What we want is the angle at O — the planet-central angle λ swept
from the sub-satellite point out to that edge user — because the footprint is
nothing more than that angle walked along the ground.
The triangle. Two sides are known before anything is launched:
R and R + h. The angle at U is the one worth pausing
on — the user's zenith runs along O → U extended and the satellite stands
ε above the horizon, so the interior angle there is 90° + ε. Obtuse,
which is precisely why the mask is expensive.
What ε costs. Drop the mask to the horizon and the same satellite reaches 7,461 km instead of 4,931. The violet arc is ground the red star's noise floor takes from us. The tax is not evenly spread: it shrinks the 300 km shelf by 3.41× and the 20,000 km shelf by only 1.45×.
R·λ, and the longest pass is how long the satellite takes to fly
2λ of its orbit.
The third corner is where the elevation rule enters, and it is the step worth
slowing down for. Stand at U and look flat along the horizon: that sight line
is perpendicular to the vertical O → U, so a satellite sitting exactly on the
horizon would make a right angle at U. Now lift it. Every degree of elevation
swings the sight line further away from the direction back down to O, opening
that angle by exactly as much:
∠OUS = 90° + ε
Obtuse — and that one fact is the whole reason a high mask is expensive. A
triangle has only 180 degrees to spend, and the corner at U has just taken
90° + ε of them; whatever is left over must cover both the satellite's corner
and the coverage angle λ we are actually calculating. The mask is not a filter
applied to the coverage after the fact — it is taken out of the coverage before
we start. Now the triangle is determined. The law of sines relates the angle at
S — the nadir angle η, how far off straight-down the satellite must look to
see this user — to the side opposite it, and the angles have to close:
law of sines
sin η / R = sin(90° + ε) / (R + h)
sin(90° + ε) = cos ε
sin η = (R / (R + h)) · cos ε
η = arcsin( (R / (R + h)) · cos ε )
angles close
λ + η + (90° + ε) = 180°
λ = 90° − ε − η
The top block hands us η; the bottom one is waiting for it. Substitute, and then do nothing clever at all — just move the ε out of the way:
λ = 90° − ε − η
= 90° − ε − arcsin( (R / (R + h)) · cos ε )
= [ 90° − arcsin( (R / (R + h)) · cos ε ) ] − ε
Everything now sits in the bracket, and the bracket is an identity worth seeing
rather than taking on faith. Write x for the product inside, so the bracket
reads 90° − arcsin x. Now, arcsin x is by definition the angle whose sine is
x — and the two acute angles of a right triangle add to 90°, each one's sine
equal to the other's cosine. So whatever is left of the right angle,
90° − arcsin x, must be the angle whose cosine is x. And that is
arccos x:
90° − arcsin x = arccos x
Put the product back where x was, and the whole derivation collapses to a
single line:
λ = arccos( (R / (R + h)) · cos ε ) − ε
Which is the toolkit, verbatim:
/// Planet-central angle (rad) from the sub-satellite point to the edge
/// of coverage, for a satellite at `altitude` seen at `min_elevation`.
fn coverage_half_angle(body: &CentralBody, altitude: f64, min_elevation: f64) -> f64 {
let ratio = body.radius / (body.radius + altitude);
(ratio * min_elevation.cos()).acos() - min_elevation
}
/// Great-circle radius (m) of the ground area a satellite serves.
pub fn footprint_radius(body: &CentralBody, altitude: f64, min_elevation: f64) -> f64 {
body.radius * coverage_half_angle(body, altitude, min_elevation)
}
The footprint is then just arc length: the angle λ walked along the surface,
R·λ — which is also why the angle has to arrive in radians for the
multiplication to produce a distance at all.
Two limits are worth taking, because between them they bracket everything the
survey is about to say. Set ε to zero, and the mask disappears: λ collapses to
arccos(R / (R + h)), the horizon, the farthest a satellite can be seen at
all. Then let h run away to infinity. The ratio R / (R + h) goes to zero,
its arccos saturates at a right angle, and
λ → 90° − ε
That is a wall, not a trend. No altitude, no budget, no launch vehicle buys a satellite that serves more than 65 degrees of arc out from beneath it — 7,228 km of ground radius on this planet. Hold on to that number. The survey is about to walk straight up to it and stop.
Speed of light. Radio waves travel at light speed — fast, but not instant, and the RFP's latency budget (TER-REQ-003) is written in milliseconds. Every kilometer of altitude is a kilometer the signal must climb and descend, and the worst case is a user at the footprint's edge, where the slant path is longest.
How long is that path? It is the third side of the triangle we just drew —
U → S. Stand at U again and walk out along the sight line a distance d:
you rise d sin ε straight up and travel d cos ε along the ground, so the
satellite's distance from the planet's center must satisfy the Pythagorean
theorem:
(R + d sin ε)² + (d cos ε)² = (R + h)²
Which is a quadratic in d, and it is worth turning the crank rather than
quoting the root. Expand the first square and collect. The two d² terms
carry sin²ε + cos²ε between them, which is 1, so they fold into a single d²:
expand
R² + 2R sin ε · d + d² sin²ε + d² cos²ε = (R + h)²
d² + 2R sin ε · d + R² − (R + h)² = 0
That is an ordinary a·d² + b·d + c = 0, with a = 1, b = 2R sin ε, and
c = R² − (R + h)². Hand it to the quadratic formula, and the same identity
turns up a second time — R² sin²ε − R² is −R² cos²ε:
quadratic formula
d = −R sin ε ± √( R² sin²ε − R² + (R + h)² )
d = −R sin ε ± √( (R + h)² − R² cos²ε )
Two roots, and only one of them is a distance. For any satellite above the
surface the square root exceeds R sin ε, so the − branch is negative: it is
the same straight line run backwards, down through the planet and out to where
it pierces the orbital sphere on the far side. Keep the + branch, pull an R
out from under the square root, and what is left is the slant range:
d = R · [ √( ((R + h)/R)² − cos²ε ) − sin ε ]/// Slant range (m) from a ground user at exactly the minimum elevation
/// angle to the satellite — the longest, highest-loss path in the footprint.
pub fn edge_slant_range(body: &CentralBody, altitude: f64, min_elevation: f64) -> f64 {
let ratio = (body.radius + altitude) / body.radius;
body.radius * ((ratio * ratio - min_elevation.cos().powi(2)).sqrt() - min_elevation.sin())
}
Latency is then that distance over c, and the two limits behave: point the
user straight up (ε = 90°), and d collapses to h, the altitude and nothing
more; drop the mask to the horizon, and d becomes √((R+h)² − R²), the
tangent line grazing the planet.
The edge user pays for this twice over, and again it is the low shelves that suffer. At 300 km, the edge path is 648 km — 2.16 times the altitude, because the satellite is far off to the side rather than overhead. At 50,000 km, it is only 1.07 times: from that height nearly everyone in the footprint is, near enough, straight up.
Dwell. The satellites move — the fixed hoops we chose last post made sure of that — so a user's satellite eventually slides below the acceptable sky, and another must take over the conversation mid-sentence. That handover machinery gets its own posts later; for now the survey only asks how often we would invoke it: how long does one satellite stay usably overhead on its best pass?
λ answers this one too, because it is the same cone read from the other end.
The satellite clears the mask exactly while the angle between it and the user
— measured at the planet's center — is less than λ. That window is an
arc of 2λ out of the whole 2π lap:
pass fraction = 2λ / 2π = λ / π
max pass = (λ / π) · T/// Duration (s) of the best-case pass, straight through the zenith, for a
/// ground user on a slowly rotating body: the satellite is visible while it
/// crosses `2 × coverage_half_angle` of its orbit.
pub fn max_pass_duration(body: &CentralBody, altitude: f64, min_elevation: f64) -> f64 {
let lambda = coverage_half_angle(body, altitude, min_elevation);
lambda / std::f64::consts::PI * orbital_period(body, altitude)
}
A pass is a fraction of a lap
The satellite clears the mask over the arc 2λ — the same λ the
footprint used, because it is the same cone seen from the other end. Out of a full
2π lap that is the fraction λ/π, and the pass lasts that fraction of
the orbital period. At this shelf the window is 24.6% of a lap: 85.6 minutes out of 5.79 hours.
Two honest caveats. This is the best pass — one straight through the user's
zenith; a satellite that clips the edge of the footprint is overhead for far
less. And it holds the user still while the satellite sweeps, when in truth the
ground is turning too. That is a fair simplification on a planet whose day is
11.2 of ours, and the size of the cheat states itself: during one pass the user
is carried through a fraction of the 2λ window equal to the orbital period
divided by the day. At 300 km that is 1.5 hours against 11.2 days — half a
percent, noise. At 50,000 km it is 37 hours against 11.2 days, a seventh: take
an equatorial pass, where the ground runs parallel to the track, and the
table's 12.2 hours is really about 14.1 hours flying with the spin and
10.7 hours flying against it. A polar pass is carried sideways across the
track instead, and moves less than either. Every shelf below 10,000 km keeps
the correction under a percent; only the giants feel their planet turning. None
of it is in the table, which prints the idealised pass — but both ends of the
bracket are pinned in
crates/orbits/src/coverage.rs
by spin_moves_the_top_shelf_and_nothing_under_it.
What makes dwell the table's steepest column is that altitude buys it twice. Climbing from 300 km to 50,000 km stretches the period 24.6× — and widens λ another 11.7× on top of that. The product is 287×, which is how two and a half minutes becomes twelve hours.
The survey
One command prices every shelf:
cargo run -p terminus-orbits --example regime_survey
alt (km) period (h) footprint (km) edge latency (ms) max pass (min)
300 1.51 562 2.2 2.5
1200 1.82 1701 7.4 9.3
1800 2.04 2228 10.3 13.6
10000 5.79 4931 42.1 85.6
20000 11.84 5821 76.8 206.6
50000 37.00 6574 178.1 729.1
Read it as a menu of personalities. The bottom shelf, 300 km, is the sprinter: two milliseconds up, a signal so short and strong the terminals could be simple — and a footprint barely 560 km in radius that drags out from under you in two and a half minutes. Serving the whole twilight band with sprinters means swarms of them and a handover every few minutes for every terminal on the planet. The middle shelves, 1,200–1,800 km, are the honest laborers: ten milliseconds, footprints a couple of thousand kilometers across, a quarter hour per pass. The high shelves, 10,000 km and up, are the slow giants: one satellite shades nearly a third of the planet and lingers for hours — an afternoon-long conversation instead of a corridor of quick introductions — but every word now pays a 40-to-180-millisecond toll, and a signal spread over that distance demands bigger antennas, more power, more satellite.
That last clause deserves a number, and the table has not given it one. A
radio signal spreads like paint on an expanding balloon: at distance d, the
power that left the antenna is smeared over a sphere of area 4πd², so the
flux arriving on each square meter falls as 1/d². Twice the distance, a
quarter the signal. This is geometry and nothing else — no band has been
chosen, no antenna designed, and neither will change this term when they are.
Ratios this size want a unit built for multiplying. A decibel (dB) is ten
times the base-ten logarithm of a power ratio: 10 dB is a factor of ten, 20 dB
a factor of a hundred, and 3 dB — near enough — a factor of two. Radio links
are chains of multiplications, and logarithms turn the chain into a sum, which
is why every link budget is written in this unit. One convention to watch:
when the thing being compared is a distance, it enters the power ratio
squared, so its term reads 20·log₁₀ rather than ten. The whole spreading
bill is then one line, referenced to the best case any shelf offers — the
bottom shelf, seen straight overhead:
spread = 20 · log₁₀( d / d_ref ) d_ref = 300 km/// Inverse-square spreading loss in dB of a link over `distance` (m)
/// relative to one over `reference` (m): 20·log₁₀(d/d_ref).
pub fn spreading_db(distance: f64, reference: f64) -> f64 {
20.0 * (distance / reference).log10()
}
Feed it the edge slant range we solved above, and every shelf's worst user is priced:
Signal spread at the footprint edge (inverse square, vs 300 km overhead):
alt (km) slant (km) spread (dB) overhead (dB)
300 648 6.7 0.0
1200 2204 17.3 12.0
1800 3089 20.3 15.6
10000 12626 32.5 30.5
20000 23039 37.7 36.5
50000 53382 45.0 44.4
every 6 dB is a factor of four in flux; frequency has not entered yet.
Forty-five decibels is a factor of thirty thousand: the giant's edge user catches one part in 30,000 of what the sprinter's overhead user does, from spreading alone, before the atmosphere or the antenna has said a word. This is not a fourth currency. It is the latency column charged a second time, because both are the same slant range — and the two columns on the right say how evenly each shelf charges it: the sprinter's edge user is 6.7 dB worse off than their overhead neighbor, the giant's only 0.6 dB, because from 50,000 km nearly everyone is straight up. When this proposal chooses its bands, the unit will carry the whole link budget; for now it carries one column.
Watch the whole survey run. Pick a shelf, and one satellite crosses the usable sky of a fixed town — the wedge of elevation at least 25 degrees, the same cone read from the ground's end — with the town lit while the satellite is inside it, the pass clock counting real minutes, and the downlink fading as the slant range grows. Every shelf laps in the same twelve seconds on screen, so the fraction of the lap the town is served for is what the eye compares:
Every shelf laps in the same 12 seconds on screen — the real lap, the time compression and the drawing scale are printed on the face — so the share of the circle the town is lit for is the honest comparison. Angles, distances and the pass clock are computed live from the same triangle the prose derives. The downlink's brightness follows the signal readout: inverse-square spreading of the live slant range, referenced to the bottom shelf seen straight overhead, and compressed only so the faintest case stays visible.
regime_survey, tags terminus-post-4, -19
and -4c.
Plot the two against altitude, and the asymmetry is the whole story. The footprint runs into the wall we derived above; the latency has no wall to run into.
Footprint radius — saturates
Every kilometer of altitude buys less ground than the one below it. The
arc a satellite can serve is capped at R·(90° − ε) whatever you spend, and the
50,000 km shelf already holds 91% of that. There is no altitude where the lamp gets
meaningfully wider.
Edge latency — does not
The bill has no such courtesy. Slant range grows roughly with altitude forever, so the same climb that stops widening the footprint keeps lengthening every word spoken through it — 82× the latency between the two ends of the survey, for 12× the footprint.
That is the lamp, and the survey has now priced it. No shelf wins all three currencies. But an engineer raised under a fast yellow sun knows there is supposed to be one more shelf — the magic one.
The fixed satellite that isn't there
Every spacefaring civilization discovers the same trick early. There is always one altitude where a satellite's orbital period exactly matches the planet's rotation, so the satellite circles in lockstep with the ground and hangs, from the ground's point of view, at a fixed point in the sky forever. Around Earth, that shelf sits at about 36,000 km, and whole industries bolted their dishes to walls, aimed once, and never moved them again. A fixed point in the sky is infrastructure's dearest dream — no tracking, no handover, ever.
Notice what that shelf really is. It is the one place in the sky where the two frames from the last section stop disagreeing — where the hoop, anchored to the distant stars, and the ground, anchored to its own rotation, happen to ask for the same thing. A satellite there is not fighting the planet's turn; it is matching it, and matching it for free. Every degree of alignment we priced at 3.9 km/s a day comes included.
That shelf has an address, and finding it takes one idea: what fixes an orbit's period.
A circular orbit is a standing compromise. Gravity pulls the satellite straight at the planet's center, and the satellite is traveling at right angles to that pull. Because the pull is always sideways to the motion, it never speeds the satellite up and never slows it down — it only bends the path. Get the speed exactly right, and the bend closes into a circle instead of a crash or an escape.
How hard gravity pulls is set by a single number: the planet's mass rolled
together with Newton's constant, μ = GM, the standard gravitational
parameter. That is what body.mu means everywhere in this toolkit — for an
Earth-mass planet, 3.986 × 10¹⁴ m³/s². What gravity supplies and what a
circle demands then have to agree:
gravity supplies μ / r²
a circle demands v² / r
set equal v = √( μ / r )
Each radius permits exactly one speed, and higher orbits are slower — not intuition, arithmetic. The period is one lap at that speed:
T = 2πr / v = 2π √( r³ / μ )
That is Kepler's third law, T² ∝ r³, and it is the whole of the toolkit's
period function:
/// Circular orbital period at the given altitude, s.
pub fn orbital_period(body: &CentralBody, altitude: f64) -> f64 {
let r = body.radius + altitude;
2.0 * PI * (r.powi(3) / body.mu).sqrt()
}
The stationary shelf is that same equation run backward. We are handed T
— the planet's rotation period, 11.2 days — and we want the radius that
produces it:
r_sync = ∛( μ T_rot² / 4π² )/// Radius (from body center) of the orbit whose period equals the
/// body's rotation period — the stationary orbit, m.
pub fn synchronous_radius(body: &CentralBody) -> f64 {
let t = body.rotation_period;
(body.mu * t * t / (4.0 * PI * PI)).cbrt()
}
One radius, one speed
Gravity pulls straight at the center while the satellite moves at right angles to it, so it never speeds the satellite up or slows it down — it only bends the path. Setting the pull equal to what a circle demands leaves exactly one speed per radius, and the lap time follows.
Kepler, and where 11.2 days lands
On log axes T² ∝ r³ is a straight line of slope 3/2. Come across at
the planet's rotation period, drop down where you meet the line, and you have the stationary shelf.
The whole survey is the huddle at bottom left; the shelf is off past the far right.
r^1.5, so radius grows only as
T^(2/3) — this planet's day is 11.23× Earth's, which buys 5.01× the radius. Enough to
put the shelf somewhere it cannot survive.
Now watch what that cube root does to a slow planet. Radius grows only as
T^(2/3), so a much longer day buys a surprisingly modest amount of extra
distance — modest, at least, until you remember how far out the shelf
already starts. Earth turns once in 23.93 hours, and its shelf sits 42,164 km
from the center, which puts it at the ~36,000 km overhead quoted above. This
planet's day is 11.23 times longer, and 11.23 to the two-thirds is 5.01:
Stationary shelf, scaled from Earth's:
Earth synchronous radius: 42164 km (altitude 35793 km)
this planet spins 11.23x slower
radius grows as T^(2/3): 5.01x => 211453 km
So the shelf lies at 211,453 km from the planet's center — 205,000 km overhead. The survey already frowns: from that height, the edge user's signal takes 0.7 seconds one way, which by itself wrecks any conversation held to the RFP's latency budget. But the real objection is more fundamental. It is not that this orbit would be slow. It is that this orbit does not exist.
A satellite here is pulled by two bodies, not one: the planet it orbits, and the star that planet orbits. Near the planet, the planet's gravity rules, and orbits behave. Far enough out, the star's pull dominates, and a "satellite of the planet" quietly becomes a satellite of the star. The boundary bubble is called the Hill sphere, and its size follows from a custody arithmetic — how heavy the planet is, how heavy the star is, and above all how close they live to each other:
r_Hill ≈ a · ∛( m_planet / 3 M_star )
Three things live in that expression. The distance to the star, a, enters
plainly: move a planet twice as close, and its authority halves. The masses
enter only as a ratio — and that ratio is then buried under a cube root,
which flattens it almost out of existence. This planet is one part in 40,619 of
its star; the cube root turns that crushing deficit into roughly one part in
thirty-four, and the 3 underneath brings it to about one part in fifty. Mass
barely matters here. Distance decides.
/// Hill radius (m): the region where the planet's gravity dominates
/// the star's.
pub fn hill_radius(body: &CentralBody, star_mu: f64, orbital_distance: f64) -> f64 {
orbital_distance * (body.mu / (3.0 * star_mu)).cbrt()
}
/// Conservative outer limit for long-lived prograde satellite orbits:
/// roughly half the Hill radius.
pub fn prograde_stability_limit(body: &CentralBody, star_mu: f64, orbital_distance: f64) -> f64 {
0.5 * hill_radius(body, star_mu, orbital_distance)
}
That second function is caution rather than physics. Orbits near the rim of the Hill sphere are not forbidden so much as unreliable: the star keeps tugging, the orbit keeps stretching, and eventually it wanders off. How much margin you need depends, oddly enough, on which way you go around. Numerical work puts the survival radius near nine-tenths of the Hill radius for retrograde orbits — satellites circling against the planet's motion around the star — but only about half for prograde ones, which is why the function carries that word in its name. Half is the generous reading of a generous case, and we are about to fail the test anyway.
Proxima b is Earth's mass, but it huddles just 0.0485 AU from its 0.122-solar-mass star — twenty times closer than Earth to the Sun. Its bubble of authority is correspondingly small:
Stationary orbit check:
synchronous radius: 211453 km (altitude 205082 km)
planet / star mass ratio: 2.462e-5 (1 : 40619)
Hill radius: 146347 km (2.02% of the way to the star)
prograde stability limit: 73173 km
verdict: synchronous radius is 2.9x the stability limit —
no stationary orbit exists around this planet.
Whose satellite is it?
Near the planet, the planet wins and orbits behave. Far enough out the star wins,
and a "satellite of the planet" quietly becomes a satellite of the star. The boundary is the Hill
sphere, and the Lagrange points L1 and L2 sit on its rim, on the line to the star.
To scale, and the verdict
Everything the survey priced fits in the first quarter of this bar. The shelf we actually want sits past the stability limit, past the Hill radius, out where the planet has no say at all — not slow, not expensive, simply not available.
a·∛(m/3M). That cube root barely notices mass — this planet outweighs
nothing, at 1 part in 40,619 of its star — so the term that decides everything is a, and
this world lives twenty times closer to its star than Earth does.
The lockstep shelf lies nearly three times beyond the distance where the planet can keep a satellite at all. Park anything there, and the red star takes it — another small cold thing lost to the tide. Civilizations under yellow suns get to hold a satellite motionless over the equator and build cathedrals of fixed antennas beneath them. This sky refuses. Whatever we build for this world will move across its heavens, and every antenna, protocol, and session will be designed around that motion. We record the exclusion formally as ADR-0002.
One consolation: the same custody arithmetic hands us two fixed addresses
for free. Where the star's pull and the planet's pull strike a truce, the
star-planet line carries two of the five Lagrange points — L1 between
the planet and the star, L2 directly behind the planet — and they sit on
the rim of the bubble, about 146,000 km out. That is no coincidence: the
Hill radius is essentially defined as the distance to those two points,
which is why the number matches. Both are unstable equilibria, so a spacecraft
at either one needs an occasional nudge to stay put — quasi-fixed rather
than fixed. And nothing there can serve a conversation at 0.5 seconds one
way. But remember those addresses; they will matter when this proposal asks
where to keep things that are precious and patient.
What survives the survey
The survey leaves three findings. The low shelves are the only ones whose latency fits a conversation, so they will carry the users — at the price of small footprints, dwell measured in minutes, and therefore a large number of satellites. The high shelves reverse every term: footprints close to the 7,228 km ceiling and dwell measured in hours, bought at latencies no conversation can use, which makes them wrong for access and worth revisiting when this proposal has to place something that is not a conversation. And the synchronous shelf, the one a faster-spinning planet would give away for free, sits 2.9 times beyond the distance this planet can hold a satellite at all: not expensive, unavailable.
Next time we place hardware: how many rings, how many satellites on each, and what it takes to keep every town in the twilight band under at least one usable satellite at every moment. The count comes out higher than the arithmetic in this post suggests, and the reasons why are what turn the problem from arithmetic into simulation.