Charge down here, climb up there. Two games that would each be worse on their own, joined by the
only thing that matters ā a currency.
ClimbBall is a vertical climb sitting on top of a pinball table. You bounce upward through platforms, hazards and
spiders as far as your lift will take you; when you fall, you land on the table. Down there you flip, charge, and
earn the altitude that throws you back up. Lose the ball with nothing banked and it costs you.
The idea is not mine. It is Bally Midway's, from 1982.
The machine that was two machines
Baby Pac-Man put a thirteen-inch video screen above a real, shortened pinball table in one cabinet, and it
did one clever thing that everybody remembers and almost nobody has copied since: the maze had no energizers
in it.
Every other Pac-Man gives you the power pellets in the corners. This one did not. If you wanted to turn the tables on
the ghosts, you had to take one of two chutes at the bottom of the maze, drop out of the video game entirely, and
earn it with a metal ball and a pair of flippers. Energizers, fruit and tunnel speed were all bought downstairs.
Then you came back up and spent them.
It sold around seven thousand machines and the retrospective verdict was that it did well on the strength of the
character rather than the design, which I think is a shame, because the design contains an idea that is still
underused: make one half of your game unable to supply something the other half needs.
That is the whole hinge. The maze cannot make energizers. The table cannot chase ghosts. Neither is playable alone
and the trip between them is the game.
Charge down here, climb up there
ClimbBall inverts the geometry ā climbing is the video half and it sits above ā but keeps the dependency exactly.
The climb cannot generate its own lift. Everything that sends you upward was earned on the table, and it is spent the
moment you use it. So a run reads as a sequence of trades: flip well, get thrown high, climb until you run out,
fall, and do it again from wherever you landed. Your height carries down with you and back up again, which means a
good climb makes your next visit to the table shorter and more valuable, and a bad one leaves you starting from the
floor.
One canvas, two renderers, two independent simulations, and a single shared module resolving what each one owes the
other. The chute is where they meet.
The economy is the game, and it is the hard part
It took a while to accept that the platforms and the flippers were not the design work. The design work was the
exchange rate.
The trouble with a currency between two dissimilar games is that they have different difficulty curves and you are
pinning them together. Early on, the climb is easy and a modest charge sends you a long way, so the table feels like
a formality. Later, the climb is brutal and the same charge buys you almost nothing, so the table feels like a
punishment. Same numbers, opposite complaints, and no constant exchange rate fixes both.
The answer was to stop treating the ground as the ground. There is a death floor, and it rises with your
altitude. Early on it sits at the bottom and a bad drain costs you very little: you are back where you
started, which is nowhere. High up, the floor has come up beneath you, and draining below it costs a life and
returns you to a checkpoint rather than dumping you back at zero.
Two things fall out of that, and they are the reason it was the right fix rather than a patch.
The first is that a late-game failure stops being catastrophic. Losing an hour's climb to one unlucky drain is the
kind of thing that makes people close the tab, and no amount of "but it's fair" survives it.
The second is subtler and is really the economic fix: because the floor rises, the value of a unit of lift
falls as you get higher, automatically. You are never buying your way from nothing to everything. The exchange rate
is not a constant at all ā it is a function of where you already are, and writing it that way made a problem I had
been trying to solve with tuning disappear into a single line of arithmetic.
Breakables that are a decision, not a trap
The climb has platforms that crumble, and two rules turn them from an annoyance into a choice.
They hold for exactly one bounce. The alternative ā a platform that gives way the instant you touch
it ā is not a hazard, it is a lie: it looks like a platform and behaves like empty air, and nothing about spotting
it early helps you. One bounce means the platform is real, it does what platforms do, and then it is gone. You get
the jump; you just cannot have it twice.
Every breakable has a solid neighbour. A row that is entirely crumbling is not a decision, it is a
toll. Guaranteeing a safe tile alongside means the fast route and the safe route are both always available, and
taking the risky one is something you chose rather than something the level did to you.
Both rules are the same principle from different angles: a hazard is only interesting if the player could have
avoided it and can tell that they could.
A direction that would not let go
The most stubborn bug was a control that stuck.
You would be in the pinball half holding a flipper, the mode would switch, and the climb would begin with the chimp
already leaning ā travelling in a direction you were no longer asking for and could not immediately cancel. It
looked like drift, or like bad physics, and it was neither.
The cause was that the two halves were each tracking held input for themselves. Press during one mode, switch, and
the mode you left kept its opinion about what was being held while the mode you arrived in started from nothing, and
between them they disagreed about the state of your thumb.
The fix was to stop having two opinions. Input is now held in one place and polled, mode-agnostically ā nobody owns a
key, anybody can ask what is currently down, and a mode switch changes who is asking rather than resetting what is
true. Directions stopped sticking immediately, and the bug had the shape of most of the bugs I have written up this
month: it was not in either half, it was in the fact that there were two.
The pause that came from another game
And then the best bug, which took a long time because everything about it pointed at the wrong file.
The game would sometimes boot frozen. Not crashed ā running, rendering, and absolutely refusing to accept input, as
though paused before it had started.
It was paused. By a different game.
The pause flag was being stored under a key that was not namespaced to the app, so every game on the site shared one.
Leave another game paused, come to this one, and it booted into the state its neighbour had left behind. The shell
and the game also disagreed slightly about the name of the flag, which meant the game could see the stale value but
could not reliably clear it.
Everything else about this platform is carefully namespaced ā each app's saved data lives under a prefix derived from
its own folder ā which is exactly why one key that escaped the convention was so hard to see. I spent an
embarrassing while inside this game's own state handling before thinking to look in storage and finding a key with
no prefix on it at all, set by something else entirely.
The related half of it was keyboard focus after unpausing, which turned out to belong to the page hosting the game
rather than the game itself: the frame had to be told to take focus back. That is the right place for it ā the game
cannot reach out and grab focus from its host, and should not be able to.
Small craft
Three bits I was pleased with.
The spiders have two frames of animation and no animation timer. They already bob up and down on a sine wave, so the
frame is chosen from the phase of the bob ā the movement and the animation are the same clock. No new state, no
drift between them, and the legs are always moving in time with the body because they are not two things being kept
in sync, they are one thing read twice.
Every sound effect has a synthesised fallback. Drop an audio file in with the right name and it plays; leave it out
and the game generates that sound instead. So the game is never silent while the audio is being worked on, and a
missing file is a difference in quality rather than a bug.
And the whole control surface is the app container rather than the canvas, with the canvas kept only as the focus
target. Touch input on a canvas inside a page is a constant argument with scrolling and text selection; making the
whole panel the input surface ends the argument. The tap that dismisses the pause overlay is also explicitly
prevented from counting as a game input, which is the kind of detail nobody notices until the first thing they do on
resuming is accidentally launch the ball.