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.
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:
- The camera lives in doubles, in Lua. Lua numbers are 64-bit, so the game tracks the camera's real position precisely and only hands the GPU positions relative to it.
- Each star system gets its own frame. The nearest star's system renders in an AU frame centred on that star, with its own near and far planes, while the rest of the galaxy renders in parsecs. The engine gained a "system frame" so both draw in the same image.
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:
- Trade. NPC ships fly real routes between stars. Scan one to see its cargo, buy a lot, and sell it at a port that pays more. Markets react, so dumping everything at one star stops paying, and prices drift on their own with production and consumption cycles.
- Contracts. Stars offer delivery jobs, paid by distance.
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
- It runs on macOS, Linux and iOS, and the phone layout got its own pass. Gamepad support is built and waiting to be tried on a PS5 controller and a Steam Deck.
- There's a story mode alongside the sandbox, with stations, local traffic, rival crews competing for the same deposits, and short skill minigames on actions like surveying and selling.
- The to-do board is "verification-starved, not feature-starved": about 30 features are built but not yet confirmed in a real play session. So the next job is one long verification pass, then performance work. There's a 22.6 ms frame-time floor to get under, and the main Lua file is 8,495 lines and needs splitting.
Next time: the verification pass, and what broke when I finally played everything in one sitting.