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.

Play the whole game in your browser, from any phase. Below the game: how the port was checked against the cartridge's own code.

01 · Play

Play Balloon Fight with the keyboard

Left and right steer. Z or Space flaps once, and holding X flaps again and again. Enter is Start and Shift is Select. On the title screen, Select moves the balloon between one player, two players and Balloon Trip, and Start begins. Start also pauses a game.

  • ← → steer; in the air, lean
  • Z or Space A: one flap a press
  • X B: held, a flap every nine frames
  • Enter Start; Shift Select
  • Player 2: A D steer, G A, H B
Start at
Cheats
Flight
See inside
Time

The game as the port runs it, with pictures drawn for this page. A phase button starts a one-player game at that phase, in the state a game that had cleared the phases before it would be in: the same layout, the same count of phases cleared, which sets how fast the birds pump their balloons, and the same number of bonus rounds passed, which sets what a bonus balloon is worth. The lives and the score are a new game's.

02 · Mods

Mods: cheats, lower gravity, rewind, and a look inside

Each switch under the game changes it while you play, and each one works through the game's own variables and routines.

The cheats lean on the game itself. The shield is the protection a new player has at the start of each life: the game's invulnerable flag ($BD), which makes the routines that decide a hit let the player go ($EF37 for a bird, $CB1C for a spark). The page sets the flag only while those two routines run. On the screen the same flag makes the player flash, and in Balloon Trip it carries the player along with the scrolling. Lives that never run out keep the lives byte ($41) at 2. Calm skies keep the storm's counter ($B8) from reaching 1, the value at which a cloud starts to flash and then throws a spark. No fish makes the fish's search ($C614) find no one, so the fish stays asleep. The shield does not keep the player out of the water, and with the fish awake it does not stop a bite.

Gravity scales the twelve values in the game's own gravity table ($E601), one for each type of actor, so the birds float or sink with the player. Pace runs the game's clock faster or slower than the console's.

Hit boxes draws the 16 by 24 pixel box that the collision code gives each actor, dashed for the 20 frames after a hit, when that actor cannot be hit again. Where each bird steers draws the sideways speed the bird is aiming for, which it turns toward a player. The fish's strike line marks the one column of pixels where the fish rises: it takes an actor whose X, the position across the screen, is exactly the fish's, low over the water. How close a spark must come draws, around each spark, the area the player's position must enter for a hit. The player's position is the dot at the top left of the player's box. Memory shows the console's 2 KB of RAM, its working memory, one byte to a square: the larger the number, the brighter the square, and the bytes that changed this frame are red. Point at a square to see which of the game's variables it holds, and set a byte to change the game.

Rewind keeps a saved moment every half second for the last twenty seconds, and steps back through them while the button or R is held. A moment can be saved only at the top of the game's main loop in a phase or in Balloon Trip, so rewinding in a bonus round goes back to the end of the phase before it. In Balloon Trip the page also records the player's sprites on every frame. The run that lasts longest becomes a ghost, a see-through player that flies the same path again beside your next run.

None of the switches is part of the game. test_play.js holds the port to the game's code with every switch off. test_mods.js checks the switches: a saved moment, restored, runs on exactly as the game it came from for 3,000 frames, and each cheat is played for thousands of frames beside the same game with the cheat off.

03 · The port

Every frame checked against the cartridge's own code

Nothing on this page emulates the game's program. The game is written out again in JavaScript, one function for each routine in the listing, the annotated program on the Source code tab (reference/bf-port.js). The functions work on a copy of the console's 64 KB of memory, with every variable where the game keeps it. The one piece of the cartridge's code the page runs is the sound driver, the routine that plays the music and effects. It is the bytes at addresses $F500 to $FBC9 of the console's memory, and it runs on the site's model of the 6502 processor, as on the How it works tab. The site's rules allow a game's music driver to run this way. The rest of the program is not on this page. The page carries the game's data, its tables, its layouts and its tunes, and nothing that could rebuild the program.

Every picture on the screen is drawn new for this page. The player is a blue cat, the birds are frogs, and the fish is a sea serpent. The lettering, the clouds, the platforms and the water are new as well. Each new tile, a square of 8 by 8 pixels, sits in the slot the game draws from, in the game's own colours. So the game plays exactly as it does on the console, and none of the cartridge's pattern tables, where its pictures are kept, is on this page. The How it works and Levels tabs show the cartridge's own pictures.

test_play.js runs the port in lockstep beside the cartridge's own code: the two run side by side, frame by frame, and are compared. The cartridge's code runs on the kit's NES (kit/nes/machine.js), a model of the console that was checked frame for frame against the jsnes emulator. Both start from the reset vector, the place where the console reads the address to start at, and both get the same controller presses. A pilot plays them. It waits on the title, picks one player, two players or Balloon Trip in turn, and lets the demonstration run every fourth time. In play it taps A in bursts, holds the stick, holds B, and pauses now and then. Between games it moves the game to another of the sixteen layouts, as clearing phases would. Every frame, at the point where the game's wait for the next frame ends ($F46D), the two memories are compared. So are the nametables, the palette, the sprite table, and the registers of the picture chip and the sound chip as they were written. The stack page, the processor's scratch area for return addresses, is left out, because a port does not keep one. The NMI handler, the routine the console runs once a frame, writes two bytes that the main loop reads, frame_counter and stars_to_twinkle. The port reads those two through the lockstep, which hands it the values the game's own reads got when the handler ran in the middle of the loop. That matters on a frame the game's loop could not finish in time. There, the loop's INC (add one) of the twinkle count and the handler's DEC (take one away) land in an order no replay can fix, and before the port read them this way, the lockstep showed the difference once in 100,000 frames.

The page keeps the console's pace by keeping the console's clock, which ticks 1,789,773 cycles a second; a cycle is one tick of the processor. Each stretch of the game's loop, from one call to the next, costs the cycles it cost the game on the kit's machine, the median over the lockstep's run. The NMI handler costs its own median. The NMI falls every 29,780.5 cycles, as it does on the console. A frame the loop cannot finish in time, such as a frame spent in one of the screen builders, which write the whole background with the picture off, runs late here just as it does there. The site's model of the picture chip draws the screen sixty times a second, from the port's copy of the picture chip's memory. In Balloon Trip it draws two bands. The status bar uses the scroll the handler sets in the vertical blank, the pause between pictures. The play area uses the scroll the handler writes after its timed delay, which on the console lands between lines 31 and 43 of the picture, and here at line 32.