I have created pages for Atomic Space Navy and Tools accessible through the navbar at the top of the blog. It has been suggested that I should talk a little bit about how these programs work, and I will do so while telling you a little bit about how it got to be how it currently is. Atomic Space Navy was an idea that for a long time I couldn’t think of a good way to bring into being. It begin to actually exist when I decided to try an idea that seemed insufficiently clever but turned out to be good enough to start exploring. I will describe in order the goals, the perceived problems, and the solutions as currently implemented.
The design goals are various, but the relevant ones to this conversation are exploration of space warfare on realistic scales in terms of distance, motion, and speed. Most media depictions of space combat are as close to rational predictions as a two man knife fight in a phone booth is to actual modern infantry firefights. Probably a lot further than that, actually. I needed physics that’d handle space at least trillions of meters wide, inhabited by objects as small as a few dozens of meters, moving at speeds of thousands of meters per second or more. Collision detection and actual combat in this environment is something I still haven’t tackled.
More important to the elements I have tackled and thus can talk about in some detail, I want a game where the gravity and motion of the planets forms the terrain rather than taking the typical space game route of constructing unrealistically densely packed spaces filled with dust, rocks and ruins to hid behind and fly around. This poses problems primarily of interface and player experience. The skills people typically need to move around in a game are relatively simple, and related to human experience. In space, that all goes out the window. Everything is in constant motion, and you have to either plan far in advance to get where you want to go, or have ships so powerful that it trivializes the ‘terrain’. The core idea to crack this problem is to allow the player to plan their moves far in advance and have the game show them as quickly as possible what course those moves will put them on.
This predictive plot idea was the core of Atomic Space Navy since its inception, but I was stuck looking for a clever way to make it work. Kerbal Space Program uses a ‘patched conic’ simplification that has seen some use in actual space missions, but I rejected the idea for two reasons. First, it doesn’t allow for certain types of motion that I feel are important to an accurate depiction of a space-based civilization. Second, I had a hard time figuring out how you’d actually implement it. Point two is somewhat less true today, and I may find some use for patched conics as an optimization to give feedback faster, but point one remains true and so they’ll still only be a rough initial pass before other solutions fill in the details.
The solution that seemed too simple but turned out to be what I used is as follows: Every simulation step simply calculate the forces on every object and calculate their new positions based on the simple physical laws. To allow for the planning and predictive plot mechanics, the simulation stores a record of every objects state at each calculated time, and can calculate the forces on an object at a given position and time.
This solution has problems. First off, I’m doing the math in essentially the simplest way possible. I’ve learned or internalized a lot of things that were still fairly greek to me when I started, and there’s a lot still to go. Apparently I’m doing numerical integration with the backwards Euler method, but there’s always the chance that I’m actually doing something a bit different and don’t know it.
A consequence of this approach is that if the time step between saved states is allowed to be too large the simulation starts to become unstable and inaccurate. Lowering the timestep is in some cases sensible, but when dealing with even a small portion of the solar system it can be problematic. Some details to explain why. The vast majority of motion in the solar system is dependent on the gravity of the sun, and most of the rest is owed to the gravity and motion of Jupiter. Any simulation of the solar system essentially needs Jupiter if it’s going to have any pretension towards accuracy. Jupiter’s orbit around the sun takes almost twelve years to complete, and the rate at which you need to calculate an object’s motion to keep reasonable accuracy is roughly related to it’s orbital period. At a rough estimate, I’d say my simulation needs to calculate Jupiter’s position once every 19 days for accuracy.
One of the most interesting and fast moving locations in the solar system is in fact Jupiter’s moon system, in particular the four largest, the Galilean moons. They’re serving as the stage for a lot of my practice with orbital maneuvers. Inner-most of these four moons is Io, which orbits Jupiter every 42 hours. It takes less than two days for Io to complete the same kind of motion that takes our moon a month, or our planet a year. It’s moving fast. To get a good simulation of it’s motion I estimate that my program needs to make a calculation of it’s orbit every 1.4 seconds.
The problem comes from when you have both of these orbits in the same simulation. If you run the simulation fast enough to keep Io in its proper orbit, then you are doing the calculation of Jupiter’s motion roughly a million times more often than you need to, and since I’m storing all the states for reference during player maneuver planning, you’re taking up a million times more memory than needed. This is, obviously, somewhat undesirable.
The solution I hit on is in itself a bit of a mixed bag, in that it doesn’t entirely wipe out the processing load that Io adds to a simulation, but it does avoid the crippling memory overhead it creates. Every object in the simulation determines its own timestep rate, determined by the magnitude of the acceleration it is under. The stored state includes velocity and acceleration at each simulated timestep, and when an object is asked for a position at a timestep it doesn’t have exact data for it can refer to the state at the simulation state immediately proceeding that time and extrapolate what its state should be. This obviously has some inaccuracy, but in testing so far seems to be sufficient.
This required some effort to make advancing the simulation work properly. To be clear on why, I’ll re-summarize what happens when an object simulates a step.
- The object asks the ‘universe’ object what the force of gravity is at its location at the start of the simulation step.
- The universe asks every object but the one being simulated what their mass is and where they are at the instant in question
- The universe sums up all the forces on the location in question and returns a force.
- The object computes its acceleration under that force and compares it to the allowable acceleration per simulation step, and adjusts its timestep to fit within the allowed bounds
- The object calculates its new velocity based on its acceleration, calculates its new position based on its velocity, saves all that info to a new state and sticks that on its list of states.
The problem is that during the first steps it can only ask the question of objects that already have calculated their data for the domain in which it is asking. The consequence is that unless the time states of two or more objects are in perfect sync the simulation can only have the information it needs to calculate the next state on any one object at a time. The main simulation loop must be built to take this into account, and unfortunately this also means that this is a part of the game that probably can’t be multi-threaded.
And that’s how the gravitational simulation of Atomic Space Navy works in a nutshell. There’s some more to be said about how maneuvering is handled, but that all builds on this.