Prev: C3AF Up: Map Next: CC50
C59E: Pick a title-screen scene and enter the attract-mode wait loop
Picks one of 5 pre-scripted animation scenes, populates the 9-entry animated-object array at $BB00 from the chosen scene's object table, draws overlay text (title/credits, "PRESS ENTER FOR OPTIONS" always, and "PRESS GEAR TO PLAY" once controls have been selected -- 8001), then falls into the attract-mode wait loop (titlescr_wait_loop) which animates the scene each frame while polling for coin/fire/keyboard input to start a game.
Used by the routines at C000 and run_title_tune.
run_title_screen C59E CALL clear_and_fill_border_attrs Clear the screen bitmap and attribute buffers
C5A1 LD A,$00 A = 0 (self-modified below to persist a running value between calls -- acts as a pseudo-random/rotating scene selector)
C5A3 RLCA
C5A4 AND $1F
C5A6 JR NZ,run_title_screen_0
C5A8 INC A
run_title_screen_0 C5A9 LD ($C5A2),A Self-modify the operand at $C5A2 (the value loaded next call) with the rotated/incremented value
C5AC LD HL,$CCB7 Pick one of 5 scene tables by testing successive bits of A via RRA; first bit set selects the table
C5AF RRA
C5B0 JR C,run_title_screen_1
C5B2 LD HL,$CD4F
C5B5 RRA
C5B6 JR C,run_title_screen_1
C5B8 LD HL,$CF10
C5BB RRA
C5BC JR C,run_title_screen_1
C5BE LD HL,$CFCD
C5C1 RRA
C5C2 JR C,run_title_screen_1
C5C4 LD HL,$D16C
run_title_screen_1 C5C7 PUSH HL Save chosen scene table pointer
C5C8 LD HL,$CC50 Draw the copyright/credits text block ($CC50)
C5CB CALL print_string
C5CE LD HL,$BB00 Zero the whole $BB00-$BB4F object array (9 records x 9 bytes -- see object_script_step for field layout)
C5D1 LD DE,$BB01
C5D4 LD BC,$0050
C5D7 LD (HL),$00
C5D9 LDIR
C5DB LD IX,$BB00 Copy the 5-byte-per-object scene table (pointed to by the popped HL) into the 9 object records, one object per iteration, reordering into fields: script pointer low/high (+$07/+$08), then two more bytes into +$06 and +$04/+$05
C5DF LD DE,$0009
C5E2 POP HL
C5E3 LD B,$09
run_title_screen_2 C5E5 LD A,(HL)
C5E6 INC HL
C5E7 LD (IX+$07),A
C5EA LD A,(HL)
C5EB INC HL
C5EC LD (IX+$08),A
C5EF LD A,(HL)
C5F0 INC HL
C5F1 LD (IX+$06),A
C5F4 LD A,(HL)
C5F5 INC HL
C5F6 LD (IX+$04),A
C5F9 LD A,(HL)
C5FA INC HL
C5FB LD (IX+$05),A
C5FE ADD IX,DE
C600 DJNZ run_title_screen_2
C602 CALL setup_im2_interrupt_table Set up interrupts (see setup_im2_interrupt_table)
C605 CALL titlescr_animate_frame Draw the first animation frame before entering the wait loop, so the scene is visible immediately
C608 LD HL,$CC9D Draw the "PRESS ENTER FOR OPTIONS" text block ($CC9D), unconditionally
C60B CALL print_character
C60E LD A,($8001) If controls have already been selected (8001, set by the options menu)...
C611 AND A
C612 LD HL,$CC88 ...also draw the "PRESS GEAR TO PLAY" text block (CC88)
C615 CALL NZ,print_character
C618 XOR A
C619 CALL titlescr_start_tune See titlescr_start_tune, called with A=0
C61C EI
C61D HALT Sync to the next interrupt before the wait loop
Attract-mode wait loop: animates the current scene once per interrupt and polls for coin-insert / fire / any-key input to start the game or jump to a fresh title screen. Re-entered every frame via titlescr_wait_loop; run_title_screen is re-run (new scene) when a key other than fire is pressed.
titlescr_wait_loop C61E CALL titlescr_music Call titlescr_music (see titlescr_music)
C621 LD A,($F223) If a flag is already set, skip straight to the
C624 AND A fire-button check (demo/attract cycle already
C625 JR NZ,ts_check_fire running, don't replay the tune-wait below)
C627 LD A,$04 Otherwise play tune #4 and wait out ~180 frames
C629 CALL load_drum_script (one titlescr_music service call per frame) before
C62C LD B,$B4 falling through to the coin/name-table refresh
run_title_screen_3 C62E PUSH BC at titlescr_refresh_name_table
C62F CALL titlescr_music
C632 POP BC
C633 DJNZ run_title_screen_3
C635 INC B B wraps 0 -> 1 (DJNZ leaves B = 0)
C636 JR titlescr_refresh_name_table
ts_check_fire C638 LD A,$BF Read keyboard half-row for SPACE (fire)
C63A IN A,($FE)
C63C CPL
C63D RRA
C63E JP C,omd_redraw_and_poll Fire pressed -> start the game
C641 LD A,($8001) In coin-op mode, read the coin-slot input
C644 AND A
C645 JR Z,run_title_screen_4
C647 CALL $800E
C64A AND $10
C64C JP NZ,titlescr_credit_inserted Coin inserted -> refresh coin/credit display
run_title_screen_4 C64F LD A,($8000) If not in coin-op mode, check the "1"/"2" player
C652 AND A select keys instead
C653 JR Z,titlescr_wait_loop
C655 LD A,$EF
C657 IN A,($FE)
C659 CPL
C65A AND $10
C65C JP Z,run_title_screen_5 Neither select key held -> check for "any key"
C65F DI A player-select key was pressed: initialise a
C660 CALL stop_music_and_silence fresh 8002 (score_bcd) of $87654321 (a
C663 LD HL,$8002 recognisable placeholder/test score) and stage/
C666 LD (HL),$21 retry state, check it against the high-score
C668 INC HL table, then restart the title screen
C669 LD (HL),$43
C66B INC HL
C66C LD (HL),$65
C66E INC HL
C66F LD (HL),$87
C671 LD A,$06
C673 LD ($8007),A
C676 LD A,$03
C678 LD ($8006),A
C67B CALL check_high_score
C67E JP run_title_screen
run_title_screen_5 C681 LD A,$F7 Read keyboard half-row for ENTER
C683 IN A,($FE)
C685 CPL
C686 AND $1F
C688 JP Z,titlescr_wait_loop No key pressed -> keep waiting
C68B RRCA A key was pressed: seed the scene selector with
C68C LD ($C5A2),A the key-scan bits, then restart the title screen
C68F DI with a new scene
C690 CALL stop_music_and_silence
C693 JP run_title_screen
titlescr_credit_inserted C696 LD HL,$8011 Push $8011 as an extra "credit awarded" flag/value
C699 PUSH HL for the shared tail below
titlescr_refresh_name_table C69A PUSH BC Entry point also reached directly after the ~180- frame attract-tune wait (titlescr_wait_loop), without the $8011 flag push above
C69B DI
C69C CALL stop_music_and_silence Stop any playing tune and silence the AY chip (see stop_music_and_silence)
C69F LD HL,$C403 Copy the 3 preset high-score name/rank rows from $C403 into the work buffer pointed to by ($800A), 3 times (A = 3), each copy split into 15+7+6 bytes with 2-byte gaps skipped between segments
C6A2 LD DE,($800A)
C6A6 LD A,$03
run_title_screen_6 C6A8 LD BC,$000F
C6AB LDIR
C6AD INC HL
C6AE INC HL
C6AF LD C,$07
C6B1 LDIR
C6B3 INC HL
C6B4 LD C,$06
C6B6 LDIR
C6B8 INC HL
C6B9 INC HL
C6BA EX DE,HL
C6BB LD C,$07
C6BD ADD HL,BC
C6BE EX DE,HL
C6BF DEC A
C6C0 JR NZ,run_title_screen_6
C6C2 POP AF Discard the flag/counter pushed by the caller
C6C3 RET
Per-frame animation driver: waits for the next interrupt, draws the 6 "foreground" objects (records 0-5 of the $BB00 array) via compute_glyph_blit_params, steps the object animation scripts (object_script_step), clears the playfield bitmap (clear_playfield_buffer), then draws the 3 "background" objects (records 6-8) via the alternate blitter compute_glyph_blit_params_b. Loops forever in the ordinary case, but object_script_step (via oss_fetch_opcode_cont's $D2 "end of script" handling, `POP HL : RET` with no matching PUSH) will pop this loop's own return address as data and RET again beneath it, unwinding straight out of this self-loop back to titlescr_animate_frame's *caller* (C605's own continuation) the moment any object's script reaches $D2 -- see oss_fetch_opcode_cont. This is the normal exit: whichever scene object's script ends first stops the whole per-frame animation and hands control back to run_title_screen, which is what starts the tune and enters the (non-animating) attract-mode wait loop. blit_abort_restore_sp / run_title_screen_24-style RET-via-restored-SP inside the blitters is a separate, unrelated mechanism (aborting one partially off-screen glyph draw, not this loop). Uses EXX around each compute_glyph_blit_params/compute_glyph_blit_params_b call: the object record's script pointer (B/C) and screen-position byte (L) are loaded into the shadow registers so the blitter can use HL/DE/BC freely without disturbing the loop's own IX/DE/B state in the main set.
titlescr_animate_frame C6C4 EI
C6C5 HALT
C6C6 DI
C6C7 LD B,$06 6 foreground objects
C6C9 LD DE,$0009 Object record stride (9 bytes)
C6CC LD IX,$BB00
ts_draw_fg_objects C6D0 EXX Bank in shadow BC/DE/HL for the blitter call
C6D1 LD B,(IX+$08)
C6D4 LD C,(IX+$07)
C6D7 LD L,(IX+$06)
C6DA CALL compute_glyph_blit_params
C6DD EXX Restore main BC/DE/HL (IX/DE loop state)
C6DE ADD IX,DE
C6E0 DJNZ ts_draw_fg_objects
C6E2 EXX (unbalanced EXX vs. the loop above -- swaps back to the blitter's shadow set one more time before the object-script step, effect not fully traced)
C6E3 CALL object_script_step Step every object's animation script by one frame
C6E6 CALL clear_playfield_buffer Clear the playfield bitmap ready for the next frame
C6E9 EXX
C6EA LD IX,$BB36 Background objects start at record 6 ($BB00 + 6*9)
C6EE LD B,$03 3 background objects
ts_draw_bg_objects C6F0 EXX
C6F1 LD B,(IX+$08)
C6F4 LD C,(IX+$07)
C6F7 LD L,(IX+$06)
C6FA CALL compute_glyph_blit_params_b
C6FD EXX
C6FE ADD IX,DE
C700 DJNZ ts_draw_bg_objects
C702 JP titlescr_animate_frame
Object animation script interpreter: advances all 9 objects' scripts by one frame. Each object record (9 bytes, offsets relative to IX): +$00 current opcode / countdown-active flag (0 = idle, fetch next op) +$01 countdown value for the "wait N frames" opcode +$02 X velocity/step, +$03 Y velocity/step +$04/+$05 script pointer (low/high) -- the object's byte-code cursor +$06 screen row/position byte consumed by the blitters +$07/+$08 current X/Y screen position The script byte-code (fetched at oss_fetch_opcode) is a simple state machine: opcode bytes >= $80 (sign bit set) are treated as immediate 2-axis step deltas (oss_op_immediate_step); opcodes $C8-$D0 select a movement/velocity mode (constant velocity, decelerate-to-stop via the D272 lookup table, accelerate, or a literal position jump) and may consume further operand bytes from the script stream. $D1 is not one of these -- it is never tested by either dispatch chain below, so an object whose script emits it (this happens in every one of the 5 title scenes, always right after an initial constant-velocity burst) gets $D1 stored as its active opcode by chain 2 and then permanently frozen by chain 1: chain 1's oss_object_loop dispatch matches none of $C9-$CF for $D1, so it jumps straight to oss_next_object without ever reaching oss_countdown's countdown decrement, and the object can never go idle (opcode -> 0) to fetch a new instruction again. This is a genuine quirk of the original data/interpreter, not a disassembly error -- see $CCF2 for object 0's example. Two separate dispatch chains share this state machine: 1. oss_object_loop (the per-object per-frame entry): dispatches on the *active* opcode already stored at +$00 ($C9-$CF, one of the six movement modes below) and performs one frame's worth of incremental movement. An idle object (+$00 = 0) instead falls through to oss_fetch_opcode to fetch a fresh opcode from the script stream. 2. oss_fetch_opcode_cont (oss_fetch_opcode_cont): dispatches on the *next script byte* fetched from the stream ($C8-$D2, or an immediate step if bit 7 is clear), reads that opcode's operand bytes from the stream, and stores them into +$00-+$03 ready for chain 1 to act on next frame. The six active movement modes (chain 1) are: constant velocity ($C9, oss_op_velocity -- position += velocity every frame, no countdown); decelerate X/Y ($CA/$CB, oss_op_decel_x/oss_op_decel_y -- apply the current step, then look up the next speed from D272 indexed by the +$01/+$03 countdown value, negate it into the other axis's velocity, and count the countdown up toward zero); and three accelerate-X variants ($CC/$CD/$CE, oss_op_accel_x_a/ oss_op_accel_x_c/oss_op_accel_x_b -- apply the current step, look up the next speed from D272 the same way, and count +$02 down/up/down respectively). The "wait N frames" opcode ($CF) has no chain-1 handler of its own: chain 1 falls through to oss_countdown (oss_countdown), which just decrements +$01 and goes idle (opcode -> 0) when it reaches zero. D272 (oss_lookup_speed, oss_lookup_speed) is a 256-byte deceleration/ acceleration curve table indexed by the countdown value passed in C (B is temporarily zeroed for the table-relative add and shuttled through A so the caller's B -- the outer object-loop DJNZ counter -- survives the call); table[C] >> 2 gives that countdown step's velocity magnitude, shared by all five countdown-driven modes.
object_script_step C705 LD IX,$BB00
C709 LD DE,$0009
C70C LD B,$09
oss_object_loop C70E LD A,(IX+$00) Opcode 0 = idle -> fetch the next script opcode
C711 AND A
C712 JP Z,oss_fetch_opcode
C715 SUB $C9 Dispatch on the active movement-mode opcode
C717 JP Z,oss_op_velocity ($C9 = constant velocity)
C71A DEC A ($CA = decelerate X, via D272)
C71B JP Z,oss_op_decel_x
C71E DEC A ($CB = decelerate Y)
C71F JP Z,oss_op_decel_y
C722 DEC A ($CC = accelerate X)
C723 JP Z,oss_op_accel_x_a
C726 DEC A ($CD = accelerate X, variant c -- positive speed, counting up)
C727 JP Z,oss_op_accel_x_c
C72A DEC A ($CE = accelerate X, variant b -- positive speed, counting down)
C72B JP Z,oss_op_accel_x_b
C72E DEC A ($CF = "wait N frames": falls through to the
C72F JR NZ,oss_next_object countdown decrement below)
oss_countdown C731 DEC (IX+$01) Decrement the wait counter; when it reaches 0,
C734 JP NZ,oss_next_object go idle so the next frame fetches a new opcode
C737 LD (IX+$00),$00
oss_next_object C73B ADD IX,DE
C73D DJNZ oss_object_loop
C73F RET
oss_fetch_opcode C740 LD H,(IX+$05) HL = object's script cursor
C743 LD L,(IX+$04)
oss_fetch_opcode_cont C746 LD A,(HL) A = next script byte, advance the cursor
C747 INC HL
C748 AND A
C749 JP P,oss_op_immediate_step Sign bit clear ($00-$7F) -> immediate step opcode
C74C LD (IX+$00),A Sign bit set -> store as the new active opcode and
C74F SUB $C8 dispatch on which one it is
C751 JR Z,run_title_screen_9 ($C8 = set screen-row byte, 1 operand byte)
C753 DEC A ($C9 = set velocity, 2 operand bytes)
C754 JR Z,run_title_screen_7
C756 DEC A ($CA/$CB = decelerate, 3 operand bytes)
C757 JP Z,run_title_screen_11
C75A DEC A
C75B JR Z,run_title_screen_11
C75D DEC A ($CC/$CD/$CE = accelerate variants, 3 operand
C75E JR Z,run_title_screen_10 bytes)
C760 DEC A
C761 JR Z,run_title_screen_10
C763 DEC A
C764 JR Z,run_title_screen_10
C766 DEC A ($CF = "wait N frames", 1 operand byte)
C767 JR Z,oss_read_wait_operand
C769 DEC A ($D0 = jump to absolute position, 2 operand bytes)
C76A JR Z,run_title_screen_8
C76C SUB $02 ($D2 = end of script. No PUSH anywhere in this call
C76E JR NZ,oss_save_cursor chain, so the POP HL below pops object_script_step's own
C770 POP HL return address as data, and the RET after it
C771 RET returns to the frame *beneath* that -- unwinding past object_script_step's caller and out through titlescr_animate_frame's self-loop in one go, back to titlescr_animate_frame's own caller. Not "stop animating this object": this ends the whole frame and the whole per-frame animation loop.)
oss_save_cursor C772 LD (IX+$05),H Save the advanced script cursor back to the object
C775 LD (IX+$04),L record, then loop back to re-fetch/execute
C778 JP oss_object_loop
run_title_screen_7 C77B LD A,(HL) Read the 2 velocity operand bytes
C77C INC HL
C77D LD (IX+$02),A
C780 LD A,(HL)
C781 INC HL
C782 LD (IX+$03),A
oss_read_wait_operand C785 LD A,(HL) Read the 1-byte wait-count operand
C786 INC HL
C787 LD (IX+$01),A
C78A JP oss_save_cursor
oss_op_velocity C78D LD A,(IX+$02) Constant velocity: position += velocity each frame
C790 ADD A,(IX+$07)
C793 LD (IX+$07),A
C796 LD A,(IX+$03)
C799 ADD A,(IX+$08)
C79C LD (IX+$08),A
C79F JP oss_countdown
oss_apply_x_step C7A2 LD A,(IX+$02) Helper: X position += X velocity
C7A5 ADD A,(IX+$07)
C7A8 LD (IX+$07),A
C7AB RET
oss_apply_y_step C7AC LD A,(IX+$03) Helper: Y position += Y velocity
C7AF ADD A,(IX+$08)
C7B2 LD (IX+$08),A
C7B5 RET
run_title_screen_8 C7B6 LD A,(HL) Read the 2-byte absolute-position operand
C7B7 INC HL
C7B8 LD (IX+$07),A
C7BB LD A,(HL)
C7BC INC HL
C7BD LD (IX+$08),A
C7C0 JR oss_fetch_opcode_cont
run_title_screen_9 C7C2 LD A,(HL) Read the 1-byte screen-row operand
C7C3 INC HL
C7C4 LD (IX+$06),A
C7C7 JP oss_fetch_opcode_cont
run_title_screen_10 C7CA LD A,(HL) Read 3 operand bytes: velocity/counter pair for an
C7CB INC HL accelerate/decelerate opcode (order differs from
C7CC LD (IX+$02),A the $CA/$CB variant below)
C7CF LD A,(HL)
C7D0 INC HL
C7D1 LD (IX+$01),A
C7D4 LD A,(HL)
C7D5 INC HL
C7D6 LD (IX+$03),A
C7D9 JP oss_save_cursor
run_title_screen_11 C7DC LD A,(HL) Read 3 operand bytes for the $CA/$CB decelerate
C7DD INC HL opcodes
C7DE LD (IX+$03),A
C7E1 LD A,(HL)
C7E2 INC HL
C7E3 LD (IX+$01),A
C7E6 LD A,(HL)
C7E7 INC HL
C7E8 LD (IX+$02),A
C7EB JR oss_save_cursor
oss_op_decel_x C7ED CALL oss_apply_x_step Decelerate X: apply X step, look up the next speed
C7F0 LD C,(IX+$03) from D272 (indexed by the countdown value),
C7F3 CALL oss_lookup_speed negate it and add to Y velocity, then advance the
C7F6 NEG countdown
C7F8 ADD A,(IX+$08)
C7FB LD (IX+$08),A
C7FE INC (IX+$03)
C801 JP oss_countdown
oss_lookup_speed C804 LD A,B Helper: speed = D272[(B<<8 | C)] >> 2 -- decodes
C805 LD B,$00 a countdown value C into a velocity magnitude via
C807 LD HL,$D272 the shared deceleration curve table
C80A ADD HL,BC
C80B LD B,A
C80C LD A,(HL)
C80D SRL A
C80F SRL A
C811 RET
oss_op_decel_y C812 CALL oss_apply_x_step Decelerate Y (mirror of oss_op_decel_x for the Y axis)
C815 LD C,(IX+$03)
C818 CALL oss_lookup_speed
C81B ADD A,(IX+$08)
C81E LD (IX+$08),A
C821 DEC (IX+$03)
C824 JP oss_countdown
oss_op_accel_x_a C827 CALL oss_apply_y_step Accelerate/decelerate X variant (countdown-driven,
C82A LD C,(IX+$02) negated speed, counts down)
C82D CALL oss_lookup_speed
C830 NEG
C832 ADD A,(IX+$07)
C835 LD (IX+$07),A
C838 DEC (IX+$02)
C83B JP oss_countdown
oss_op_accel_x_b C83E CALL oss_apply_y_step Accelerate/decelerate X variant (countdown-driven,
C841 LD C,(IX+$02) positive speed, counts down)
C844 CALL oss_lookup_speed
C847 ADD A,(IX+$07)
C84A LD (IX+$07),A
C84D DEC (IX+$02)
C850 JP oss_countdown
oss_op_accel_x_c C853 CALL oss_apply_y_step Accelerate/decelerate X variant (countdown-driven,
C856 LD C,(IX+$02) positive speed, counts up)
C859 CALL oss_lookup_speed
C85C ADD A,(IX+$07)
C85F LD (IX+$07),A
C862 INC (IX+$02)
C865 JP oss_countdown
oss_op_immediate_step C868 LD C,A Immediate 2-axis step opcode ($00-$7F): the low 3
C869 AND $03 bits of each nibble-pair select a step magnitude
C86B BIT 2,C (0-3, doubled via SLA) and a sign bit chooses the
C86D JR Z,run_title_screen_12 direction for X (bits 0-2) and Y (bits 4-6, tested
C86F NEG via BIT 5) independently
run_title_screen_12 C871 SLA A
C873 ADD A,(IX+$07)
C876 LD (IX+$07),A
C879 LD A,C
C87A RRA
C87B RRA
C87C RRA
C87D AND $03
C87F BIT 5,C
C881 JR Z,run_title_screen_13
C883 NEG
run_title_screen_13 C885 SLA A
C887 ADD A,(IX+$08)
C88A LD (IX+$08),A
C88D JP oss_fetch_opcode_cont
Clear the screen bitmap ($4000-$57FF) and attribute area ($5800-$59FF) to zero. This entry point is used by the routine at insert_high_score_entry.
clear_playfield_and_attrs C890 LD HL,$5900 Clear attributes $5900-$5AFF (i.e. from the bottom
C893 LD DE,$5901 up), then... (Conv note for later C port: this
C896 LD (HL),L range actually only needs $5800-$59FF -- treat as
C897 LD BC,$01FF a plain memset)
C89A LDIR
C89C LD HL,$4800 ...clear the bitmap $4800-$57FF (leaving $4000-
C89F LD DE,$4801 $47FF, the top screen third, untouched here)
C8A2 LD BC,$0FFF
C8A5 LD (HL),L
C8A6 LDIR
C8A8 RET
Clear the bitmap/attributes (clear_playfield_and_attrs) then set the border attribute rows ($5900-$59FF) to a fixed pattern: black border rows top/bottom (2 bytes each) sandwiching 28 rows of attribute $45 (BRIGHT cyan on black: bit 6 set, ink 5, paper 0) per attribute-cell column, repeated across all 16 columns.
clear_and_fill_border_attrs C8A9 CALL clear_playfield_and_attrs
C8AC LD HL,$5900
C8AF LD C,$10 16 columns
C8B1 XOR A
run_title_screen_14 C8B2 LD (HL),A 2 black rows
C8B3 INC L
C8B4 LD (HL),A
C8B5 INC L
C8B6 LD B,$1C 28 rows of attribute $45
run_title_screen_15 C8B8 LD (HL),$45
C8BA INC L
C8BB DJNZ run_title_screen_15
C8BD LD (HL),A 2 more black rows
C8BE INC L
C8BF LD (HL),A
C8C0 INC HL
C8C1 DEC C
C8C2 JR NZ,run_title_screen_14
C8C4 RET
Compute the screen address and masked-sprite blitter parameters for a single title-screen glyph/object, then dispatch to a width-specific unrolled OR-blit routine (blit_masked_sprite_dispatch onward) which draws it into the playfield bitmap. Called (via EXX-banked BC/L, see ts_draw_fg_objects) once per foreground object per frame. B = object Y screen position (+$08, clamped to a max of $6F -- the overflow past $6F is banked via run_title_screen_16 and used later as a row-skip count), C = object X screen position (+$07), L = the object's screen row/height byte (+$06, set by the script's $C8 opcode). compute_glyph_blit_params- C8CC clamp B; run_title_screen_16-C8EC then build a masked-sprite screen destination address in D/E from B and C, using the same "rotate through carry, XOR, mask $F8, XOR" idiom the ROM uses to interleave a pixel row into the non-linear screen-third layout (not traced bit- exactly here: getting each rotate/XOR wrong silently corrupts only the row within a third, which is easy to get subtly wrong without emulator verification -- flag for later verification against a working build). C8ED-C8FE then use C (object X) and L (screen row/height byte) to index the 4-byte-per-entry glyph table at glyphs (mask width, byte-count, source address), giving B/C/HL = mask width, byte count, and source glyph address. C902 restores the row-clamp flag banked at run_title_screen_16: if set, the source address is used as-is; otherwise cgb_row_offset_loop-C916 (cgb_row_offset_loop) walks the source pointer forward row-by-row (stride = C<<1, i.e. mask-width*2 bytes/row) for the overflow-past-$6F row count, before falling into the blit dispatch at blit_masked_sprite_dispatch.
compute_glyph_blit_params C8C5 LD A,B
C8C6 CP $70
C8C8 JR C,run_title_screen_16
C8CA LD B,$6F
C8CC SUB B
run_title_screen_16 C8CD EX AF,AF'
C8CE LD A,$AF
C8D0 SUB B
C8D1 LD B,A
C8D2 AND A
C8D3 RRA
C8D4 SCF
C8D5 RRA
C8D6 AND A
C8D7 RRA
C8D8 XOR B
C8D9 AND $F8
C8DB XOR B
C8DC LD D,A
C8DD LD A,C
C8DE RLCA
C8DF RLCA
C8E0 RLCA
C8E1 XOR B
C8E2 AND $C7
C8E4 XOR B
C8E5 RLCA
C8E6 RLCA
C8E7 LD E,A
C8E8 LD A,C
C8E9 RRA
C8EA AND $03
C8EC ADD A,L
C8ED LD B,$00
C8EF SLA A
C8F1 SLA A
C8F3 LD C,A
C8F4 RL B
C8F6 LD HL,$D296
C8F9 ADD HL,BC
C8FA LD B,(HL) B/C/HL = mask width, byte-count, source address
C8FB INC HL from the glyphs glyph table entry
C8FC LD C,(HL)
C8FD INC HL
C8FE LD A,(HL)
C8FF INC HL
C900 LD H,(HL)
C901 LD L,A
C902 EX AF,AF' Restore the flag from the earlier RRA (row clamp)
C903 JP C,blit_masked_sprite_dispatch -- if set, skip straight to the blit dispatch
cgb_row_offset_loop C906 PUSH DE Otherwise walk the source pointer forward by one
C907 LD E,C row at a time (not traced bit-for-bit here) until
C908 SLA E the row-clamp countdown reaches zero
C90A SRL A
C90C LD D,$00
run_title_screen_17 C90E ADD HL,DE
C90F DEC B
C910 JR Z,cgb_row_offset_done
C912 DEC A
C913 JP NZ,run_title_screen_17
C916 POP DE
Masked-sprite blit dispatch: HL/DE hold the destination screen address (from the caller), the popped HL/DE here hold the source glyph address and its mask width in C. Dispatches to one of 6 width-specific unrolled OR-blit routines (blit_width1 .. blit_width7) which draw the glyph into the bitmap by treating the source address as a stack pointer (LD SP,HL) and POPping pixel/mask byte pairs -- see the project's known "LD SP,HL; POP x N sprite copy" translation pitfall. The real SP is saved at $C93D and restored by each blit routine (or by blit_abort_restore_sp on early abort) before returning.
blit_masked_sprite_dispatch C917 PUSH BC
C918 LD ($C93D),SP Save the real stack pointer
C91C LD SP,HL SP now points at the glyph source data
C91D EX DE,HL HL = destination screen address
C91E DEC C
C91F JP Z,blit_width1
C922 DEC C
C923 JP Z,blit_width2
C926 DEC C
C927 JP Z,blit_width3
C92A DEC C
C92B JP Z,blit_width4
C92E DEC C
C92F JP Z,blit_width5
C932 JP blit_width6
cgb_row_offset_done C935 POP DE
C936 LD B,$2D Small fixed delay (not blitting this frame --
run_title_screen_18 C938 DJNZ run_title_screen_18 object scrolled fully off, nothing to draw)
C93A JR cgb_delay_tail
Shared "abort the blit early" tail: restores the real SP (saved at $C93D by blit_masked_sprite_dispatch/blit_masked_sprite_dispatch_b) and returns to the object-draw loop.
blit_abort_restore_sp C93C LD SP,$0000 (operand self-modified by C918/C9B4)
C93F POP BC
cgb_delay_tail C940 LD A,B
C941 CPL
C942 ADD A,$0F
C944 RET NC
C945 RET Z
C946 LD C,$14
run_title_screen_19 C948 LD B,C
run_title_screen_20 C949 DJNZ run_title_screen_20
C94B DEC A
C94C JR NZ,run_title_screen_19
C94E RET
Second glyph blit-parameter computation, structurally identical to compute_glyph_blit_params above but feeding the alternate dispatch table at blit_masked_sprite_dispatch_b. Used for the 3 "background" objects (see ts_draw_bg_objects).
compute_glyph_blit_params_b C94F LD A,B
C950 CP $70
C952 JR C,run_title_screen_21
C954 LD B,$6F
C956 SUB B
run_title_screen_21 C957 EX AF,AF'
C958 LD A,$AF
C95A SUB B
C95B LD B,A
C95C AND A
C95D RRA
C95E SCF
C95F RRA
C960 AND A
C961 RRA
C962 XOR B
C963 AND $F8
C965 XOR B
C966 LD D,A
C967 LD A,C
C968 RLCA
C969 RLCA
C96A RLCA
C96B XOR B
C96C AND $C7
C96E XOR B
C96F RLCA
C970 RLCA
C971 LD E,A
C972 LD A,C
C973 RRA
C974 AND $03
C976 ADD A,L
C977 LD B,$00
C979 SLA A
C97B SLA A
C97D LD C,A
C97E RL B
C980 LD HL,$D296
C983 ADD HL,BC
C984 LD B,(HL)
C985 INC HL
C986 LD C,(HL)
C987 INC HL
C988 LD A,(HL)
C989 INC HL
C98A LD H,(HL)
C98B LD L,A
C98C EX AF,AF'
C98D JP C,blit_masked_sprite_dispatch_b
cgbb_row_offset_loop C990 PUSH DE
C991 EX AF,AF'
C992 LD E,C
C993 LD A,E
C994 SUB $06
C996 JR C,run_title_screen_22
C998 CPL
C999 ADD A,$03
C99B LD E,A
run_title_screen_22 C99C EX AF,AF'
C99D LD D,$00
C99F SLA E
C9A1 SRL A
C9A3 JR NZ,run_title_screen_23
C9A5 INC A
run_title_screen_23 C9A6 ADD HL,DE
C9A7 DEC B
C9A8 JR Z,run_title_screen_24
C9AA DEC A
C9AB JP NZ,run_title_screen_23
C9AE POP DE
Alternate masked-sprite blit dispatch used by the background-object draw path (ts_draw_bg_objects via compute_glyph_blit_params_b). Identical width-selection logic to blit_masked_sprite_dispatch, but preserves B (via an extra push/pop) as a small fixed delay count, and its early-exit tail (run_title_screen_24) simply restores DE rather than aborting via blit_abort_restore_sp.
blit_masked_sprite_dispatch_b C9AF LD A,B
C9B0 LD B,$14
C9B2 PUSH BC
C9B3 LD B,A
C9B4 LD ($C93D),SP
C9B8 LD SP,HL
C9B9 EX DE,HL
C9BA DEC C
C9BB JR Z,blit_width1
C9BD DEC C
C9BE JR Z,blit_width2
C9C0 DEC C
C9C1 JP Z,blit_width3
C9C4 DEC C
C9C5 JP Z,blit_width4
C9C8 DEC C
C9C9 JP Z,blit_width5
C9CC DEC C
C9CD JP Z,blit_width6
C9D0 JP blit_width7
run_title_screen_24 C9D3 POP DE
C9D4 RET
Width-1 (mask-only, no fill byte) unrolled OR-blit. blit_masked_sprite_dispatch/blit_masked_sprite_dispatch_b dispatch here when the width selector (C, decremented to 0) picks this variant. Draws 2 scanlines per POP DE: E blits into the current HL byte (LD A,(HL) / OR E / LD (HL),A -- a masked, non-overwriting sprite draw), then INC H steps to the next screen row; if that crosses a third boundary (H AND $07 = 0), the SUB $08 / ADD A,$20 / carry-adjust pattern at advance_glyph_scanline fixes up H/L, otherwise the row falls straight through. D then blits the same way into the following row, with its own boundary fix-up at run_title_screen_28. A short fixed delay (run_title_screen_26, a 30-iteration DEC-A loop) pads out the timing before DJNZ repeats for the next row-pair (B counts row-pairs); once B reaches 0, execution falls into blit_abort_restore_sp, which restores the real SP and returns.
blit_width1 C9D5 POP DE
C9D6 LD A,(HL)
C9D7 OR E
C9D8 LD (HL),A
C9D9 INC H
C9DA LD A,H
C9DB AND $07
C9DD JR Z,advance_glyph_scanline
run_title_screen_25 C9DF LD A,(HL)
C9E0 OR D
C9E1 LD (HL),A
C9E2 INC H
C9E3 LD A,H
C9E4 AND $07
C9E6 JR Z,run_title_screen_28
run_title_screen_26 C9E8 LD A,$1E
run_title_screen_27 C9EA DEC A
C9EB JP NZ,run_title_screen_27
C9EE DJNZ blit_width1
C9F0 JP blit_abort_restore_sp
advance_glyph_scanline C9F3 LD A,H
C9F4 SUB $08
C9F6 LD H,A
C9F7 LD A,L
C9F8 ADD A,$20
C9FA LD L,A
C9FB JP NC,run_title_screen_25
C9FE LD A,H
C9FF ADD A,$08
CA01 LD H,A
CA02 JP run_title_screen_25
run_title_screen_28 CA05 LD A,H
CA06 SUB $08
CA08 LD H,A
CA09 LD A,L
CA0A ADD A,$20
CA0C LD L,A
CA0D JP NC,run_title_screen_26
CA10 LD A,H
CA11 ADD A,$08
CA13 LD H,A
CA14 JP run_title_screen_26
Width-2 unrolled OR-blit variant (see blit_width1 for the general pattern).
blit_width2 CA17 POP DE
CA18 LD A,(HL)
CA19 OR E
CA1A LD (HL),A
CA1B INC L
CA1C LD A,(HL)
CA1D OR D
CA1E LD (HL),A
CA1F DEC L
CA20 INC H
CA21 LD A,H
CA22 AND $07
CA24 JR Z,run_title_screen_32
run_title_screen_29 CA26 POP DE
CA27 LD A,(HL)
CA28 OR E
CA29 LD (HL),A
CA2A INC L
CA2B LD A,(HL)
CA2C OR D
CA2D LD (HL),A
CA2E DEC L
CA2F INC H
CA30 LD A,H
CA31 AND $07
CA33 JR Z,run_title_screen_33
run_title_screen_30 CA35 LD A,$14
run_title_screen_31 CA37 DEC A
CA38 JP NZ,run_title_screen_31
CA3B DJNZ blit_width2
CA3D JP blit_abort_restore_sp
run_title_screen_32 CA40 LD A,H
CA41 SUB $08
CA43 LD H,A
CA44 LD A,L
CA45 ADD A,$20
CA47 LD L,A
CA48 JP NC,run_title_screen_29
CA4B LD A,H
CA4C ADD A,$08
CA4E LD H,A
CA4F JP run_title_screen_29
run_title_screen_33 CA52 LD A,H
CA53 SUB $08
CA55 LD H,A
CA56 LD A,L
CA57 ADD A,$20
CA59 LD L,A
CA5A JP NC,run_title_screen_30
CA5D LD A,H
CA5E ADD A,$08
CA60 LD H,A
CA61 JP run_title_screen_30
Width-3 unrolled OR-blit variant (see blit_width1 for the general pattern).
blit_width3 CA64 LD C,L
CA65 POP DE
CA66 LD A,(HL)
CA67 OR E
CA68 LD (HL),A
CA69 INC L
CA6A LD A,(HL)
CA6B OR D
CA6C LD (HL),A
CA6D INC L
CA6E POP DE
CA6F LD A,(HL)
CA70 OR E
CA71 LD (HL),A
CA72 LD L,C
CA73 INC H
CA74 LD A,H
CA75 AND $07
CA77 JR Z,run_title_screen_37
run_title_screen_34 CA79 LD C,L
CA7A LD A,(HL)
CA7B OR D
CA7C LD (HL),A
CA7D INC L
CA7E POP DE
CA7F LD A,(HL)
CA80 OR E
CA81 LD (HL),A
CA82 INC L
CA83 LD A,(HL)
CA84 OR D
CA85 LD (HL),A
CA86 LD L,C
CA87 INC H
CA88 LD A,H
CA89 AND $07
CA8B JR Z,run_title_screen_38
run_title_screen_35 CA8D LD A,$0A
run_title_screen_36 CA8F DEC A
CA90 JP NZ,run_title_screen_36
CA93 DJNZ blit_width3
CA95 JP blit_abort_restore_sp
run_title_screen_37 CA98 LD A,H
CA99 SUB $08
CA9B LD H,A
CA9C LD A,L
CA9D ADD A,$20
CA9F LD L,A
CAA0 JP NC,run_title_screen_34
CAA3 LD A,H
CAA4 ADD A,$08
CAA6 LD H,A
CAA7 JP run_title_screen_34
run_title_screen_38 CAAA LD A,H
CAAB SUB $08
CAAD LD H,A
CAAE LD A,L
CAAF ADD A,$20
CAB1 LD L,A
CAB2 JP NC,run_title_screen_35
CAB5 LD A,H
CAB6 ADD A,$08
CAB8 LD H,A
CAB9 JP run_title_screen_35
Width-4 unrolled OR-blit variant (see blit_width1 for the general pattern).
blit_width4 CABC LD C,L
CABD POP DE
CABE LD A,(HL)
CABF OR E
CAC0 LD (HL),A
CAC1 INC L
CAC2 LD A,(HL)
CAC3 OR D
CAC4 LD (HL),A
CAC5 INC L
CAC6 POP DE
CAC7 LD A,(HL)
CAC8 OR E
CAC9 LD (HL),A
CACA INC L
CACB LD A,(HL)
CACC OR D
CACD LD (HL),A
CACE LD L,C
CACF INC H
CAD0 LD A,H
CAD1 AND $07
CAD3 JR Z,run_title_screen_41
run_title_screen_39 CAD5 LD C,L
CAD6 POP DE
CAD7 LD A,(HL)
CAD8 OR E
CAD9 LD (HL),A
CADA INC L
CADB LD A,(HL)
CADC OR D
CADD LD (HL),A
CADE INC L
CADF POP DE
CAE0 LD A,(HL)
CAE1 OR E
CAE2 LD (HL),A
CAE3 INC L
CAE4 LD A,(HL)
CAE5 OR D
CAE6 LD (HL),A
CAE7 LD L,C
CAE8 INC H
CAE9 LD A,H
CAEA AND $07
CAEC JR Z,run_title_screen_42
run_title_screen_40 CAEE DJNZ blit_width4
CAF0 JP blit_abort_restore_sp
run_title_screen_41 CAF3 LD A,H
CAF4 SUB $08
CAF6 LD H,A
CAF7 LD A,L
CAF8 ADD A,$20
CAFA LD L,A
CAFB JP NC,run_title_screen_39
CAFE LD A,H
CAFF ADD A,$08
CB01 LD H,A
CB02 JP run_title_screen_39
run_title_screen_42 CB05 LD A,H
CB06 SUB $08
CB08 LD H,A
CB09 LD A,L
CB0A ADD A,$20
CB0C LD L,A
CB0D JP NC,run_title_screen_40
CB10 LD A,H
CB11 ADD A,$08
CB13 LD H,A
CB14 JP run_title_screen_40
Width-5 unrolled OR-blit variant (see blit_width1 for the general pattern).
blit_width5 CB17 LD C,L
CB18 POP DE
CB19 LD A,(HL)
CB1A OR E
CB1B LD (HL),A
CB1C INC L
CB1D LD A,(HL)
CB1E OR D
CB1F LD (HL),A
CB20 INC L
CB21 POP DE
CB22 LD A,(HL)
CB23 OR E
CB24 LD (HL),A
CB25 INC L
CB26 LD A,(HL)
CB27 OR D
CB28 LD (HL),A
CB29 INC L
CB2A POP DE
CB2B LD A,(HL)
CB2C OR E
CB2D LD (HL),A
CB2E LD L,C
CB2F INC H
CB30 LD A,H
CB31 AND $07
CB33 JR Z,run_title_screen_45
run_title_screen_43 CB35 LD C,L
CB36 LD A,(HL)
CB37 OR D
CB38 LD (HL),A
CB39 INC L
CB3A POP DE
CB3B LD A,(HL)
CB3C OR E
CB3D LD (HL),A
CB3E INC L
CB3F LD A,(HL)
CB40 OR D
CB41 LD (HL),A
CB42 INC L
CB43 POP DE
CB44 LD A,(HL)
CB45 OR E
CB46 LD (HL),A
CB47 INC L
CB48 LD A,(HL)
CB49 OR D
CB4A LD (HL),A
CB4B LD L,C
CB4C INC H
CB4D LD A,H
CB4E AND $07
CB50 JR Z,run_title_screen_46
run_title_screen_44 CB52 DJNZ blit_width5
CB54 JP blit_abort_restore_sp
run_title_screen_45 CB57 LD A,H
CB58 SUB $08
CB5A LD H,A
CB5B LD A,L
CB5C ADD A,$20
CB5E LD L,A
CB5F JP NC,run_title_screen_43
CB62 LD A,H
CB63 ADD A,$08
CB65 LD H,A
CB66 JP run_title_screen_43
run_title_screen_46 CB69 LD A,H
CB6A SUB $08
CB6C LD H,A
CB6D LD A,L
CB6E ADD A,$20
CB70 LD L,A
CB71 JP NC,run_title_screen_44
CB74 LD A,H
CB75 ADD A,$08
CB77 LD H,A
CB78 JP run_title_screen_44
Width-6 unrolled OR-blit variant (see blit_width1 for the general pattern).
blit_width6 CB7B POP DE
CB7C LD A,(HL)
CB7D OR E
CB7E LD (HL),A
CB7F INC L
CB80 LD A,(HL)
CB81 OR D
CB82 LD (HL),A
CB83 DEC L
CB84 INC H
CB85 LD A,H
CB86 AND $07
CB88 JR Z,run_title_screen_49
run_title_screen_47 CB8A POP DE
CB8B LD A,(HL)
CB8C OR E
CB8D LD (HL),A
CB8E INC L
CB8F LD A,(HL)
CB90 OR D
CB91 LD (HL),A
CB92 DEC L
CB93 INC H
CB94 LD A,H
CB95 AND $07
CB97 JR Z,run_title_screen_50
run_title_screen_48 CB99 DJNZ blit_width6
CB9B LD HL,($C93D)
CB9E LD SP,HL
CB9F POP BC
CBA0 RET
run_title_screen_49 CBA1 LD A,H
CBA2 SUB $08
CBA4 LD H,A
CBA5 LD A,L
CBA6 ADD A,$20
CBA8 LD L,A
CBA9 JP NC,run_title_screen_47
CBAC LD A,H
CBAD ADD A,$08
CBAF LD H,A
CBB0 JP run_title_screen_47
run_title_screen_50 CBB3 LD A,H
CBB4 SUB $08
CBB6 LD H,A
CBB7 LD A,L
CBB8 ADD A,$20
CBBA LD L,A
CBBB JP NC,run_title_screen_48
CBBE LD A,H
CBBF ADD A,$08
CBC1 LD H,A
CBC2 JP run_title_screen_48
Width-7 unrolled OR-blit variant (see blit_width1 for the general pattern); the widest/final entry in the dispatch chain, reached when C reaches 0 without matching any of the DEC C; JP Z tests above.
blit_width7 CBC5 POP DE
CBC6 LD A,(HL)
CBC7 OR E
CBC8 LD (HL),A
CBC9 INC H
CBCA LD A,H
CBCB AND $07
CBCD JR Z,run_title_screen_53
run_title_screen_51 CBCF LD A,(HL)
CBD0 OR D
CBD1 LD (HL),A
CBD2 INC H
CBD3 LD A,H
CBD4 AND $07
CBD6 JR Z,run_title_screen_54
run_title_screen_52 CBD8 DJNZ blit_width7
CBDA LD HL,($C93D) Restore the real SP (saved by blit_masked_sprite_dispatch/blit_masked_sprite_dispatch_b)
CBDD LD SP,HL
CBDE POP BC
CBDF RET
run_title_screen_53 CBE0 LD A,H
CBE1 SUB $08
CBE3 LD H,A
CBE4 LD A,L
CBE5 ADD A,$20
CBE7 LD L,A
CBE8 JP NC,run_title_screen_51
CBEB LD A,H
CBEC ADD A,$08
CBEE LD H,A
CBEF JP run_title_screen_51
run_title_screen_54 CBF2 LD A,H
CBF3 SUB $08
CBF5 LD H,A
CBF6 LD A,L
CBF7 ADD A,$20
CBF9 LD L,A
CBFA JP NC,run_title_screen_52
CBFD LD A,H
CBFE ADD A,$08
CC00 LD H,A
CC01 JP run_title_screen_52
Clear the playfield bitmap ($4800-$57FF, i.e. the lower two screen thirds, excluding the top third at $4000-$47FF which holds the fixed title/credits text) to zero, ready for the next frame's objects. Classic "LD SP,HL; PUSH x N" fast-fill trick (see the project's known translation pitfall of the same name): SP is repointed at the bitmap, then 14 PUSH DE instructions fill 28 bytes per pass, DJNZ-looped 8 times per screen-third row (C = 8, giving 8 x 28 = 224 bytes per call through the inner loop, i.e. one attribute-row's worth), stepping through third-boundary wraps via the usual ADD A,$20 / carry pattern. The real SP is saved at $CC4D and restored before returning.
clear_playfield_buffer CC04 LD ($CC4D),SP Save the real stack pointer
CC08 LD HL,$481E
CC0B LD DE,$0000
CC0E LD C,$08
cpb_third1_row_loop CC10 LD B,C
cpb_third1_fill CC11 LD SP,HL
CC12 PUSH DE
CC13 PUSH DE
CC14 PUSH DE
CC15 PUSH DE
CC16 PUSH DE
CC17 PUSH DE
CC18 PUSH DE
CC19 PUSH DE
CC1A PUSH DE
CC1B PUSH DE
CC1C PUSH DE
CC1D PUSH DE
CC1E PUSH DE
CC1F PUSH DE
CC20 INC H
CC21 DJNZ cpb_third1_fill
CC23 LD H,$48
CC25 LD A,L
CC26 ADD A,$20
CC28 LD L,A
CC29 JP NC,cpb_third1_row_loop
CC2C LD H,$50 Move on to the second screen third ($5000-$57FF)
cpb_third2_row_loop CC2E LD B,C
cpb_third2_fill CC2F LD SP,HL
CC30 PUSH DE
CC31 PUSH DE
CC32 PUSH DE
CC33 PUSH DE
CC34 PUSH DE
CC35 PUSH DE
CC36 PUSH DE
CC37 PUSH DE
CC38 PUSH DE
CC39 PUSH DE
CC3A PUSH DE
CC3B PUSH DE
CC3C PUSH DE
CC3D PUSH DE
CC3E INC H
CC3F DJNZ cpb_third2_fill
CC41 LD H,$50
CC43 LD A,L
CC44 ADD A,$20
CC46 LD L,A
CC47 CP $A0
CC49 JP C,cpb_third2_row_loop
CC4C LD SP,$0000 Restore the real stack pointer (operand self-
CC4F RET modified by $CC04) and return
Prev: C3AF Up: Map Next: CC50