1987 · Imagine

Arkanoid

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

Which capsule falls from a broken brick is decided by the frame counter, the ball's position and how many lives you have left. Martin Galway's music runs on the page from the game's own driver.

01 · Capsules

Secret: the capsules are dealt by the clock

When a brick breaks, choose_capsule ($B2E6) looks at the low byte of a frame counter that counts up 50 times a second, and that one number sorts every broken brick into three groups.

Lives Frame counter high byte $0241
Silver and gold bricks in the round

Each bar is the share of 10,000 broken bricks that gave that capsule, with the frame counter, the ball's X and its height drawn at random, and each result fed back as the "last capsule". The chooser on this page is a port of $B204-$B21B, checked against the game's own code run in a 6502 simulator on 40,000 random cases, with no difference.

Frame counter $0201What falls
$A0-$FF and $0A-$1F (118 frames of 256)nothing
$20-$9F (128 frames)S or C: bits 0-1 of the counter XOR the ball's X give S once in four, C three times in four
$00-$09 (10 frames)the first that applies: B if bit 1 of the lives is set, L if $0241 is $12 or more, D if the round has four or more silver and gold bricks, E if the ball is in the upper half of the screen, else S or C as above

Then $B209-$B219 apply two rules. A capsule of the same type as the last one is bumped up by one, so a second C in a row arrives as an E, and a second S as a C. And the extra life, P, is never chosen directly: the only way to get one is a B when the last capsule was also a B. A capsule falls at all only when no other is falling and exactly one ball is in play ($B1E5-$B204).

The lives test reads the count as stored, in binary-coded decimal, so with 2, 3, 6 or 7 lives the rare window gives the Break capsule, and with 1, 4, 5, 8 or 9 it never does. The laser only becomes possible once $0241, the counter's high byte, reaches $12: 18 × 256 frames, a little over 90 seconds.

TypeLetterEffectCode
1Sball speed down by one, never below 1$A8BA
2Ccatch: the ball sticks to the Vaus until fire, or until the counter reaches $80$A8AC, $AA76
3Ethe Vaus grows from width 4 to 12$AA44, $AD4B
4Dthree balls, and the speed up by one$A8CD
5Llaser, two shots at a time$A870, $A5F3
6Ban exit opens in the right wall; leaving through it ends the round$A868, $A41F
7Pan extra life, up to 99$A882

Catching any capsule first switches off catch, laser, break and the enlarged Vaus (cancel_powers, $AA17). A capsule caught is worth 1,000 points ($A9DD).

02 · Music

Music: Martin Galway's tunes, played by the game's driver

The player below runs the game's own sound driver, $2615-$3429, in a small 6502 interpreter, frame by frame, and plays what it writes to the SID.

The driver's bytes come from this game's Source tab. Every tune was checked against the same code run in the kit's 6502 simulator: every write to the sound chip, in order, on every frame, with no difference. The title and ending tunes advance once a frame and the others twice, as measured in the emulator (the game calls the driver from its NMI once on the title and twice in play). The tunes set the filter through voice 2, so the player models one, a 6581's: real chips differ from each other in cutoff by large factors. The drums on the title screen are not here: they are samples written to the volume register by other code, which this model of the chip does not play.

A tune is started with music_start_tune ($262B), Y = 7 × tune + 5, which picks a 7-byte record in the table at $342A: three voice pointers and a tempo byte. Each voice reads its own sequence of two-byte notes and commands $C0-$F0, dispatched through three vector tables at $3F00, $3F32 and $3F64, one per voice. A tick is seven calls: the filter ($2B5D), a sequencer per voice and an effects routine per voice. Only voice 2 drives the filter, and only voice 2 can call 6502 code from inside a tune, which the title tune uses to set filter bits that another routine ORs into the volume register.

Tune 7 is complete and nothing in the game starts it: a search for its start value finds no caller. Tune 3's record is empty.

03 · The screen

One frame of round 1, rebuilt from memory

One frame of play recorded in the emulator: the video chip's registers, every change made to them during the frame, and the memory they read, drawn line by line. It matches the emulator's own picture of the same frame with 0 pixels different.

Play uses video bank 3: the screen at $C000 and one character set at $C800, in multicolour mode. Characters $00-$3F are the font and the logo, $40-$7F the background tiles, copied in each round, and $80-$FF the bricks, walls and panel. The bricks are character pairs, not sprites: $80 $81 for a coloured brick, $85 $89 for silver and $8D $91 for gold (draw_wall, $F40E).

The Vaus, the balls, the capsule, the laser shots and the enemies are twelve sprite objects on eight hardware sprites. A chain of three raster interrupts ($F720, $F75E, $FBEE) sorts the objects by height once a frame and places them down the screen. The right-hand panel is drawn once from a template of 25 rows of 13 screen codes at $7800 ($F2B5).

04 · The rounds

Thirty-two walls in 5 KB

Drawn from the round's 160 bytes at $8000 + 160 × (round − 1), with the game's own wall routine ported: tile pattern, colours, brick characters and the shading each brick casts. For round 1 the port's screen codes match the game's screen in the play snapshot in every cell but the three bricks already broken there. The Maps / levels tab shows all 32.

Each round is 16 rows of 10 bytes: a 16-bit mask saying which of the 14 columns hold a brick, then 16 four-bit brick types ($F40E). A type below 8 is a coloured brick in colour type + 8. From 8 up, type AND 3 = 1 makes a silver brick and anything else a gold one, which is never counted and never breaks. Round 33 has no map: it is Doh.

RoundsHits to break silver
1-82
9-163
17-244
25-325

From the table at $F52A, plus one ($F4DF).

A coloured brick's value depends on its colour, from 70 to 130 points (brick_points, $B2C4): white 100, red 80, cyan 120, purple 90, green 110, blue 70, yellow 130. The background alternates between two sets of 64 tile characters ($EC00 for rounds 1, 2, 5, 6…, $EE00 for 3, 4, 7, 8…) in blue or red ($F552).

05 · The bat

The two sides of the Vaus are not mirror images

Where the ball meets the Vaus, measured from a point 6 pixels right of its centre, and the angle it leaves at (bat_collision, $ABB3). Steep, diagonal and shallow are the step ratios the code sets: $FF/$40, $FF/$F0 and $80/$FF, vertical against horizontal.

The left half sends the ball left and the right half sends it right, each in three zones: two of 4 pixels, then the rest. Near the middle the left side gives a diagonal and the next zone out a steep bounce; on the right side the order is the other way round ($AC12-$AC3A against $AD0F-$AD38). Both outer zones give the shallow angle. On the enlarged Vaus the distance is halved first, so the zones are twice as wide.

The ball's speed is a number of steps per frame, from 1 to 11 (move_balls, $ADB3). Every brick hit adds 4 to a fraction that carries into it ($AF5F), and the time since the life began sets a floor that rises by one every 1,024 frames ($ADE7). A new life starts at 2. With the joystick the Vaus moves faster as the ball does: each frame it steps 2 × (speed + 1) pixels ($FDBF).

06 · Round 33

Doh takes 23 hits

Round 33: Doh

Round 33 in the emulator, reached by setting the round number and the bricks-left count so that the next brick broken in round 1 ended the round.

Doh is not a sprite. The round copies a 26 × 24 screen of characters from $7B90 into the playfield and swaps in its own character set (doh_setup, $9C59). The bricks-left counter becomes a hit counter, set to 22 ($95F4); every ball hit on one of Doh's characters takes one off ($B11E), and the ending starts when it goes below zero ($9637): the 23rd hit. Doh fires one shot per cycle of its mouth, aimed at the Vaus ($9819-$987B).

After the last hit Doh dissolves, the ending tune and the title picture play, and the story's last page is printed ($B8B0): "DIMENSION CONTROLLING FORT DOH HAS NOW BEEN DEMOLISHED AND TIME STARTED FLOWING BACKWARDS". Then the game sets the player's lives to none and goes to game over ($96B7).

07 · Protection

Secret: the protection scrambles its own second check

The game starts at $9400 and hides its first steps behind undocumented opcodes: a NOP with an operand at $9404 and another at $095A, which a disassembler that knows only the documented set shows as data. The bytes after the JSR $B9C3 at $095D look like the continuation and never run, because $B9C3 leaves by JMP $F000.

The start-up then sends a byte out through the first CIA's serial port and waits for the chip to report it sent ($5F42), a test of the hardware rather than the disk. Both routines erase themselves when they finish: $5F42 fills itself with $01, and $B9C3 overwrites its first 40 bytes with $20.

On the way it XORs 97 bytes at $BA0A with a key at $A71A, which is live game code. The bytes at $BA0A are a longer serial-port check, and in the file they are plain code: the XOR turns them into noise. Nothing calls that check in this build, and the flag it would set, $0944, is the trigger for a routine that wipes memory ($A523). The only other writer of the flag is a checksum over $9500-$A5FF at $F9BC, which nothing calls either, so the wipe never runs.

08 · Quirks

Bug: a ball moving left cannot hit an enemy from the side

The ball-against-enemy test has a horizontal check for each direction. The one for a ball moving left subtracts 24 where it should add ($9E9B-$9EAB), so it never matches; only the vertical test catches such a ball. This was read in the code and not tried in play.

The Break exit pays nothing

In the arcade game, leaving through the Break exit is worth 10,000 points. Here the exit ends the round ($A41F to round_cleared, $95CF) and nothing on that path adds to the score.

An extra life every 20,000 points, all at once

The threshold starts at 20,000 and rises by 20,000 with each life awarded (check_extra_life, $FAA5). In the emulator a score set to 199,900 paid out ten lives in a row, taking the lives from 3 to 13 and the threshold to 220,000. The published cheat POKE 39801,189 turns the store at $9B79 that takes a life away into a load, so a lost ball costs nothing: in the emulator three lost balls left the lives at 3.

Two programs in one file

The 63 KB file carries an older build of much of the game beside the one that runs: an earlier assembly of $A971-$B5FF from $096B, of the interrupt and input code at $BA6B-$BFFF (with a 30-sprite multiplexer and a joystick reader for port 2 only), and of the sound driver at $3B8B, assembled $1000 higher. Nothing reads or runs any of it. The enemies' second gate is unused in the same way: its path needs $0246, which nothing writes, so every enemy comes in through the gate at X $47.