How Exile works
04 · How Exile works

How Exile moves

Gravity, momentum, drag, buoyancy, wind and bouncing — the physics Exile was famous for, computed for every object in 8-bit arithmetic, one carry flag at a time.

The previous articles covered how Exile (BBC Micro, 1988) generates its world and draws it. This part is about the thing the game was actually famous for: the physics. Exile’s marketing called it the first game with realistic physics, and for once the marketing undersold the engineering — gravity, momentum, drag, buoyancy, wind, bouncing, all computed for every active object, in 8-bit arithmetic on the same 2MHz 6502 that was also generating the world and drawing the screen.

As before, the code here is reimplemented from the disassembly in TypeScript and verified bit-identical against the emulated original. The tests run one hundred thousand fuzzed object states, one full physics tick each, zero mismatches (plus twenty-five thousand more with real object update routines running). And as before there’s an interactive demo on this page — a physics sandbox on the real map, steppable one tick at a time, with overlays and a live register panel showing what the machine is doing.

The number format

⌗ 6502 source

An object’s position is a pair per axis: a tile coordinate and an 8-bit fraction. 256 fractions to a tile, which is 16 fractions per pixel horizontally and 8 vertically, because tiles are 16×32. Velocities are signed 8-bit fractions-per-tick, so the fastest anything normally moves is a quarter of a tile per tick.

Adding a velocity to a position is where the carry flag earns its keep again:

&2a6b  CLC
&2a6c  ADC &4f,X    ; fraction += velocity
&2a6e  STA &4f,X
&2a70  BCC skip
&2a72  INC &53,X    ; carry ripples into the tile coordinate

A negative velocity pre-decrements the tile coordinate and lets the carry from the fraction add correct it back if there was no underflow. Two instructions of cleverness per axis, every object, every tick.

One tick

▶ demo: drop a boulder down a real shaft ⌗ 6502 source

Each of the sixteen object slots gets the same treatment per update (update_objects, &1a2f):

It’s in that last step that gravity lives, in the most 6502 way imaginable:

&1f36  CPX #&02     ; X = 2 on the y pass: sets the carry
&1f38  LDA &40,X    ; acceleration
&1f3b  ADC &43,X    ; velocity + acceleration + CARRY

The y-axis pass compares the loop index against 2, which sets the carry flag, which the add silently includes. Gravity is one carry flag per tick with no constant, no table, basically the cheapest possible downward pull. In the drop demo you can watch it happen a tick at a time: the velocity byte counts up by exactly one per tick, courtesy of a compare instruction.

The same routine (apply_acceleration_to_velocities, &1f2e) enforces a soft speed limit with a subtlety that took a while to figure out: if the new velocity crosses ±&40 the code reloads the old velocity and keeps it — objects don’t accelerate past the limit, they freeze at whatever speed they’d already reached (terminal velocity for free), unless they were under the limit, in which case they clamp to exactly ±&40. And every sixteen ticks, every velocity loses 1 toward zero — a universal inertia that makes everything eventually settle. A problem modern games still struggle with!

Water and wind

▶ demo: splash into the flooded caverns ⌗ 6502 source

Water. Each x-range of the world has a waterline (the first article’s water sidebar). The tick computes how much of the object is below it, or inside actual water tiles, and then runs a beautifully compact buoyancy loop (&2f86): up to four iterations, each subtracting a quarter of the object’s height from the submerged amount; each surviving iteration decrements the vertical velocity once or twice depending on weight. Short objects are more buoyant; light objects are more buoyant; heavy ones sink. Every four ticks, anything in water has both velocities multiplied by ⅞ — water drag as two shifts and a subtract.

▶ demo: fight the downdraft ⌗ 6502 source

Wind. Wind isn’t a global system, and this surprised me, it’s tiles. I mean it makes sense but never occurred to me as a player. Variable-wind tiles (the windy square caverns) compute a wind vector whose angle rotates through a full circle every 64 ticks and whose magnitude mixes a random byte with the tile’s position; the two caverns below the west stone door are horizontally flipped variable-wind tiles, which the code special-cases into a constant downdraft. Flowing water tiles are the same mechanism with the current vector taken from the tile’s flip bits. All of it funnels into one routine that nudges velocity toward the wind vector, scaled by weight, clamped to a maximum acceleration of &0c.

Above ground there’s also the surface wind (apply_surface_wind, &2e5c): a weight-scaled gale centred on the crash site that doubles in strength as you climb, which is why flying high with a heavy object is such hard work.

Colliding with a function

▶ demo: grenade off the cavern walls ⌗ 6502 source

The landscape doesn’t exist, so what do you collide with? The first article’s answer: get_tile is queried for the tiles the object overlaps. But tiles aren’t solid boxes, each tile type has an 8-byte obstruction profile (one height per two-pixel column, stored in the bottom of the stack page at &0100), which is how slopes are walkable and half-tiles obstruct only half a tile.

The collision pass checks the object’s left and right edges against the profiles, counts obstructed sections along the top and bottom edges, and condenses everything into four numbers

If the object is embedded with no way out, the code restores last tick’s position and halves the velocities: squirming out of rock.

Everything here is that same five-instruction 8-bit style: the angle arithmetic is a shift-and-subtract division building five bits of arctangent (calculate_angle_from_vector), the bounce trig is a five-bit multiply (calculate_vector_from_magnitude_and_angle). Nothing is floating point and nothing is even 16-bit. Crazy.

The player is just another object

Everything above applies to the player, the grenades you throw, the creatures, the furniture. The player object adds an intent layer on top — a state machine reading the keys, aiming angle, thrust from the jetpack (dice-rolled for reliability every sixteen ticks!), energy and damage — which then simply writes accelerations for the same pipeline. Creatures do the same with AI instead of keys. That’s the deep reason Exile’s world feels coherent: there is exactly one notion of motion, and everything obeys it.

The intent layer which includes the keys, the angle state machine, walking, the dice-rolled jetpack is in a later article, where you can fly the real suit yourself. The famous consequences are already visible in the free physics, though: grenade arcs you must judge against wind, objects that float or sink by weight, the downdraft traps, and bounces that steal just enough energy that things come to rest naturally.

Rebuilding it

The port followed the same process that I used to build parts one and two — transliterate line by line first, let an emulator running the actual code bytes arbitrate, and only then rewrite into ordinary pure functions with the flags as explicit values, under the same gate. Every bug the fuzzing caught was flag threading, exactly the failure class the first article warned about (and that continued to be troublesome right to the end):

The physics demo on this page runs the validated kernel with step-by-step controls, force toggles, and a live register panel — try switching gravity or wind off mid-flight; the toggles are demo affordances around code paths the game itself never turns off. The next article puts you in the suit: the player’s intent layer, validated the same way, with the real spacesuit sprites.

Exile © 1988 Peter Irvin and Jeremy Smith · published by Superior Software
Reconstructed by James Randall from the enhanced version disassembly created by level7
Presented as a tribute to its authors.