Orion Devlog #1: A Real Galaxy, to Scale, on My Own Engine

Orion is the game I've been building since the end of June. The pitch is simple: fly a real galaxy, make a living moving cargo between real star systems, and pour the profit into a better ship. Every star is a real star. Every planet that has been discovered is where astronomers say it is. Everything else is generated to fill the gaps.

It runs on Lumos, my open-source C++ engine, and the game itself is written in Lua. This is the first of what I hope will be regular devlogs: where it came from, the problems that ate the most time, and where it is now.

Orion's star map with Sirius selected, showing its distance, magnitude, spectral type and constellation, a search for Tau, and the settings panel
Sirius, selected. Canis Major's lines run through it, and the search on the right is finding every star with "Tau" in its name.

A real sky first

The first day was all data. The HYG catalogue has about 120,000 stars with positions, magnitudes and colours, so the engine got a CSV parser and a PointCloud renderer: one instanced, camera-facing billboard per star. Each star's colour comes from its B-V index converted to a blackbody colour, and its size and brightness from its magnitude.

Constellations came next, from Stellarium's line data. Because they're drawn as real 3D segments between real stars, they hold their shape from Earth and warp as you fly away from Sol, which is one of my favourite things in the game.

Then the NASA Exoplanet Archive: 6,298 confirmed planets, matched to HYG stars by their HIP and HD catalogue numbers. Select a star with known planets and you see their real orbits. Select one without, and you get a procedural system.

The scale problem

Space is big in a way that breaks a renderer. Stars are parsecs apart, planets orbit in AU, and a planet's radius is a few thousand kilometres. That's about nine orders of magnitude between the furthest star and the smallest moon, and 32-bit floats can't hold all of it. At 7.68 parsecs from the origin, float positions are off by about 0.2 AU, so a planet's orbit is noise.

The fix has two parts:

That made everything true to scale, which immediately caused the next problem: the planets disappeared. They weren't missing. They were rendering correctly at their real size, which is far under a pixel. Even Earth from 0.01 AU is about 7 pixels across. The fix is a minimum on-screen size in the planet shader. A distant planet draws as a small, tinted disc, then turns into the real ray-traced sphere as you get close.

One more gotcha from that week: Sol isn't at the origin. HYG puts the Sun about 0.000005 parsecs from (0, 0, 0). That sounds like nothing, until you notice it's about 1.03 AU and everything that assumed "Sol is at zero" was parked an Earth orbit away from where it should be.

The bug that froze the camera

The first time NPC ships flew, the camera froze and the whole scene jittered. The cause is a good lesson about entity component systems.

Lumos uses EnTT, which stores each component type in a tightly packed pool. The Lua bindings handed out raw pointers into those pools. The camera script cached a pointer to its Transform at startup, then 14 ships were spawned, the Transform pool grew and reallocated, and that cached pointer now pointed at freed memory. Every camera update after that wrote into memory that no longer belonged to it.

The fix was a rule rather than a patch: keep entity handles, not component pointers, and look components up again each frame. They're stable within a frame, not across them.

Making it a game

A pretty star map isn't a game, so most of July went on the loop. You start with 500 credits and a 100-tonne hold, and there are two ways to earn:

Credits go into the ship: a bigger hold, cheaper fuel, a faster drive, a better scanner. Then the mothership grew into a small operation. Planets carry real resources worked out from their radius, mass and temperature. You can mine them, refine raw ore into denser goods, find blueprints in NPC cargo, and send out drones to mine for you.

Travel is a real trip, not a teleport. Your ship flies there at your drive's speed, burning fuel and sim time, so where you go is an actual decision. The first ending is an economic milestone, and the sandbox carries on after it.

A game that tests itself

The biggest change to how I work was making the game drive itself. Lumos can now run a project headless with a scripted test: Lua coroutines that click, fly, wait and assert, then save screenshots. That meant I could iterate on visuals and check whole gameplay paths without sitting in front of it.

It also taught me to distrust my eyes. More than once I chased a bug that was really the image viewer auto-brightening a dark screenshot. Now I sample raw pixel values before believing anything. A planet label that "looked fine" turned out to be cyan text on a cyan fill, and the numbers said so.

Where it is now

Next time: the verification pass, and what broke when I finally played everything in one sitting.