Charlie KrugThe Build Log

← All posts

A cheaper integrator would let the probe gain energy from nothing.

Perigee is a daily gravity-slingshot puzzle. You draw one aim vector and a real n-body simulation flies the probe. The trajectory is computed every frame, never scripted.

Drag out from the probe and a dashed line curls around the nearest planet, bends past a second one, and threads a green ring on the far side of the screen. Let go, and the probe flies that exact curve. Not a similar curve. That one, because the dashed preview was never a drawing. It was the same physics, run a few seconds into the future.

Perigee is a daily gravity puzzle. Each day hands you one level: a probe, a target, and a handful of planets in the dark between them. You get exactly one input, an aim vector you drag out and release, and then a real n-body gravitational simulation takes over and flies the shot. There is no thrust, no steering, no second chance to correct mid-flight. The entire skill is reading the gravity before you commit. If you liked the arc-reading of Angry Birds or the orbital intuition of Kerbal Space Program but don't want a sandbox with no goal or a campaign that eats an evening, this is one sharp puzzle at lunch.

The trajectory is computed, not authored

Most "physics" puzzle games cheat here. The satisfying arc was drawn by a designer and replayed as an animation; the simulation is set dressing. Perigee has no authored paths. Every frame, the probe sums the gravitational pull of every planet in the level and integrates its motion forward. A slingshot behaves like a slingshot because it is one: the probe genuinely falls toward a planet, gains speed, and gets flung out the far side on a new heading. Chain two planets and you can bend a shot twice.

The interesting choice was how to do that integration. The obvious method, Euler, takes the current velocity, steps the position, updates the velocity, and repeats. It is one line of code and it is wrong in a way you can feel. Euler quietly leaks energy into or out of the system on every step, and over a multi-second flight that drift compounds. A probe on what should be a clean flyby slowly spirals out or crashes in, and the puzzle stops feeling fair because the physics is lying to you a little more each frame.

Perigee uses velocity-Verlet instead. It splits the velocity update around the position step, using the acceleration at both the start and the end of the interval, which makes it symplectic: it conserves energy over long runs instead of bleeding it. That stability is the whole reason tight slingshots are possible. A close, dramatic pass reads as sharp without blowing up numerically, so I never had to fudge the math or cap how close a probe is allowed to skim a planet.

The near-miss that would divide by zero

Real gravity follows an inverse-square law, and inverse-square has a nasty habit: as distance goes to zero, the force goes to infinity. A probe that clips a planet's center would hand the integrator a divide-by-zero and launch the numbers into nonsense. So the force uses a softened well, adding a small constant to the distance term in the denominator. The pull stays enormous during a near-miss, which is exactly the drama you want, but it never reaches the undefined spike that breaks the simulation. It is one extra term that turns a mathematical landmine into a design knob.

One decision I want to be honest about: the planets don't move. Full mutual n-body, where the planets also drift under each other's gravity, would make every level chaotic and impossible to hand-design a fair solution for. Fixing the planets keeps each daily puzzle reproducible and shareable while the probe still integrates the pull of all of them at once. That is the trade that lets "daily, everyone gets the same level" actually work. And because the whole thing ships as data in a static bundle with date-keyed level selection, "daily" needs no server at all.

Try it

Open today's level, drag out from the probe, and watch the ghost path curl before you let go. Aim a little into a planet rather than straight at the target and let its gravity do the bending. Miss and the probe resets for another read; thread the ring under par and you get a share string with just the date and your shot count, no spoilers. Keyboard works too: arrow keys aim, Enter or Space fires. The code is on GitHub if you'd rather read the integrator than trust it.

Perigee is live. Free, in your browser, no signup.

This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.

Comments

Loading comments…