// BLOG

World Down Stops Working on a Planet

World Down Stops Working on a Planet

Most character code gets to assume that down means negative Z. Ashes of the Unbound lost that luxury as soon as a character needed to stand on a spherical world.

On a planet or moon, down points toward the local body’s centre. Walk far enough and both the gravity vector and the character’s up vector change. Move into space and the strongest gravity source may change entirely. Camera control, animation, ground speed and hair all have to follow the same local frame or the character begins revealing where world-space assumptions are hiding.

Building the local gravity frame

Applying an acceleration toward a planet is the easy part. The hard part is making every dependent system agree on what that direction means.

Character orientation needs a stable basis built from local up and the intended facing direction. Movement input has to be projected onto the tangent plane. A floor test needs to trace along local down. Camera yaw should rotate around local up, not world Z. Pitch needs a consistent tangent axis. None of those operations can quietly fall back to the level’s original axes.

The initial implementation therefore treated gravity as the source of a local coordinate frame, not merely another vector added to velocity.

Gravity-relative camera controls

A wrong movement transform makes the character drift. A wrong camera transform makes the whole planet roll.

The gravity-relative camera work had to preserve player intent while the reference frame rotated underneath it. Looking left should remain left. Walking over a curved surface should not slowly accumulate roll. Transitions need to avoid sudden flips when a forward vector approaches a degenerate alignment with local up.

This is one of those systems where correct-looking code can still feel terrible. The frame has to be mathematically coherent and temporally stable.

The useful debugging approach was to expose the basis itself: local up, tangent directions, the derived control frame and the velocity after conversion. Once those vectors are visible, a camera roll problem stops being “something feels wrong” and becomes a specific disagreement between frames.

Converting velocity for animation

Animation blueprints commonly calculate ground speed from a world-space horizontal velocity. That works until “horizontal” is not the XY plane.

For a gravity-relative character, locomotion needs velocity expressed in the gravity-local tangent frame. The same is true for direction, slope response and transitions which distinguish falling from grounded motion. A character moving around a moon may have a large world-Z component while running perfectly level relative to the surface.

Hair and other secondary motion expose the same category of assumption. If the character and camera agree on local down but hair simulation still believes in world down, the result advertises the bug from across the level.

Per-client floating origins

Planetary scale introduces another problem before any interplanetary travel: floating-point precision.

Unreal actors still need a local world in which centimetres behave like centimetres. Celestial bodies need a much larger durable coordinate system. The project therefore began moving toward per-client floating origins. Each client can keep its nearby world numerically comfortable while server-authoritative state remains expressible in a stable larger frame.

That creates a rule which later touches almost everything: a vector is incomplete unless its frame is known. A position relative to the current client origin is not a durable save position. A world-space velocity from one rebased view is not directly meaningful in another. Visual effects, replication and restoration all need an explicit conversion point.

Debugging the horizon

The earliest high-resolution captures also preserved a lighting symptom at the horizon. In the broken frame, the transition between world and sky lost the intended lighting contribution and produced a conspicuous dark boundary.

In-engine capture showing the broken dark horizon before the lighting correction.

The corrected baseline restored the transition:

In-engine capture after the horizon lighting correction.

That issue would later lead into much deeper work on celestial illumination and reflection captures. At this stage, it was a useful reminder that planetary movement and planetary rendering are not independent. The surface normal, receiver body, atmosphere and local origin all describe the same world from different systems.

What was working by 25 April

By 25 April, the project had gravity-relative movement and camera foundations, local-frame animation work and the beginning of a per-client origin strategy. It was enough to stand on a curved world without pretending the level was flat.

It was not enough to represent a whole star system. For that, the project needed coordinate namespaces larger than a single Unreal world—and a way to keep each body stable while it moved. That became Building a Multi-World Coordinate System in Unreal Engine.