← 8888 Per-level wave/spawn scripts Map 8D9C → Quit-confirmation handler (per-frame)
8BA8: Program entry and the main per-frame loop
Execution starts here, and the game never hands back. Setup runs once, then the front end, then a level's screen, then the frame loop.
A cleared level, a spent last life or a confirmed quit sends control back around that ring.
game_entry 8BA8 LD H,L A leftover source marker: these three bytes spell "end" as opcodes and run harmlessly before the real start.
8BA9 LD L,(HL)
8BAA LD H,H
8BAB DI Interrupts stay off, except for the front end's one-frame wait. The stack sits at the top of RAM. The main loop is not HALT-synced.
8BAC LD SP,STACK_TOP
8BAF LD HL,spr_visor_1 Copy the source bank – the canonical sprite bitmaps – into the sprite bank in the system-variable area. Play draws from the sprite bank, and the source page is reused as the back buffer.
8BB2 LD DE,slot_visor_1
8BB5 LD BC,$0B20
8BB8 LDIR
8BBA LD A,$09 Seed the pool entry count that bounds the per-frame object walk.
8BBC LD (object_count),A
New game / restart entry. Runs the front end, then the full new-game initialization, then falls into per-level setup.
new_game 8BBF CALL title_screen
8BC2 CALL init_game_vars
8BC5 CALL clear_screen
8BC8 LD A,$47 Paint every attribute cell white-on-black.
8BCA CALL fill_attrs
8BCD XOR A Black border
8BCE OUT ($FE),A
Per-level screen setup. It clears the work buffers and play state, draws the playfield border and composes the level's brick field, and renders the tile map. It then draws the lives, level-number, and score readouts. Falls into the per-frame loop.
setup_level 8BD0 CALL clear_buffers
8BD3 LD A,$FF Plant the terminator that ends the work-buffer scans of the cell recomposer and the dirty-cell blitter, in the first cell of the bottom character row. That cell is drawable, and an object that reaches it stamps the terminator away – see Scramble.
8BD5 LD (WORKBUF_FB+$2E0),A
8BD8 CALL clear_state_buffers
8BDB CALL draw_playfield_border
8BDE CALL compose_brick_field
8BE1 CALL render_tilemap
8BE4 CALL draw_lives
8BE7 CALL draw_level_number
8BEA CALL draw_score_readout
Top of the per-frame loop (also the loop-back target). While the expand prize is in effect, the loop stages the expanded bat for this frame in the object pool. A grow or shrink animation holds that off, so the grow flag and the shrink flag must both be zero. This stage activates the extension and limits how low the expanded bat may sit. It seeds the extension's position from the bat's. It aims the bat's sprite at the top half of the expanded-bat sprites, and the extension's at the bottom half.
frame_loop 8BED LD A,(expand_active) Stage the expanded bat only while the bat is expanded.
8BF0 OR A
8BF1 JR Z,idle_track
8BF3 LD A,(fx_grow_arm) The grow animation holds it off while it runs.
8BF6 OR A
8BF7 JR NZ,idle_track
8BF9 LD A,(fx_shrink_arm) So does the shrink animation.
8BFC OR A
8BFD JR NZ,idle_track
8BFF LD A,$01
8C01 LD (bat_ext_active),A
8C04 LD A,(bat_y) Clamp the bat's Y to the bat's lowest expanded position.
8C07 CP $8A
8C09 JR C,seed_bat_extension
8C0B LD A,$8A At or below that line on screen, pin it there.
8C0D LD (bat_y),A
seed_bat_extension 8C10 LD HL,(object_table) Seed the extension's position from the bat's and aim both at their sprites.
8C13 LD A,H
8C14 ADD A,$10
8C16 LD H,A
8C17 LD (bat_ext_object),HL
8C1A LD HL,slot_bat_exp_top
8C1D LD (bat_sprite_ptr),HL
8C20 LD HL,slot_bat_exp_bottom
8C23 LD (bat_ext_sprite_ptr),HL
Anti-stuck nudge. Monitors ball A's on-screen position. If ball A stays within 16 pixels of the saved position on both axes for 30 consecutive frames, the nudge fires. The box reaches 15 pixels on one side and 16 on the other. It turns ball A's heading half a turn around to break the stuck rally. When the ball moves out of the box, or the nudge fires, the routine saves the new position and resets the idle counter.
idle_track 8C26 LD HL,(ball_a_object) Take ball A's position and how far its X moved from the saved position.
8C29 LD A,(idle_ball_x)
8C2C SUB L
8C2D JP P,idle_check_dx
8C30 CPL Fold a negative difference. CPL leaves one less than its magnitude, so the box reaches a pixel further on that side.
idle_check_dx 8C31 CP $10 Moved far enough across, so the rally is not stuck.
8C33 JR NC,idle_save
8C35 LD A,(idle_ball_y) Measure the Y difference the same way.
8C38 SUB H
8C39 JP P,idle_eval_dy
8C3C CPL Fold the Y difference the same way, one short again.
idle_eval_dy 8C3D CP $10 Moved far enough on Y either way, so the rally is not stuck.
8C3F JR NC,idle_save
8C41 LD A,(idle_frame_count) Inside the box on both axes: count one more idle frame, and act once the count reaches its limit.
8C44 INC A
8C45 LD (idle_frame_count),A
8C48 CP $1E
8C4A JR NZ,frame_pipeline
8C4C LD A,(ball_a_heading) Flip ball A's heading by a half-turn to break the rally.
8C4F SUB $24
8C51 CP $48
8C53 JR NC,idle_wrap_dir
8C55 ADD A,$48 A heading that fell below the range wraps back up.
idle_wrap_dir 8C57 LD (ball_a_heading),A Store the flipped heading.
idle_save 8C5A LD (idle_ball_x),HL Remember where the ball is and clear the idle count. The ball moved, or the nudge fired.
8C5D XOR A
8C5E LD (idle_frame_count),A
Per-frame subsystem pipeline. Runs each update in the fixed order of the stages below. The end-of-frame state checks come last.
frame_pipeline 8C61 CALL read_ctrl Stage 1: sample the player's controls into the control bitfield.
8C64 CALL update_balls Stage 2: advance every ball – motion, wall and bat bounce, brick hits.
8C67 CALL manage_objects Stage 3: animate the prize / effect objects.
8C6A CALL move_bat Stage 4: move the bat from the sampled control.
8C6D CALL update_inhabitants Stage 5: run the inhabitants' behaviors.
8C70 CALL scan_brick_field Stage 6: sweep the brick field, play queued break animations and detect a cleared level.
8C73 CALL remove_lost_ball Stage 7: retire a ball that fell past the bat's losing edge (drives life loss).
8C76 CALL process_wave_script Stage 8: spawn objects scheduled by the current wave script.
8C79 CALL spawn_wheel_swarm Stage 9: spawn the wheel swarm into the free inhabitant slots.
8C7C CALL run_prize_safety_net Stage 10: animate the safety net barrier's color flash.
8C7F CALL add_pending_score Stage 11: fold pending points into the displayed score.
8C82 CALL award_score_life Stage 12: grant a bonus life when the score passes a milestone.
8C85 CALL handle_pause Stage 13: poll the pause key; a press freezes the loop until the next press.
8C88 CALL update_objects Stage 14: update and redraw all active objects.
8C8B CALL sfx_net_flash Stage 15: advance the seven sound-effect players.
8C8E CALL sfx_bomb_detonate
8C91 CALL sfx_bat_grows
8C94 CALL sfx_bat_shrinks
8C97 CALL sfx_death_burst
8C9A CALL sfx_spawn
8C9D CALL sfx_chew
8CA0 LD A,(orb_spit_timer) Stage 16: tick the orb-spit sound delay and play the orb-spit sound on the tick before it runs out.
8CA3 OR A
8CA4 JR Z,poll_quit
8CA6 DEC A
8CA7 LD (orb_spit_timer),A
8CAA CP $01
8CAC CALL Z,sfx_orb_spit
poll_quit 8CAF CALL handle_quit_confirm Stage 17: poll the quit key (Q opens the are-you-sure dialog).
Stage 18: prize-reveal tune sequencer. Once armed it plays the prize-reveal tune one note per frame through the tune player, and stops at the tune's terminator.
8CB2 LD A,(prize_reveal_tune_gate)
8CB5 OR A
8CB6 JR Z,frame_end_checks
8CB8 CP $01 On the tune's first active frame, aim the tune pointer at it.
8CBA JR NZ,tune_play
8CBC LD HL,prize_reveal_tune
8CBF LD (tune_ptr),HL
tune_play 8CC2 CALL play_tune_note Emit this frame's note; the terminator clears the state and stops the tune.
8CC5 CP $0D
8CC7 JR NZ,tune_advance
8CC9 XOR A
8CCA LD (prize_reveal_tune_gate),A
8CCD JR frame_end_checks
tune_advance 8CCF LD HL,prize_reveal_tune_gate Otherwise bump the state, so the tune pointer is aimed only once.
8CD2 INC (HL)
End-of-frame state checks. Once the death pause reaches 60 the life is over. With no lives left, play goes to game over. Otherwise play returns to the top of the frame loop, unless an active warp or the level-cleared flag diverts it to the level-advance handler.
frame_end_checks 8CD3 LD A,(death_pause_timer) The death pause ran its course, so the life is over. With no lives left, the game ends.
8CD6 CP $3C
8CD8 JR C,check_warp_countdown
8CDA LD A,(lives)
8CDD OR A
8CDE JR Z,game_over
While a warp runs, the main loop advances one level per frame until the warp countdown runs out, then clears the warp flag. A warp thus jumps the player forward 1 to 4 levels.
check_warp_countdown 8CE0 LD A,(warp_active_flag) When no warp runs, there is nothing to advance.
8CE3 OR A
8CE4 JR Z,check_level_cleared
8CE6 LD HL,warp_countdown Count the warp down, and divert to the level-advance handler until it runs out.
8CE9 DEC (HL)
8CEA LD A,(HL)
8CEB OR A
8CEC JR NZ,level_complete
8CEE XOR A Countdown spent: clear the warp and fall through.
8CEF LD (HL),A
8CF0 LD (warp_active_flag),A
check_level_cleared 8CF3 LD A,(level_cleared) No level cleared: loop back to the frame top for the next frame.
8CF6 OR A
8CF7 JP Z,frame_loop
Level cleared. After a pause it awards a level-clear bonus of 100 points per step of the level index level, capped at 2500, then advances the index. An index that reaches 50 or 100 speeds up the rally: it lowers rally_accel_interval by 3, with a floor of 2. At 100 the index also wraps back to 0 at wrap_level_index. Re-enters setup_level for the next level.
level_complete 8CFA LD BC,$0000
8CFD CALL delay
8D00 LD A,(level) Clamp the level index before the bonus calculation.
8D03 CP $19
8D05 JR C,level_bonus_calc
8D07 LD A,$19 Above that, the bonus grows no more.
level_bonus_calc 8D09 LD DE,$000A Multiply the clamped index by ten, ready for the score's own ten-times commit.
8D0C LD HL,$0000
8D0F LD B,A
level_bonus_loop 8D10 ADD HL,DE
8D11 DJNZ level_bonus_loop
8D13 LD A,L
8D14 LD (pending_score),A
8D17 CALL add_pending_score
8D1A LD HL,level
8D1D INC (HL)
8D1E LD A,(HL)
8D1F CP $32
8D21 JR Z,speed_up_rally
8D23 CP $64
8D25 JR NZ,next_level
Wrap the level index back to 0. The brick-field layouts, spawn cells and wave scripts hold 100 records each, one per level. They therefore repeat from here. The displayed level in the level caption does not restart with it.
wrap_level_index 8D27 LD (HL),$00 The hundredth clear wraps the index back to the first layout.
speed_up_rally 8D29 LD A,(rally_accel_interval) Shorten the rally interval a step, down to a floor of two.
8D2C CP $02
8D2E JR Z,next_level
8D30 SUB $03
8D32 LD (rally_accel_interval),A
next_level 8D35 JP setup_level Set up the next level's screen.
Game over. Draws the GAME OVER banner with the double-height printer and plays the game-over tune to its $0D terminator. It then runs the high-score check to test and enter the score. Plays the title tune while it waits for fire, any key or the tune's end, then restarts at the new-game entry.
game_over 8D38 LD HL,SCREEN_BASE+$84C Print the two GAME OVER lines and aim the tune pointer at the game-over tune.
8D3B LD DE,msg_game_over
8D3E LD C,$4E
8D40 CALL print_string_2x
8D43 LD HL,SCREEN_BASE+$88C
8D46 LD DE,msg_over
8D49 LD C,$4E
8D4B CALL print_string_2x
8D4E LD HL,game_over_tune
8D51 LD (tune_ptr),HL
game_over_tune_play 8D54 CALL play_tune_note Play the tune a note per pass until its terminator.
8D57 CP $0D
8D59 JR Z,game_over_pause
8D5B LD BC,$0BB8 Pause between notes.
8D5E CALL delay
8D61 JR game_over_tune_play
game_over_pause 8D63 LD BC,$0000 The tune reached its terminator. Two full-length delays run, then the high-score check offers the score to the table and the tune pointer takes the title tune.
8D66 CALL delay
8D69 CALL delay
8D6C CALL check_highscore
8D6F LD HL,title_tune
8D72 LD (tune_ptr),HL
Wait for restart. Loop the title tune until its terminator, any key, or fire. Any of the three restarts a new game at the new-game entry.
await_restart 8D75 CALL play_tune_note When the title tune reaches its terminator, the game restarts on its own.
8D78 CP $0D
8D7A JP Z,new_game
8D7D LD BC,$0BB8
8D80 CALL delay
8D83 LD BC,$00FE Any key restarts the game, because the port read selects every keyboard half-row.
8D86 IN A,(C)
8D88 AND $1F
8D8A XOR $1F
8D8C JP NZ,new_game
8D8F CALL read_ctrl Fire restarts the game.
8D92 LD HL,controls
8D95 BIT 4,(HL)
8D97 JR Z,await_restart
8D99 JP new_game
← 8888 Per-level wave/spawn scripts Map 8D9C → Quit-confirmation handler (per-frame)
Fork me on GitHub