← All transmissions
Deep Dive

The Ship Is a Coordinate System

How moving reference frames let a player board a ship, fly from a planet into orbit, and step into EVA without a teleport or a special case.

A cutaway spacecraft carrying its local coordinate frame from a planet into orbit
Visual thesis / featured Planet, ship, and EVA—three local frames describing one continuous journey.

A character stands on a planet, walks up a ramp, takes the pilot’s seat, and flies into orbit. They leave the seat while the ship is moving, walk through the cabin, and step out of the airlock into EVA.

That sounds like one continuous sequence. In an engine, it is an ambush.

The planet has radial gravity. The cabin has artificial gravity. The ship is accelerating and rotating. Planetary positions need double precision, but a character controller and physics solver want small, local floating-point coordinates. Boarding changes what the character is moving relative to. EVA must inherit the velocity of the airlock rather than begin at zero. Meanwhile, every one of those transitions has to look like nothing happened.

It is tempting to solve each symptom separately: a moving-platform mode for the character, a ship-local transform, a gravity-volume arbiter, an origin rebasing system, an EVA launch correction, and another handoff when the ship leaves the planet. Each fix creates a new owner for some part of the character’s state. The seams between them become the bugs.

AsteronEngine takes a different position:

A ship is not merely an object moving through the world. It is a moving coordinate system containing a world of its own.

That one choice turns the whole journey into movement through a graph of reference frames.

One world, many local truths

Every entity belongs to exactly one frame. Its transform is expressed locally in that frame; the engine does not store a second, canonical global position.

Reference-frame hierarchy from the star system through planet and ship to its occupants

The hierarchy is allowed to change. A landed ship’s frame is parented to a local surface frame. As the hull climbs, it moves first to the planet frame and then to the star-system root. The character and cargo inside remain in the ship frame throughout. Their local coordinates do not change because, from the cabin’s point of view, nothing happened.

The ship therefore wears two hats:

  • The hull is an entity in a parent frame. It can fly, land, collide, and be targeted.
  • The interior is a frame driven by that hull. It contains the deck, seats, cargo, and occupants.

The hull is the one writer of the ship frame’s pose. The frame does not independently integrate itself and hope to remain synchronized. One physical fact has one owner.

Transforming through the frame graph

Suppose frame (F) has position (p_F) and orientation (R_F) in its parent. A point with local position (p) moves into the parent as

p=pF+RFp.p' = p_F + R_F p.

Orientation composes in the same direction:

R=RFR.R' = R_F R.

Converting between arbitrary frames means walking upward to their lowest common ancestor and then downward to the target. Every composition happens in 64-bit floating point. Narrowing to 32-bit happens only after a position is relative to a nearby camera or local physics frame.

A state is composed upward to the common ancestor and inverted downward into its new frame

Position is the easy half. Velocity is where moving-frame implementations often reveal the cheat.

For a local point at (r = R_F p), its velocity in the parent is

v=vF+RFv+ωF×r.v' = v_F + R_F v + \omega_F \times r.

The three terms are:

  1. the frame’s translational velocity, (v_F);
  2. the entity’s local velocity, rotated into the parent, (R_F v);
  3. the tangential velocity caused by the frame’s rotation, ωF×r\omega_F \times r.

That last term is why an astronaut leaving a rotating station does not mysteriously lose the velocity of the airlock. It is also why walking in a rotating habitat and stepping into EVA use the same conversion—not two gameplay-specific fixes.

Angular velocity follows the same ownership rule:

ω=ωF+RFω.\omega' = \omega_F + R_F \omega.

A frame transition converts position, orientation, linear velocity, and angular velocity together as one kinematic state. The source and destination are merely two descriptions of the same physical state. The transition is continuous by construction.

Boarding is membership, not attachment

The engine has one system that decides frame membership, once per simulation tick. Its ordered rule is deliberately small:

  1. Choose the deepest joinable frame volume containing the entity.
  2. Otherwise choose the planet-surface frame beneath it.
  3. Otherwise remain in, or fall back to, the parent frame.

Frame membership transitions between a planet surface, ship interior, seat, and EVA

Seats do not create another coordinate system. Sitting changes control and pose ownership, but the occupant remains in the ship frame. Similarly, “EVA” is not a special spatial mode. It is what happens when an entity leaves the last containing frame volume and the membership resolver reparents it to a gravity-free parent.

This distinction matters. An attachment system says, “copy the ship’s motion onto the character.” A membership system says, “the character’s coordinates already mean ship-local space.” There is no motion to copy and no per-frame correction to accumulate.

Small physics worlds instead of one enormous one

Planetary space and contact physics want contradictory numeric scales. A 32-bit float near six million metres has spacing on the order of half a metre—larger than a character controller’s contact tolerance. Making only a few engine types double precision does not repair a solver whose broad phase, manifolds, and constraints still operate on floats.

AsteronEngine instead creates a physics world per active frame. Jolt continues to work in ordinary 32-bit local coordinates:

  • a ship interior spans tens of metres;
  • a planet surface frame has a measured 4 km extent;
  • the star-system graph and all frame composition use doubles.

The relevant precision is approximately

ULP(x)2log2x23\operatorname{ULP}(x) \approx 2^{\lfloor \log_2 |x| \rfloor - 23}

for a normal 32-bit floating-point value (x). At a local coordinate of 40 m, the spacing is only a few micrometres. At 4 km it is a fraction of a millimetre. At planetary radius it becomes roughly half a metre. The answer is not to make the local solver understand a solar system; it is to stop asking it to.

Logarithmic comparison of 32-bit floating-point spacing at ship, surface-frame, and planetary scales

Inside a flying ship, the ship’s journey through the star system is invisible to its local physics world. A crate on the deck remains near ((3, 1, 2)), not near a multi-million-metre world coordinate. The solver sees the problem it is good at: a crate, a floor, and gravity in a small room.

This is not origin rebasing. No global origin jumps, and no system has to repair cached world-space state afterward. Each frame has always been local.

Why the ship itself is kinematic

There is a trap hiding here. If the ship interior gets a bounded physics world but the hull is a dynamic body in the planetary or root frame, the hull still crosses the very precision range the architecture was meant to avoid.

AsteronEngine therefore integrates ship flight in 64-bit precision. The hull is kinematic in every frame it occupies. It still collides, carries characters, and pushes objects, but the general-purpose solver does not own its pose.

For linear motion, the fixed-tick integration is the familiar semi-implicit form:

vt+1=vt+atΔt,pt+1=pt+vt+1Δt.v_{t+1} = v_t + a_t \Delta t, \qquad p_{t+1} = p_t + v_{t+1} \Delta t.

Landing is resolved from gear probes against the one terrain-height function, rather than from a hard-coded hull altitude or a planet-sized collision body. This gives the ship system one coherent job: integrate flight, resolve gear contact, and write the hull state. The driven interior frame then copies that state exactly once.

The tradeoff is explicit. This is not yet a solution for two large dynamic ships grinding together and exchanging physically solved impulses. If that becomes a required experience, it needs a bounded shared frame or an intentional handoff—not a quiet return to planet-scale float physics.

Proving the whole journey, not the pieces

Unit tests establish individual properties: frame conversion is invertible, boarding chooses the deepest volume, leaving a hull produces EVA, landing gear finds terrain, and a ship changing parent carries its interior continuously.

The more important test is a deterministic acceptance run called frames_in_anger. It executes the actual sequence at a fixed 60 Hz tick:

The frames-in-anger acceptance journey from planet surface through orbit and EVA and back

The test records the hull and character in the root frame every tick, even while their stored state belongs to changing local frames. It requires all journey beats to occur and checks three kinds of failure:

  • no frame crossing produces a teleport;
  • no frame crossing creates an artificial velocity discontinuity;
  • ordinary acceleration, contact, and landing remain bounded separately from reparenting.

That separation is important. A character can legitimately stop when sitting or accelerate when landing. The claim is narrower and stronger: changing the coordinate system used to describe an entity does not change the entity.

The payoff extends beyond movement

Once the frame is the common spatial language, later systems inherit useful properties.

Rendering converts each visible frame relative to the camera in doubles, then submits nearby floats. The renderer does not need to know whether it is drawing a planet, a station, or a ship interior.

Networking can replicate (frame, local position, local orientation, local velocity) instead of enormous absolute coordinates. A crate bolted inside a ship travelling at 800 m/s is stationary in the frame that matters. Its encoded state does not change merely because the ship moves.

Interest management can partition by frame before partitioning spatially. A player inside one ship usually does not need the contents of another ship’s cabin, even when the hulls are close in root space.

Gravity becomes a property of the frame: radial on a planet, uniform on a deck, absent in EVA. The character motor reads gravity; it does not arbitrate among competing gravity providers.

These are not bonus abstractions added for elegance. They are consequences of giving every system the same answer to a foundational question: relative to what?

A coordinate system you can walk into

The useful mental shift is simple:

Do not move a miniature world through a larger world and repeatedly compensate everything inside it. Let the miniature world be a coordinate system, and move that coordinate system through the graph.

Then boarding is a frame transition. A seat is control ownership inside a frame. Takeoff is the ship frame changing parent as its hull crosses spatial regimes. EVA is leaving a joinable volume. Local physics stays local. Rendering and networking carry the same frame identity forward.

The hard engineering does not disappear. The transform math has to include motion, membership needs one unambiguous owner, and the end-to-end journey has to be tested as a journey. But those difficulties live in a small number of explicit mechanisms instead of leaking into every gameplay system.

The player sees a person walk onto a ship, fly to orbit, and step outside.

The engine sees the same state, continuously re-expressed.