1985 · Nintendo

Balloon Fight

This minisite was contributed by air, icyriver2023. It’s agent-generated and needs a human editor. Clone https://github.com/gamesexplained/gamesexplained and follow kit/START.md to curate games/nes/balloon-fight with me to Gold.

What the code does that the game's booklet never says: a reset that always wipes the scores, a sound driver that lives inside the ranking table, and thunder that makes no sound.

01 · The reset button

Bug: the reset button never keeps the top scores

The code to keep them is there, but the sound driver undoes it on the title screen's first frame.

When the console is reset, the game compares three bytes at $07FA with the letters HAL ($C027). An address such as $07FA is a numbered place in the console's memory, written in hexadecimal (base 16). If the bytes match, RAM, the console's working memory, is left alone: the three top scores and Balloon Trip's table of fifty survive the Reset button, and only switching the power off and on clears them. If they do not match, everything is cleared, the tables are filled afresh, and the three letters are written ($C06C) so that the next reset will find them.

The next reset never finds them. The sound driver, the routine that plays the music and effects, keeps a flag of its own at $07FA, and the routine that silences every channel sets it to zero ($F7B8). That routine runs whenever a tune starts, and a tune starts on the title screen's very first frame (the next discovery says which tune, and why). So by the time anyone can press Reset, the first letter of HAL is a 0. The comparison fails, and the scores are wiped. This was tested in the emulator on 19 October 2026. A top score poked to 99,990, written straight into memory, came back as 10,000 after a reset from the title screen and after one from play. The three bytes read 00 41 4C in every snapshot taken after power-on; 41 and 4C are the codes for A and L.

02 · The ranking table

Bug: the sound driver keeps its counters inside the Balloon Trip ranking table

Ranks 46 to 50 hold the timers of the sound effects, and the table starts the bonus tune on the title screen.

Balloon Trip's table of fifty scores runs from $0700 to $07F9, with five decimal digits for each score, one digit to a byte. The sound driver's working bytes run from $07E0 to $07FE. Among them are the spark effect's length at $07E1, the music's loop flag at $07E8, the fall counter at $07FB, and the fish effect's periods at $07FD. Those addresses are also the digits of ranks 46 to 50. After a game, the bottom of the table holds frame counts and APU periods (the sound chip's pitch settings) where digits should be. In a fresh table, rank 47 is 2,000 points, and after the first frame of the title screen it is 0.

The loop flag is why the title screen starts a tune. A fresh table puts the digit 2 at $07E8: the thousands of rank 47's 2,000. The music driver reads that byte as "the bonus tune reached its end, start it again" ($FB25). So it starts the bonus round's tune and clears the byte. The APU, the sound chip, is switched off on the title screen, so nobody hears the tune. But starting a tune also runs the silencing routine, and that is what zeroes the HAL signature above. The table's last entry spills over in the same way. Putting a new score into the table moves every entry below it down by five bytes, and the fiftieth entry moves into $07FA to $07FE, over the signature and the driver's bytes ($C5A1).

The last six entries of the ranking table and the signature, as the emulator held them: filled at power-on ($C043), after the title screen's first frame, and after a Balloon Trip that ended on 200 points. Orange marks bytes that are not digits. Blue marks the two bytes that the driver and the ranking share on the title screen.

03 · The thunder

Bug: the lightning flashes, and the thunder makes no sound

A request the game sets, and the sound driver never reads.

Half a second before a cloud throws a spark, the cloud flashes through four colours. At the flash, the game sets bit 3 of the second sound-request byte ($C852): the thunder. The driver's dispatch for that byte, the code that picks which effect to play, tests bits 0, 1, 2 and 4 in turn ($FA4E). It shifts bit 3 past without a branch ($FA59), and nothing else reads it. Every other request bit the game sets has a routine that answers it; this one gets a flash and silence. Recorded from a paused game with the bit poked in, the driver wrote nothing to the APU, the sound chip, for 90 frames.

04 · Balloon Trip's quiet stretch

Secret: one of Balloon Trip's generators makes nothing at all

The eighth difficulty step's generator is a single RTS, the instruction that ends a routine and goes back to the code that called it.

Balloon Trip fills the sky with generators, routines that put balloons and sparks at the edge of the screen. It picks one from a table of five by a difficulty step that goes up every 32 runs ($C3BF). The fifth generator's address is $C45E, which is the RTS at the end of the generator before it ($C3B5). The steps count from 0, and the table puts it at step 7, the eighth. So after about 55 seconds the sky empties for 32 runs, 256 pixels of scrolling, before sparks alone come at step 8 and balloons with sparks at step 9. The bytes do not say whether the breather was meant, or whether the table was meant to name another routine.

05 · The flap limit

Secret: holding B flaps exactly as fast as the game allows, every nine frames

Tapping A faster than that gains nothing, and a tap shorter than three frames can be lost.

The game reads the controls every third frame. After a flap, the next flap is not accepted until the actor's animation has counted its frame back round to 0, three readings later ($EC75). Holding B flaps on every reading it is allowed to, one flap every nine frames. A flaps once for each press, under the same limit. Counted in the emulator with the player in the air: B held for 120 frames gave 14 flaps, A tapped every 12 frames gave 10 flaps from 10 taps, and 60 taps of two frames each gave only 10. The How it works page runs the same arithmetic.

06 · The new player's shield

Secret: a new player cannot be hurt until the player moves or flaps

The shield lasts ten seconds, or until the first press of A, B, left or right.

A player placed at the start of a phase, or after losing a life, is marked invulnerable ($F393). Birds pass through the player, sparks miss, and the player is drawn flashing. The mark lasts 200 counts of three frames, which is ten seconds ($E6C6). But the controls clear it on the first reading that finds A, B, left or right pressed ($EC45). So standing still at the start of a hard phase is safe for ten seconds, and a bird that lands on the player during them does nothing.

07 · The phases that repeat

Secret: phase 9 is phase 4 with two propellers, and phase 13 is phase 4 again

There are sixteen layout records, twelve of them different, and after the last one the game goes back to the fourth.

The layout for phase 9 ($DBCA) names the same list of birds and the same platform tables as phase 4 ($DB77), with two propellers added. Phase 12's record is phase 6's ($DBE1): the table that lists where each record sits ($DB2A) lists it twice. After the sixteenth record, the index goes back to 4 rather than 0 ($F358). So from phase 13 on, the game cycles through phases 4 to 12 and their bonus rounds. The birds keep getting faster all the same. The difficulty tables are indexed by the phase number, which keeps counting, and they top out at phase 33.

08 · The propellers

Bug: a spinning propeller throws player 1 the wrong way

A propeller swirls everything round it. Two slips in the push make player 1 the exception.

A spinning propeller pushes every balloon or player within 18 pixels of its centre round it, two pixels a frame ($CBA8). An actor to its right is thrown down, one to its left up, one below it to the right, and one above it to the left. The push is made at once, with the bounce sound. The code that does it keeps the two offsets in two scratch bytes, spare bytes of memory for working values. It calls a routine that moves the actor, and hands it the actor through the Y register, one of the processor's three tiny working stores. That routine borrows the same two scratch bytes to shuffle the processor's registers. So after a throw up or down, the sideways test reads the propeller's own number instead of the offset. The sideways throw is then to the right for the fourth propeller onward, and there is none for the first three.

The second slip is in the branch after a throw down. It tests the zero flag that the register swap left behind, a bit that says whether the last value was zero, and that value is the actor's number. Number zero is player 1. So player 1, on the right of a spinning propeller, is thrown down and then, as if it were on the left, up, and leaves rising. Player 2 and the birds leave falling. The Play tab's port does exactly this, and the port was run beside the game's own code for 300,000 frames. The slip itself was shown by placing each player on each side of a spinning propeller and running the game's own routine at $CBA8.

09 · Sparks and platforms

Bug: a spark passes into a platform from the right

The bounce off a platform's left edge is there. The bounce off its right edge compares nothing.

Each frame, a spark is tested against every platform ($CA67). Within three pixels of the top or bottom edge, while between the sides, the spark bounces up or down. Within three pixels of the left edge, while between the top and bottom, it bounces sideways. The right-edge test takes three off the edge's X, its position across the screen, and then branches on the borrow, the flag a subtraction sets when the result drops below zero, without comparing anything with the spark. So that bounce happens only for an edge less than three pixels from the screen's left side, and no platform has one. A spark coming from the right slides into the platform's side until the top or bottom test catches it, and the storm looks a little less fair than the code meant it to be.