; Time Pilot (Konami, 1982). A free-roaming aerial shooter: your fighter holds
; the centre of the screen and turns to face the way you steer, while the
; whole world scrolls and banks around it, and you gun down swarms of enemy
; craft and the boss mother-ship that anchors each wave. Clearing a wave
; carries you forward through five eras of flight, each faster and more
; crowded than the last; parachutists drifting down are worth extra points if
; you collect them. Run out of fighters to end the game.
;
; Architecture: on reset ($0000) the CPU jumps to
; seatTheStackAndSettleTheControlLatch ($07B1). What follows is the code
; reached from the reset and interrupt entry points, shown as instructions;
; spans never reached appear as data (the "---- data ----" blocks).
; a bare transfer to 0x07B1 and no return; no cell is read or written and
; no register moves
trampolineToSeatTheStackAndSettleTheControlLatch:
0000: C3 B1 07 JP seatTheStackAndSettleTheControlLatch;
; ---- $0003-$0007: data ----
0003: FF FF FF 33 4B
; step a table pointer on by an index and return the byte it lands on,
; leaving the pointer at that entry
fetchTableByte:
0008: 85 ADD A,L ; step the byte-table pointer on by the index
0009: 6F LD L,A
000A: 30 01 JR NC,loc_000d ; no carry out of the low byte -- the pointer's high half stands
000C: 24 INC H ; carry into the table pointer's high byte
loc_000d:
000D: 7E LD A,(HL) ; read the byte the pointer now lands on
000E: C9 RET
; ---- $000F-$000F: data ----
000F: 4F
; fetch the two-byte entry an index selects from a word table and hand
; back both the word and the address past it
fetchTableWord:
0010: 87 ADD A,A ; double the entry number to reach its two-byte entry
0011: DF RST $18 ; add that offset onto the word-table pointer
0012: 5E LD E,(HL) ; read the entry's low byte
0013: 23 INC HL
0014: 56 LD D,(HL) ; and its high byte
0015: 23 INC HL ; leave the pointer just past the entry
0016: C9 RET
; ---- $0017-$0017: data ----
0017: 4E
; move a 16-bit address forward by an unsigned byte offset, echoing the
; low half of the result back
offsetAddress:
0018: 85 ADD A,L ; add the unsigned byte offset onto the address
0019: 6F LD L,A
001A: D0 RET NC ; no carry -- the address's high half stands
001B: 24 INC H ; carry into the address's high byte
001C: C9 RET
; ---- $001D-$001F: data ----
001D: FF FF 41
; step the character-cell cursor on to the next cell of the line being
; drawn
advanceCharCursor:
0020: 7B LD A,E
0021: D6 20 SUB $20 ; drop the cursor thirty-two addresses on to the next cell of the line
0023: 5F LD E,A
0024: D0 RET NC ; no borrow -- the cursor's high byte stands
0025: 15 DEC D ; borrow into the cursor's high byte
0026: C9 RET
; ---- $0027-$0027: data ----
0027: 4D
; step the character-cell cursor one cell back along the line being drawn,
; the inverse of the advance vector
retreatCharCursor:
0028: 7B LD A,E
0029: C6 20 ADD A,$20 ; push the cursor thirty-two addresses back, one cell along the line
002B: 5F LD E,A
002C: D0 RET NC ; no carry -- the cursor's high byte stands
002D: 14 INC D ; carry into the cursor's high byte
002E: C9 RET
; ---- $002F-$002F: data ----
002F: 49
loc_0030:
0030: E1 POP HL ; take the inline word table the caller left behind -- its return address points straight at it
0031: D7 RST $10 ; index that table by the selector in A and read the entry it picks
0032: EB EX DE,HL ; bring the selected arm's address into the jump register
0033: E9 JP (HL) ; jump to the selected arm -- control never returns to the table
; ---- $0034-$0037: data ----
0034: FF FF FF FF
; queue a command byte and its argument in the command ring, dropping the
; pair when the cursor's cell is still occupied
postCommand:
0038: E5 PUSH HL
0039: 26 AC LD H,$AC ; point the high byte at the command ring's page
003B: 3A B2 A9 LD A,(workRam+1B2) ; fetch the ring's write cursor
003E: 6F LD L,A ; complete the pointer to the target cell
003F: CB 7E BIT 7,(HL) ; is that cell free? -- a free cell carries its high bit set
0041: 28 0A JR Z,loc_004d ; still occupied -- drop this command and return
0043: 72 LD (HL),D ; write the command byte
0044: 2C INC L
0045: 73 LD (HL),E ; write its argument
0046: 2C INC L
0047: 7D LD A,L
0048: E6 3F AND $3F ; wrap the cursor inside the sixty-four-cell ring
004A: 32 B2 A9 LD (workRam+1B2),A ; store the advanced write cursor
loc_004d:
004D: E1 POP HL
004E: C9 RET
; ---- $004F-$0065: data ----
004F: 0F A7 11 ED 77 68 D7 34 F1 D7 A5 3B 7C FD 3B 7D
005F: F1 DC A5 8C 57 34 B9
; the per-frame (vblank) interrupt vector: hardware dispatches it once per
; interrupt and it transfers straight to the frame-service handler at
; 0x00d8, writing nothing of its own
enterVblankInterrupt:
0066: C3 D8 00 JP saveAccumulatorForFrameInterrupt; hand the per-frame interrupt straight on to the frame-service handler
; cold-start clear reached once at boot via 0x07B1: kicks the watchdog
; four times, zeroes the 0xB410 sprite-bank run and the whole 2 KB work
; RAM, sums the fixed 256-byte program run at 0x00D8 and runs the frame
; service out of band on a non-genuine total, then hands off to the
; screen-RAM clear and image verify
clearWorkRamAndSpriteBanksThenColdInit:
0069: 32 00 C2 LD (dsw1),A ; kick the watchdog
006C: 21 11 B4 LD HL,$B411 ; point at the sprite-bank run
006F: 06 30 LD B,$30 ; forty-eight bytes of it to clear
loc_0071:
0071: 36 00 LD (HL),$00 ; clear this sprite-bank byte
0073: 23 INC HL
0074: 10 FB DJNZ loc_0071 ; on across the whole run
0076: 32 00 C2 LD (dsw1),A ; kick the watchdog
0079: 21 10 B4 LD HL,$B410 ; point at the second sprite bank
007C: 06 30 LD B,$30 ; another forty-eight bytes to clear
loc_007e:
007E: 36 00 LD (HL),$00 ; clear this sprite-bank byte
0080: 23 INC HL
0081: 10 FB DJNZ loc_007e ; on across the run
0083: 32 00 C2 LD (dsw1),A ; kick the watchdog
0086: 21 00 A8 LD HL,$A800 ; point at the base of work RAM
0089: 11 01 A8 LD DE,$A801 ; one cell on -- the block-fill destination
008C: 01 FF 07 LD BC,$07FF ; two kilobytes, less the seed cell, to clear
008F: 36 00 LD (HL),$00 ; seed the first cell with zero
0091: ED B0 LDIR ; copy the zero forward through the whole of work RAM
0093: 32 00 C2 LD (dsw1),A ; kick the watchdog
0096: 06 00 LD B,$00 ; two hundred fifty-six bytes to fold
0098: 21 D8 00 LD HL,$00D8 ; point at the fixed program run to fold -- the frame service's own bytes
009B: AF XOR A ; clear the running total
loc_009c:
009C: 86 ADD A,(HL) ; fold this byte into the total
009D: 23 INC HL
009E: 10 FC DJNZ loc_009c ; on across all two hundred fifty-six bytes
00A0: D6 87 SUB $87 ; weigh the total against a genuine image's value
00A2: C4 D8 00 CALL NZ,saveAccumulatorForFrameInterrupt; on a tampered image, run the frame service out of band
00A5: C3 66 58 JP clearScreenRamAndVerifyImageThenColdInit; hand off to the screen-RAM clear and image verify
; bring the machine up and never come back: set the interrupt-enable bit
; of the output latch from the low bit of the byte the caller carries, pet
; the watchdog, and fall into the foreground loop -- neither store reaches
; work RAM, and there is no return path
enableInterruptAndEnterForegroundLoop:
00A8: 32 00 C3 LD (in0),A ; raise the interrupt-enable line from the low bit the caller carries
00AB: 32 00 C2 LD (dsw1),A ; kick the watchdog
00AE: C3 93 0B JP runCommandRingDrainLoop; fall into the foreground command loop, never to return
; tile the character plane with a lattice of boxes -- fourteen bands of
; sixteen, each box two cells wide and two lines deep, every one of them
; laid down by stampGridBox -- walking a cursor that starts a full line
; above the first band it writes and skips a line before each band, so the
; lattice keeps clear of the top of the plane and its bands come out
; contiguous; every position is counted out here and nothing is read to
; decide where a box goes
tileCharPlaneWithBoxLattice:
00B1: 21 20 A4 LD HL,$A420 ; point the cursor one line above the lattice's first band
00B4: 0E 0E LD C,$0E ; fourteen bands to lay down
loc_00b6:
00B6: 11 20 00 LD DE,$0020
00B9: 19 ADD HL,DE ; skip a line before this band -- keeps the lattice off the top and spaces the bands
00BA: 06 10 LD B,$10 ; sixteen boxes across the band
loc_00bc:
00BC: CD C7 00 CALL stampGridBox ; stamp one box at the cursor
00BF: 23 INC HL ; step the cursor two cells on to the next box
00C0: 23 INC HL
00C1: 10 F9 DJNZ loc_00bc ; on across all sixteen boxes of the band
00C3: 0D DEC C ; one band done
00C4: 20 F0 JR NZ,loc_00b6 ; go lay the next band
00C6: C9 RET
; lay the four corner tiles of one hollow sixteen-by-sixteen box into the
; character plane at the cursor -- two cells across and two rows down --
; and give the cursor back unmoved
stampGridBox:
00C7: E5 PUSH HL ; remember the cursor so it comes back unmoved
00C8: 36 56 LD (HL),$56 ; stamp the box's top-left corner tile
00CA: 23 INC HL
00CB: 36 83 LD (HL),$83 ; stamp the top-right corner, one cell along
00CD: 11 1F 00 LD DE,$001F ; step down to the row below
00D0: 19 ADD HL,DE
00D1: 36 C7 LD (HL),$C7 ; stamp the bottom-left corner
00D3: 23 INC HL
00D4: 36 EF LD (HL),$EF ; stamp the bottom-right corner
00D6: E1 POP HL ; put the cursor back where it was found
00D7: C9 RET
; one byte, push af, falling into the register-save prologue at 0x00D9
; that owns the rest of the frame service and the frame's work; the two
; bytes it stacks land in work RAM, so they are part of what the machine
; leaves behind
saveAccumulatorForFrameInterrupt:
00D8: F5 PUSH AF ; save the interrupted program's registers -- both banks and the index pair
loc_00d9:
00D9: C5 PUSH BC
00DA: D5 PUSH DE
00DB: E5 PUSH HL
00DC: 08 EX AF,AF'
00DD: D9 EXX
00DE: F5 PUSH AF
00DF: C5 PUSH BC
00E0: D5 PUSH DE
00E1: E5 PUSH HL
00E2: DD E5 PUSH IX
00E4: FD E5 PUSH IY
00E6: CD 65 03 CALL publishSpriteShadow ; copy the sprite shadow out to the hardware first, so the picture lands during blanking
00E9: CD 86 52 CALL drainBothDeferredCellLists; flush both deferred character-cell lists into the planes
00EC: AF XOR A
00ED: 32 00 C3 LD (in0),A ; disarm this interrupt's own enable line
00F0: 32 00 C2 LD (dsw1),A ; kick the watchdog
00F3: 3C INC A
00F4: 32 87 A9 LD (workRam+187),A ; provisionally mark the screen to flip this frame
00F7: 3A 32 AD LD A,(workRam+532) ; read the cabinet flip setting
00FA: A7 AND A
00FB: 28 09 JR Z,loc_0106 ; setting clear: leave the flip mark as it stands
00FD: 3A C2 A9 LD A,(workRam+1C2) ; read which player is up
0100: A7 AND A
0101: 20 03 JR NZ,loc_0106 ; the second player is up: leave the flip mark as it stands
0103: 32 87 A9 LD (workRam+187),A ; first player on a flip-enabled cabinet: clear the flip mark
loc_0106:
0106: 3A 87 A9 LD A,(workRam+187) ; take the frame's flip decision
0109: 32 02 C3 LD ($C302),A ; drive the screen-flip latch line
010C: 3A 00 C2 LD A,(dsw1) ; read the dip-switch bank
010F: 2F CPL ; invert it -- the ports read active-low
0110: 32 AD A9 LD (workRam+1AD),A ; store the mirror the game reads instead of the live port
0113: 3A 00 C3 LD A,(in0) ; read the player-one controls
0116: 2F CPL
0117: 32 AE A9 LD (workRam+1AE),A ; store the player-one-control mirror
011A: 3A 20 C3 LD A,(in1) ; read the next input port
011D: 2F CPL
011E: 32 AF A9 LD (workRam+1AF),A ; store its mirror
0121: 3A 40 C3 LD A,(in2) ; read the next input port
0124: 2F CPL
0125: 32 B0 A9 LD (workRam+1B0),A ; store its mirror
0128: 3A 60 C3 LD A,(dsw0) ; read the last input port
012B: 2F CPL
012C: 32 B1 A9 LD (workRam+1B1),A ; store its mirror
012F: 21 80 A9 LD HL,$A980
0132: 34 INC (HL) ; bump the plain binary frame counter
0133: 21 CE A9 LD HL,$A9CE
0136: 7E LD A,(HL)
0137: 3C INC A ; step the decimal frame counter
0138: 27 DAA ; keep it counting in decimal
0139: 77 LD (HL),A
013A: 21 17 A8 LD HL,$A817 ; point at the first frame-countdown timer
013D: 7E LD A,(HL)
013E: A7 AND A
013F: 28 01 JR Z,loc_0142 ; already run out: leave it at zero
0141: 35 DEC (HL) ; otherwise tick it down one frame
loc_0142:
0142: 21 12 A8 LD HL,$A812 ; point at the second countdown timer
0145: 7E LD A,(HL)
0146: A7 AND A
0147: 28 01 JR Z,loc_014a ; already at zero: leave it
0149: 35 DEC (HL) ; tick it down one frame
loc_014a:
014A: 21 F4 A8 LD HL,$A8F4 ; point at the third countdown timer
014D: 7E LD A,(HL)
014E: A7 AND A
014F: 28 01 JR Z,loc_0152 ; already at zero: leave it
0151: 35 DEC (HL) ; tick it down one frame
loc_0152:
0152: CD BE 48 CALL serviceCoinInputs ; run the coin service
0155: 21 74 01 LD HL,$0174
0158: E5 PUSH HL ; push the epilogue's address as the dispatched arm's return
0159: 3A AB A9 LD A,(workRam+1AB) ; read the game-mode selector
015C: E6 03 AND $03 ; keep its low two bits -- one of four modes
015E: F7 RST $30 ; dispatch through the inline table that follows, entering the selected mode handler
; ---- $015F-$0166: jump table ----
015F: C2 15 51 16 FE 17 1F 0F
loc_0167:
0167: 6F LD L,A
0168: A6 AND (HL)
0169: 14 INC D
016A: 88 ADC A,B
016B: 57 LD D,A
016C: A5 AND L
016D: BF CP A
016E: 34 INC (HL)
016F: D7 RST $10
0170: F1 POP AF
0171: 96 SUB (HL)
0172: F1 POP AF
0173: B9 CP C
loc_0174:
0174: CD D4 55 CALL sendOldestQueuedSoundCommand; hand one queued byte off to the sound processor
0177: FD E1 POP IY
0179: DD E1 POP IX
017B: E1 POP HL
017C: D1 POP DE
017D: C1 POP BC
017E: F1 POP AF
017F: D9 EXX
0180: 08 EX AF,AF'
0181: E1 POP HL
0182: D1 POP DE
0183: C1 POP BC
0184: 3A 00 16 LD A,(rom+1600) ; read the interrupt-enable pattern from the program image
0187: 32 00 C3 LD (in0),A ; re-arm this interrupt for the next frame
018A: F1 POP AF
018B: C9 RET
; fetch the word an index selects from a word table, with the index
; doubling carrying into the high byte so the table may run past the reach
; of its narrow sibling
fetchWideTableWord:
018C: 87 ADD A,A ; double the index to reach its two-byte entry
018D: 30 01 JR NC,loc_0190 ;
018F: 24 INC H ; the doubling overflowed -- carry into the table's high byte so it may run past 256 entries
loc_0190:
0190: 85 ADD A,L ; add the doubled index onto the table pointer
0191: 6F LD L,A
0192: 30 01 JR NC,loc_0195 ;
0194: 24 INC H ; carry into the pointer's high byte
loc_0195:
0195: 5E LD E,(HL) ; read the entry's low byte
0196: 23 INC HL
0197: 56 LD D,(HL) ; and its high byte
0198: 23 INC HL ; leave the pointer just past the entry
0199: C9 RET
; seat the character-plane wipe on the plane's very first cell and put a
; whole plane's worth of lines against the counter beside it, so the next
; pass starts at the top with everything still to do; then fold a fixed
; 240-byte run of the program image into one eight-bit total and, on
; anything but the total a genuine image gives, transfer into bytes that
; carry no routine. The wipe is armed EITHER WAY -- the fold gates nothing
; above it, and the run it folds lies elsewhere in the image and has
; nothing to do with the wipe -- so a reader who takes this for a guarded
; arm will be wrong on every dispatch
armWholePlaneWipeThenDerailOnATamperedImage:
019A: 21 00 A4 LD HL,$A400 ; the character plane's first cell
019D: 22 89 A9 LD (workRam+189),HL ; seat the wipe cursor there
01A0: 3E 20 LD A,$20 ; thirty-two lines -- the whole plane
01A2: 32 88 A9 LD (workRam+188),A ; set that as the lines-left count
01A5: 06 F0 LD B,$F0 ; two hundred forty bytes of the image to fold
01A7: 21 A5 4B LD HL,$4BA5 ; point at the checked run of the program image
01AA: AF XOR A ; clear the running total
loc_01ab:
01AB: 86 ADD A,(HL) ; fold this byte into the total
01AC: 23 INC HL
01AD: 10 FC DJNZ loc_01ab ; on across all two hundred forty bytes
01AF: D6 11 SUB $11 ; weigh the total against a genuine image's value
01B1: C4 67 01 CALL NZ,loc_0167 ; on a tampered image, transfer into bytes that carry no routine
01B4: C9 RET
; arm the character-plane wipe to start at the plane's fifth cell and to
; run for a count taken from a fixed cell of the program image rather than
; carried as an immediate; neither armed cell is read here, and nothing a
; caller held survives into either
armLineWipeFromFifthLine:
01B5: 21 04 A4 LD HL,$A404 ; the plane's fifth cell, where this wipe starts
01B8: 22 89 A9 LD (workRam+189),HL ; seat the wipe cursor there
01BB: 3A CD 0C LD A,(rom+CCD) ; take the line count from a fixed cell of the program image
01BE: 32 88 A9 LD (workRam+188),A ; set it as the lines-left count
01C1: C9 RET
; blank one line of the character plane in both planes, step the wipe's
; cursor on to the next line, and count the lines still owed down by one;
; the zero test is left in the flags for the caller
blankNextLine:
01C2: 2A 89 A9 LD HL,(workRam+189) ; fetch the wipe cursor -- where this line starts
01C5: 06 20 LD B,$20 ; thirty-two cells across the line
01C7: 11 20 00 LD DE,$0020 ; the step from one cell to the next
loc_01ca:
01CA: 36 F1 LD (HL),$F1 ; lay a blank glyph in this cell
01CC: CB 94 RES 2,H ; cross to the colour plane for the same cell
01CE: 36 10 LD (HL),$10 ; set its colour
01D0: CB D4 SET 2,H ; cross back to the glyph plane
01D2: 19 ADD HL,DE ; step to the next cell of the line
01D3: 10 F5 DJNZ loc_01ca ; on across all thirty-two cells
01D5: 2A 89 A9 LD HL,(workRam+189) ; reload the wipe cursor
01D8: 23 INC HL ; step it on to the start of the next line
01D9: 22 89 A9 LD (workRam+189),HL ; store the advanced cursor
01DC: 21 88 A9 LD HL,$A988
01DF: 35 DEC (HL) ; count the lines still owed down by one
01E0: C9 RET
; put the cell-stamping pen back at the start of its route -- leg index to
; zero and both coordinates to the route's first point, each written a
; word at a time so the whole-cell part and the fraction below it land
; together, and each lifted out of a fixed pair of program bytes rather
; than carried as a literal -- then fold a fixed 256-byte run of the image
; into one eight-bit total and, on anything but the total a genuine image
; gives, transfer into the cold start, which clears the work RAM the stack
; sits on and never comes back here. The arming is unconditional: the fold
; gates nothing above it
armThePenRouteThenColdStartOnATamperedImage:
01E1: AF XOR A
01E2: 32 E2 A9 LD (workRam+1E2),A ; send the pen back to leg zero of its route
01E5: 2A 45 0D LD HL,(rom+D45) ; the route's first row coordinate, from the program image
01E8: 22 E3 A9 LD (workRam+1E3),HL ; seat the pen's row -- whole cell and fraction together
01EB: 2A 0C 28 LD HL,(rom+280C) ; the route's first column coordinate
01EE: 22 E5 A9 LD (workRam+1E5),HL ; seat the pen's column the same way
01F1: 06 00 LD B,$00 ; two hundred fifty-six bytes to fold
01F3: 21 33 0E LD HL,$0E33 ; point at the checked run of the program image
01F6: AF XOR A ; clear the running total
loc_01f7:
01F7: 86 ADD A,(HL) ; fold this byte into the total
01F8: 23 INC HL
01F9: 10 FC DJNZ loc_01f7 ; on across all two hundred fifty-six bytes
01FB: D6 FD SUB $FD ; weigh the total against a genuine image's value
01FD: C4 69 00 CALL NZ,clearWorkRamAndSpriteBanksThenColdInit; on a tampered image, drop into the cold start, which never returns here
0200: C9 RET
; draw one interpolated run of pen-glyph cells from the current row/column
; toward a target pair (signed per-step increment (target-current)>>4),
; stamping each cell until the stamped video cell hits the run's end cell,
; then advance the run index, load the next run's endpoint from the word
; table at 0x0290, reseat the pen, and leave Z set when the new row
; integer is 0 (callers ret nz on it)
drawInterpolatedPenRun:
0201: CD 6F 02 CALL plotPenCell ; stamp the pen glyph at the run's start cell
0204: 2A F5 32 LD HL,(rom+32F5) ; the run's target row, from the program image
0207: ED 4B E3 A9 LD BC,(workRam+1E3) ; the pen's current row
020B: A7 AND A
020C: ED 42 SBC HL,BC ; target row minus current row
020E: 29 ADD HL,HL
020F: 29 ADD HL,HL
0210: 29 ADD HL,HL
0211: 29 ADD HL,HL ; the difference times sixteen
0212: 3E 00 LD A,$00
0214: DE 00 SBC A,$00 ; carry the difference's sign into the top byte
0216: 6C LD L,H
0217: 67 LD H,A ; keep the top byte, sign-extended -- the per-step row increment
0218: 22 E7 A9 LD (workRam+1E7),HL ; store the row step
021B: 2A 45 0B LD HL,(rom+B45) ; the run's target column
021E: ED 4B E5 A9 LD BC,(workRam+1E5) ; the pen's current column
0222: A7 AND A
0223: ED 42 SBC HL,BC ; target column minus current column
0225: 29 ADD HL,HL
0226: 29 ADD HL,HL
0227: 29 ADD HL,HL
0228: 29 ADD HL,HL ; the difference times sixteen
0229: 3E 00 LD A,$00
022B: DE 00 SBC A,$00 ; carry the difference's sign into the top byte
022D: 6C LD L,H
022E: 67 LD H,A ; keep the top byte, sign-extended -- the per-step column increment
022F: 22 E9 A9 LD (workRam+1E9),HL ; store the column step
loc_0232:
0232: 2A E3 A9 LD HL,(workRam+1E3) ; take the pen's current row
0235: ED 4B E7 A9 LD BC,(workRam+1E7) ; the row step
0239: 09 ADD HL,BC ; advance the row by one step
023A: 22 E3 A9 LD (workRam+1E3),HL ; store it back
023D: 2A E5 A9 LD HL,(workRam+1E5) ; take the pen's current column
0240: ED 4B E9 A9 LD BC,(workRam+1E9) ; the column step
0244: 09 ADD HL,BC ; advance the column by one step
0245: 22 E5 A9 LD (workRam+1E5),HL ; store it back
0248: CD 6F 02 CALL plotPenCell ; stamp the pen glyph at the new cell
024B: ED 5B B2 14 LD DE,(rom+14B2) ; the run's end cell, from the program image
024F: A7 AND A
0250: ED 52 SBC HL,DE ; has the stamped cell reached the run's end?
0252: C2 32 02 JP NZ,loc_0232 ; not yet -- step and stamp again
0255: 21 E2 A9 LD HL,$A9E2
0258: 34 INC (HL) ; advance to the next run of the route
0259: 7E LD A,(HL) ; take the new run index
025A: 21 90 02 LD HL,$0290 ; point at the route's endpoint word table
025D: D7 RST $10 ; read the new run's endpoint
025E: 21 E3 A9 LD HL,$A9E3
0261: 36 00 LD (HL),$00 ; clear the row's fraction
0263: 23 INC HL
0264: 73 LD (HL),E ; seat the row's whole cell from the endpoint
0265: 21 E5 A9 LD HL,$A9E5
0268: 36 00 LD (HL),$00 ; clear the column's fraction
026A: 23 INC HL
026B: 72 LD (HL),D ; seat the column's whole cell from the endpoint
026C: 7B LD A,E
026D: A7 AND A ; test the new run's row cell -- callers keep drawing runs until it reaches zero
026E: C9 RET
; stamp the current pen glyph and pen colour into the one character cell a
; row cell and a column cell name, and hand back the video-plane address
; of that cell
plotPenCell:
026F: 3A E4 A9 LD A,(workRam+1E4) ; read the pen's row cell
0272: 87 ADD A,A ; scale the row up toward its offset down the plane -- a byte-wide doubling, so a row past the thirty-second folds back to the top
0273: 87 ADD A,A
0274: 87 ADD A,A
0275: 6F LD L,A
0276: 26 00 LD H,$00
0278: 29 ADD HL,HL ; finish scaling the row to thirty-two cells, one whole plane row
0279: 29 ADD HL,HL
027A: 3A E6 A9 LD A,(workRam+1E6) ; read the pen's column cell
027D: 85 ADD A,L
027E: 6F LD L,A ; fold the column into the low half of the address, so a column past the row's end wraps within the row
027F: 3E A4 LD A,$A4
0281: 84 ADD A,H
0282: 67 LD H,A ; land the address in the character plane
0283: 3A 0B AD LD A,(workRam+50B) ; read the pen glyph
0286: 77 LD (HL),A ; stamp the glyph into the cell
0287: CB 94 RES 2,H ; cross to the colour plane for the same cell
0289: 3A 0C AD LD A,(workRam+50C) ; read the pen colour
028C: 77 LD (HL),A ; paint the cell that colour
028D: CB D4 SET 2,H ; cross back to the glyph cell -- the address handed back to the caller
028F: C9 RET
; ---- $0290-$0364: data ----
0290: 10 04 11 04 12 04 13 04 14 04 15 04 16 04 17 04
02A0: 18 04 19 04 1A 04 1B 04 1C 04 1D 04 1D 05 1D 06
02B0: 1D 07 1D 08 1D 09 1D 0A 1D 0B 1D 0C 1D 0D 1D 0E
02C0: 1D 0F 1D 10 1D 11 1D 12 1D 13 1D 14 1D 15 1D 16
02D0: 1D 17 1D 18 1D 19 1D 1A 1D 1B 1D 1C 1D 1D 1D 1E
02E0: 1C 1E 1B 1E 1A 1E 19 1E 18 1E 17 1E 16 1E 15 1E
02F0: 14 1E 13 1E 12 1E 11 1E 10 1E 0F 1E 0E 1E 0D 1E
0300: 0C 1E 0B 1E 0A 1E 09 1E 08 1E 07 1E 06 1E 05 1E
0310: 04 1E 03 1E 02 1E 02 1D 02 1C 02 1B 02 1A 02 19
0320: 02 18 02 17 02 16 02 15 02 14 02 13 02 12 02 11
0330: 02 10 02 0F 02 0E 02 0D 02 0C 02 0B 02 0A 02 09
0340: 02 08 02 07 02 06 02 05 02 04 03 04 04 04 05 04
0350: 06 04 07 04 08 04 09 04 0A 04 0B 04 0C 04 0D 04
0360: 0E 04 0F 04 00
; gather the sprite shadow into the two hardware banks, three runs per
; bank in an order that is not their order in memory, transforming each
; byte by which half of its sprite it is and which way round the cabinet
; has the picture; then, inside one window of the sequence, ask for the
; eight scenery slots to be shown a second time half a screen away
publishSpriteShadow:
0365: 21 30 AA LD HL,$AA30 ; point at the sprite shadow, the working copy of the sprites
0368: 11 10 B0 LD DE,$B010 ; point at the first hardware sprite bank
036B: 3A 87 A9 LD A,(workRam+187) ; read the cabinet-orientation flag
036E: A7 AND A
036F: CA 56 05 JP Z,loc_0556 ; picture turned round: take the flipped copy path
0372: ED A0 LDI ; copy the six scenery-slot-zero sprite bytes straight into the bank
0374: ED A0 LDI
0376: ED A0 LDI
0378: ED A0 LDI
037A: ED A0 LDI
037C: ED A0 LDI
037E: 21 10 AA LD HL,$AA10 ; point at the player's thirty-two sprite bytes
0381: ED A0 LDI ; copy them straight in
0383: ED A0 LDI
0385: ED A0 LDI
0387: ED A0 LDI
0389: ED A0 LDI
038B: ED A0 LDI
038D: ED A0 LDI
038F: ED A0 LDI
0391: ED A0 LDI
0393: ED A0 LDI
0395: ED A0 LDI
0397: ED A0 LDI
0399: ED A0 LDI
039B: ED A0 LDI
039D: ED A0 LDI
039F: ED A0 LDI
03A1: ED A0 LDI
03A3: ED A0 LDI
03A5: ED A0 LDI
03A7: ED A0 LDI
03A9: ED A0 LDI
03AB: ED A0 LDI
03AD: ED A0 LDI
03AF: ED A0 LDI
03B1: ED A0 LDI
03B3: ED A0 LDI
03B5: ED A0 LDI
03B7: ED A0 LDI
03B9: ED A0 LDI
03BB: ED A0 LDI
03BD: ED A0 LDI
03BF: ED A0 LDI
03C1: 21 36 AA LD HL,$AA36 ; point at scenery slot three's ten sprite bytes
03C4: ED A0 LDI ; copy them straight in
03C6: ED A0 LDI
03C8: ED A0 LDI
03CA: ED A0 LDI
03CC: ED A0 LDI
03CE: ED A0 LDI
03D0: ED A0 LDI
03D2: ED A0 LDI
03D4: ED A0 LDI
03D6: ED A0 LDI
03D8: 21 60 AA LD HL,$AA60 ; point at the sprite-attribute shadow
03DB: 11 10 B4 LD DE,$B410 ; point at the second hardware sprite bank
03DE: ED A0 LDI ; pass this sprite's first attribute byte through unchanged
03E0: 7E LD A,(HL) ; read its second attribute byte
03E1: C6 0E ADD A,$0E ; bias it by fourteen
03E3: 2F CPL ; complement it -- the upright transform for the second attribute half
03E4: 12 LD (DE),A ; store it -- the run carries on this way, one attribute pair at a time
03E5: 2C INC L
03E6: 1C INC E
03E7: ED A0 LDI
03E9: 7E LD A,(HL)
03EA: C6 0E ADD A,$0E
03EC: 2F CPL
03ED: 12 LD (DE),A
03EE: 2C INC L
03EF: 1C INC E
03F0: ED A0 LDI
03F2: 7E LD A,(HL)
03F3: C6 0E ADD A,$0E
03F5: 2F CPL
03F6: 12 LD (DE),A
03F7: 2C INC L
03F8: 1C INC E
03F9: 21 40 AA LD HL,$AA40 ; move the source on to the player's attribute block
03FC: ED A0 LDI
03FE: 7E LD A,(HL)
03FF: C6 0E ADD A,$0E
0401: 2F CPL
0402: 12 LD (DE),A
0403: 2C INC L
0404: 1C INC E
0405: ED A0 LDI
0407: 7E LD A,(HL)
0408: C6 0E ADD A,$0E
040A: 2F CPL
040B: 12 LD (DE),A
040C: 2C INC L
040D: 1C INC E
040E: ED A0 LDI
0410: 7E LD A,(HL)
0411: C6 0E ADD A,$0E
0413: 2F CPL
0414: 12 LD (DE),A
0415: 2C INC L
0416: 1C INC E
0417: ED A0 LDI
0419: 7E LD A,(HL)
041A: C6 0E ADD A,$0E
041C: 2F CPL
041D: 12 LD (DE),A
041E: 2C INC L
041F: 1C INC E
0420: ED A0 LDI
0422: 7E LD A,(HL)
0423: C6 0E ADD A,$0E
0425: 2F CPL
0426: 12 LD (DE),A
0427: 2C INC L
0428: 1C INC E
0429: ED A0 LDI
042B: 7E LD A,(HL)
042C: C6 0E ADD A,$0E
042E: 2F CPL
042F: 12 LD (DE),A
0430: 2C INC L
0431: 1C INC E
0432: ED A0 LDI
0434: 7E LD A,(HL)
0435: C6 0E ADD A,$0E
0437: 2F CPL
0438: 12 LD (DE),A
0439: 2C INC L
043A: 1C INC E
043B: ED A0 LDI
043D: 7E LD A,(HL)
043E: C6 0E ADD A,$0E
0440: 2F CPL
0441: 12 LD (DE),A
0442: 2C INC L
0443: 1C INC E
0444: ED A0 LDI
0446: 7E LD A,(HL)
0447: C6 0E ADD A,$0E
0449: 2F CPL
044A: 12 LD (DE),A
044B: 2C INC L
044C: 1C INC E
044D: ED A0 LDI
044F: 7E LD A,(HL)
0450: C6 0E ADD A,$0E
0452: 2F CPL
0453: 12 LD (DE),A
0454: 2C INC L
0455: 1C INC E
0456: ED A0 LDI
0458: 7E LD A,(HL)
0459: C6 0E ADD A,$0E
045B: 2F CPL
045C: 12 LD (DE),A
045D: 2C INC L
045E: 1C INC E
045F: ED A0 LDI
0461: 7E LD A,(HL)
0462: C6 0E ADD A,$0E
0464: 2F CPL
0465: 12 LD (DE),A
0466: 2C INC L
0467: 1C INC E
0468: ED A0 LDI
046A: 7E LD A,(HL)
046B: C6 0E ADD A,$0E
046D: 2F CPL
046E: 12 LD (DE),A
046F: 2C INC L
0470: 1C INC E
0471: ED A0 LDI
0473: 7E LD A,(HL)
0474: C6 0E ADD A,$0E
0476: 2F CPL
0477: 12 LD (DE),A
0478: 2C INC L
0479: 1C INC E
047A: ED A0 LDI
047C: 7E LD A,(HL)
047D: C6 0E ADD A,$0E
047F: 2F CPL
0480: 12 LD (DE),A
0481: 2C INC L
0482: 1C INC E
0483: ED A0 LDI
0485: 7E LD A,(HL)
0486: C6 0E ADD A,$0E
0488: 2F CPL
0489: 12 LD (DE),A
048A: 2C INC L
048B: 1C INC E
048C: 21 66 AA LD HL,$AA66 ; move the source on to scenery slot three's attributes
048F: ED A0 LDI
0491: 7E LD A,(HL)
0492: C6 0E ADD A,$0E
0494: 2F CPL
0495: 12 LD (DE),A
0496: 2C INC L
0497: 1C INC E
0498: ED A0 LDI
049A: 7E LD A,(HL)
049B: C6 0E ADD A,$0E
049D: 2F CPL
049E: 12 LD (DE),A
049F: 2C INC L
04A0: 1C INC E
04A1: ED A0 LDI
04A3: 7E LD A,(HL)
04A4: C6 0E ADD A,$0E
04A6: 2F CPL
04A7: 12 LD (DE),A
04A8: 2C INC L
04A9: 1C INC E
04AA: ED A0 LDI
04AC: 7E LD A,(HL)
04AD: C6 0E ADD A,$0E
04AF: 2F CPL
04B0: 12 LD (DE),A
04B1: 2C INC L
04B2: 1C INC E
04B3: ED A0 LDI
04B5: 7E LD A,(HL)
04B6: C6 0E ADD A,$0E
04B8: 2F CPL
04B9: 12 LD (DE),A
04BA: 2C INC L
04BB: 1C INC E
loc_04bc:
04BC: 3A AB A9 LD A,(workRam+1AB) ; read the sequence phase
04BF: FE 03 CP $03 ; is this the third sequence phase?
04C1: C0 RET NZ ; other phase: leave the sprites untouched
04C2: 3A AC A9 LD A,(workRam+1AC) ; read the sequence sub-step
04C5: 21 32 08 LD HL,$0832 ; point at the sub-step floor value
04C8: BE CP (HL) ; below the floor sub-step?
04C9: D8 RET C ; yes: nothing to raise
04CA: FE 08 CP $08 ; at or past sub-step eight?
04CC: D0 RET NC ; yes: nothing to raise
04CD: 3A 11 B4 LD A,(spriteRam1+11) ; read this sprite's second bank byte
04D0: C6 80 ADD A,$80 ; add its own top bit back to probe it
04D2: 38 0A JR C,loc_04de ; already raised: skip to the next sprite
04D4: 32 11 B4 LD (spriteRam1+11),A ; raise the second byte's top bit
04D7: 21 10 B0 LD HL,$B010 ; point at the matching first-bank byte
04DA: 7E LD A,(HL)
04DB: C6 80 ADD A,$80 ; raise its top bit too
04DD: 77 LD (HL),A
loc_04de:
04DE: 3A 13 B4 LD A,(spriteRam1+13) ; same top-bit raise for the next of the eight sprites
04E1: C6 80 ADD A,$80
04E3: 38 0A JR C,loc_04ef ;
04E5: 32 13 B4 LD (spriteRam1+13),A ;
04E8: 21 12 B0 LD HL,$B012
04EB: 7E LD A,(HL)
04EC: C6 80 ADD A,$80
04EE: 77 LD (HL),A
loc_04ef:
04EF: 3A 15 B4 LD A,(spriteRam1+15) ; same raise for the next sprite
04F2: C6 80 ADD A,$80
04F4: 38 0A JR C,loc_0500 ;
04F6: 32 15 B4 LD (spriteRam1+15),A ;
04F9: 21 14 B0 LD HL,$B014
04FC: 7E LD A,(HL)
04FD: C6 80 ADD A,$80
04FF: 77 LD (HL),A
loc_0500:
0500: 3A 37 B4 LD A,(spriteRam1+37) ; same raise for the next sprite
0503: C6 80 ADD A,$80
0505: 38 0A JR C,loc_0511 ;
0507: 32 37 B4 LD (spriteRam1+37),A ;
050A: 21 36 B0 LD HL,$B036
050D: 7E LD A,(HL)
050E: C6 80 ADD A,$80
0510: 77 LD (HL),A
loc_0511:
0511: 3A 39 B4 LD A,(spriteRam1+39) ; same raise for the next sprite
0514: C6 80 ADD A,$80
0516: 38 0A JR C,loc_0522 ;
0518: 32 39 B4 LD (spriteRam1+39),A ;
051B: 21 38 B0 LD HL,$B038
051E: 7E LD A,(HL)
051F: C6 80 ADD A,$80
0521: 77 LD (HL),A
loc_0522:
0522: 3A 3B B4 LD A,(spriteRam1+3B) ; same raise for the next sprite
0525: C6 80 ADD A,$80
0527: 38 0A JR C,loc_0533 ;
0529: 32 3B B4 LD (spriteRam1+3B),A ;
052C: 21 3A B0 LD HL,$B03A
052F: 7E LD A,(HL)
0530: C6 80 ADD A,$80
0532: 77 LD (HL),A
loc_0533:
0533: 3A 3D B4 LD A,(spriteRam1+3D) ; same raise for the next sprite
0536: C6 80 ADD A,$80
0538: 38 0A JR C,loc_0544 ;
053A: 32 3D B4 LD (spriteRam1+3D),A ;
053D: 21 3C B0 LD HL,$B03C
0540: 7E LD A,(HL)
0541: C6 80 ADD A,$80
0543: 77 LD (HL),A
loc_0544:
0544: 3A 3F B4 LD A,(spriteRam1+3F) ; same raise for the last of the eight sprites
0547: C6 80 ADD A,$80
0549: 38 0A JR C,loc_0555 ;
054B: 32 3F B4 LD (spriteRam1+3F),A ;
054E: 21 3E B0 LD HL,$B03E
0551: 7E LD A,(HL)
0552: C6 80 ADD A,$80
0554: 77 LD (HL),A
loc_0555:
0555: C9 RET ; done: return
loc_0556:
0556: 7E LD A,(HL) ; read this sprite's first byte
0557: C6 0F ADD A,$0F ; bias it by fifteen
0559: 2F CPL ; complement it -- the flipped transform for the first half
055A: 12 LD (DE),A ; store it
055B: 2C INC L
055C: 1C INC E
055D: ED A0 LDI ; pass the second byte through unchanged
055F: 7E LD A,(HL)
0560: C6 0F ADD A,$0F
0562: 2F CPL
0563: 12 LD (DE),A
0564: 2C INC L
0565: 1C INC E
0566: ED A0 LDI
0568: 7E LD A,(HL)
0569: C6 0F ADD A,$0F
056B: 2F CPL
056C: 12 LD (DE),A
056D: 2C INC L
056E: 1C INC E
056F: ED A0 LDI
0571: 21 10 AA LD HL,$AA10 ; move the source on to the player's thirty-two sprite bytes
0574: 7E LD A,(HL)
0575: C6 0F ADD A,$0F
0577: 2F CPL
0578: 12 LD (DE),A
0579: 2C INC L
057A: 1C INC E
057B: ED A0 LDI
057D: 7E LD A,(HL)
057E: C6 0F ADD A,$0F
0580: 2F CPL
0581: 12 LD (DE),A
0582: 2C INC L
0583: 1C INC E
0584: ED A0 LDI
0586: 7E LD A,(HL)
0587: C6 0F ADD A,$0F
0589: 2F CPL
058A: 12 LD (DE),A
058B: 2C INC L
058C: 1C INC E
058D: ED A0 LDI
058F: 7E LD A,(HL)
0590: C6 0F ADD A,$0F
0592: 2F CPL
0593: 12 LD (DE),A
0594: 2C INC L
0595: 1C INC E
0596: ED A0 LDI
0598: 7E LD A,(HL)
0599: C6 0F ADD A,$0F
059B: 2F CPL
059C: 12 LD (DE),A
059D: 2C INC L
059E: 1C INC E
059F: ED A0 LDI
05A1: 7E LD A,(HL)
05A2: C6 0F ADD A,$0F
05A4: 2F CPL
05A5: 12 LD (DE),A
05A6: 2C INC L
05A7: 1C INC E
05A8: ED A0 LDI
05AA: 7E LD A,(HL)
05AB: C6 0F ADD A,$0F
05AD: 2F CPL
05AE: 12 LD (DE),A
05AF: 2C INC L
05B0: 1C INC E
05B1: ED A0 LDI
05B3: 7E LD A,(HL)
05B4: C6 0F ADD A,$0F
05B6: 2F CPL
05B7: 12 LD (DE),A
05B8: 2C INC L
05B9: 1C INC E
05BA: ED A0 LDI
05BC: 7E LD A,(HL)
05BD: C6 0F ADD A,$0F
05BF: 2F CPL
05C0: 12 LD (DE),A
05C1: 2C INC L
05C2: 1C INC E
05C3: ED A0 LDI
05C5: 7E LD A,(HL)
05C6: C6 0F ADD A,$0F
05C8: 2F CPL
05C9: 12 LD (DE),A
05CA: 2C INC L
05CB: 1C INC E
05CC: ED A0 LDI
05CE: 7E LD A,(HL)
05CF: C6 0F ADD A,$0F
05D1: 2F CPL
05D2: 12 LD (DE),A
05D3: 2C INC L
05D4: 1C INC E
05D5: ED A0 LDI
05D7: 7E LD A,(HL)
05D8: C6 0F ADD A,$0F
05DA: 2F CPL
05DB: 12 LD (DE),A
05DC: 2C INC L
05DD: 1C INC E
05DE: ED A0 LDI
05E0: 7E LD A,(HL)
05E1: C6 0F ADD A,$0F
05E3: 2F CPL
05E4: 12 LD (DE),A
05E5: 2C INC L
05E6: 1C INC E
05E7: ED A0 LDI
05E9: 7E LD A,(HL)
05EA: C6 0F ADD A,$0F
05EC: 2F CPL
05ED: 12 LD (DE),A
05EE: 2C INC L
05EF: 1C INC E
05F0: ED A0 LDI
05F2: 7E LD A,(HL)
05F3: C6 0F ADD A,$0F
05F5: 2F CPL
05F6: 12 LD (DE),A
05F7: 2C INC L
05F8: 1C INC E
05F9: ED A0 LDI
05FB: 7E LD A,(HL)
05FC: C6 0F ADD A,$0F
05FE: 2F CPL
05FF: 12 LD (DE),A
0600: 2C INC L
0601: 1C INC E
0602: ED A0 LDI
0604: 21 36 AA LD HL,$AA36 ; move the source on to scenery slot three's ten bytes
0607: 7E LD A,(HL)
0608: C6 0F ADD A,$0F
060A: 2F CPL
060B: 12 LD (DE),A
060C: 2C INC L
060D: 1C INC E
060E: ED A0 LDI
0610: 7E LD A,(HL)
0611: C6 0F ADD A,$0F
0613: 2F CPL
0614: 12 LD (DE),A
0615: 2C INC L
0616: 1C INC E
0617: ED A0 LDI
0619: 7E LD A,(HL)
061A: C6 0F ADD A,$0F
061C: 2F CPL
061D: 12 LD (DE),A
061E: 2C INC L
061F: 1C INC E
0620: ED A0 LDI
0622: 7E LD A,(HL)
0623: C6 0F ADD A,$0F
0625: 2F CPL
0626: 12 LD (DE),A
0627: 2C INC L
0628: 1C INC E
0629: ED A0 LDI
062B: 7E LD A,(HL)
062C: C6 0F ADD A,$0F
062E: 2F CPL
062F: 12 LD (DE),A
0630: 2C INC L
0631: 1C INC E
0632: ED A0 LDI
0634: 21 60 AA LD HL,$AA60 ; point at the sprite-attribute shadow
0637: 11 10 B4 LD DE,$B410 ; point at the second hardware sprite bank
063A: 7E LD A,(HL) ; read this attribute's first byte
063B: EE C0 XOR $C0 ; toggle its top two bits -- the flipped transform for the first attribute half
063D: 12 LD (DE),A ; store it
063E: 2C INC L
063F: 1C INC E
0640: 7E LD A,(HL) ; read its second byte
0641: 3C INC A ; step it on by one -- the flipped transform for the second attribute half
0642: 12 LD (DE),A ; store it -- the run carries on this way, one attribute pair at a time
0643: 2C INC L
0644: 1C INC E
0645: 7E LD A,(HL)
0646: EE C0 XOR $C0
0648: 12 LD (DE),A
0649: 2C INC L
064A: 1C INC E
064B: 7E LD A,(HL)
064C: 3C INC A
064D: 12 LD (DE),A
064E: 2C INC L
064F: 1C INC E
0650: 7E LD A,(HL)
0651: EE C0 XOR $C0
0653: 12 LD (DE),A
0654: 2C INC L
0655: 1C INC E
0656: 7E LD A,(HL)
0657: 3C INC A
0658: 12 LD (DE),A
0659: 2C INC L
065A: 1C INC E
065B: 21 40 AA LD HL,$AA40 ; move the source on to the player's attribute block
065E: 7E LD A,(HL)
065F: EE C0 XOR $C0
0661: 12 LD (DE),A
0662: 2C INC L
0663: 1C INC E
0664: 7E LD A,(HL)
0665: 3C INC A
0666: 12 LD (DE),A
0667: 2C INC L
0668: 1C INC E
0669: 7E LD A,(HL)
066A: EE C0 XOR $C0
066C: 12 LD (DE),A
066D: 2C INC L
066E: 1C INC E
066F: 7E LD A,(HL)
0670: 3C INC A
0671: 12 LD (DE),A
0672: 2C INC L
0673: 1C INC E
0674: 7E LD A,(HL)
0675: EE C0 XOR $C0
0677: 12 LD (DE),A
0678: 2C INC L
0679: 1C INC E
067A: 7E LD A,(HL)
067B: 3C INC A
067C: 12 LD (DE),A
067D: 2C INC L
067E: 1C INC E
067F: 7E LD A,(HL)
0680: EE C0 XOR $C0
0682: 12 LD (DE),A
0683: 2C INC L
0684: 1C INC E
0685: 7E LD A,(HL)
0686: 3C INC A
0687: 12 LD (DE),A
0688: 2C INC L
0689: 1C INC E
068A: 7E LD A,(HL)
068B: EE C0 XOR $C0
068D: 12 LD (DE),A
068E: 2C INC L
068F: 1C INC E
0690: 7E LD A,(HL)
0691: 3C INC A
0692: 12 LD (DE),A
0693: 2C INC L
0694: 1C INC E
0695: 7E LD A,(HL)
0696: EE C0 XOR $C0
0698: 12 LD (DE),A
0699: 2C INC L
069A: 1C INC E
069B: 7E LD A,(HL)
069C: 3C INC A
069D: 12 LD (DE),A
069E: 2C INC L
069F: 1C INC E
06A0: 7E LD A,(HL)
06A1: EE C0 XOR $C0
06A3: 12 LD (DE),A
06A4: 2C INC L
06A5: 1C INC E
06A6: 7E LD A,(HL)
06A7: 3C INC A
06A8: 12 LD (DE),A
06A9: 2C INC L
06AA: 1C INC E
06AB: 7E LD A,(HL)
06AC: EE C0 XOR $C0
06AE: 12 LD (DE),A
06AF: 2C INC L
06B0: 1C INC E
06B1: 7E LD A,(HL)
06B2: 3C INC A
06B3: 12 LD (DE),A
06B4: 2C INC L
06B5: 1C INC E
06B6: 7E LD A,(HL)
06B7: EE C0 XOR $C0
06B9: 12 LD (DE),A
06BA: 2C INC L
06BB: 1C INC E
06BC: 7E LD A,(HL)
06BD: 3C INC A
06BE: 12 LD (DE),A
06BF: 2C INC L
06C0: 1C INC E
06C1: 7E LD A,(HL)
06C2: EE C0 XOR $C0
06C4: 12 LD (DE),A
06C5: 2C INC L
06C6: 1C INC E
06C7: 7E LD A,(HL)
06C8: 3C INC A
06C9: 12 LD (DE),A
06CA: 2C INC L
06CB: 1C INC E
06CC: 7E LD A,(HL)
06CD: EE C0 XOR $C0
06CF: 12 LD (DE),A
06D0: 2C INC L
06D1: 1C INC E
06D2: 7E LD A,(HL)
06D3: 3C INC A
06D4: 12 LD (DE),A
06D5: 2C INC L
06D6: 1C INC E
06D7: 7E LD A,(HL)
06D8: EE C0 XOR $C0
06DA: 12 LD (DE),A
06DB: 2C INC L
06DC: 1C INC E
06DD: 7E LD A,(HL)
06DE: 3C INC A
06DF: 12 LD (DE),A
06E0: 2C INC L
06E1: 1C INC E
06E2: 7E LD A,(HL)
06E3: EE C0 XOR $C0
06E5: 12 LD (DE),A
06E6: 2C INC L
06E7: 1C INC E
06E8: 7E LD A,(HL)
06E9: 3C INC A
06EA: 12 LD (DE),A
06EB: 2C INC L
06EC: 1C INC E
06ED: 7E LD A,(HL)
06EE: EE C0 XOR $C0
06F0: 12 LD (DE),A
06F1: 2C INC L
06F2: 1C INC E
06F3: 7E LD A,(HL)
06F4: 3C INC A
06F5: 12 LD (DE),A
06F6: 2C INC L
06F7: 1C INC E
06F8: 7E LD A,(HL)
06F9: EE C0 XOR $C0
06FB: 12 LD (DE),A
06FC: 2C INC L
06FD: 1C INC E
06FE: 7E LD A,(HL)
06FF: 3C INC A
0700: 12 LD (DE),A
0701: 2C INC L
0702: 1C INC E
0703: 7E LD A,(HL)
0704: EE C0 XOR $C0
0706: 12 LD (DE),A
0707: 2C INC L
0708: 1C INC E
0709: 7E LD A,(HL)
070A: 3C INC A
070B: 12 LD (DE),A
070C: 2C INC L
070D: 1C INC E
070E: 21 66 AA LD HL,$AA66 ; move the source on to scenery slot three's attributes
0711: 7E LD A,(HL)
0712: EE C0 XOR $C0
0714: 12 LD (DE),A
0715: 2C INC L
0716: 1C INC E
0717: 7E LD A,(HL)
0718: 3C INC A
0719: 12 LD (DE),A
071A: 2C INC L
071B: 1C INC E
071C: 7E LD A,(HL)
071D: EE C0 XOR $C0
071F: 12 LD (DE),A
0720: 2C INC L
0721: 1C INC E
0722: 7E LD A,(HL)
0723: 3C INC A
0724: 12 LD (DE),A
0725: 2C INC L
0726: 1C INC E
0727: 7E LD A,(HL)
0728: EE C0 XOR $C0
072A: 12 LD (DE),A
072B: 2C INC L
072C: 1C INC E
072D: 7E LD A,(HL)
072E: 3C INC A
072F: 12 LD (DE),A
0730: 2C INC L
0731: 1C INC E
0732: 7E LD A,(HL)
0733: EE C0 XOR $C0
0735: 12 LD (DE),A
0736: 2C INC L
0737: 1C INC E
0738: 7E LD A,(HL)
0739: 3C INC A
073A: 12 LD (DE),A
073B: 2C INC L
073C: 1C INC E
073D: 7E LD A,(HL)
073E: EE C0 XOR $C0
0740: 12 LD (DE),A
0741: 2C INC L
0742: 1C INC E
0743: 7E LD A,(HL)
0744: 3C INC A
0745: 12 LD (DE),A
0746: 2C INC L
0747: 1C INC E
0748: C3 BC 04 JP loc_04bc ; go raise the eight sprites, then return
; attract-sequence arm (phase 1, sub-step 0, reached by rst-30 computed
; dispatch from dispatchSequencePhase1SubStepArm): fold the fixed 256-byte
; run at 0x4AA0 into an eight-bit total and derail into the checksum-
; failure landing 0x08FA on any total but 0xB8; otherwise set the pen
; colour 0xAD0C to 5 and the stamp glyph 0xAD0B to the blanking glyph 0xF1
; (so the pen erases), re-arm the pen route via 0x01E1, then step the
; sequence sub-step 0x0F1A -- twice when the pen colour already held 5
erasePenRouteThenAdvanceStep:
074B: 06 00 LD B,$00 ; two hundred fifty-six program bytes to fold -- a zero count means the full round
074D: 21 A0 4A LD HL,$4AA0 ; point at the program run to be checksummed
0750: AF XOR A ; clear the running total
loc_0751:
0751: 86 ADD A,(HL) ; add the next program byte into the total
0752: 23 INC HL
0753: 10 FC DJNZ loc_0751 ;
0755: D6 B8 SUB $B8 ; subtract the genuine total
0757: C2 FA 08 JP NZ,loc_08fa ; any other total means a tampered image: derail to the failure landing
075A: 3A 0C AD LD A,(workRam+50C) ; read the current pen colour
075D: FE 05 CP $05 ; was the pen colour already the set value?
075F: F5 PUSH AF
0760: 3E 05 LD A,$05
0762: 32 0C AD LD (workRam+50C),A ; set the pen colour to five
0765: 3E F1 LD A,$F1
0767: 32 0B AD LD (workRam+50B),A ; set the stamp glyph to the blanking glyph, so the pen erases
076A: CD E1 01 CALL armThePenRouteThenColdStartOnATamperedImage; arm the pen to the start of its route
076D: F1 POP AF
076E: CC 1A 0F CALL Z,advanceSequenceSubStep; step the sequence sub-step an extra time when the pen colour already held five
0771: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-step
loc_0774:
0774: 06 00 LD B,$00 ; two hundred fifty-six program bytes to fold -- a zero count means the full round
0776: 21 99 4C LD HL,$4C99 ; point at the program run to verify
0779: 97 SUB A ; clear the running total
loc_077a:
077A: AE XOR (HL) ; fold the next byte into the total by exclusive-or
077B: 23 INC HL
077C: 10 FC DJNZ loc_077a ;
077E: C6 95 ADD A,$95 ; add the genuine total's complement -- zero only when the fold matches
0780: C4 11 0F CALL NZ,advanceSequencePhase; step the sequence phase when the fold does not match -- the image-tamper response
0783: 3A 30 AD LD A,(workRam+530) ; read the first setup flag
0786: A7 AND A
0787: 28 17 JR Z,loc_07a0 ; nothing set: skip to the shared tail
0789: ED 5B 5B 12 LD DE,(rom+125B) ; read a fixed command pair from the program image
078D: 3A 32 AD LD A,(workRam+532) ; read the second setup flag
0790: A7 AND A
0791: 28 01 JR Z,loc_0794 ; clear: leave the argument as is
0793: 1C INC E ; set: bump the command argument
loc_0794:
0794: FF RST $38 ; post the command pair to the ring
0795: 3A 0E AD LD A,(workRam+50E) ; read the third setup flag
0798: A7 AND A
0799: 28 05 JR Z,loc_07a0 ; clear: skip to the shared tail
079B: 16 07 LD D,$07 ; set the command to number seven
079D: FF RST $38 ; post that command to the ring
079E: 18 04 JR loc_07a4 ; join the shared tail
loc_07a0:
07A0: 11 02 02 LD DE,$0202 ; command two, argument two
07A3: FF RST $38 ; post it to the ring
loc_07a4:
07A4: CD 09 08 CALL drawKillMeter ; repaint the kill meter
07A7: CD F0 19 CALL resetPlayfieldAndArmNewRound; reset the playfield and arm a new round
07AA: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-step
; park the eight-bit total the image fold arrives with into B, where the
; helper the verdict arm calls hands it back to A after its own address
; arithmetic has clobbered A; then hand on by jump, so the verdict's own
; exits carry this entry too. Nothing is read or written and no flag moves
parkTheImageTotalForTheTamperVerdict:
07AD: 47 LD B,A ; set the folded program total aside for the tamper verdict
07AE: C3 03 53 JP advanceSequenceUnlessImageTampered; hand on to the verdict
; power-on: probe the expansion socket and give the machine away to it if
; a board answers there, otherwise seat the stack at the top of work RAM,
; kick the watchdog, drive the four control lines the latch's first eight
; addresses carry low, raise the video-enable line from a byte of the
; program image, and hand on to the cold start. No work memory is touched
; -- the whole effect is the seated stack and the latched lines. Latch
; bits 5, 6 and 7 are NEVER WRITTEN here: the walk stops at 0xC307 and the
; only other store is to 0xC308, so 'settle the control latch' is five of
; its eight lines and not all eight
seatTheStackAndSettleTheControlLatch:
07B1: 3A 00 60 LD A,($6000) ; read the expansion socket
07B4: FE 55 CP $55 ; is an expansion board fitted?
07B6: CA 00 60 JP Z,$6000 ; fitted: hand control to the expansion -- never taken on a stock board, an empty socket floats high, not the magic value
07B9: 31 00 B0 LD SP,$B000 ; seat the stack just below sprite RAM, so it grows down through work RAM
07BC: 32 00 C2 LD (dsw1),A ; kick the watchdog
07BF: 21 00 C3 LD HL,$C300 ; point at the control latch
07C2: 06 08 LD B,$08 ; eight latch addresses to clear
loc_07c4:
07C4: 36 00 LD (HL),$00 ; clear this latch line
07C6: 23 INC HL
07C7: 10 FB DJNZ loc_07c4 ;
07C9: 3A 4B 2D LD A,(rom+2D4B) ; read a byte of the program image
07CC: 32 08 C3 LD ($C308),A ; drive the video-enable line from it
07CF: C3 69 00 JP clearWorkRamAndSpriteBanksThenColdInit; hand on to the cold start
; blank a fixed run of fourteen character cells, walking back one native
; row at a time from a fixed cell, and give every one of them the same
; colour
blankFourteenCharCells:
07D2: 21 9F A7 LD HL,$A79F ; point at the first cell of the run
07D5: 11 E0 FF LD DE,$FFE0 ; step of minus thirty-two -- walk one plane row back each cell
07D8: 06 0E LD B,$0E ; fourteen cells to blank
loc_07da:
07DA: 36 F1 LD (HL),$F1 ; blank this cell's glyph
07DC: CB 94 RES 2,H ; cross to the colour plane
07DE: 36 16 LD (HL),$16 ; give it the fixed colour
07E0: CB D4 SET 2,H ; cross back to the glyph plane
07E2: 19 ADD HL,DE ; step back one plane row
07E3: 10 F5 DJNZ loc_07da ;
07E5: C9 RET
; copyright / insert-coin attract sequence arm (table-dispatched): re-
; stamp the copyright strip, re-request the flashing copyright line,
; sample one character cell (0xA61C) into a two-byte record (0xABFE), then
; read the IN0 mirror -- hand off to the one-player game start when
; 1-player start (bit 3) is held, return when the credit count at 0xA986
; is one, otherwise queue ring command 1/argument 25 and step the sequence
; sub-step
stepCopyrightScreenAwaitingStart:
07E6: CD 06 0B CALL stampCopyrightStrip ; re-stamp the copyright caption strip
07E9: CD 39 0B CALL flashCopyrightLine ; re-request the flashing copyright line
07EC: 21 1C A6 LD HL,$A61C ; point at the title cell to sample
07EF: 11 FE AB LD DE,$ABFE ; and where to stash its glyph and colour
07F2: CD FC 1A CALL sampleCellGlyphAndColour; sample the cell
07F5: 3A AE A9 LD A,(workRam+1AE) ; read the player-one controls
07F8: CB 5F BIT 3,A ; is the one-player start held?
07FA: C2 15 32 JP NZ,startOnePlayerGame; held: begin a one-player game
07FD: 3A 86 A9 LD A,(workRam+186) ; read the credit count
0800: 3D DEC A
0801: C8 RET Z ; just one credit: hold on this screen rather than advancing
0802: 11 19 01 LD DE,$0119 ; command one, caption argument twenty-five
0805: FF RST $38 ; post it to the command ring
0806: C3 1A 0F JP advanceSequenceSubStep; step the attract sequence
; repaint the meter that shows how many kills are still owed: a bar of
; era-selected glyphs one cell long per four kills, an end glyph carrying
; the remainder, and one blanking cell past it
drawKillMeter:
0809: 3A 04 AD LD A,(workRam+504) ; read the era
080C: 87 ADD A,A ; scale the era by ten to reach its row in the glyph table
080D: 47 LD B,A
080E: 87 ADD A,A
080F: 87 ADD A,A
0810: 80 ADD A,B
0811: 21 7C 08 LD HL,$087C ; point at the table of per-era glyph rows
0814: DF RST $18 ; index into it by the era offset
0815: 46 LD B,(HL) ; take the first bar glyph
0816: 23 INC HL
0817: 4E LD C,(HL) ; and the second bar glyph
0818: 23 INC HL ; step past to the eight end glyphs
0819: 3A 02 AD LD A,(workRam+502) ; read how many kills are still owed
081C: 5F LD E,A
081D: E6 07 AND $07 ; its low three bits choose which end glyph
081F: CF RST $08 ; read that end glyph from the row
0820: 08 EX AF,AF' ; set the end glyph aside
0821: 7B LD A,E ; bring the count back
0822: 21 9F A7 LD HL,$A79F ; point at the fixed end of the meter line
0825: 11 E0 FF LD DE,$FFE0 ; step of minus thirty-two -- one cell back per bar cell laid
0828: 0F RRCA ; divide the count by four -- one bar cell per four kills
0829: 0F RRCA
082A: E6 1F AND $1F ; cap the bar at thirty-one cells
082C: 28 0A JR Z,loc_0838 ; no full cells: skip straight to the end glyph
loc_082e:
082E: 70 LD (HL),B ; lay a first-glyph bar cell
082F: 19 ADD HL,DE
0830: 3D DEC A
0831: 28 05 JR Z,loc_0838 ; count exhausted: lay the end glyph
0833: 71 LD (HL),C ; lay a second-glyph bar cell
0834: 19 ADD HL,DE
0835: 3D DEC A
0836: 20 F6 JR NZ,loc_082e ; more cells to lay: go back for another pair
loc_0838:
0838: 08 EX AF,AF' ; bring the end glyph back
0839: 77 LD (HL),A ; lay the end glyph
083A: 19 ADD HL,DE
083B: 36 F1 LD (HL),$F1 ; blank the cell past the end glyph
083D: C9 RET
; title/attract copyright-screen layout arm (table-dispatched, no static
; call site): request the flashing copyright line, stamp the copyright
; caption strip, post caption commands (command 1, arguments
; 0,1,3..7,20,21) to the command ring, then XOR-fold the 24-byte program
; block at 0x176A and step the sequence sub-step when the fold matches
; 0xC9, else transfer to the checksum-failure landing
buildCopyrightScreenThenVerifyImage:
083E: CD 39 0B CALL flashCopyrightLine ; request the flashing copyright line
0841: CD 06 0B CALL stampCopyrightStrip ; stamp the copyright caption strip
0844: 11 00 01 LD DE,$0100 ; command one, first caption argument
0847: 06 02 LD B,$02 ; two captions to post in this run
loc_0849:
0849: FF RST $38 ; post this caption command
084A: 1C INC E ; step to the next caption argument
084B: 10 FC DJNZ loc_0849 ;
084D: 1C INC E ; skip an argument
084E: 06 05 LD B,$05 ; five more captions to post
loc_0850:
0850: FF RST $38 ; post this caption command
0851: 1C INC E
0852: 10 FC DJNZ loc_0850 ;
0854: 1E 14 LD E,$14 ; the twentieth caption argument
0856: FF RST $38 ; post it
0857: 1C INC E ; step to the twenty-first caption argument
0858: FF RST $38 ; post it
0859: 21 6A 17 LD HL,$176A ; point at the program block to verify
085C: 06 18 LD B,$18 ; twenty-four bytes to fold
085E: AF XOR A ; clear the running total
loc_085f:
085F: AE XOR (HL) ; fold the next byte in by exclusive-or
0860: 2C INC L
0861: 10 FC DJNZ loc_085f ;
0863: D6 C9 SUB $C9 ; subtract the genuine total
0865: C2 FA 08 JP NZ,loc_08fa ; any other total means a tampered image: derail to the failure landing
0868: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-step
; ---- $086B-$08AD: data ----
086B: BC A6 10 30 F1 7C 68 3B A5 38 FD F1 96 5D 17 9B
087B: B9 4C 4F F1 41 72 A6 F1 8D E2 FB 37 A7 F1 AB 31
088B: 07 F1 5A 75 85 D9 1B F1 C1 E1 FA F1 B3 A0 47 7B
089B: 78 F1 04 05 C2 F1 DE F9 BB 93 AC F1 36 06 4B F1
08AB: EE D3 D4
; hand back where a fixed block of the program image starts and how many
; bytes of it to take; nothing is read and nothing is written
selectFoldBlock:
08AE: 21 5E 33 LD HL,$335E ; point at the program block to fold
08B1: 06 1E LD B,$1E ; thirty bytes of it to take
08B3: C9 RET
loc_08b4:
08B4: CD 01 02 CALL drawInterpolatedPenRun; draw the interpolated pen run
08B7: C0 RET NZ ; leave if the run has not finished
08B8: 06 00 LD B,$00 ; two hundred fifty-six bytes to fold -- zero count means the full round
08BA: 21 80 48 LD HL,$4880 ; point at the program run to verify
08BD: 97 SUB A ; clear the running total
loc_08be:
08BE: AE XOR (HL) ; fold the next byte in by exclusive-or
08BF: 23 INC HL
08C0: 10 FC DJNZ loc_08be ;
08C2: C6 D0 ADD A,$D0 ; add the genuine total's complement
08C4: C2 D9 00 JP NZ,loc_00d9 ; mismatch means a tampered image: derail
08C7: 11 13 01 LD DE,$0113 ; command one, caption argument nineteen
08CA: FF RST $38 ; post it to the ring
08CB: 1E 00 LD E,$00 ; command one, caption argument zero
08CD: FF RST $38 ; post it
08CE: 1E 14 LD E,$14 ; caption argument twenty
08D0: FF RST $38 ; post it
08D1: 1C INC E ; caption argument twenty-one
08D2: FF RST $38 ; post it
08D3: 1E 0C LD E,$0C ; caption argument twelve
08D5: FF RST $38 ; post it
08D6: CD DC 4B CALL paintFiveLabelledNumericReadouts; paint the five labelled numeric readouts
08D9: 21 95 A9 LD HL,$A995 ; point at the five bytes to clear
08DC: AF XOR A ; the value to clear them with: zero
08DD: 06 05 LD B,$05 ; five bytes to clear
loc_08df:
08DF: 77 LD (HL),A ; clear this byte
08E0: 23 INC HL
08E1: 10 FC DJNZ loc_08df ;
08E3: 36 03 LD (HL),$03 ; set the sixth byte to three
08E5: ED 5B 93 A9 LD DE,(workRam+193) ; read the destination pointer
08E9: 3A 99 A9 LD A,(workRam+199) ; read the index
08EC: 21 C7 12 LD HL,$12C7 ; point at the lookup table
08EF: CF RST $08 ; read the entry the index selects
08F0: 12 LD (DE),A ; store it at the destination
08F1: CB 92 RES 2,D ; cross to the other plane for the same cell
08F3: 1A LD A,(DE) ; read what stands there
08F4: 32 90 A9 LD (workRam+190),A ; keep it
08F7: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-step
loc_08fa:
08FA: 4B LD C,E
08FB: 01 4A 01 LD BC,$014A
08FE: 49 LD C,C
08FF: 01 48 01 LD BC,$0148
0902: 47 LD B,A
0903: 01 46 01 LD BC,$0146
0906: 45 LD B,L
0907: 01 40 01 LD BC,$0140
090A: 3E 01 LD A,$01
090C: 3C INC A
090D: 01 3A 01 LD BC,$013A
0910: 38 01 JR C,rom+913 ;
0912: 32 01 2F LD (rom+2F01),A ;
0915: 01 2D 01 LD BC,$012D
0918: 27 DAA
0919: 01 24 01 LD BC,$0124
091C: 21 01 1E LD HL,$1E01
091F: 01 18 01 LD BC,$0118
0922: 15 DEC D
loc_0923:
0923: 01 12 01 LD BC,$0112
0926: 0C INC C
0927: 01 09 01 LD BC,$0109
092A: 06 01 LD B,$01
092C: 00 NOP
092D: 01 FD 00 LD BC,$00FD
0930: FA 00 F7 JP M,$F700
0933: 00 NOP
0934: F1 POP AF
0935: 00 NOP
0936: EE 00 XOR $00
0938: EB EX DE,HL
0939: 00 NOP
093A: E5 PUSH HL
093B: 00 NOP
093C: E2 00 DE JP PO,$DE00
093F: 00 NOP
0940: D8 RET C
0941: 00 NOP
0942: D5 PUSH DE
0943: 00 NOP
0944: D1 POP DE
0945: 00 NOP
0946: CA 00 C6 JP Z,$C600
0949: 00 NOP
094A: C3 00 BC JP $BC00
; ---- $094D-$0B05: data ----
094D: 00 B6 00 AE 00 A9 00 9F 00 9C 00 93 00 8A 00 84
095D: 00 7B 00 71 00 6B 00 61 00 57 00 50 00 45 00 3B
096D: 00 34 00 29 00 1E 00 13 00 08 00 00 00 00 00 F8
097D: FF ED FF 00 00 D7 FF CC FF C5 FF BB FF B0 FF A9
098D: FF 9F FF 95 FF 8F FF 85 FF 7C FF 76 FF 6D FF 64
099D: FF 61 FF 64 FF 52 FF 4A FF 44 FF 3D FF 3A FF 36
09AD: FF 2F FF 2B FF 28 FF 22 FF 1E FF 1B FF 15 FF 12
09BD: FF 0F FF 0F FF 06 FF 03 FF 00 FF FA FE F7 FE F4
09CD: FE EE FE EB FE E8 FE E2 FE DF FE DC FE D9 FE D3
09DD: FE D1 FE CE FE C8 FE C6 FE C4 FE C2 FE C0 FE BB
09ED: FE BA FE B9 FE B8 FE B7 FE B6 FE B5 FE B5 FE B6
09FD: FE B7 FE B8 FE B9 FE BA FE BB FE C0 FE C2 FE C4
0A0D: FE C6 FE C8 FE CE FE D1 FE D3 FE D9 FE DC FE DF
0A1D: FE E2 FE E8 FE EB FE EE FE F4 FE F7 FE FA FE 00
0A2D: FF 03 FF 06 FF 09 FF 0F FF 12 FF 15 FF 1B FF 1E
0A3D: FF 22 FF 28 FF 2B FF 2F FF 36 FF 3A FF 3D FF 44
0A4D: FF 4A FF 52 FF 57 FF 61 FF 64 FF 6D FF 76 FF 7C
0A5D: FF 85 FF 8F FF 95 FF 9F FF A9 FF B0 FF BB FF C5
0A6D: FF CC FF D7 FF E2 FF ED FF F8 FF 00 00 00 00 08
0A7D: 00 13 00 1E 00 29 00 34 00 3B 00 45 00 50 00 57
0A8D: 00 61 00 6B 00 71 00 7B 00 84 00 8A 00 93 00 9C
0A9D: 00 9F 00 9F 00 AE 00 B6 00 BC 00 C3 00 C6 00 CA
0AAD: 00 D1 00 D5 00 D8 00 DE 00 E2 00 E5 00 EB 00 EE
0ABD: 00 F1 00 EE 00 FA 00 FD 00 00 01 06 01 09 01 0C
0ACD: 01 12 01 15 01 18 01 1E 01 21 01 24 01 27 01 2D
0ADD: 01 2F 01 27 01 38 01 3A 01 3C 01 3E 01 40 01 45
0AED: 01 46 01 47 01 48 01 49 01 4A 01 4B 01 77 A6 13
0AFD: ED DC A5 7D 34 F1 F1 F1 B9
; stamp the four fixed pieces of the copyright caption into the display-
; list shadow; it reads nothing, so re-stamping changes nothing
stampCopyrightStrip:
0B06: FD 21 10 AA LD IY,$AA10 ; point at the strip's four display-list entries
0B0A: 06 04 LD B,$04 ; four pieces to lay
0B0C: 0E 04 LD C,$04 ; first shape number, counting up one per piece
0B0E: 16 A0 LD D,$A0 ; the leading edge on the stepping axis
0B10: 1E D8 LD E,$D8 ; the shared position on the fixed axis
loc_0b12:
0B12: FD 72 31 LD (IY+$31),D ; set this piece's position on the stepping axis
0B15: FD 73 00 LD (IY+$00),E ; set its position on the fixed axis
0B18: FD 71 01 LD (IY+$01),C ; set its shape number
0B1B: FD 36 30 6C LD (IY+$30),$6C ; set its colour and attributes
0B1F: FD 23 INC IY
0B21: FD 23 INC IY
0B23: 0C INC C ; next shape number
0B24: 7A LD A,D
0B25: D6 10 SUB $10 ; step the leading edge back by sixteen for the next piece
0B27: 57 LD D,A
0B28: 10 E8 DJNZ loc_0b12 ;
0B2A: C9 RET
; park the four sprites of the copyright caption above the first visible
; line by zeroing the vertical byte of each, leaving the rest of their
; slots standing
hideCaptionSprites:
0B2B: 21 41 AA LD HL,$AA41 ; point at the first caption sprite's position
0B2E: 11 02 00 LD DE,$0002 ; step two slots at a time
0B31: 06 04 LD B,$04 ; four caption sprites to hide
0B33: AF XOR A ; the value that hides them: zero
loc_0b34:
0B34: 77 LD (HL),A ; push this sprite above the top line by zeroing its vertical byte
0B35: 19 ADD HL,DE
0B36: 10 FC DJNZ loc_0b34 ;
0B38: C9 RET
; make the copyright line change colour every frame: ask for the same
; glyph run at the same place in one of two colours, choosing between them
; on the low bit of the frame counter, which it only reads. The request
; goes on the command ring and is dropped when the slot the write cursor
; names has not been consumed, so a frame can silently miss its turn
flashCopyrightLine:
0B39: 3A 80 A9 LD A,(workRam+180) ; read the frame counter
0B3C: CB 47 BIT 0,A ; test its low bit, which flips every frame
0B3E: 28 06 JR Z,enqueueFixedCommandOnRing; even frame: queue the other version of the line
0B40: 11 00 01 LD DE,$0100 ; command one, this frame's argument
0B43: C3 38 00 JP postCommand ; post it to the command ring, so the line flashes frame to frame
; queue one fixed command, with its one fixed argument, in the command
; ring -- both bytes are chosen here and whatever the caller held is
; discarded; the pair is dropped when the slot the write cursor names has
; not been consumed, and this entry never learns that
enqueueFixedCommandOnRing:
0B46: 11 1F 01 LD DE,$011F ; command one, argument thirty-one -- the other version of the copyright line
0B49: C3 38 00 JP postCommand ; post it to the command ring
; add a run of bytes together and answer whether the total is the byte the
; caller named; the length means a full 256 when it is zero, the total
; wraps at eight bits, nothing is written, and the answer is left for the
; caller rather than acted on here
sumByteRunAndCompareToExpected:
0B4C: AF XOR A ; clear the running total before folding the run
loc_0b4d:
0B4D: 86 ADD A,(HL) ; add the next byte into the total
0B4E: 23 INC HL
0B4F: 10 FC DJNZ loc_0b4d ;
0B51: B9 CP C ; does the total match the byte the caller named?
0B52: C8 RET Z ; yes: answer that it matches
0B53: C9 RET ; no: answer that it does not
; ---- $0B54-$0B8F: data ----
0B54: AF AE 23 10 FC B9 C8 C3 00 00 AF 86 23 0D 28 02
0B64: 18 F9 CB 47 C8 C3 00 00 21 06 0B 06 24 0E 00 7E
0B74: 91 23 10 FB EB BE C9 0F A7 13 88 0D ED C4 F1 ED
0B84: DC A5 D7 DC F1 8C 0D DC DC 68 3B B9
; tail transfer into the foreground command-ring loop: a jp that hands
; control to the drain and never comes back; touches no memory or
; register, so its whole product is the drain's continuation handed
; straight back
enterCommandRingDrain:
0B90: C3 93 0B JP runCommandRingDrainLoop; back to the top of the command-ring drain
; the foreground loop: take commands off the ring one at a time and run
; each, for ever. A read cursor names a cell; while its high bit is set
; the cell holds nothing and the loop looks again, which is the only wait
; for the vblank among the foreground loops a coin-and-play tape reaches
; -- the ring is refilled from outside the loop. An occupied cell gives up
; a command byte and an argument byte, both cells are freed BEFORE the
; command runs so a command may reuse the pair it arrived in, and the low
; nibble of the command indexes a sixteen-way table. Where the handler
; lands is the exit test: it is handed one fixed place to come back to,
; and anything else means it has taken the machine somewhere this loop no
; longer owns
runCommandRingDrainLoop:
0B93: 26 AC LD H,$AC ; the command ring lives on page $AC
0B95: 3A B3 A9 LD A,(workRam+1B3) ; read the ring's read cursor
0B98: 6F LD L,A ; point at the cell it names
0B99: 7E LD A,(HL) ; take that cell's command byte
0B9A: 07 RLCA ; rotate its top bit out
0B9B: DA 90 0B JP C,enterCommandRingDrain; top bit set means the cell is empty: wait a frame and look again
0B9E: 4E LD C,(HL) ; keep the command
0B9F: 36 FF LD (HL),$FF ; free the cell so it can be filled again
0BA1: 23 INC HL ; step to the argument cell
0BA2: 46 LD B,(HL) ; take the argument
0BA3: 36 FF LD (HL),$FF ; free that cell too
0BA5: 23 INC HL
0BA6: 7D LD A,L
0BA7: E6 3F AND $3F ; keep the cursor inside the sixty-four-cell ring
0BA9: 32 B3 A9 LD (workRam+1B3),A ; save the advanced read cursor
0BAC: 79 LD A,C
0BAD: E6 0F AND $0F ; the command's low nibble picks one of sixteen handlers
0BAF: 21 BC 0B LD HL,$0BBC ; point at the handler table
0BB2: CD 8C 01 CALL fetchWideTableWord ; read this handler's address from it
0BB5: 78 LD A,B ; hand the argument to the handler
0BB6: 21 90 0B LD HL,$0B90
0BB9: E5 PUSH HL ; make the loop top the handler's return
0BBA: EB EX DE,HL
0BBB: E9 JP (HL) ; run the handler
; ---- $0BBC-$0BF1: data ----
0BBC: DD 0B F2 0B 0F 0C 39 0C 90 0C 72 4D D7 0D AC 0E
0BCC: DC 0B DC 0B 21 34 23 0C DC 0B DC 0B DC 0B DC 0B
0BDC: C9 21 50 0C CD 8C 01 EB 5E 23 56 23 23 7E FE B9
0BEC: C8 12 23 E7 18 F7
; paint the caption an index selects: the index picks a record from one
; word table, and the record supplies the destination cell, the colour and
; the glyph run that drawTextRun then paints
drawTextRunByIndex:
0BF2: 21 50 0C LD HL,$0C50 ; point at the caption-record table
0BF5: CD 8C 01 CALL fetchWideTableWord ; read the record pointer this caption index selects
0BF8: EB EX DE,HL ; point at that record
0BF9: 5E LD E,(HL) ; read the low byte of the cell the caption starts at
0BFA: 23 INC HL ; step to the high byte
0BFB: 56 LD D,(HL) ; read the high byte -- the caption's first cell
0BFC: 23 INC HL ; step on
0BFD: 4E LD C,(HL) ; read the colour every cell of the caption takes
0BFE: 23 INC HL ; step to the glyph run, then fall into the painter
; paint one caption into the character plane and give every cell of it one
; colour, taking glyphs in order from a run that ends at a fixed
; terminating code
drawTextRun:
0BFF: 7E LD A,(HL) ; read the next glyph of the run
0C00: FE B9 CP $B9 ; is it the run-ending code?
0C02: C8 RET Z ; end reached: stop, leaving the pointer on the terminator
0C03: 12 LD (DE),A ; write the glyph into the character cell
0C04: CB 92 RES 2,D ; drop to the colour plane beneath it
0C06: 79 LD A,C ; fetch the caption's colour
0C07: 12 LD (DE),A ; give the cell its colour
0C08: CB D2 SET 2,D ; back up to the character plane
0C0A: 23 INC HL ; step to the next glyph
0C0B: E7 RST $20 ; step the cursor one cell along the line
0C0C: C3 FF 0B JP drawTextRun ; round the loop for the next glyph
; ---- $0C0F-$0D6A: data ----
0C0F: 21 50 0C CD 8C 01 EB 5E 23 56 23 23 3A 0C AD E6
0C1F: 0F 4F 18 DC 21 50 0C CD 8C 01 EB 5E 23 56 23 23
0C2F: 3A 0C AD C6 0A E6 0F 4F 18 C6 21 50 0C CD 8C 01
0C3F: EB 5E 23 56 23 23 7E FE B9 C8 3E F1 12 23 E7 18
0C4F: F5 6B 08 73 16 7F 30 1D 58 FA 49 D6 15 4C 58 09
0C5F: 25 CA 15 67 01 42 4E 10 18 CE 48 A4 1B FA 0A 31
0C6F: 24 3B 12 9B 45 A4 2C 4F 00 9E 31 6E 29 7B 0B 5C
0C7F: 34 D2 3E 48 33 49 0F 14 4C 54 59 ED 55 D8 23 00
0C8F: 49 4F 06 00 3A 30 AD A7 CA E8 0C 79 A7 CA E9 0C
0C9F: 21 27 0D 09 09 09 11 33 AD 3A 32 AD A7 28 03 11
0CAF: 36 AD 1A 86 27 12 13 23 1A 8E 27 12 13 23 1A 8E
0CBF: 27 12 21 8D A9 01 03 00 1A BE 38 0F 20 07 1B 2B
0CCF: 0D 20 F5 18 06 EB ED B8 CD 6B 0D 3A 32 AD A7 20
0CDF: 05 CD 57 0D 18 03 CD 61 0D C9 3A 31 AD A7 20 1B
0CEF: 3A 31 0B CD F2 0B CD 57 0D 3A C6 15 CD 39 0C 11
0CFF: 01 A5 06 06 3E F1 12 E7 10 FA C9 3E 06 CD F2 0B
0D0F: CD 57 0D 3E 07 CD F2 0B CD 61 0D C9 3C A2 C7 AC
0D1F: 7C A2 43 AB FC A1 BE AC 00 00 00 00 01 00 00 02
0D2F: 00 00 03 00 00 04 00 00 05 00 00 06 00 00 07 00
0D3F: 00 08 00 00 09 00 00 10 00 00 15 00 00 20 00 00
0D4F: 30 00 00 40 00 00 50 00 11 81 A7 21 35 AD 0E 10
0D5F: 18 12 11 01 A5 21 38 AD 0E 10 18 08
; enter the shared packed-decimal digit routine at 0x0D73 with a third
; fixed triple -- first cell 0xA641, the field whose high end is 0xA98D,
; and a fixed colour; the routine walks the field downward, so the high
; end is where it starts
paintHighScoreReadout:
0D6B: 11 41 A6 LD DE,$A641 ; the leftmost digit lands here
0D6E: 21 8D A9 LD HL,$A98D ; print from the high score's top byte -- the field is walked downward
0D71: 0E 10 LD C,$10 ; colour for every digit, then fall into the printer
; paint a six-digit field: two packed bytes through the suppressing
; painter, sharing one suppression flag this entry clears, then a third
; through the plain painter so the last two digits always show, walking
; the source pointer backwards as it goes
paintSixDigitFieldSuppressingLeadingZeros:
0D73: 06 00 LD B,$00 ; clear the leading-zero suppression flag for the whole field
0D75: CD A0 0D CALL paintTwoSuppressedDigitsFromByte; print the top two digits, suppressing leading zeros
0D78: 2B DEC HL ; step back one source byte
0D79: CD A0 0D CALL paintTwoSuppressedDigitsFromByte; print the next two, still suppressing
0D7C: 2B DEC HL
0D7D: CD 81 0D CALL paintTwoUnsuppressedDigitsFromByte; print the last two plainly, so they always show
0D80: C9 RET
; paint the two decimal digits packed into one byte, the high one first,
; stepping the cursor one cell on after each; the byte is read twice from
; the pointer the caller is walking, shifted down for the high digit and
; taken whole for the low, and the colour and cursor arrive as the caller
; left them
paintTwoUnsuppressedDigitsFromByte:
0D81: 7E LD A,(HL) ; read the packed byte
0D82: 0F RRCA ; rotate the high nibble down into the low four bits
0D83: 0F RRCA
0D84: 0F RRCA
0D85: 0F RRCA
0D86: CD 90 0D CALL paintUnsuppressedDigit; print the high digit
0D89: E7 RST $20 ; step the cursor one cell on
0D8A: 7E LD A,(HL) ; read the byte again for the low digit
0D8B: CD 90 0D CALL paintUnsuppressedDigit; print the low digit
0D8E: E7 RST $20 ; step the cursor on
0D8F: C9 RET
; paint one decimal digit and the caller's colour into the cell a cursor
; names, taking the glyph from the table at 0x0DCC by the value's low four
; bits -- a zero always paints the digit 0, where the suppressing twin
; paints the blank instead while no significant digit has been seen yet --
; and leaving the cursor on the glyph side and the caller's run pointer
; where it was
paintUnsuppressedDigit:
0D90: E6 0F AND $0F ; keep the low four bits -- the digit value
0D92: E5 PUSH HL ; save the caller's run pointer
0D93: 21 CC 0D LD HL,$0DCC ; point at the digit-glyph table
0D96: CF RST $08 ; look up this digit's glyph (zero prints a '0' here)
0D97: E1 POP HL ; restore the run pointer
0D98: 12 LD (DE),A ; write the glyph into the cell
0D99: CB 92 RES 2,D ; drop to the colour plane
0D9B: 79 LD A,C ; take the caller's colour
0D9C: 12 LD (DE),A ; colour the cell
0D9D: CB D2 SET 2,D ; back to the character plane
0D9F: C9 RET
; paint the two decimal digits packed into one byte with a leading zero
; suppressed, the high one first, stepping the cursor one cell on after
; each; the caller's suppression flag arrives, carries across both digits
; and goes back out, so a longer run of digits suppresses as one field
paintTwoSuppressedDigitsFromByte:
0DA0: 7E LD A,(HL) ; read the packed byte
0DA1: 0F RRCA ; rotate the high nibble down into the low four bits
0DA2: 0F RRCA
0DA3: 0F RRCA
0DA4: 0F RRCA
0DA5: CD AF 0D CALL paintSuppressedDigit; print the high digit, suppressing a leading zero
0DA8: E7 RST $20 ; step the cursor on
0DA9: 7E LD A,(HL) ; read the byte again for the low digit
0DAA: CD AF 0D CALL paintSuppressedDigit; print the low digit, still suppressing
0DAD: E7 RST $20 ; step the cursor on
0DAE: C9 RET
; paint one four-bit digit into the cell the cursor names with the
; caller's colour a plane below, using the blank glyph instead when the
; digit is zero and no significant digit has been seen yet, and stepping
; the caller's flag on at the first that is
paintSuppressedDigit:
0DAF: E6 0F AND $0F ; keep the low four bits -- the digit
0DB1: 28 03 JR Z,loc_0db6 ; a zero digit: decide blank versus '0'
0DB3: 04 INC B ; non-zero: mark that a significant digit has been seen
0DB4: 18 08 JR loc_0dbe ; and print it by its own value
loc_0db6:
0DB6: 3A 46 32 LD A,(rom+3246) ; a zero: take the blank-glyph index from the program image
0DB9: 04 INC B ; momentarily bump the seen-digit count
0DBA: 05 DEC B ; drop it back, setting the flags: has a significant digit been seen yet?
0DBB: 28 01 JR Z,loc_0dbe ; none yet: print the blank
0DBD: AF XOR A ; one already seen: print '0' instead
loc_0dbe:
0DBE: E5 PUSH HL ; save the caller's run pointer
0DBF: 21 CC 0D LD HL,$0DCC ; point at the digit-glyph table
0DC2: CF RST $08 ; look up the glyph for this index
0DC3: E1 POP HL ; restore the run pointer
0DC4: 12 LD (DE),A ; write the glyph into the cell
0DC5: CB 92 RES 2,D ; drop to the colour plane
0DC7: 79 LD A,C ; take the caller's colour
0DC8: 12 LD (DE),A ; colour the cell
0DC9: CB D2 SET 2,D ; back to the character plane
0DCB: C9 RET
; ---- $0DCC-$0DD6: data ----
0DCC: 13 96 9B CD F3 7F 65 02 17 5D F1
; draw a clamped 0..99 value as a right-to-left row of denomination tiles
; (thirties, tens, fives, ones) from display cell 0xa463, pad the rest of
; the row to 0xa623 with the blank glyph, then verify a fixed three-word
; checksum (0x009d/0x00a0/0x00a3) and hard-reset via 0x0000 on mismatch
drawCountAsPictogramStrip:
0DD7: 11 63 A4 LD DE,$A463 ; point at the start of the pictogram row
0DDA: FE 64 CP $64 ; is the count 100 or more?
0DDC: 38 02 JR C,loc_0de0 ; under 100: keep the value
0DDE: 3E 63 LD A,$63 ; clamp to 99
loc_0de0:
0DE0: D9 EXX ; tally the denominations in the spare registers, leaving the row cursor alone
0DE1: 06 00 LD B,$00 ; no thirties yet
loc_0de3:
0DE3: D6 1E SUB $1E ; subtract thirty
0DE5: 38 03 JR C,loc_0dea ; gone negative: no more thirties
0DE7: 04 INC B ; one more thirty
0DE8: 18 F9 JR loc_0de3 ; keep subtracting
loc_0dea:
0DEA: C6 1E ADD A,$1E ; add the last thirty back
0DEC: 0E 00 LD C,$00 ; no tens yet
loc_0dee:
0DEE: D6 0A SUB $0A ; subtract ten
0DF0: 38 03 JR C,loc_0df5 ; gone negative: no more tens
0DF2: 0C INC C ; one more ten
0DF3: 18 F9 JR loc_0dee ; keep subtracting
loc_0df5:
0DF5: C6 0A ADD A,$0A ; add the last ten back
0DF7: 16 00 LD D,$00 ; no fives yet
loc_0df9:
0DF9: D6 05 SUB $05 ; subtract five
0DFB: 38 03 JR C,loc_0e00 ; gone negative: no more fives
0DFD: 14 INC D ; one more five
0DFE: 18 F9 JR loc_0df9 ; keep subtracting
loc_0e00:
0E00: C6 05 ADD A,$05 ; add the last five back
0E02: 5F LD E,A ; what remains is the count of ones
0E03: D9 EXX ; return to the drawing registers
0E04: D9 EXX ; dip into the spare registers for the ones count
0E05: 7B LD A,E ; take the count of ones
0E06: D9 EXX ; back to the drawing registers
0E07: A7 AND A ; any ones to draw?
0E08: 28 0C JR Z,loc_0e16 ; none: on to the fives
0E0A: 06 01 LD B,$01 ; ones tile code
0E0C: 0E 13 LD C,$13 ; ones colour
loc_0e0e:
0E0E: 08 EX AF,AF' ; park the remaining count out of the way
0E0F: CD 8D 0E CALL drawSlotWithOneGlyph; draw one 'ones' block
0E12: 08 EX AF,AF' ; take the count back
0E13: 3D DEC A ; one fewer to draw
0E14: 20 F8 JR NZ,loc_0e0e ; more ones? round again
loc_0e16:
0E16: D9 EXX ; dip into the spare registers
0E17: 7A LD A,D ; take the count of fives
0E18: D9 EXX ; back to the drawing registers
0E19: A7 AND A ; any fives to draw?
0E1A: 28 0C JR Z,loc_0e28 ; none: on to the tens
0E1C: 06 32 LD B,$32 ; fives tile code
0E1E: 0E 11 LD C,$11 ; fives colour
loc_0e20:
0E20: 08 EX AF,AF' ; park the count
0E21: CD 9C 0E CALL paintDoubleTile ; draw one 'fives' block (a two-tile mark)
0E24: 08 EX AF,AF' ; take the count back
0E25: 3D DEC A ; one fewer
0E26: 20 F8 JR NZ,loc_0e20 ; more fives? round again
loc_0e28:
0E28: D9 EXX ; dip into the spare registers
0E29: 79 LD A,C ; take the count of tens
0E2A: D9 EXX ; back to the drawing registers
0E2B: A7 AND A ; any tens to draw?
0E2C: 28 0C JR Z,loc_0e3a ; none: on to the thirties
0E2E: 06 CE LD B,$CE ; tens tile code
0E30: 0E 16 LD C,$16 ; tens colour
loc_0e32:
0E32: 08 EX AF,AF' ; park the count
0E33: CD 70 0E CALL paintQuadTile ; draw one 'tens' block (a four-tile mark)
0E36: 08 EX AF,AF' ; take the count back
0E37: 3D DEC A ; one fewer
0E38: 20 F8 JR NZ,loc_0e32 ; more tens? round again
loc_0e3a:
0E3A: D9 EXX ; dip into the spare registers
0E3B: 78 LD A,B ; take the count of thirties
0E3C: D9 EXX ; back to the drawing registers
0E3D: A7 AND A ; any thirties to draw?
0E3E: 28 0C JR Z,loc_0e4c ; none: on to padding the row
0E40: 06 23 LD B,$23 ; thirties tile code
0E42: 0E 11 LD C,$11 ; thirties colour
loc_0e44:
0E44: 08 EX AF,AF' ; park the count
0E45: CD 70 0E CALL paintQuadTile ; draw one 'thirties' block (a four-tile mark)
0E48: 08 EX AF,AF' ; take the count back
0E49: 3D DEC A ; one fewer
0E4A: 20 F8 JR NZ,loc_0e44 ; more thirties? round again
loc_0e4c:
0E4C: 01 10 F1 LD BC,$F110 ; blank glyph in B, its colour in C
loc_0e4f:
0E4F: 21 DD 59 LD HL,$59DD ; load the end-of-row test value
0E52: 19 ADD HL,DE ; reached the end of the row?
0E53: 38 05 JR C,loc_0e5a ; row full: run the integrity check
0E55: CD 8D 0E CALL drawSlotWithOneGlyph; blank the next slot
0E58: 18 F5 JR loc_0e4f ; keep padding
loc_0e5a:
0E5A: AF XOR A ; start the running total at zero
0E5B: 2A A0 00 LD HL,(rom+A0) ; take the first integrity word from the program image
0E5E: ED 5B A3 00 LD DE,(rom+A3) ; take the second integrity word
loc_0e62:
0E62: ED 4B 9D 00 LD BC,(rom+9D) ; take the third integrity word
loc_0e66:
0E66: 19 ADD HL,DE ; fold the words together
0E67: 09 ADD HL,BC ; fold in the third
0E68: 85 ADD A,L ; add in the low half
0E69: 84 ADD A,H ; add in the high half
0E6A: D6 69 SUB $69 ; a genuine image nets to this constant
0E6C: C2 00 00 JP NZ,trampolineToSeatTheStackAndSettleTheControlLatch; tampered: restart the machine from the reset entry
0E6F: C9 RET
; lay one four-tile block into the character plane from a base code the
; caller fixes, give all four the caller's colour a plane below, and leave
; the cursor clear of the block for the next one
paintQuadTile:
0E70: 78 LD A,B ; take the base tile code
0E71: 3C INC A ; the top-right quarter is base+1
0E72: 12 LD (DE),A ; lay it in the cursor cell
0E73: 3D DEC A ; back to the base code
0E74: 1B DEC DE ; step one cell back
0E75: 12 LD (DE),A ; lay the top-left quarter (the base code)
0E76: EF RST $28 ; drop down one line
0E77: 78 LD A,B ; the base code again
0E78: C6 02 ADD A,$02 ; the bottom-left quarter is base+2
0E7A: 12 LD (DE),A ; lay it in
0E7B: 3C INC A ; the bottom-right quarter is base+3
0E7C: 13 INC DE ; step one cell on
0E7D: 12 LD (DE),A ; lay it in
0E7E: 21 00 FC LD HL,$FC00 ; offset down to the colour plane
0E81: 19 ADD HL,DE ; point at the bottom-right quarter's colour cell
0E82: EF RST $28 ; leave the cursor two lines on, clear of the block
0E83: 71 LD (HL),C ; colour the bottom-right quarter
0E84: 2B DEC HL ; step back one cell
0E85: 71 LD (HL),C ; colour the bottom-left quarter
0E86: EB EX DE,HL
0E87: E7 RST $20 ; step up to the top row's colour cells
0E88: EB EX DE,HL
0E89: 71 LD (HL),C ; colour the top-left quarter
0E8A: 23 INC HL ; step on one cell
0E8B: 71 LD (HL),C ; colour the top-right quarter
0E8C: C9 RET
; paint a two-cell character slot with a single glyph, blanking the other
; cell of the slot, give both the caller's colour, and step the cursor on
; to the next slot
drawSlotWithOneGlyph:
0E8D: EB EX DE,HL ; work with the cursor in HL
0E8E: 70 LD (HL),B ; write the glyph into the cell
0E8F: 2B DEC HL ; step one cell back
0E90: 36 F1 LD (HL),$F1 ; blank that cell
0E92: CB 94 RES 2,H ; drop to the colour plane
0E94: 71 LD (HL),C ; colour the blanked cell
0E95: 23 INC HL ; step one cell on
0E96: 71 LD (HL),C ; colour the glyph cell
0E97: CB D4 SET 2,H ; back to the character plane
0E99: EB EX DE,HL ; cursor back into place
0E9A: EF RST $28 ; step on to the next slot
0E9B: C9 RET
; lay one two-tile block into the character plane from a base code the
; caller fixes -- the base below the cursor and the base plus one at it --
; colour both cells a plane below, and step the cursor clear of the block
paintDoubleTile:
0E9C: EB EX DE,HL ; work with the cursor in HL
0E9D: 04 INC B ; the upper tile code is base+1
0E9E: 70 LD (HL),B ; lay it in the cursor cell
0E9F: 05 DEC B ; back to the base code
0EA0: 2B DEC HL ; the cell below
0EA1: 70 LD (HL),B ; lay the lower tile (the base code)
0EA2: CB 94 RES 2,H ; drop to the colour plane
0EA4: 71 LD (HL),C ; colour the lower cell
0EA5: 23 INC HL ; the cell above
0EA6: 71 LD (HL),C ; colour the upper cell
0EA7: CB D4 SET 2,H ; back to the character plane
0EA9: EB EX DE,HL ; cursor back into place
0EAA: EF RST $28 ; step on past the block
0EAB: C9 RET
; ---- $0EAC-$0F10: data ----
0EAC: 3A 01 AD FE 64 D0 3E 0E CD 0F 0C EF EF 21 01 AD
0EBC: 06 01 3A 0C AD 4F C5 0E 00 7E D6 0A 38 03 0C 18
0ECC: F9 C6 0A 08 79 C1 CD EB 0E E7 08 CD EB 0E E7 11
0EDC: 48 17 01 8C 10 1A 81 4F 13 10 FA C2 09 25 C9 E6
0EEC: 0F 28 10 06 00 E5 21 06 0F CF E1 12 CB 92 79 12
0EFC: CB D2 C9 78 A7 28 EE 05 EF C9 E3 49 A8 64 27 AE
0F0C: 42 B0 D5 86 F1
; advance the outer sequence phase and restart its inner step index at
; zero
advanceSequencePhase:
0F11: 21 AB A9 LD HL,$A9AB ; point at the sequence phase
0F14: 34 INC (HL) ; step to the next phase
0F15: AF XOR A
0F16: 32 AC A9 LD (workRam+1AC),A ; restart the inner sub-step at zero for the new phase
0F19: C9 RET
; step the jump-table sequence index on by one; reached as a tail jump so
; the caller's own return carries it
advanceSequenceSubStep:
0F1A: 21 AC A9 LD HL,$A9AC ; point at the sequence sub-step
0F1D: 34 INC (HL) ; step it on by one
0F1E: C9 RET
; the inner level of the two-level sequence machine for one outer mode:
; run the arm the LOW NIBBLE of the inner index selects out of a sixteen-
; word table laid inline just after this entry, then one fixed block; the
; arm returns through a slot this entry parks for it
dispatchSequenceSubStepArm:
0F1F: 21 54 0F LD HL,$0F54 ; the continuation to run once the arm returns
0F22: E5 PUSH HL ; park it as the arm's return
0F23: 3A AC A9 LD A,(workRam+1AC) ; take the sub-step
0F26: E6 0F AND $0F ; its low nibble picks the arm
0F28: F7 RST $30 ; jump into that arm through the inline table just past here
; ---- $0F29-$0F48: jump table ----
0F29: B1 27 5E 33 D7 5B 75 4C 74 07 AF 16 94 56 99 11
0F39: 0B 33 B4 08 C3 18 E2 12 FB 12 0F 4A 23 13 B5 15
; ---- $0F49-$0F53: data ----
0F49: 73 A6 14 7E 29 F8 96 5D 96 13 B9
; guarded tail of the phase-3 image-service step, reached as
; dispatchSequenceSubStepArm's pushed continuation: returns while the
; play-active flag (0xAD30) is set; on a nonzero credit count (0xA986) it
; zeroes the sequence sub-step (0xA9AC) and reloads the phase (0xA9AB)
; from the ROM constant at 0x1736; otherwise, only when the free-play flag
; (0xA9C0) is set and one of two input bits (0xA9AE & 0x18) is held, it
; zero-fills the work table 0x15b6 clears and tail-calls $1690
advanceAttractTowardGameStart:
0F54: 3A 30 AD LD A,(workRam+530) ; read the play-active flag
0F57: A7 AND A
0F58: C0 RET NZ ; a game is running: nothing to do here
0F59: 3A 86 A9 LD A,(workRam+186) ; read the credit count
0F5C: A7 AND A
0F5D: 20 11 JR NZ,loc_0f70 ; a credit is waiting: arm the start
0F5F: 3A C0 A9 LD A,(workRam+1C0) ; read the free-play flag
0F62: A7 AND A
0F63: C8 RET Z ; not free play: nothing to start
0F64: 3A AE A9 LD A,(workRam+1AE) ; read the control inputs
0F67: E6 18 AND $18 ; mask the two start buttons
0F69: C8 RET Z ; neither held: wait
0F6A: CD B6 15 CALL hideAllSprites ; hide all the sprites
0F6D: C3 90 16 JP startGameOnFreePlay ; start the free-play game
loc_0f70:
0F70: AF XOR A ; clear the sequence sub-step
0F71: 32 AC A9 LD (workRam+1AC),A ;
0F74: 3A 36 17 LD A,(rom+1736) ; take the "credit taken" phase from the program image
0F77: 32 AB A9 LD (workRam+1AB),A ; set the sequence phase to it
0F7A: C9 RET
; copy the four-byte record an index selects out of a fixed table and into
; the four cells that hold the difficulty settings in force; scaling the
; index by the record width is done as a BYTE, so an index of sixty-four
; or more selects a record a wider multiply would not
loadDifficultyRecord:
0F7B: 87 ADD A,A ; double the index...
0F7C: 87 ADD A,A ; ...and again: x4 for the four-byte record (kept to a byte, so index 64+ wraps onto a wrong record)
0F7D: 21 6A 18 LD HL,$186A ; point at the difficulty-record table
0F80: 11 D3 A9 LD DE,$A9D3 ; point at the live difficulty settings
0F83: DF RST $18 ; step to the selected record
0F84: ED A0 LDI ; copy the first setting byte
0F86: ED A0 LDI ; copy the second
0F88: ED A0 LDI ; copy the third
0F8A: ED A0 LDI ; copy the fourth
0F8C: C9 RET
loc_0f8d:
0F8D: F1 POP AF
0F8E: 01 F1 02 LD BC,$02F1
0F91: F1 POP AF
0F92: 03 INC BC
0F93: F1 POP AF
0F94: 04 INC B
0F95: F1 POP AF
0F96: 05 DEC B
; scanline-gated sprite position fixup over 8 slots: for each slot whose Y
; byte (sprite bank 1) has bit 7 set and whose Y + scanline counter
; carries, clears bit 7 of that Y byte and toggles bit 7 of the paired X
; byte (sprite bank 0)
multiplexSpriteSlotsSkipping:
0F97: 3A 11 B4 LD A,(spriteRam1+11) ; read the first scenery slot's Y byte -- bit 7 marks a slot wanting a second image this frame
0F9A: CB 7F BIT 7,A ; test that request bit
0F9C: 28 19 JR Z,loc_0fb7 ; clear -- move on to the next slot
0F9E: 4F LD C,A
0F9F: 3A 00 C0 LD A,(scanline) ; read the live scanline counter
0FA2: 81 ADD A,C ; add the slot's line -- carry once the beam has passed it
0FA3: 30 12 JR NC,loc_0fb7 ; beam not past yet -- leave this slot untouched this pass
0FA5: 23 INC HL
0FA6: 23 INC HL
0FA7: 2B DEC HL
0FA8: 2B DEC HL
0FA9: 79 LD A,C
0FAA: E6 7F AND $7F ; clear bit 7 -- shifts the slot half a screen and clears its request
0FAC: 32 11 B4 LD (spriteRam1+11),A ; store the moved Y byte
0FAF: 3A 10 B0 LD A,(spriteRam0+10) ; read the slot's paired X byte
0FB2: C6 80 ADD A,$80 ; add half a screen across
0FB4: 32 10 B0 LD (spriteRam0+10),A ; store it -- the same sprite draws again half a screen away, with no extra hardware slot
loc_0fb7:
0FB7: 3A 13 B4 LD A,(spriteRam1+13) ; read the next slot's Y byte
0FBA: CB 7F BIT 7,A ; test its request bit
0FBC: 28 19 JR Z,loc_0fd7 ; clear -- next slot
0FBE: 4F LD C,A
0FBF: 3A 00 C0 LD A,(scanline) ; read the scanline counter
0FC2: 81 ADD A,C ; carry once the beam is past this slot's line
0FC3: 30 12 JR NC,loc_0fd7 ; beam not past -- skip this slot this pass
0FC5: 23 INC HL
0FC6: 23 INC HL
0FC7: 2B DEC HL
0FC8: 2B DEC HL
0FC9: 79 LD A,C
0FCA: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
0FCC: 32 13 B4 LD (spriteRam1+13),A ; store the moved Y byte
0FCF: 3A 12 B0 LD A,(spriteRam0+12) ; read the paired X byte
0FD2: C6 80 ADD A,$80 ; add half a screen across
0FD4: 32 12 B0 LD (spriteRam0+12),A ; store it -- second image placed
loc_0fd7:
0FD7: 3A 15 B4 LD A,(spriteRam1+15) ; read the next slot's Y byte
0FDA: CB 7F BIT 7,A ; test its request bit
0FDC: 28 19 JR Z,loc_0ff7 ; clear -- next slot
0FDE: 4F LD C,A
0FDF: 3A 00 C0 LD A,(scanline) ; read the scanline counter
0FE2: 81 ADD A,C ; carry once the beam is past this slot's line
0FE3: 30 12 JR NC,loc_0ff7 ; beam not past -- skip this slot this pass
0FE5: 23 INC HL
0FE6: 23 INC HL
0FE7: 2B DEC HL
0FE8: 2B DEC HL
0FE9: 79 LD A,C
0FEA: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
0FEC: 32 15 B4 LD (spriteRam1+15),A ; store the moved Y byte
0FEF: 3A 14 B0 LD A,(spriteRam0+14) ; read the paired X byte
0FF2: C6 80 ADD A,$80 ; add half a screen across
0FF4: 32 14 B0 LD (spriteRam0+14),A ; store it -- second image placed
loc_0ff7:
0FF7: 3A 37 B4 LD A,(spriteRam1+37) ; read the next slot's Y byte
0FFA: CB 7F BIT 7,A ; test its request bit
0FFC: 28 19 JR Z,loc_1017 ; clear -- next slot
0FFE: 4F LD C,A
0FFF: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1002: 81 ADD A,C ; carry once the beam is past this slot's line
1003: 30 12 JR NC,loc_1017 ; beam not past -- skip this slot this pass
1005: 23 INC HL
1006: 23 INC HL
1007: 2B DEC HL
1008: 2B DEC HL
1009: 79 LD A,C
100A: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
100C: 32 37 B4 LD (spriteRam1+37),A ; store the moved Y byte
100F: 3A 36 B0 LD A,(spriteRam0+36) ; read the paired X byte
1012: C6 80 ADD A,$80 ; add half a screen across
1014: 32 36 B0 LD (spriteRam0+36),A ; store it -- second image placed
loc_1017:
1017: 3A 39 B4 LD A,(spriteRam1+39) ; read the next slot's Y byte
101A: CB 7F BIT 7,A ; test its request bit
101C: 28 19 JR Z,loc_1037 ; clear -- next slot
101E: 4F LD C,A
101F: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1022: 81 ADD A,C ; carry once the beam is past this slot's line
1023: 30 12 JR NC,loc_1037 ; beam not past -- skip this slot this pass
1025: 23 INC HL
1026: 23 INC HL
1027: 2B DEC HL
1028: 2B DEC HL
1029: 79 LD A,C
102A: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
102C: 32 39 B4 LD (spriteRam1+39),A ; store the moved Y byte
102F: 3A 38 B0 LD A,(spriteRam0+38) ; read the paired X byte
1032: C6 80 ADD A,$80 ; add half a screen across
1034: 32 38 B0 LD (spriteRam0+38),A ; store it -- second image placed
loc_1037:
1037: 3A 3B B4 LD A,(spriteRam1+3B) ; read the next slot's Y byte
103A: CB 7F BIT 7,A ; test its request bit
103C: 28 19 JR Z,loc_1057 ; clear -- next slot
103E: 4F LD C,A
103F: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1042: 81 ADD A,C ; carry once the beam is past this slot's line
1043: 30 12 JR NC,loc_1057 ; beam not past -- skip this slot this pass
1045: 23 INC HL
1046: 23 INC HL
1047: 2B DEC HL
1048: 2B DEC HL
1049: 79 LD A,C
104A: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
104C: 32 3B B4 LD (spriteRam1+3B),A ; store the moved Y byte
104F: 3A 3A B0 LD A,(spriteRam0+3A) ; read the paired X byte
1052: C6 80 ADD A,$80 ; add half a screen across
1054: 32 3A B0 LD (spriteRam0+3A),A ; store it -- second image placed
loc_1057:
1057: 3A 3D B4 LD A,(spriteRam1+3D) ; read the next slot's Y byte
105A: CB 7F BIT 7,A ; test its request bit
105C: 28 19 JR Z,loc_1077 ; clear -- next slot
105E: 4F LD C,A
105F: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1062: 81 ADD A,C ; carry once the beam is past this slot's line
1063: 30 12 JR NC,loc_1077 ; beam not past -- skip this slot this pass
1065: 23 INC HL
1066: 23 INC HL
1067: 2B DEC HL
1068: 2B DEC HL
1069: 79 LD A,C
106A: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
106C: 32 3D B4 LD (spriteRam1+3D),A ; store the moved Y byte
106F: 3A 3C B0 LD A,(spriteRam0+3C) ; read the paired X byte
1072: C6 80 ADD A,$80 ; add half a screen across
1074: 32 3C B0 LD (spriteRam0+3C),A ; store it -- second image placed
loc_1077:
1077: 3A 3F B4 LD A,(spriteRam1+3F) ; read the last slot's Y byte
107A: CB 7F BIT 7,A ; test its request bit
107C: 28 19 JR Z,loc_1097 ; clear -- done
107E: 4F LD C,A
107F: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1082: 81 ADD A,C ; carry once the beam is past this slot's line
1083: 30 12 JR NC,loc_1097 ; beam not past -- skip this slot this pass
1085: 23 INC HL
1086: 23 INC HL
1087: 2B DEC HL
1088: 2B DEC HL
1089: 79 LD A,C
108A: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
108C: 32 3F B4 LD (spriteRam1+3F),A ; store the moved Y byte
108F: 3A 3E B0 LD A,(spriteRam0+3E) ; read the paired X byte
1092: C6 80 ADD A,$80 ; add half a screen across
1094: 32 3E B0 LD (spriteRam0+3E),A ; store it -- second image placed
loc_1097:
1097: C9 RET
; wait until the raster has passed each of eight scenery slots, then move
; that slot half a screen in both axes so the same sprite shows twice in
; one frame; a slot whose request bit is clear is left alone
multiplexSpriteSlots:
1098: 3A 11 B4 LD A,(spriteRam1+11) ; read the first scenery slot's Y byte -- this pass waits on the beam rather than skipping
109B: CB 7F BIT 7,A ; test its request bit
109D: 28 19 JR Z,loc_10b8 ; clear -- next slot
109F: 4F LD C,A
10A0: 3A 00 C0 LD A,(scanline) ; read the scanline counter
10A3: 81 ADD A,C ; carry once the beam is past this slot's line
10A4: 30 F2 JR NC,multiplexSpriteSlots; beam not past -- hold here until it is
10A6: 23 INC HL
10A7: 23 INC HL
10A8: 2B DEC HL
10A9: 2B DEC HL
10AA: 79 LD A,C
10AB: E6 7F AND $7F ; clear bit 7 -- shift the slot half a screen, clear its request
10AD: 32 11 B4 LD (spriteRam1+11),A ; store the moved Y byte
10B0: 3A 10 B0 LD A,(spriteRam0+10) ; read the paired X byte
10B3: C6 80 ADD A,$80 ; add half a screen across
10B5: 32 10 B0 LD (spriteRam0+10),A ; store it -- second image placed
loc_10b8:
10B8: 3A 13 B4 LD A,(spriteRam1+13) ; read the next slot's Y byte
10BB: CB 7F BIT 7,A ; test its request bit
10BD: 28 19 JR Z,loc_10d8 ; clear -- next slot
10BF: 4F LD C,A
10C0: 3A 00 C0 LD A,(scanline) ; read the scanline counter
10C3: 81 ADD A,C ; carry once the beam is past this slot's line
10C4: 30 F2 JR NC,loc_10b8 ; beam not past -- hold here until it is
10C6: 23 INC HL
10C7: 23 INC HL
10C8: 2B DEC HL
10C9: 2B DEC HL
10CA: 79 LD A,C
10CB: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
10CD: 32 13 B4 LD (spriteRam1+13),A ; store the moved Y byte
10D0: 3A 12 B0 LD A,(spriteRam0+12) ; read the paired X byte
10D3: C6 80 ADD A,$80 ; add half a screen across
10D5: 32 12 B0 LD (spriteRam0+12),A ; store it -- second image placed
loc_10d8:
10D8: 3A 15 B4 LD A,(spriteRam1+15) ; read the next slot's Y byte
10DB: CB 7F BIT 7,A ; test its request bit
10DD: 28 19 JR Z,loc_10f8 ; clear -- next slot
10DF: 4F LD C,A
10E0: 3A 00 C0 LD A,(scanline) ; read the scanline counter
10E3: 81 ADD A,C ; carry once the beam is past this slot's line
10E4: 30 F2 JR NC,loc_10d8 ; beam not past -- hold here until it is
10E6: 23 INC HL
10E7: 23 INC HL
10E8: 2B DEC HL
10E9: 2B DEC HL
10EA: 79 LD A,C
10EB: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
10ED: 32 15 B4 LD (spriteRam1+15),A ; store the moved Y byte
10F0: 3A 14 B0 LD A,(spriteRam0+14) ; read the paired X byte
10F3: C6 80 ADD A,$80 ; add half a screen across
10F5: 32 14 B0 LD (spriteRam0+14),A ; store it -- second image placed
loc_10f8:
10F8: 3A 37 B4 LD A,(spriteRam1+37) ; read the next slot's Y byte
10FB: CB 7F BIT 7,A ; test its request bit
; reused subroutine entry into the five-slot display-list split pass,
; joined inside the first slot: trades the first slot from the caller's
; held byte (or, below the raster line, restarts the whole pass and re-
; reads every slot from memory), then trades slots 2-5 wherever their top
; bit is set
spinRemainingSpriteMultiplexSlots:
10FD: 28 19 JR Z,loc_1118 ; clear -- skip ahead to the following slot
10FF: 4F LD C,A
1100: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1103: 81 ADD A,C ; carry once the beam is past this slot's line
1104: 30 F2 JR NC,loc_10f8 ; still above the line -- restart the whole pass, re-reading every slot
1106: 23 INC HL
1107: 23 INC HL
1108: 2B DEC HL
1109: 2B DEC HL
110A: 79 LD A,C
110B: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
110D: 32 37 B4 LD (spriteRam1+37),A ; store the moved Y byte
1110: 3A 36 B0 LD A,(spriteRam0+36) ; read the paired X byte
1113: C6 80 ADD A,$80 ; add half a screen across
1115: 32 36 B0 LD (spriteRam0+36),A ; store it -- second image placed
loc_1118:
1118: 3A 39 B4 LD A,(spriteRam1+39) ; read the next slot's Y byte
111B: CB 7F BIT 7,A ; test its request bit
111D: 28 19 JR Z,loc_1138 ; clear -- next slot
111F: 4F LD C,A
1120: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1123: 81 ADD A,C ; carry once the beam is past this slot's line
1124: 30 F2 JR NC,loc_1118 ; beam not past -- hold here until it is
1126: 23 INC HL
1127: 23 INC HL
1128: 2B DEC HL
1129: 2B DEC HL
112A: 79 LD A,C
112B: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
112D: 32 39 B4 LD (spriteRam1+39),A ; store the moved Y byte
1130: 3A 38 B0 LD A,(spriteRam0+38) ; read the paired X byte
1133: C6 80 ADD A,$80 ; add half a screen across
1135: 32 38 B0 LD (spriteRam0+38),A ; store it -- second image placed
loc_1138:
1138: 3A 3B B4 LD A,(spriteRam1+3B) ; read the next slot's Y byte
113B: CB 7F BIT 7,A ; test its request bit
113D: 28 19 JR Z,loc_1158 ; clear -- next slot
113F: 4F LD C,A
1140: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1143: 81 ADD A,C ; carry once the beam is past this slot's line
1144: 30 F2 JR NC,loc_1138 ; beam not past -- hold here until it is
1146: 23 INC HL
1147: 23 INC HL
1148: 2B DEC HL
1149: 2B DEC HL
114A: 79 LD A,C
114B: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
114D: 32 3B B4 LD (spriteRam1+3B),A ; store the moved Y byte
1150: 3A 3A B0 LD A,(spriteRam0+3A) ; read the paired X byte
1153: C6 80 ADD A,$80 ; add half a screen across
1155: 32 3A B0 LD (spriteRam0+3A),A ; store it -- second image placed
loc_1158:
1158: 3A 3D B4 LD A,(spriteRam1+3D) ; read the next slot's Y byte
115B: CB 7F BIT 7,A ; test its request bit
115D: 28 19 JR Z,loc_1178 ; clear -- next slot
115F: 4F LD C,A
1160: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1163: 81 ADD A,C ; carry once the beam is past this slot's line
1164: 30 F2 JR NC,loc_1158 ; beam not past -- hold here until it is
1166: 23 INC HL
1167: 23 INC HL
1168: 2B DEC HL
1169: 2B DEC HL
116A: 79 LD A,C
116B: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
116D: 32 3D B4 LD (spriteRam1+3D),A ; store the moved Y byte
1170: 3A 3C B0 LD A,(spriteRam0+3C) ; read the paired X byte
1173: C6 80 ADD A,$80 ; add half a screen across
1175: 32 3C B0 LD (spriteRam0+3C),A ; store it -- second image placed
loc_1178:
1178: 3A 3F B4 LD A,(spriteRam1+3F) ; read the last slot's Y byte
117B: CB 7F BIT 7,A ; test its request bit
117D: 28 19 JR Z,loc_1198 ; clear -- done
117F: 4F LD C,A
1180: 3A 00 C0 LD A,(scanline) ; read the scanline counter
1183: 81 ADD A,C ; carry once the beam is past this slot's line
1184: 30 F2 JR NC,loc_1178 ; beam not past -- hold here until it is
1186: 23 INC HL
1187: 23 INC HL
1188: 2B DEC HL
1189: 2B DEC HL
118A: 79 LD A,C
118B: E6 7F AND $7F ; clear bit 7 -- shift half a screen, clear the request
118D: 32 3F B4 LD (spriteRam1+3F),A ; store the moved Y byte
1190: 3A 3E B0 LD A,(spriteRam0+3E) ; read the paired X byte
1193: C6 80 ADD A,$80 ; add half a screen across
1195: 32 3E B0 LD (spriteRam0+3E),A ; store it -- second image placed
loc_1198:
1198: C9 RET
; the round engine's service list (substep 7 of the phase-3 dispatch at
; 0x0f29; runs per dispatch, short of the frame count): run each subsystem
; service in fixed order, then read the player-state byte at 0xa800 and
; advance the round when it is 0xff (alive), hand a life over when it is 0
; (dead), else return
serviceRoundThenResolvePlayerState:
1199: CD B4 31 CALL reaimAndAnimateEnemyCraftOnPhaseTick; re-aim and animate the enemy craft for this phase tick
119C: CD DF 1E CALL dispatchPlayerFrameByState; update the player ship for this frame
119F: CD E3 23 CALL fireAndSweepPlayerShots; fire and advance the player's shots
11A2: CD AF 36 CALL driveEnemyWaveForLifePhase; drive the enemy wave for the current life phase
11A5: CD 97 0F CALL multiplexSpriteSlotsSkipping; run a sprite-doubling pass -- called repeatedly through the frame so scenery sprites redraw as the beam descends
11A8: CD B3 47 CALL runParachutistSlot ; run the parachutist
11AB: CD B7 43 CALL armMotherShipOrStep ; arm or step the mother ship
11AE: CD A1 28 CALL stepSevenCraftSlots ; step the seven enemy-craft slots
11B1: CD 97 0F CALL multiplexSpriteSlotsSkipping; another sprite-doubling pass
11B4: CD BC 2C CALL runSceneryForEra ; draw the scenery for the current era
11B7: CD D6 40 CALL sweepEra2PlusObjectBank; sweep the era-2-and-up object bank
11BA: CD 97 0F CALL multiplexSpriteSlotsSkipping; another sprite-doubling pass
11BD: CD 5F 3B CALL serviceEra1BomberObject; service the era-1 bomber
11C0: CD DA 3D CALL serviceFixedSlotInEra1; service the era-1 fixed object slot
11C3: CD 36 3E CALL stepFourActorSlots ; step the four actor slots
11C6: CD 97 0F CALL multiplexSpriteSlotsSkipping; another sprite-doubling pass
11C9: CD EA 3F CALL serviceEra0BallisticObjectBank; service the era-0 ballistic object bank
11CC: CD 4F 4E CALL dispatchCollisionPassByEra; run the collision pass for this era
11CF: CD B8 40 CALL askForSoundWhileTheGroupIsClear; ask for a sound while the formation is clear
11D2: CD 97 0F CALL multiplexSpriteSlotsSkipping; another sprite-doubling pass
11D5: CD DE 4D CALL awardBonusLifeAtScoreMark; grant a bonus life at the score mark
11D8: CD 05 52 CALL expireHitChain ; expire the hit chain
11DB: CD 3A 4D CALL escalateDifficultyRungOnCounterWrap; step difficulty up when the counter wraps
11DE: CD 09 08 CALL drawKillMeter ; repaint the kill meter
11E1: CD 98 10 CALL multiplexSpriteSlots; a final sprite-doubling pass (the variant that waits on the beam)
11E4: 3A 00 A8 LD A,(workRam) ; read the player-state byte
11E7: 3C INC A ; 0xFF (still alive) rolls to zero here
11E8: CA 71 12 JP Z,advanceRoundWhenFieldCleared; alive -- advance the round once the field is cleared
11EB: 3D DEC A ; back to the raw state -- zero flags a dead player
11EC: C0 RET NZ ; neither alive nor dead -- nothing to resolve, return
; process a player's death: hide the sprite band, apply a pending round-
; advance when its flag is set, and queue the frame's fixed sound
; requests; then decrement LIVES_REMAINING at the head of the live 16-byte
; context block and checkpoint that block into the active player's save
; slot — on lives reaching zero it tail-calls the game-over banner,
; otherwise, when the other player's saved block still shows lives, it
; flips the active-player index, arms a delay and re-steps the sequence
; for the next life
loseLifeAndHandOver:
11ED: CD B6 15 CALL hideAllSprites ; blank the whole sprite band
11F0: 3A C6 AC LD A,(workRam+4C6) ; read the pending round-advance flag
11F3: A7 AND A ; is it set?
11F4: C4 B8 2D CALL NZ,startNextRound ; set -- start the next round
11F7: CD 34 56 CALL enqueueTransitionSoundBurst; queue this frame's fixed sound requests
11FA: 21 00 AD LD HL,$AD00 ; point at the live context block -- its first byte is the lives count
11FD: 35 DEC (HL) ; drop the lives count by one
11FE: F5 PUSH AF ; remember whether that reached zero
11FF: 3A 32 AD LD A,(workRam+532) ; read the active-player selector
1202: A7 AND A ; which player is up?
1203: 11 10 AD LD DE,$AD10 ; default the save slot to player 1's
1206: 28 03 JR Z,loc_120b ; player 1 -- keep it
1208: 11 20 AD LD DE,$AD20 ; player 2 -- use the other save slot
loc_120b:
120B: 21 00 AD LD HL,$AD00 ; source is the live context block
120E: 01 10 00 LD BC,$0010 ; sixteen bytes
1211: ED B0 LDIR ; checkpoint the live block into the active player's save slot
1213: F1 POP AF ; recall whether lives hit zero
1214: 28 3D JR Z,postGameOverBanner; out of lives -- go post the game-over banner
1216: 3A 32 AD LD A,(workRam+532) ; read the active-player selector again
1219: A7 AND A ; which player is up?
121A: 21 20 AD LD HL,$AD20 ; point at player 2's save slot
121D: 28 03 JR Z,loc_1222 ; player 1 up -- the other player is player 2
121F: 21 10 AD LD HL,$AD10 ; player 2 up -- the other player is player 1
loc_1222:
1222: 7E LD A,(HL) ; read the other player's saved lives count
1223: A7 AND A ; does the other player still have lives?
1224: 28 09 JR Z,loc_122f ; no -- skip the flip and just re-arm the delay
; give the turn to the other player: flip the one-bit active-player index,
; re-arm the shared sequence delay with a fixed span, and reseat the inner
; sequence index from a byte of the program image; nothing is copied here,
; and the flip is the only effect the skipped arm does not also have
handPlayOverToOtherPlayer:
1226: 3A 32 AD LD A,(workRam+532) ; read the active-player selector
1229: 3C INC A ; flip it...
122A: E6 01 AND $01 ; ...to the other player (one-bit index)
122C: 32 32 AD LD (workRam+532),A ; store the new active player -- this flip is the hand-over itself
loc_122f:
122F: 3E 5A LD A,$5A ; hold value 90...
1231: 32 EB A9 LD (workRam+1EB),A ; ...into the shared sequence delay
1234: 3A 52 4B LD A,(rom+4B52) ; take the inner sequence step from the program image
1237: 32 AC A9 LD (workRam+1AC),A ; reseat the inner sequence step for the next life
123A: C9 RET
; ---- $123B-$1252: data ----
123B: 18 A7 13 A5 3B 87 F1 34 0E 34 D7 BF F1 7F 13 13
124B: 13 13 F1 88 DC ED 11 B9
; the last life is gone: queue the PLAYER-n caption and the GAME OVER
; caption, hold them for three seconds and step the sequence on; when no
; game is running it branches instead into the shared teardown
; restartAttractSequence, which hands the machine back to attract
postGameOverBanner:
1253: 3A 30 AD LD A,(workRam+530) ; read the in-play flag
1256: A7 AND A ; is a game running?
1257: CA FB 12 JP Z,restartAttractSequence; not running -- hand the machine back to attract
125A: 11 09 02 LD DE,$0209 ; caption command 2, argument 9 -- the PLAYER 1 banner
125D: 3A 32 AD LD A,(workRam+532) ; read the active-player selector
1260: A7 AND A ; player 1 or 2?
1261: 28 01 JR Z,loc_1264 ; player 1 -- keep argument 9
1263: 1C INC E ; player 2 -- bump the argument to 10 (PLAYER 2)
loc_1264:
1264: FF RST $38 ; queue that PLAYER-n banner command
1265: 11 0B 0A LD DE,$0A0B ; caption command 10, argument 11 -- the GAME OVER banner
1268: FF RST $38 ; queue the GAME OVER banner command
1269: 3E B4 LD A,$B4 ; hold value 180 (about three seconds)...
126B: 32 EB A9 LD (workRam+1EB),A ; ...into the sequence delay, holding the banners on screen
126E: C3 1A 0F JP advanceSequenceSubStep; step the sequence on
; gated two-arm state transition: fires only when 0xad02=0, 0xacc6!=0 and
; all 15 slots at 0xa810 are empty, then queues the fixed sound set and
; runs one of two arms on 0xad30 — disarm+reset a cell cluster, or clear a
; strided run and copy a 16-byte record into 0xad10/0xad20
advanceRoundWhenFieldCleared:
1271: 3A 02 AD LD A,(workRam+502) ; read the round-advance guard cell
1274: A7 AND A ; is it clear?
1275: C0 RET NZ ; not clear -- do nothing
1276: 3A C6 AC LD A,(workRam+4C6) ; read the round-advance arm
1279: A7 AND A ; is it armed?
127A: C8 RET Z ; not armed -- do nothing
127B: 21 10 A8 LD HL,$A810 ; point at the first of fifteen object slots
127E: 11 10 00 LD DE,$0010 ; sixteen bytes per slot
1281: 06 0F LD B,$0F ; fifteen slots to check
loc_1283:
1283: 7E LD A,(HL) ; read this slot's head byte
1284: A7 AND A ; is the slot empty?
1285: C0 RET NZ ; a slot is still occupied -- the field isn't clear, return
1286: 19 ADD HL,DE
1287: 10 FA DJNZ loc_1283 ; check every one of the fifteen slots
1289: CD 34 56 CALL enqueueTransitionSoundBurst; queue this frame's fixed sound requests
128C: 3A 30 AD LD A,(workRam+530) ; read the in-play flag
128F: A7 AND A ; is a game running?
1290: 28 29 JR Z,loc_12bb ; attract mode -- take the reset arm
1292: 21 43 AA LD HL,$AA43 ; point at a strided run of cells
1295: 06 17 LD B,$17 ; twenty-three of them
1297: AF XOR A ; the zero to write
loc_1298:
1298: 77 LD (HL),A ; zero every other cell along the run
1299: 2C INC L
129A: 2C INC L
129B: 10 FB DJNZ loc_1298 ; clear all twenty-three
129D: CD B8 2D CALL startNextRound ; start the next round
12A0: 3A 32 AD LD A,(workRam+532) ; read the active-player selector
12A3: A7 AND A ; which player is up?
12A4: 11 10 AD LD DE,$AD10 ; default the save slot to player 1's
12A7: 28 03 JR Z,loc_12ac ; player 1 -- keep it
12A9: 11 20 AD LD DE,$AD20 ; player 2 -- use the other save slot
loc_12ac:
12AC: 21 00 AD LD HL,$AD00 ; source is the live context block
12AF: 01 10 00 LD BC,$0010 ; sixteen bytes
12B2: ED B0 LDIR ; checkpoint the live block into the player's save slot
12B4: 3A 35 4A LD A,(rom+4A35) ; take the inner sequence step from the program image
12B7: 32 AC A9 LD (workRam+1AC),A ; reseat the inner sequence step
12BA: C9 RET
loc_12bb:
12BB: 3A D1 07 LD A,(rom+7D1) ; take the round-advance arm value from the program image
12BE: 32 C6 AC LD (workRam+4C6),A ; re-arm the round-advance flag
12C1: CD B6 15 CALL hideAllSprites ; blank the whole sprite band
12C4: C3 FB 12 JP restartAttractSequence; hand the machine back to attract
; ---- $12C7-$12E1: data ----
12C7: 74 B1 CC EC 5C 16 39 50 67 21 7A C5 F7 BE 54 80
12D7: 2F 5F 9F 6D 44 B8 E7 BD 89 59 1A
loc_12e2:
12E2: 21 EB A9 LD HL,$A9EB ; point at the sequence delay counter
12E5: 35 DEC (HL) ; tick it down one
12E6: C0 RET NZ ; still counting -- return; at zero it falls through into the turn-pass logic
; hand the turn over to the other player when that player's saved lives
; count is non-zero, and otherwise step the inner sequence index; both
; exits are tails, so this entry chooses between two continuations rather
; than returning to anything
passTurnToOtherPlayerIfLivesElseStepSequence:
12E7: 3A 32 AD LD A,(workRam+532) ; read the active-player selector
12EA: A7 AND A ; which player is up?
12EB: 21 20 AD LD HL,$AD20 ; point at player 2's saved block
12EE: 28 03 JR Z,loc_12f3 ; player 1 up -- the other is player 2
12F0: 21 10 AD LD HL,$AD10 ; player 2 up -- the other is player 1
loc_12f3:
12F3: 7E LD A,(HL) ; read the other player's saved lives count
12F4: A7 AND A ; does the other player still have lives?
12F5: C2 26 12 JP NZ,handPlayOverToOtherPlayer; yes -- hand the turn to the other player
12F8: C3 1A 0F JP advanceSequenceSubStep; no -- just step the inner sequence step on
; put the machine back at the top of the attract sequence: clear the play
; flag, the active-player index and the inner sequence step, then set the
; outer phase from a byte of the program image, and write the inner step a
; SECOND time through a fold over three more image bytes -- on an
; unaltered image that fold comes to zero and agrees with the first write,
; on an altered one it does not and the sequence restarts at some other
; step
restartAttractSequence:
12FB: AF XOR A ; clear A
12FC: 32 30 AD LD (workRam+530),A ; clear the in-play flag
12FF: 32 AC A9 LD (workRam+1AC),A ; clear the inner sequence step
1302: 32 32 AD LD (workRam+532),A ; clear the active-player index
1305: 3A D3 16 LD A,(rom+16D3) ; take the outer sequence phase from the program image
1308: 32 AB A9 LD (workRam+1AB),A ; set the outer sequence phase -- top of attract
130B: 3A 01 49 LD A,(rom+4901) ; read a program-image byte as a signed offset
130E: 2A 02 49 LD HL,(rom+4902) ; read a program-image address
1311: DF RST $18 ; step that address by the signed offset
1312: AC XOR H ; fold the stepped address's high byte into the offset byte
1313: D6 9B SUB $9B ; subtract the bias -- on an untouched image this comes out zero
1315: 32 AC A9 LD (workRam+1AC),A ; write the inner step a second time -- agrees with the clear on an intact ROM, diverges if it was altered
1318: C9 RET
; fill a fixed-length run of character cells with one byte, stepping a
; cell at a time along the line
fillCellRun:
1319: 11 E0 FF LD DE,$FFE0 ; step of -32 -- one cell up the column per write
131C: 06 0D LD B,$0D ; thirteen cells to fill
loc_131e:
131E: 77 LD (HL),A ; write the fill byte into this cell
131F: 19 ADD HL,DE ; back up one native row (32 cells)
1320: 10 FC DJNZ loc_131e ; fill all thirteen
1322: C9 RET
; phase-14 arm of the sequence dispatchSequenceSubStepArm dispatches off
; the 0x0F29 table (keyed on SEQUENCE_SUBSTEP & 0x0F): only on alternate
; frames (bit 1 of FRAME_TICK clear), dispatch on the animation sub-step
; at 0xA9F0 -- steps 0/1 flash the player ship and advance a scripted
; char-plane animation, steps 2/3 tick a two-colour animation and run a
; title-plane pass, step 4 floods the colour plane; the final step sets
; SEQUENCE_DELAY, hides every sprite, sets up the active player's turn
; (loadActivePlayerContextAndPostRoundHud) and reloads SEQUENCE_SUBSTEP
; from ROM byte 0x2750 (=3) to wind the outer sequence on
stepRoundStartIntroAnimation:
1323: 3A 80 A9 LD A,(workRam+180) ; read the frame tick
1326: E6 02 AND $02 ; act only on alternate frames (bit 1)
1328: C0 RET NZ ; odd frame -- skip this frame
1329: 3A F0 A9 LD A,(workRam+1F0) ; read the intro-animation step
132C: A7 AND A ; step 0?
132D: 20 04 JR NZ,loc_1333 ; not step 0 -- try the next arm
132F: CD 67 13 CALL flashPlayerWhiteEveryOtherFrame; step 0 -- flash the player ship white
1332: C9 RET
loc_1333:
1333: 3D DEC A ; step 1?
1334: 20 07 JR NZ,loc_133d ; no -- next arm
1336: CD 67 13 CALL flashPlayerWhiteEveryOtherFrame; flash the player ship white
1339: CD 2A 14 CALL advanceScriptedCharPlaneBandTo2; advance the character-plane band animation toward stage 2
133C: C9 RET
loc_133d:
133D: 3D DEC A ; step 2?
133E: 20 07 JR NZ,loc_1347 ; no -- next arm
1340: CD 93 13 CALL cyclePlayerSpriteColourThenAdvanceStepAtZero; cycle the player ship's colour
1343: CD C5 14 CALL advanceScriptedCharPlaneBandTo4; advance the character-plane band animation toward stage 4
1346: C9 RET
loc_1347:
1347: 3D DEC A ; step 3?
1348: 20 04 JR NZ,loc_134e ; no -- next arm
134A: CD C5 14 CALL advanceScriptedCharPlaneBandTo4; advance the character-plane band animation toward stage 4
134D: C9 RET
loc_134e:
134E: 3D DEC A ; step 4?
134F: 20 04 JR NZ,loc_1355 ; no -- final arm
1351: CD CC 13 CALL floodColourPlaneWithSavedPlayerColour; flood the colour plane with the player's saved colour
1354: C9 RET
loc_1355:
1355: 3E 5A LD A,$5A ; hold value 90...
1357: 32 EB A9 LD (workRam+1EB),A ; ...into the sequence delay
135A: CD B6 15 CALL hideAllSprites ; blank the whole sprite band
135D: CD 75 4C CALL loadActivePlayerContextAndPostRoundHud; set up the active player's turn and post the round HUD
1360: 3A 50 27 LD A,(rom+2750) ; take the sub-step reload from the program image (= 3)
1363: 32 AC A9 LD (workRam+1AC),A ; reseat the inner sequence step, winding the outer sequence on
1366: C9 RET
; one frame of the flash that runs the player's ship white and back: the
; two flip bits of the player's sprite control byte are kept and the
; colour under them is driven from the low bit of the animation's own
; tick, alternating between the all-white palette entry and the colour the
; ship normally wears; the tick is stepped last and wraps at eight bits,
; and on the single tick where it reads the threshold the routine also
; hands the animation on to its next step and asks for one sound
flashPlayerWhiteEveryOtherFrame:
1367: 3A F1 A9 LD A,(workRam+1F1) ; read this flash animation's tick counter
136A: FE 08 CP $08 ; has it reached eight?
136C: 20 08 JR NZ,loc_1376 ; not yet -- just recolour
136E: 3E 01 LD A,$01 ; value 1...
1370: 32 F0 A9 LD (workRam+1F0),A ; ...into the intro-animation step -- move it on
1373: CD 11 58 CALL requestPlayerSpawnFlashSound; ask for the player-spawn flash sound
loc_1376:
1376: 3A F1 A9 LD A,(workRam+1F1) ; read the tick again
1379: E6 01 AND $01 ; take its low bit
137B: 3E 3E LD A,$3E ; default colour 0x3E -- the ship's normal colour
137D: 28 02 JR Z,loc_1381 ; even tick -- keep it
137F: 3E 00 LD A,$00 ; odd tick -- colour 0 (all white)
loc_1381:
1381: 47 LD B,A ; hold the chosen colour
1382: 3A 40 AA LD A,(workRam+240) ; read the player ship's sprite attribute
1385: E6 C0 AND $C0 ; keep only its two mirroring bits
1387: 80 ADD A,B ; merge in the chosen colour
1388: 32 40 AA LD (workRam+240),A ; write it back -- flips the ship white and back
138B: 3A F1 A9 LD A,(workRam+1F1) ; read the tick
138E: 3C INC A ; step it
138F: 32 F1 A9 LD (workRam+1F1),A ; store it -- wraps at eight bits
1392: C9 RET
; one tick of a two-colour animation inside the round engine's step-14
; sub-sequence: step a count down by one and, from a single bit of that
; count, drive the colour field of the shadow byte that the sprite
; publisher copies into the player ship's sprite attribute, so a colour
; holds for four consecutive ticks; the top two bits of that byte, which
; carry the sprite's mirroring, are left alone. The tick that finds the
; count already at zero also moves the sub-sequence's step cell on to 3,
; and the count still steps on that tick, wrapping below zero. Its one
; call site is that sub-sequence's step 2, which follows it with one other
; routine
cyclePlayerSpriteColourThenAdvanceStepAtZero:
1393: 3A F3 A9 LD A,(workRam+1F3) ; read the ship-colour cycle countdown
1396: A7 AND A ; is the countdown already at zero?
1397: 20 09 JR NZ,loc_13a2 ; not yet -- pick a colour from the count
1399: 3E 03 LD A,$03 ; value 3...
139B: 32 F0 A9 LD (workRam+1F0),A ; ...into the intro-animation step -- countdown spent, move on to step 3
139E: 3E 3F LD A,$3F ; use colour 0x3F
13A0: 18 18 JR loc_13ba ; go apply it
loc_13a2:
13A2: E6 04 AND $04 ; test bit 2 of the count -- a colour holds for four ticks
13A4: 20 04 JR NZ,loc_13aa ; bit set -- take the alternate colour
13A6: 3E 3F LD A,$3F ; bit clear -- colour 0x3F
13A8: 18 10 JR loc_13ba ; apply it
loc_13aa:
13AA: 3D DEC A ; walk the selector toward the alternate colour
13AB: 20 04 JR NZ,loc_13b1 ; branch on
13AD: 3E 36 LD A,$36 ; colour 0x36 -- an arm this path never reaches
13AF: 18 09 JR loc_13ba ; apply it
loc_13b1:
13B1: 3D DEC A ; walk the selector on
13B2: 20 04 JR NZ,loc_13b8 ; branch on
13B4: 3E 3E LD A,$3E ; colour 0x3E -- also unreached on this path
13B6: 18 02 JR loc_13ba ; apply it
loc_13b8:
13B8: 3E 37 LD A,$37 ; the alternate colour 0x37
loc_13ba:
13BA: 47 LD B,A ; hold the chosen colour
13BB: 3A 40 AA LD A,(workRam+240) ; read the player ship's sprite attribute
13BE: E6 C0 AND $C0 ; keep only the two mirroring bits
13C0: 80 ADD A,B ; merge in the colour
13C1: 32 40 AA LD (workRam+240),A ; write it back -- recolours the ship
13C4: 3A F3 A9 LD A,(workRam+1F3) ; read the countdown
13C7: 3D DEC A ; step it down (wraps below zero)
13C8: 32 F3 A9 LD (workRam+1F3),A ; store it
13CB: C9 RET
; the step-4 arm of the round engine's step-14 sub-sequence: flood a fixed
; block of the colour plane with one byte, and hand the sub-sequence the
; step whose arm winds it up. The byte comes from one of two parallel
; cells — the same offset in each of the two per-player save blocks —
; chosen by the active-player index, so it is a saved value rather than
; the live one. The block is twenty-eight rows of twenty-seven cells:
; every row the driver leaves visible, and all but five of the plane's
; thirty-two columns. When the picture is turned round the painting runs
; from the far corner backwards, which changes the ORDER the cells are
; touched in and not WHICH, so the two directions leave the plane
; identical. A separate count is stepped down by one on the way out
floodColourPlaneWithSavedPlayerColour:
13CC: 3E 05 LD A,$05 ; value 5...
13CE: 32 F0 A9 LD (workRam+1F0),A ; ...into the intro-animation step -- move on to step 5
13D1: 3A 32 AD LD A,(workRam+532) ; read the active-player selector
13D4: A7 AND A ; which player is up?
13D5: 3A 1C AD LD A,(workRam+51C) ; read player 1's saved pen colour
13D8: 47 LD B,A ; hold it as the flood colour
13D9: 28 04 JR Z,loc_13df ; player 1 -- use it
13DB: 3A 2C AD LD A,(workRam+52C) ; player 2 -- read player 2's saved pen colour
13DE: 47 LD B,A ; hold it instead
loc_13df:
13DF: 3A 87 A9 LD A,(workRam+187) ; read the screen-orientation flag
13E2: A7 AND A ; is the screen turned round?
13E3: 78 LD A,B ; put the flood colour in A
13E4: 28 22 JR Z,loc_1408 ; turned round -- paint from the far corner backwards
13E6: 21 44 A0 LD HL,$A044 ; forward: first cell of the colour-plane rectangle
13E9: 11 45 A0 LD DE,$A045 ; copy destination one cell along
13EC: D9 EXX
13ED: 06 1C LD B,$1C ; twenty-eight rows
loc_13ef:
13EF: D9 EXX
13F0: 01 1A 00 LD BC,$001A ; twenty-six trailing copies per row
13F3: 77 LD (HL),A ; paint the row's first cell
13F4: ED B0 LDIR ; flood the rest of the row with the same colour
13F6: 11 06 00 LD DE,$0006 ; skip forward six cells...
13F9: 19 ADD HL,DE ; ...to the start of the next row (27 painted, 32-cell stride)
13FA: 54 LD D,H
13FB: 5D LD E,L
13FC: 13 INC DE
13FD: D9 EXX
13FE: 10 EF DJNZ loc_13ef ; paint all twenty-eight rows
1400: 3A F6 A9 LD A,(workRam+1F6) ; read the flood countdown
1403: 3D DEC A ; step it down
1404: 32 F6 A9 LD (workRam+1F6),A ; store it
1407: C9 RET
loc_1408:
1408: 21 BE A3 LD HL,$A3BE ; backwards: last cell of the rectangle
140B: 11 BD A3 LD DE,$A3BD ; copy destination one cell back
140E: D9 EXX
140F: 06 1C LD B,$1C ; twenty-eight rows
loc_1411:
1411: D9 EXX
1412: 01 1A 00 LD BC,$001A ; twenty-six trailing copies per row
1415: 77 LD (HL),A ; paint the row's cell
1416: ED B8 LDDR ; flood the rest of the row backwards with the same colour
1418: 11 FA FF LD DE,$FFFA ; step back six cells...
141B: 19 ADD HL,DE ; ...to the previous row
141C: 54 LD D,H
141D: 5D LD E,L
141E: 1B DEC DE
141F: D9 EXX
1420: 10 EF DJNZ loc_1411 ; paint all twenty-eight rows
1422: 3A F6 A9 LD A,(workRam+1F6) ; read the flood countdown
1425: 3D DEC A ; step it down
1426: 32 F6 A9 LD (workRam+1F6),A ; store it
1429: C9 RET
; advance one frame of a script-driven character-plane animation: bit 0 of
; a countdown cell alternates a blanking pass (fill two thirteen-cell
; columns and six lead cells with one tile code) with a drawing pass
; (restore the working column from its saved run, nudge four counters by
; the low bit of the next two script bytes, step the band up then back
; down, and gather the column back); a terminator byte instead clears the
; countdown, arms the next sequence step and rewinds the script pointer
; one, ending early, and every non-terminating call then decrements the
; countdown
advanceScriptedCharPlaneBandTo2:
142A: 3A F2 A9 LD A,(workRam+1F2) ; read the pass countdown
142D: CB 47 BIT 0,A ; which pass -- odd draws, even blanks
142F: 28 6C JR Z,loc_149d ; even -- go blank the band
1431: 2A F7 A9 LD HL,(workRam+1F7) ; load the script cursor
1434: 7E LD A,(HL) ; read the byte under it
1435: FE FF CP $FF ; end-of-script marker (0xFF)?
1437: 20 12 JR NZ,loc_144b ; no -- draw this frame
1439: 3E 00 LD A,$00 ; value 0...
143B: 32 F2 A9 LD (workRam+1F2),A ; ...clears the pass countdown -- script done
143E: 3E 02 LD A,$02 ; value 2...
1440: 32 F0 A9 LD (workRam+1F0),A ; ...into the intro-animation step -- advance to stage 2
1443: 2A F7 A9 LD HL,(workRam+1F7) ; reload the script cursor
1446: 2B DEC HL ; step it back one
1447: 22 F7 A9 LD (workRam+1F7),HL ; save it -- ending early with no decrement
144A: C9 RET
loc_144b:
144B: CD 63 15 CALL restoreColumnFromSavedRun; restore the working column from its saved run
144E: 2A F7 A9 LD HL,(workRam+1F7) ; load the script cursor
1451: 7E LD A,(HL) ; read the next script byte
1452: E6 01 AND $01 ; take its low bit
1454: 23 INC HL
1455: 22 F7 A9 LD (workRam+1F7),HL ; step the cursor past it
1458: 28 0F JR Z,loc_1469 ; bit clear -- skip this nudge
145A: 11 20 00 LD DE,$0020 ; row stride 32
145D: 21 F0 A5 LD HL,$A5F0 ; point at the column's top counter
1460: 34 INC (HL) ; nudge it up
1461: 19 ADD HL,DE
1462: 34 INC (HL) ; nudge the counter one row below
1463: 21 F2 A5 LD HL,$A5F2 ; point at another column counter
1466: 34 INC (HL) ; nudge it up
1467: 19 ADD HL,DE
1468: 34 INC (HL) ; nudge the counter one row below
loc_1469:
1469: 2A F7 A9 LD HL,(workRam+1F7) ; load the cursor
146C: 7E LD A,(HL) ; read the next script byte
146D: E6 01 AND $01 ; take its low bit
146F: 23 INC HL
1470: 22 F7 A9 LD (workRam+1F7),HL ; step the cursor past it
1473: 28 09 JR Z,loc_147e ; bit clear -- skip this nudge
1475: 11 20 00 LD DE,$0020 ; row stride 32
1478: 21 F1 A5 LD HL,$A5F1 ; point at a column counter
147B: 34 INC (HL) ; nudge it up
147C: 19 ADD HL,DE
147D: 34 INC (HL) ; nudge the counter one row below
loc_147e:
147E: 0E 02 LD C,$02 ; colour set 2
1480: 11 D1 A5 LD DE,$A5D1 ; column of glyph cells
1483: CD 9D 4A CALL stepThirteenScriptedGlyphCells; advance thirteen scripted glyph cells down the column
1486: 2A F7 A9 LD HL,(workRam+1F7) ; load the cursor
1489: 11 F3 FF LD DE,$FFF3 ; step of -13...
148C: 19 ADD HL,DE ; ...rewind the cursor thirteen bytes
148D: 22 F7 A9 LD (workRam+1F7),HL ; save it
1490: 0E 00 LD C,$00 ; colour set 0
1492: 11 31 A6 LD DE,$A631 ; the same column in the other plane
1495: CD 9D 4A CALL stepThirteenScriptedGlyphCells; advance those thirteen glyph cells in the other plane
1498: CD 8C 15 CALL gatherCharColumnIntoBackingRun; gather the column back into its saved run
149B: 18 20 JR loc_14bd ; on to the decrement
loc_149d:
149D: 3E F1 LD A,$F1 ; the blank tile
149F: 21 B1 A7 LD HL,$A7B1 ; first column to blank
14A2: CD 19 13 CALL fillCellRun ; blank that thirteen-cell column
14A5: 21 D1 A5 LD HL,$A5D1 ; second column to blank
14A8: CD 19 13 CALL fillCellRun ; blank it too
14AB: 21 10 A6 LD HL,$A610 ; point at a lead cell
14AE: 77 LD (HL),A ; blank it
14AF: 19 ADD HL,DE
14B0: 77 LD (HL),A ; blank the cell one row above it
14B1: 21 11 A6 LD HL,$A611 ; next lead cell
14B4: 77 LD (HL),A ; blank it
14B5: 19 ADD HL,DE
14B6: 77 LD (HL),A ; blank the cell one row above it
14B7: 21 12 A6 LD HL,$A612 ; last lead cell
14BA: 77 LD (HL),A ; blank it
14BB: 19 ADD HL,DE
14BC: 77 LD (HL),A ; blank the cell one row above it
loc_14bd:
14BD: 3A F2 A9 LD A,(workRam+1F2) ; read the pass countdown
14C0: 3D DEC A ; step it down
14C1: 32 F2 A9 LD (workRam+1F2),A ; store it
14C4: C9 RET
; one pass of a cursor-scripted character-plane animation that runs during
; the inter-round / player-change transition — NOT the title (the title
; logo is a caption strip, and this arm is dispatched only from the life-
; loss / round-advance path and is reach-0 across attract): erases two
; columns + six loose cells on even passes, refills/steps them from the
; script on odd passes, ends the script by clearing the counter, advancing
; the stage to 4 and requesting sounds; decrements the pass counter
; otherwise
advanceScriptedCharPlaneBandTo4:
14C5: 3A F4 A9 LD A,(workRam+1F4) ; read the pass countdown
14C8: CB 47 BIT 0,A ; which pass -- odd draws, even blanks
14CA: 28 6F JR Z,loc_153b ; even -- go blank the band
14CC: 2A F7 A9 LD HL,(workRam+1F7) ; load the script cursor
14CF: 7E LD A,(HL) ; read the byte under it
14D0: E6 FE AND $FE ; any bit above bit 0 set (terminator)?
14D2: 28 15 JR Z,loc_14e9 ; no -- draw this frame
14D4: 3E 00 LD A,$00 ; value 0...
14D6: 32 F4 A9 LD (workRam+1F4),A ; ...clears the pass countdown -- script done
14D9: 3E 04 LD A,$04 ; value 4...
14DB: 32 F0 A9 LD (workRam+1F0),A ; ...into the intro-animation step -- advance to stage 4
14DE: CD E4 56 CALL requestInterRoundSoundPair; request the inter-round sound pair
14E1: 2A F7 A9 LD HL,(workRam+1F7) ; reload the cursor
14E4: 23 INC HL
14E5: 22 F7 A9 LD (workRam+1F7),HL ; step the cursor on -- ending early
14E8: C9 RET
loc_14e9:
14E9: CD 63 15 CALL restoreColumnFromSavedRun; restore the working column from its saved run
14EC: 0E 01 LD C,$01 ; colour set 1
14EE: 11 51 A4 LD DE,$A451 ; column of glyph cells
14F1: CD 9D 4A CALL stepThirteenScriptedGlyphCells; advance thirteen scripted glyph cells down the column
14F4: 2A F7 A9 LD HL,(workRam+1F7) ; load the cursor
14F7: 11 0D 00 LD DE,$000D ; step of +13...
14FA: 19 ADD HL,DE ; ...skip the cursor thirteen bytes forward
14FB: 22 F7 A9 LD (workRam+1F7),HL ; save it
14FE: 0E 03 LD C,$03 ; colour set 3
1500: 11 B1 A7 LD DE,$A7B1 ; the same column in the other plane
1503: CD 9D 4A CALL stepThirteenScriptedGlyphCells; advance those thirteen glyph cells in the other plane
1506: 2A F7 A9 LD HL,(workRam+1F7) ; load the cursor
1509: 7E LD A,(HL) ; read the next script byte
150A: E6 01 AND $01 ; take its low bit
150C: 2B DEC HL
150D: 22 F7 A9 LD (workRam+1F7),HL ; step the cursor back
1510: 28 09 JR Z,loc_151b ; bit clear -- skip this nudge
1512: 11 20 00 LD DE,$0020 ; row stride 32
1515: 21 F1 A5 LD HL,$A5F1 ; point at a lead counter
1518: 35 DEC (HL) ; lower it
1519: 19 ADD HL,DE
151A: 35 DEC (HL) ; lower the counter one row below
loc_151b:
151B: 2A F7 A9 LD HL,(workRam+1F7) ; load the cursor
151E: 7E LD A,(HL) ; read the next script byte
151F: E6 01 AND $01 ; take its low bit
1521: 2B DEC HL
1522: 22 F7 A9 LD (workRam+1F7),HL ; step the cursor back
1525: 28 0F JR Z,loc_1536 ; bit clear -- skip this nudge
1527: 11 20 00 LD DE,$0020 ; row stride 32
152A: 21 F0 A5 LD HL,$A5F0 ; point at a column counter
152D: 35 DEC (HL) ; lower it
152E: 19 ADD HL,DE
152F: 35 DEC (HL) ; lower the counter one row below
1530: 21 F2 A5 LD HL,$A5F2 ; point at another column counter
1533: 35 DEC (HL) ; lower it
1534: 19 ADD HL,DE
1535: 35 DEC (HL) ; lower the counter one row below
loc_1536:
1536: CD 8C 15 CALL gatherCharColumnIntoBackingRun; gather the column back into its saved run
1539: 18 20 JR loc_155b ; on to the decrement
loc_153b:
153B: 3E F1 LD A,$F1 ; the blank tile
153D: 21 B1 A7 LD HL,$A7B1 ; first column to blank
1540: CD 19 13 CALL fillCellRun ; blank the thirteen-cell column
1543: 21 D1 A5 LD HL,$A5D1 ; second column to blank
1546: CD 19 13 CALL fillCellRun ; blank it too
1549: 21 10 A6 LD HL,$A610 ; point at a lead cell
154C: 77 LD (HL),A ; blank it
154D: 19 ADD HL,DE
154E: 77 LD (HL),A ; blank the cell one row above it
154F: 21 11 A6 LD HL,$A611 ; next lead cell
1552: 77 LD (HL),A ; blank it
1553: 19 ADD HL,DE
1554: 77 LD (HL),A ; blank the cell one row above it
1555: 21 12 A6 LD HL,$A612 ; last lead cell
1558: 77 LD (HL),A ; blank it
1559: 19 ADD HL,DE
155A: 77 LD (HL),A ; blank the cell one row above it
loc_155b:
155B: 3A F4 A9 LD A,(workRam+1F4) ; read the pass countdown
155E: 3D DEC A ; step it down
155F: 32 F4 A9 LD (workRam+1F4),A ; store it
1562: C9 RET
; put a saved thirty-two cell picture back onto the character plane:
; twenty-eight bytes down one column of cells a row apart, then four into
; two two-cell columns beside it. Every address is fixed here -- the run
; it reads, the column it lays and the two stubs are all this entry's
; choice, not a caller's -- and it overwrites the cells whole rather than
; merging into them
restoreColumnFromSavedRun:
1563: 11 00 A4 LD DE,$A400 ; point at the saved thirty-two byte run
1566: 21 51 A4 LD HL,$A451 ; point at the column of cells to repaint
1569: 01 20 00 LD BC,$0020 ; one cell-row stride
156C: D9 EXX
156D: 06 1C LD B,$1C ; twenty-eight cells down the column
loc_156f:
156F: D9 EXX
1570: 1A LD A,(DE) ; take the next saved byte
1571: 77 LD (HL),A ; lay it into the column cell
1572: 13 INC DE ; next saved byte
1573: 09 ADD HL,BC ; step down one row to the next cell
1574: D9 EXX
1575: 10 F8 DJNZ loc_156f ; repeat for all twenty-eight cells
1577: D9 EXX
1578: 21 F0 A5 LD HL,$A5F0 ; point at the first two-cell stub column
157B: 1A LD A,(DE) ; take the next saved byte
157C: 77 LD (HL),A ; lay it into the stub cell
157D: 09 ADD HL,BC ; step down one row
157E: 13 INC DE ; next saved byte
157F: 1A LD A,(DE) ; take the next saved byte
1580: 77 LD (HL),A ; lay it into the cell below
1581: 13 INC DE ; next saved byte
1582: 21 F2 A5 LD HL,$A5F2 ; point at the second stub column
1585: 1A LD A,(DE) ; take the next saved byte
1586: 77 LD (HL),A ; lay it in
1587: 09 ADD HL,BC ; step down one row
1588: 13 INC DE ; next saved byte
1589: 1A LD A,(DE) ; take the last saved byte
158A: 77 LD (HL),A ; lay it into the last cell
158B: C9 RET
; gather one column of the character plane into a thirty-two byte run --
; the column's twenty-eight cells a row apart, then the two two-cell
; columns beside it -- overwriting the run whole rather than merging into
; it; it is the exact inverse of 0x1563 over the same cells in the same
; order
gatherCharColumnIntoBackingRun:
158C: 11 00 A4 LD DE,$A400 ; point at the thirty-two byte backing run
158F: 21 51 A4 LD HL,$A451 ; point at the column of cells to read
1592: 01 20 00 LD BC,$0020 ; one cell-row stride
1595: D9 EXX
1596: 06 1C LD B,$1C ; twenty-eight cells down the column
loc_1598:
1598: D9 EXX
1599: 7E LD A,(HL) ; read this column cell
159A: 12 LD (DE),A ; store it into the run
159B: 13 INC DE ; next run byte
159C: 09 ADD HL,BC ; step down one row to the next cell
159D: D9 EXX
159E: 10 F8 DJNZ loc_1598 ; repeat for all twenty-eight cells
15A0: D9 EXX
15A1: 21 F0 A5 LD HL,$A5F0 ; point at the first two-cell stub column
15A4: 7E LD A,(HL) ; read the stub cell
15A5: 12 LD (DE),A ; store it into the run
15A6: 09 ADD HL,BC ; step down one row
15A7: 13 INC DE ; next run byte
15A8: 7E LD A,(HL) ; read the cell below
15A9: 12 LD (DE),A ; store it
15AA: 13 INC DE ; next run byte
15AB: 21 F2 A5 LD HL,$A5F2 ; point at the second stub column
15AE: 7E LD A,(HL) ; read the stub cell
15AF: 12 LD (DE),A ; store it
15B0: 09 ADD HL,BC ; step down one row
15B1: 13 INC DE ; next run byte
15B2: 7E LD A,(HL) ; read the last cell
15B3: 12 LD (DE),A ; store it
15B4: C9 RET
loc_15b5:
15B5: C9 RET
; zero every slot of the vertical sprite shadow band, which parks all of
; them above the first visible line, hiding them without retiring any
hideAllSprites:
15B6: 21 41 AA LD HL,$AA41 ; point at the sprite vertical-position band
15B9: 06 18 LD B,$18 ; twenty-four sprite slots
15BB: AF XOR A ; zero -- the value that parks a slot
loc_15bc:
15BC: 77 LD (HL),A ; park this slot above the top line, hiding it
15BD: 2C INC L
15BE: 2C INC L ; skip two cells to the next slot's vertical byte
15BF: 10 FB DJNZ loc_15bc ; over all twenty-four slots
15C1: C9 RET
; run the arm the LOW THREE BITS of the inner sequence step select out of
; a word table laid down inline just behind this entry; the arm is entered
; as a transfer with no place parked for it to come back to, so it returns
; past this entry and nothing here runs after it, and all eight indices
; are carried out through the machine's own arithmetic rather than assumed
; away
dispatchSequencePhase0SubStepArm:
15C2: 3A AC A9 LD A,(workRam+1AC) ; take the inner sequence sub-step
15C5: E6 07 AND $07 ; keep its low three bits -- eight arms
15C7: F7 RST $30 ; jump to the selected arm through the inline word table
; ---- $15C8-$15C9: jump table ----
15C8: E2 15
loc_15ca:
15CA: 5F LD E,A
15CB: A5 AND L
15CC: 13 INC DE
15CD: 77 LD (HL),A
15CE: D7 RST $10
15CF: 34 INC (HL)
15D0: 87 ADD A,A
15D1: FD DC B9 FE CALL C,$FEB9
15D5: 15 DEC D
15D6: 60 LD H,B
15D7: A6 AND (HL)
15D8: 14 INC D
15D9: C4 FD 10 CALL NZ,spinRemainingSpriteMultiplexSlots;
15DC: ED 77 ; NOP
15DE: 68 LD L,B
15DF: D7 RST $10
15E0: 34 INC (HL)
15E1: B9 CP C
; the first arm of the sequence machine's outer phase zero: arm the whole-
; plane wipe, then hand the inner index the step that actually runs that
; wipe, then subtract a 256-byte block of the program image from the outer
; phase and exclusive-or a fixed key into the difference. Neither number
; lands as an immediate -- the inner index is read out of a program byte
; that is the low half of an address inside an instruction, and the phase
; is never assigned, only folded -- so it is a tamper test that CORRUPTS
; the sequence rather than refusing to run. The dispatch that reaches it
; masks with and 0x03, so arrival proves only that the phase is
; congruent to zero modulo four, which is less than it looks like: 0x04,
; 0x08 and 0x0C are not fixed points of the fold. That the phase is left
; standing rests on SEQUENCE_PHASE's own registered range of four values
; and not on anything this arrival establishes
startTheWholePlaneWipeAndFoldAnImageBlockIntoThePhase:
15E2: CD 9A 01 CALL armWholePlaneWipeThenDerailOnATamperedImage; arm the whole-plane wipe -- derailing if the image was altered
15E5: 3A 49 17 LD A,(rom+1749) ; read a program byte -- the low half of a call operand, so an edit moves it
15E8: 32 AC A9 LD (workRam+1AC),A ; seat it as the inner sequence sub-step
15EB: 0E 00 LD C,$00 ; 256 bytes to fold
15ED: 21 48 56 LD HL,$5648 ; point at that program-image block
15F0: 3A AB A9 LD A,(workRam+1AB) ; take the outer sequence phase
loc_15f3:
15F3: 96 SUB (HL) ; fold: subtract each image byte from the phase
15F4: 23 INC HL ; next image byte
15F5: 0D DEC C
15F6: 20 FB JR NZ,loc_15f3 ; over all 256 bytes
15F8: EE 4E XOR $4E ; exclusive-or a fixed key into the result
15FA: 32 AB A9 LD (workRam+1AB),A ; write it back as the phase -- corrupts the sequence if the image was altered
15FD: C9 RET
; once a per-frame countdown lapses, arm a fresh screen: enqueue four
; fixed ring commands, seed a marker byte into two cells, patch six cells
; from a following table (value + 0x05 marker), print the six-digit
; readout, set two sub-states, and enqueue a fifth command when the gate
; cell is set
armAttractScreenShowingHighScore:
15FE: CD C2 01 CALL blankNextLine ; blank one line of the character plane
1601: C0 RET NZ ; more lines still to blank -- leave until the block is cleared
1602: 11 05 01 LD DE,$0105 ; command 1, argument 5
1605: FF RST $38 ; queue it on the request ring
1606: 1C INC E ; argument 6
1607: FF RST $38 ; queue it
1608: 1C INC E ; argument 7
1609: FF RST $38 ; queue it
160A: 11 01 06 LD DE,$0601 ; command 6, argument 1
160D: FF RST $38 ; queue it
160E: 3E 13 LD A,$13 ; marker glyph
1610: 32 01 A7 LD (videoRam+301),A ; seed it into one cell
1613: 32 E1 A6 LD (videoRam+2E1),A ; and into another
1616: 21 3F 16 LD HL,$163F ; point at the six-entry patch list that follows
1619: 06 06 LD B,$06 ; six cells to patch
loc_161b:
161B: 5E LD E,(HL) ; read the destination address low byte
161C: 23 INC HL
161D: 56 LD D,(HL) ; and its high byte
161E: 23 INC HL
161F: 7E LD A,(HL) ; read the value
1620: 12 LD (DE),A ; write it into the destination cell
1621: 13 INC DE ; the cell beside it
1622: EB EX DE,HL
1623: 36 05 LD (HL),$05 ; stamp marker 5 next to it
1625: EB EX DE,HL
1626: 23 INC HL ; next patch entry
1627: 10 F2 DJNZ loc_161b ; over all six
1629: CD 6B 0D CALL paintHighScoreReadout; print the six-digit high-score readout
162C: 3E 01 LD A,$01
162E: 32 AB A9 LD (workRam+1AB),A ; set the outer sequence phase to 1
1631: 3C INC A
1632: 32 AC A9 LD (workRam+1AC),A ; set the inner sub-step to 2
1635: 3A C0 A9 LD A,(workRam+1C0) ; read the free-play flag
1638: A7 AND A
1639: C8 RET Z ; leave unless free play
163A: 11 0D 01 LD DE,$010D ; command 1, argument 0x0D
163D: FF RST $38 ; queue it
163E: C9 RET
; ---- $163F-$1650: data ----
163F: FB AD FD 39 AD 68 43 AB 7C FE AB A5 BE AC 38 C7
164F: AC 3B
; the inner level of the two-level sequence machine for one outer mode:
; run the arm the RAW inner index selects out of a word table laid inline
; just after this entry, then this mode's shared tail at 0x167B; the
; doubling that turns the index into an offset wraps at eight bits, so a
; large index folds back onto the head of the table
dispatchSequencePhase1SubStepArm:
1651: 21 7B 16 LD HL,$167B ; point at the shared tail
1654: E5 PUSH HL ; park it as the return each arm falls back to
1655: 3A AC A9 LD A,(workRam+1AC) ; take the inner sequence sub-step
1658: F7 RST $30 ; jump to the selected arm through the inline word table
; ---- $1659-$1672: jump table ----
1659: 4B 07 34 17 3F 2D 3E 08 48 17 6A 17 8C 17 B9 17
1669: 52 32 E2 17 19 4B FB 17 30 27
; ---- $1673-$167A: data ----
1673: 26 A6 13 88 57 A5 BF B9
; a shared tail of the two-level sequence machine: when the packed-decimal
; credit count (0xA986) is nonzero, step the outer sequence phase and
; return; otherwise, only when the free-play flag (0xA9C0) is set and a
; start-button bit (0xA9AE & 0x18) is held, hide every sprite and start a
; game charging no credit
advanceSequenceElseStartFreePlayGame:
167B: 3A 86 A9 LD A,(workRam+186) ; read the credit count
167E: A7 AND A
167F: C2 11 0F JP NZ,advanceSequencePhase; credits on hand: step the outer sequence phase and leave
1682: 3A C0 A9 LD A,(workRam+1C0) ; read the free-play flag
1685: A7 AND A
1686: C8 RET Z ; not free play: leave
1687: 3A AE A9 LD A,(workRam+1AE) ; read the panel input mirror
168A: E6 18 AND $18 ; keep the two start-button bits
168C: C8 RET Z ; neither start held: leave
168D: CD B6 15 CALL hideAllSprites ; sweep every sprite off the picture
; start a game for whichever start button the input mirror shows held --
; two players if the two-player bit is set, one if only the one-player bit
; is -- stocking each started player's block with the lives setting, and
; charging no credit
startGameOnFreePlay:
1690: 3A AE A9 LD A,(workRam+1AE) ; read the panel input mirror
1693: CB 67 BIT 4,A ; is the two-player start held?
1695: 20 05 JR NZ,loc_169c ; yes: start a two-player game
1697: CB 5F BIT 3,A ; is the one-player start held?
1699: 20 7E JR NZ,loc_1719 ; yes: start a one-player game
169B: C9 RET ; neither held: do nothing
loc_169c:
169C: 3E FF LD A,$FF
169E: 32 30 AD LD (workRam+530),A ; raise the play-active flag
16A1: 32 31 AD LD (workRam+531),A ; raise the two-player flag
16A4: 3A C1 A9 LD A,(workRam+1C1) ; read the per-game life allowance
16A7: 32 10 AD LD (workRam+510),A ; stock player one's lives
16AA: 32 20 AD LD (workRam+520),A ; stock player two's lives
16AD: 18 7B JR seatSequencePhase3AndResetSubStep; seat the round-start phase
loc_16af:
16AF: 06 00 LD B,$00 ; 256 bytes to fold
16B1: 21 9F 4D LD HL,$4D9F ; point at a program-image block
16B4: 3A AB A9 LD A,(workRam+1AB) ; take the outer sequence phase
loc_16b7:
16B7: 96 SUB (HL) ; fold: subtract each image byte from the phase
16B8: 23 INC HL ; next image byte
16B9: 10 FC DJNZ loc_16b7 ; over all 256 bytes
16BB: EE A2 XOR $A2 ; exclusive-or a fixed key into the result
16BD: 32 AB A9 LD (workRam+1AB),A ; write it back as the phase -- corrupts the sequence if the image was altered
16C0: CD 97 0F CALL multiplexSpriteSlotsSkipping; run the sprite multiplexer
16C3: CD DF 1E CALL dispatchPlayerFrameByState; advance the player this frame by its state
16C6: CD 97 0F CALL multiplexSpriteSlotsSkipping; run the sprite multiplexer again
16C9: CD BC 2C CALL runSceneryForEra ; run the scenery for this era
16CC: CD 98 10 CALL multiplexSpriteSlots; run the sprite multiplexer
16CF: 3A 80 A9 LD A,(workRam+180) ; read the frame counter
16D2: E6 01 AND $01
16D4: 28 1C JR Z,loc_16f2 ; even frame: skip the delay tick
16D6: 21 EB A9 LD HL,$A9EB ; point at this step's delay counter
16D9: 35 DEC (HL) ; count it down one frame
16DA: 20 16 JR NZ,loc_16f2 ; not expired yet
16DC: 11 09 03 LD DE,$0309 ; on expiry: command 3, argument 9
16DF: FF RST $38 ; queue it on the request ring
16E0: 1E 0E LD E,$0E ; argument 0x0E
16E2: FF RST $38 ; queue it
16E3: 1E 1A LD E,$1A ; argument 0x1A
16E5: FF RST $38 ; queue it
16E6: AF XOR A
16E7: 32 0E AD LD (workRam+50E),A ; clear the round-armed flag
16EA: 3E 2A LD A,$2A
16EC: 32 EB A9 LD (workRam+1EB),A ; reload the step-delay counter to 0x2A frames
16EF: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index and leave
loc_16f2:
16F2: 3A 0E AD LD A,(workRam+50E) ; read the round-armed flag
16F5: A7 AND A
16F6: C8 RET Z ; not armed: leave
16F7: 3A 80 A9 LD A,(workRam+180) ; read the frame counter
16FA: E6 0F AND $0F ; keep its low nibble
16FC: 28 09 JR Z,loc_1707 ; frame nibble 0
16FE: FE 05 CP $05
1700: 28 09 JR Z,loc_170b ; frame nibble 5
1702: FE 0A CP $0A
1704: 28 09 JR Z,loc_170f ; frame nibble 0x0A
1706: C9 RET ; any other frame: leave
loc_1707:
1707: 16 02 LD D,$02 ; command 2
1709: 18 06 JR loc_1711 ;
loc_170b:
170B: 16 0A LD D,$0A ; command 0x0A
170D: 18 02 JR loc_1711 ;
loc_170f:
170F: 16 0B LD D,$0B ; command 0x0B
loc_1711:
1711: 3A 04 AD LD A,(workRam+504) ; read the era index
1714: C6 1A ADD A,$1A ; offset it to the caption argument
1716: 5F LD E,A
1717: FF RST $38 ; queue that command onto the request ring
1718: C9 RET
loc_1719:
1719: AF XOR A ; zero
171A: 32 31 AD LD (workRam+531),A ; clear the two-player flag
171D: 32 20 AD LD (workRam+520),A ; clear player two's lives
1720: 3D DEC A
1721: 32 30 AD LD (workRam+530),A ; raise the play-active flag
1724: 3A C1 A9 LD A,(workRam+1C1) ; read the per-game life allowance
1727: 32 10 AD LD (workRam+510),A ; stock player one's lives
; jump the sequence machine to its last outer phase and restart the inner
; index at zero; both stores are constants and neither cell is read first,
; so this is an unconditional jump to a fixed place rather than a step
seatSequencePhase3AndResetSubStep:
172A: 3E 03 LD A,$03
172C: 32 AB A9 LD (workRam+1AB),A ; seat the outer sequence at its last phase, 3
172F: AF XOR A
1730: 32 AC A9 LD (workRam+1AC),A ; restart the inner sub-step at zero
1733: C9 RET
; one interpolated-run sequence step: call drawInterpolatedPenRun to
; draw/advance one pen run and ret nz unless it reseated to a zero row
; integer, then store the two's-complement checksum of the 34-byte code
; block at 0x1748 into 0xA817 (0x00 on a clean image) and tail-jump to
; 0x0F1A (advanceSequenceSubStep) to step the sequence sub-index
advancePenRunAnimationStep:
1734: CD 01 02 CALL drawInterpolatedPenRun; draw and advance one interpolated pen run
1737: C0 RET NZ ; not yet reseated to a zero row -- leave
1738: 21 48 17 LD HL,$1748 ; point at the guarded code block
173B: 06 22 LD B,$22 ; its length -- thirty-four bytes
173D: AF XOR A ; start the running total at zero
loc_173e:
173E: 96 SUB (HL) ; subtract each byte -- a two's-complement checksum
173F: 23 INC HL ; next byte
1740: 10 FC DJNZ loc_173e ; over all thirty-four bytes
1742: 32 17 A8 LD (workRam+17),A ; store the checksum -- zero on an untampered block
1745: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index
; hold one sequence step for as long as its delay cell counts, restamping
; the copyright strip and flashing its line on every frame of the wait,
; and on the frame the delay expires queue two erase requests -- caption
; records 3 and 4, whose glyph runs read PLEASE DEPOSIT COIN and AND TRY
; THIS GAME -- then step the sequence on. A cell holding zero on arrival
; wraps to 255 and waits the long way round rather than leaving at once.
; The expiry frame also does the load-bearing thing the name drops: it
; copies the glyph showing at 0xA63C and the colour of the same cell into
; the pair at 0xACC7, and that pair is a COPYRIGHT TAMPER WITNESS rather
; than a screen save. 0xA63C is the fifth cell of the (c) KONAMI 1982
; caption -- the N, glyph 0x3B -- and the arm at 0x30E3 reads the pair
; back, tests the glyph against 0x3B and the colour against 0x05 or 0x10,
; and derails to 0x315B on anything else
holdCopyrightThenEraseTheCoinInvitation:
1748: CD 06 0B CALL stampCopyrightStrip ; re-stamp the copyright caption strip
174B: CD 39 0B CALL flashCopyrightLine ; flash the copyright line
174E: 21 EB A9 LD HL,$A9EB ; point at this step's delay counter
1751: 35 DEC (HL) ; count it down one frame
1752: C0 RET NZ ; still waiting -- leave
1753: 21 3C A6 LD HL,$A63C ; point at the sampled copyright caption cell
1756: 11 C7 AC LD DE,$ACC7 ; point at the tamper-witness pair
1759: 7E LD A,(HL) ; read its glyph
175A: 12 LD (DE),A ; save the glyph
175B: 13 INC DE
175C: CB 94 RES 2,H ; cross to the colour plane of the same cell
175E: 7E LD A,(HL) ; read its colour
175F: 12 LD (DE),A ; save the colour beside the glyph
1760: 11 03 03 LD DE,$0303 ; command 3, argument 3
1763: FF RST $38 ; queue it -- erase one coin-invitation caption
1764: 1C INC E ; argument 4
1765: FF RST $38 ; queue it -- erase the other
1766: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index
; ---- $1769-$1769: data ----
1769: 31
loc_176a:
176A: CD DA 19 CALL checkTheCopyrightLineColoursOrDerail; verify the copyright line's colours -- derail if wrong
176D: 3A 7C A6 LD A,(videoRam+27C) ; read a caption cell
1770: FE 7C CP $7C ; is its glyph the expected one?
1772: C2 9B 45 JP NZ,rom+459B ; wrong glyph: derail into the mother-ship warp-flash handler
1775: 11 13 01 LD DE,$0113 ; command 1, argument 0x13
1778: FF RST $38 ; queue it
1779: CD DC 4B CALL paintFiveLabelledNumericReadouts; paint the five labelled numeric readouts
177C: 21 DC A5 LD HL,$A5DC ; point at a character cell
177F: 11 FB AD LD DE,$ADFB ; point at a tamper-witness pair
1782: 7E LD A,(HL) ; read its glyph
1783: 12 LD (DE),A ; save the glyph
1784: 13 INC DE
1785: CB 94 RES 2,H ; cross to the colour plane of the same cell
1787: 7E LD A,(HL) ; read its colour
1788: 12 LD (DE),A ; save the colour beside it
1789: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index
loc_178c:
178C: CD 06 0B CALL stampCopyrightStrip ; re-stamp the copyright caption strip
178F: CD 39 0B CALL flashCopyrightLine ; flash the copyright line
1792: 21 EB A9 LD HL,$A9EB ; point at this step's delay counter
1795: 35 DEC (HL) ; count it down one frame
1796: C0 RET NZ ; still waiting -- leave
1797: CD DA 19 CALL checkTheCopyrightLineColoursOrDerail; verify the copyright line's colours -- derail if wrong
179A: 3A B3 47 LD A,(rom+47B3) ; read a program byte
179D: C6 02 ADD A,$02 ; offset it
179F: 6F LD L,A ; as the pointer low byte
17A0: C6 6A ADD A,$6A ; offset it again
17A2: 67 LD H,A ; as the pointer high byte
17A3: 7E LD A,(HL) ; read the addressed cell
17A4: FE 3B CP $3B ; is its glyph the expected one?
17A6: C2 CA 15 JP NZ,loc_15ca ; wrong glyph: derail into the jump-table bytes, run as code
17A9: 21 7C A6 LD HL,$A67C ; point at a character cell
17AC: 11 43 AB LD DE,$AB43 ; point at a tamper-witness pair
17AF: 7E LD A,(HL) ; read its glyph
17B0: 12 LD (DE),A ; save the glyph
17B1: 13 INC DE
17B2: CB 94 RES 2,H ; cross to the colour plane of the same cell
17B4: 7E LD A,(HL) ; read its colour
17B5: 12 LD (DE),A ; save the colour beside it
17B6: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index
; fold a block of the program image and let the sequence step on only if
; it still adds up; otherwise switch the display off and copy one
; character cell into TAMPER_WITNESS
guardBlockOrBlankDisplay:
17B9: 3A 0D 59 LD A,(rom+590D) ; read a byte of the program image
17BC: 4F LD C,A
17BD: 3A 40 4A LD A,(rom+4A40) ; seed the running total from a program byte
17C0: 21 06 0B LD HL,$0B06 ; point at the guarded block
17C3: 06 33 LD B,$33 ; fifty-one bytes to add
loc_17c5:
17C5: 86 ADD A,(HL) ; add each byte into the total
17C6: 23 INC HL ; next byte
17C7: 10 FC DJNZ loc_17c5 ; over all fifty-one bytes
17C9: FE EF CP $EF ; does the total match the expected signature?
17CB: CA 1A 0F JP Z,advanceSequenceSubStep; matches: step the sequence sub-index and leave
17CE: 3A 89 4C LD A,(rom+4C89) ; mismatch: take the display-off value from the image
17D1: 32 08 C3 LD ($C308),A ; switch the display off through the video-enable latch
17D4: 21 5C A6 LD HL,$A65C ; point at one character cell
17D7: 11 39 AD LD DE,$AD39 ; point at the tamper-witness pair
17DA: 7E LD A,(HL) ; read its glyph
17DB: 12 LD (DE),A ; save the glyph
17DC: 13 INC DE
17DD: CB 94 RES 2,H ; cross to the colour plane of the same cell
17DF: 7E LD A,(HL) ; read its colour
17E0: 12 LD (DE),A ; save the colour beside it
17E1: C9 RET
; raise one flag cell to all bits, fold a fixed block of the program image
; into a running total seeded from an image byte and bank the result, then
; step the inner sequence index -- one step of the tamper-check sequence
foldImageBlockIntoSignatureThenAdvanceSequence:
17E2: 3E FF LD A,$FF
17E4: 32 3F AA LD (workRam+23F),A ; raise a flag cell to all bits set
17E7: 11 B9 17 LD DE,$17B9 ; point a second pointer at a block of the program image
17EA: 0E 08 LD C,$08
17EC: CD D9 4B CALL trampolineToSelectFoldBlock; select the guarded block -- its start and thirty-byte length
17EF: 3A C0 27 LD A,(rom+27C0) ; seed the running total from a program byte
17F2: CD 1E 29 CALL foldBlockIntoTotal ; fold the block into the total
17F5: 32 6F AA LD (workRam+26F),A ; bank the result as the image signature
17F8: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index
; a sequence step that does no work of its own -- it only moves the inner
; index on, so reaching it costs one turn and changes nothing else
trampolineToAdvanceSequenceSubStep:
17FB: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index
; the inner level of the two-level sequence machine for one outer mode:
; run the arm the RAW inner index selects out of a word table laid inline
; just after this entry; this mode's tail does nothing at all, which is
; why every arm here simply ends
dispatchSequencePhase2SubStepArm:
17FE: 21 1D 18 LD HL,$181D ; point at the mode's do-nothing tail
1801: E5 PUSH HL ; park it as the arm's return
1802: 3A AC A9 LD A,(workRam+1AC) ; take the inner sequence sub-step
1805: F7 RST $30 ; jump to the selected arm through the inline word table
; ---- $1806-$180F: jump table ----
1806: 1E 18 DB 2C 30 18 E6 07 8A 18
; ---- $1810-$181C: data ----
1810: 72 A6 14 7D A5 38 34 F1 68 0E 34 D7 B9
; an arrival point with nothing to do: no cell is read or written and no
; register moves
noOpSequencePhase2Tail:
181D: C9 RET ; the mode's tail -- return at once, nothing runs after the arm
; one step of a screen-clearing sequence: park every sprite out of sight,
; copy the glyph and colour showing at one fixed character cell into one
; fixed two-byte record, arm the line wipe to run from the plane's fifth
; line, and step the sequence's inner index on last; both the cell and the
; record are fixed here, so nothing a caller was holding chooses either
parkSpritesAndArmLineWipeThenAdvanceSequence:
181E: CD B6 15 CALL hideAllSprites ; sweep every sprite off the picture
1821: 21 FC A5 LD HL,$A5FC ; point at the character cell to sample
1824: 11 BE AC LD DE,$ACBE ; point at the two-byte record to hold it
1827: CD FC 1A CALL sampleCellGlyphAndColour; copy the cell's glyph and colour into the record
182A: CD B5 01 CALL armLineWipeFromFifthLine; arm the line wipe from the plane's fifth line
182D: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index
; one arm of the two-level sequence machine (inner index 2 of
; dispatchSequencePhase2SubStepArm): after two setup calls it posts a
; fixed run of display codes to the writer at 0x0038 as (D=1,code) pairs
; -- 0x01,0x14,0x15, a code that flips 0x0F/0x11 on cell 0xA9C3 and its
; successor, 0x16, 0x00, and a tail 0x19/0x17 chosen by 0xA986 --
; advancing the sequence counter 0xA9AC through 0x0F1A twice on the
; 0xA986>=2 branch and once below
postAttractInfoCaptions:
1830: CD 06 0B CALL stampCopyrightStrip ; re-stamp the copyright caption strip
1833: CD 39 0B CALL flashCopyrightLine ; flash the copyright line
1836: 11 01 01 LD DE,$0101 ; command 1, argument 1
1839: FF RST $38 ; queue it on the request ring
183A: 1E 14 LD E,$14 ; argument 0x14
183C: FF RST $38 ; queue it
183D: 1C INC E ; argument 0x15
183E: FF RST $38 ; queue it
183F: 1E 0F LD E,$0F ; default caption glyph
1841: 3A C3 A9 LD A,(workRam+1C3) ; read the bonus-life setting
1844: A7 AND A
1845: 28 02 JR Z,loc_1849 ; setting clear: keep the default glyph
1847: 1C INC E ; setting set: step to the alternate glyph
1848: 1C INC E
loc_1849:
1849: FF RST $38 ; queue the chosen caption glyph
184A: 1C INC E ; the next glyph
184B: FF RST $38 ; queue it
184C: 1E 16 LD E,$16 ; argument 0x16
184E: FF RST $38 ; queue it
184F: 1E 00 LD E,$00 ; argument 0
1851: FF RST $38 ; queue it
1852: 3A 86 A9 LD A,(workRam+186) ; read the credit count
1855: FE 02 CP $02 ; two or more credits?
1857: 30 07 JR NC,loc_1860 ; yes: take the two-credit tail
1859: 11 17 01 LD DE,$0117 ; command 1, argument 0x17
185C: FF RST $38 ; queue it
185D: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index and leave
loc_1860:
1860: 11 19 01 LD DE,$0119 ; command 1, argument 0x19
1863: FF RST $38 ; queue it
1864: CD 1A 0F CALL advanceSequenceSubStep; step the sequence sub-index
1867: C3 1A 0F JP advanceSequenceSubStep; step it again
; ---- $186A-$1889: data ----
186A: 00 02 06 0D 00 03 07 0C 00 04 08 0B 02 06 0A 0A
187A: 04 08 0C 09 07 0A 0D 07 0B 0D 0E 05 0F 0F 0F 05
; the two-credit copyright screen's await-start step: stamp the fixed
; copyright caption strip and flash its line, then dispatch on the two
; start-button bits of IN0_MIRROR (0xA9AE) -- bit 4 tail-calls the two-
; player start, bit 3 the one-player start (bit 4 wins when both are
; held), and with neither held it returns so the screen shows again
stepTwoCreditCopyrightScreenAwaitingStart:
188A: CD 06 0B CALL stampCopyrightStrip ; redraw the fixed copyright caption strip
188D: CD 39 0B CALL flashCopyrightLine ; flash its line for this frame
1890: 3A AE A9 LD A,(workRam+1AE) ; read the panel's start-button bits
1893: CB 67 BIT 4,A ; is two-player start held? -- bit 4
1895: C2 9E 18 JP NZ,startTwoPlayerGame; two-player start held: begin a two-player game
1898: CB 5F BIT 3,A ; is one-player start held? -- bit 3
189A: C2 15 32 JP NZ,startOnePlayerGame; one-player start held: begin a one-player game
189D: C9 RET ; neither held: leave the copyright screen up
; start a two-player game: park the caption sprites, raise PLAY_ACTIVE and
; the flag beside it, load both players' lives from the starting-count
; settings cell, run the two-player-start arm, deduct two credits in
; packed BCD from 0xA986 and repaint the panel field, then send the
; sequence machine to its last phase
startTwoPlayerGame:
189E: CD 2B 0B CALL hideCaptionSprites ; park the caption sprites
18A1: 3E FF LD A,$FF
18A3: 32 30 AD LD (workRam+530),A ; raise the play-active flag
18A6: 32 31 AD LD (workRam+531),A ; raise the two-player-game flag beside it
18A9: 3A C1 A9 LD A,(workRam+1C1) ; take the starting-lives setting
18AC: 32 10 AD LD (workRam+510),A ; seat it as player one's life count
18AF: 32 20 AD LD (workRam+520),A ; and as player two's life count
18B2: CD 0E 46 CALL setUpTwoPlayerStartObjectOnce; run the two-player-start object arm
18B5: 21 86 A9 LD HL,$A986 ; point at the on-screen credit count
18B8: 7E LD A,(HL)
18B9: D6 02 SUB $02 ; take two credits off it
18BB: 27 DAA ; fix the packed-decimal result
18BC: 77 LD (HL),A
18BD: CD FB 4A CALL paintCreditCountPanel; repaint the credit field
18C0: C3 2A 17 JP seatSequencePhase3AndResetSubStep; send the sequence machine to its last phase
loc_18c3:
18C3: 3A 80 A9 LD A,(workRam+180) ; read the frame counter
18C6: E6 01 AND $01 ; keep its low bit -- run this scan every other frame
18C8: C2 84 19 JP NZ,loc_1984 ; on the off frames, hand over to the cursor-flash and game-over arm
18CB: CD D1 1E CALL readPlayerControls ; read the entry panel's controls
18CE: 21 95 A9 LD HL,$A995 ; point at the first of four rolling press-history bytes
18D1: 0F RRCA
18D2: CB 16 RL (HL) ; roll the letter-back control into its history
18D4: 23 INC HL
18D5: 0F RRCA
18D6: CB 16 RL (HL) ; roll the letter-forward control into its history
18D8: 23 INC HL
18D9: 0F RRCA
18DA: 0F RRCA
18DB: 0F RRCA
18DC: CB 16 RL (HL) ; roll a commit button into its history
18DE: 23 INC HL
18DF: 0F RRCA
18E0: CB 16 RL (HL) ; roll the other commit button into its history
18E2: 7E LD A,(HL)
18E3: E6 07 AND $07 ; keep the last three samples -- a fresh press reads as 001
18E5: 3D DEC A
18E6: 28 3B JR Z,loc_1923 ; a commit button just went down: lock in the current letter
18E8: 2B DEC HL
18E9: 7E LD A,(HL)
18EA: E6 07 AND $07
18EC: 3D DEC A
18ED: 28 34 JR Z,loc_1923 ; the other commit button went down: lock in the letter
18EF: 2B DEC HL
18F0: 7E LD A,(HL)
18F1: FE FF CP $FF ; the letter-forward control held to saturation?
18F3: CC 80 19 CALL Z,rearmHeldControlRepeat; if so, clear its history so a held press repeats
18F6: E6 07 AND $07
18F8: 3D DEC A
18F9: 28 1B JR Z,loc_1916 ; a fresh forward press: step the shown letter forward
18FB: 2B DEC HL
18FC: 7E LD A,(HL)
18FD: FE 7F CP $7F ; the letter-back control held to saturation?
18FF: CC 80 19 CALL Z,rearmHeldControlRepeat; if so, clear its history so a held press repeats
1902: E6 07 AND $07
1904: 3D DEC A
1905: 28 02 JR Z,loc_1909 ; a fresh back press: step the shown letter back
1907: 18 5A JR loc_1963 ; nothing pressed: go blink the cursor
loc_1909:
1909: 21 99 A9 LD HL,$A999 ; point at the letter index
190C: 35 DEC (HL) ; step the shown letter back one
190D: 7E LD A,(HL)
190E: FE 80 CP $80 ; did the index run below the first letter?
1910: 38 3C JR C,loc_194e ; still in range: redraw it
1912: 36 1A LD (HL),$1A ; wrapped: set it to the last letter
1914: 18 38 JR loc_194e ;
loc_1916:
1916: 21 99 A9 LD HL,$A999 ; point at the letter index
1919: 34 INC (HL) ; step the shown letter forward one
191A: 7E LD A,(HL)
191B: FE 1B CP $1B ; past the last letter?
191D: 38 2F JR C,loc_194e ; still in range: redraw it
191F: 36 00 LD (HL),$00 ; wrapped: back to the first letter
1921: 18 2B JR loc_194e ;
loc_1923:
1923: 3A 99 A9 LD A,(workRam+199) ; take the chosen letter index
1926: 21 C7 12 LD HL,$12C7 ; point at the letter-glyph table
1929: CF RST $08 ; look up its glyph
192A: 2A 91 A9 LD HL,(workRam+191) ; fetch the video write pointer
192D: ED 5B 93 A9 LD DE,(workRam+193) ; fetch the colour write pointer
1931: 12 LD (DE),A ; stamp the glyph through the colour-side pointer
1932: 77 LD (HL),A ; stamp it through the video-side pointer
1933: 3A 90 A9 LD A,(workRam+190) ; take the colour a locked-in letter wears
1936: CB 92 RES 2,D ; aim the pointer at the colour plane
1938: 12 LD (DE),A ; paint that colour under the glyph
1939: CB D2 SET 2,D
193B: E7 RST $20 ; step the write pointer on to the next cell
193C: 23 INC HL
193D: 22 91 A9 LD (workRam+191),HL ; save the advanced video pointer
1940: ED 53 93 A9 LD (workRam+193),DE ; save the advanced colour pointer
1944: 21 9A A9 LD HL,$A99A
1947: 35 DEC (HL) ; one fewer initial left to enter
1948: 28 2B JR Z,loc_1975 ; all initials in: finish the entry
194A: AF XOR A
194B: 32 99 A9 LD (workRam+199),A ; reset the letter index for the next slot
loc_194e:
194E: ED 5B 93 A9 LD DE,(workRam+193) ; fetch the cursor write pointer
1952: 3A 99 A9 LD A,(workRam+199) ; take the letter now being shown
1955: 21 C7 12 LD HL,$12C7
1958: CF RST $08 ; look up its glyph
1959: 12 LD (DE),A ; draw it at the cursor cell
195A: CB 92 RES 2,D ; aim at the colour plane
195C: 3E 10 LD A,$10
195E: 12 LD (DE),A ; paint the bright cursor colour under it
195F: AF XOR A
1960: 32 9C A9 LD (workRam+19C),A ; restart the cursor-flash counter
loc_1963:
1963: 3A 80 A9 LD A,(workRam+180) ; read the frame counter
1966: E6 07 AND $07 ; act one frame in eight
1968: 20 30 JR NZ,loc_199a ; other frames: go to the flash and game-over tail
196A: 21 EB A9 LD HL,$A9EB
196D: 35 DEC (HL) ; count the cursor-blink timer down
196E: 20 2A JR NZ,loc_199a ; not fired yet: go to the tail
1970: 2A 93 A9 LD HL,(workRam+193) ; fetch the cursor cell pointer
1973: 36 F1 LD (HL),$F1 ; blank the cursor -- the blink's dark phase
loc_1975:
1975: 3E 3C LD A,$3C
1977: 32 EB A9 LD (workRam+1EB),A ; reload the cursor-blink timer -- sixty frames
197A: CD 34 56 CALL enqueueTransitionSoundBurst;
197D: C3 1A 0F JP advanceSequenceSubStep; step the sequence machine on
; clear the one-bit press history a caller points at, and hand back a
; zero. A history is a byte a control's bit is rolled into every other
; frame, and its owner acts on the frame the low three bits read 001;
; while a control stays held the byte fills and that pattern cannot recur,
; so clearing it is what lets the same press act again
rearmHeldControlRepeat:
1980: 36 00 LD (HL),$00 ; empty the press-history byte the caller points at
1982: AF XOR A ; hand back zero
1983: C9 RET
loc_1984:
1984: 21 9C A9 LD HL,$A99C ; point at the cursor-flash counter
1987: 34 INC (HL) ; step it on one
1988: 2A 93 A9 LD HL,(workRam+193) ; fetch the cursor's colour cell
198B: CB 94 RES 2,H ; aim at the colour plane
198D: 3A 9C A9 LD A,(workRam+19C) ;
1990: CB 67 BIT 4,A ; is the counter's slow bit set? -- picks the flash phase
1992: 28 04 JR Z,loc_1998 ;
1994: 36 14 LD (HL),$14 ; one phase: paint the cursor its bright colour
1996: 18 02 JR loc_199a ;
loc_1998:
1998: 36 10 LD (HL),$10 ; the other phase: paint it its dim colour
loc_199a:
199A: 21 20 AD LD HL,$AD20 ; point at player two's life count
199D: 3A 10 AD LD A,(workRam+510) ; take player one's life count
19A0: B6 OR (HL) ; fold in player two's
19A1: C0 RET NZ ; either player still has a life: leave the entry running
19A2: 3A C0 A9 LD A,(workRam+1C0) ; read the free-play flag
19A5: A7 AND A
19A6: 20 26 JR NZ,loc_19ce ; free play: jump to the free-play start check
19A8: 3A 86 A9 LD A,(workRam+186) ; read the credit count
19AB: FE 01 CP $01 ; is it below one?
19AD: D8 RET C ; no credits: keep waiting
19AE: 28 10 JR Z,loc_19c0 ; exactly one credit: go to the one-credit check
19B0: 3A AE A9 LD A,(workRam+1AE) ; read the start-button bits
19B3: E6 18 AND $18
19B5: C8 RET Z ; none held: keep waiting
19B6: FE 08 CP $08 ; the one-player start alone?
19B8: 28 0E JR Z,loc_19c8 ; yes: begin a one-player game
19BA: CD B6 15 CALL hideAllSprites ; hide every sprite
19BD: C3 9E 18 JP startTwoPlayerGame ; otherwise begin a two-player game
loc_19c0:
19C0: 3A AE A9 LD A,(workRam+1AE) ; read the start-button bits
19C3: E6 18 AND $18
19C5: FE 08 CP $08 ; must be the one-player start alone
19C7: C0 RET NZ ; anything else: keep waiting
loc_19c8:
19C8: CD B6 15 CALL hideAllSprites ; hide every sprite
19CB: C3 15 32 JP startOnePlayerGame ; begin a one-player game
loc_19ce:
19CE: 3A AE A9 LD A,(workRam+1AE) ; read the start-button bits
19D1: E6 18 AND $18
19D3: C8 RET Z ; none held: keep waiting
19D4: CD B6 15 CALL hideAllSprites ; hide every sprite
19D7: C3 90 16 JP startGameOnFreePlay ; begin a game charging no credit
; walk the thirteen colour cells under the copyright line and derail on
; the first one that has been changed: starting at 0xA2BC and stepping
; back 32 a cell, every cell must hold one of exactly two colours, and the
; first that holds anything else transfers into bytes that carry no
; routine and never come back. The two accepted colours are the COLOUR
; BYTES OF THE LINE'S TWO RECORDS, which differ in nothing else -- 0x10 in
; the record at 0x086B and 0x05 in the record at 0x4900, both carrying
; destination 0xA6BC and the same thirteen glyphs -- so the pair is what
; the line's own flashing writes, and not a wipe colour beside a pen
; colour. Thirteen good cells return having done nothing
checkTheCopyrightLineColoursOrDerail:
19DA: 21 BC A2 LD HL,$A2BC ; point at the copyright line's first colour cell
19DD: 06 0D LD B,$0D ; thirteen cells to check
loc_19df:
19DF: 7E LD A,(HL) ; read this cell's colour
19E0: FE 10 CP $10 ; is it the first accepted colour?
19E2: 28 05 JR Z,loc_19e9 ; yes: on to the next cell
19E4: FE 05 CP $05 ; is it the second accepted colour?
19E6: C2 FA 49 JP NZ,rom+49FA ; anything else: the line has been tampered -- transfer away and never return
loc_19e9:
19E9: 11 E0 FF LD DE,$FFE0 ; the stride back one cell along the line
19EC: 19 ADD HL,DE ; step back to the previous cell
19ED: 10 F0 DJNZ loc_19df ; loop over all thirteen
19EF: C9 RET ; thirteen good cells: return having changed nothing
; reset the whole playfield for a new round: clear scroll/control cells,
; seat the ship sprite + shot slots, retire every object slot
; (hold/shared-cooldown/cooldown/sub-pixel variants), clear four sprite
; entries, seat the era scenery band via
; seatEraSceneryRowThenClearAndRunScenery, then scatter one era-selected
; 10-byte record from the 0x1B04 word table into the cells that arm the
; round
resetPlayfieldAndArmNewRound:
19F0: 21 00 00 LD HL,$0000 ; clear the value to write
19F3: 22 08 A8 LD (workRam+8),HL ; zero the world-scroll X
19F6: 22 0A A8 LD (workRam+A),HL ; zero the world-scroll Y
19F9: 22 06 AD LD (workRam+506),HL ; zero the paired scroll cell
19FC: AF XOR A
19FD: 32 0D AD LD (workRam+50D),A ; clear the mother-ship-armed flag
1A00: 32 F7 A8 LD (workRam+F7),A ; clear the parachutist rung
1A03: 32 05 AD LD (workRam+505),A ; clear the base-sixty life-tick's low place
1A06: 3A D6 A9 LD A,(workRam+1D6) ;
1A09: 32 D7 A9 LD (workRam+1D7),A ; reload the escalation-rung timer from its period
1A0C: 3A 0A AD LD A,(workRam+50A) ;
1A0F: 32 C0 AC LD (workRam+4C0),A ; copy the round's opening difficulty rung into the live rung cell
1A12: AF XOR A
1A13: 32 81 AA LD (workRam+281),A ; clear a sprite cell
1A16: 32 C6 AC LD (workRam+4C6),A ; clear the wave-hold flag
1A19: 3E 80 LD A,$80
1A1B: 32 02 A8 LD (workRam+2),A ; seat the player heading to its start direction
1A1E: AF XOR A
1A1F: 32 01 A8 LD (workRam+1),A ; clear the heading fraction
1A22: 3E FF LD A,$FF
1A24: 32 00 A8 LD (workRam),A ; set the player state to alive
1A27: 3E 78 LD A,$78
1A29: 32 41 AA LD (workRam+241),A ; seat the player's sprite Y to its start row
1A2C: 3E 84 LD A,$84
1A2E: 32 10 AA LD (workRam+210),A ; seat the player's sprite entry to its start column
1A31: CD AF 20 CALL dressPlayerSpriteForHeading; dress the player sprite for its heading
1A34: CD 55 27 CALL freeAllShotSlots ; free every shot slot
1A37: DD 21 C0 A8 LD IX,$A8C0
1A3B: FD 21 28 AA LD IY,$AA28
1A3F: CD 0D 3C CALL retireObjectAndHold ; retire the first object slot into hold
1A42: 06 07 LD B,$07 ; seven sub-pixel slots to retire
1A44: DD 21 50 A8 LD IX,$A850
1A48: FD 21 1A AA LD IY,$AA1A
1A4C: DD 21 E0 A8 LD IX,$A8E0
1A50: FD 21 2C AA LD IY,$AA2C
1A54: CD FB 3D CALL retireSlotIntoSharedCooldown; retire the next object slot into shared cooldown
1A57: DD 21 F0 A8 LD IX,$A8F0
1A5B: FD 21 2E AA LD IY,$AA2E
1A5F: CD AD 48 CALL retireSlotIntoCooldown; retire the parachutist slot into cooldown
loc_1a62:
1A62: CD DE 2B CALL retireSlotAndSubPixel; retire this slot and clear its sub-pixel fraction
1A65: 11 10 00 LD DE,$0010 ; the sixteen-byte record stride
1A68: DD 19 ADD IX,DE
1A6A: FD 23 INC IY
1A6C: FD 23 INC IY
1A6E: 10 F2 DJNZ loc_1a62 ; loop over the seven slots
1A70: CD E4 1A CALL freeAndNumberEveryObjectSlot; free and number every object slot
1A73: FD 21 28 AA LD IY,$AA28 ; point at the object sprite entries
1A77: FD 36 00 00 LD (IY+$00),$00 ; clear eight entry cells across the two banks
1A7B: FD 36 02 00 LD (IY+$02),$00
1A7F: FD 36 04 00 LD (IY+$04),$00
1A83: FD 36 06 00 LD (IY+$06),$00
1A87: FD 36 31 00 LD (IY+$31),$00
1A8B: FD 36 33 00 LD (IY+$33),$00
1A8F: FD 36 35 00 LD (IY+$35),$00
1A93: FD 36 37 00 LD (IY+$37),$00
1A97: CD A5 30 CALL seatEraSceneryRowThenClearAndRunScenery; seat the era's scenery band and run it
; apply the tuning row that the era and its escalation rung together
; select, scattering the row's ten bytes over twelve cells -- two spawner
; caps, two aim windows, two cooldown periods and their live countdowns,
; and two thresholds
applyEraRungSettings:
1A9A: 3A 04 AD LD A,(workRam+504) ; take the era number
1A9D: 07 RLCA ; shift it up into the high nibble
1A9E: 07 RLCA
1A9F: 07 RLCA
1AA0: 07 RLCA
1AA1: E6 F0 AND $F0 ; keep just that nibble
1AA3: 47 LD B,A
1AA4: 3A C0 AC LD A,(workRam+4C0) ; take the escalation rung
1AA7: 80 ADD A,B ; combine them into the tuning-row index
1AA8: 21 04 1B LD HL,$1B04 ; point at the table of tuning-row addresses
1AAB: D7 RST $10 ; fetch the chosen row's address
1AAC: 1A LD A,(DE)
1AAD: 32 44 A8 LD (workRam+44),A ; first byte: a launch-bank slot cap
1AB0: 13 INC DE
1AB1: 1A LD A,(DE)
1AB2: 32 37 A8 LD (workRam+37),A ; the bank's near-approach X half-window
1AB5: 13 INC DE
1AB6: 1A LD A,(DE)
1AB7: 32 27 A8 LD (workRam+27),A ; the bank's near-approach Y half-window
1ABA: 13 INC DE
1ABB: 1A LD A,(DE)
1ABC: 32 17 A8 LD (workRam+17),A ; the bank-launch cooldown
1ABF: 32 14 A8 LD (workRam+14),A ; -- into its paired reload cell too
1AC2: 13 INC DE
1AC3: 1A LD A,(DE)
1AC4: 32 C1 AC LD (workRam+4C1),A ; the craft-per-round count
1AC7: 13 INC DE
1AC8: 1A LD A,(DE)
1AC9: 32 C4 AC LD (workRam+4C4),A ; a script-pick threshold
1ACC: 13 INC DE
1ACD: 1A LD A,(DE)
1ACE: 32 C6 A8 LD (workRam+C6),A ; an attacker-spawn slot cap
1AD1: 13 INC DE
1AD2: 1A LD A,(DE)
1AD3: 32 D6 A8 LD (workRam+D6),A ; the attacker-spawn spread half-window
1AD6: 13 INC DE
1AD7: 1A LD A,(DE)
1AD8: 32 E6 A8 LD (workRam+E6),A ; the attacker-spawn aim half-window
1ADB: 13 INC DE
1ADC: 1A LD A,(DE)
1ADD: 32 F4 A8 LD (workRam+F4),A ; the attacker-spawn cooldown
1AE0: 32 F6 A8 LD (workRam+F6),A ; -- into its paired reload cell too
1AE3: C9 RET
; lay out the object array's twenty-three records, sixteen bytes apart
; from a fixed start: clear each record's occupancy byte and stamp its
; sixteenth byte with that record's position in the run, counting from
; one. Nothing is read, so the run comes out the same however it went in
freeAndNumberEveryObjectSlot:
1AE4: DD 21 10 A8 LD IX,$A810 ; point at the first object record
1AE8: 3E 01 LD A,$01 ; record numbers start at one
1AEA: 06 17 LD B,$17 ; twenty-three records
1AEC: 11 10 00 LD DE,$0010 ; sixteen bytes apart
loc_1aef:
1AEF: DD 36 00 00 LD (IX+$00),$00 ; clear this record's occupancy byte
1AF3: DD 77 0F LD (IX+$0F),A ; stamp its number into its sixteenth byte
1AF6: 3C INC A ; count on to the next number
1AF7: DD 19 ADD IX,DE
1AF9: 10 F4 DJNZ loc_1aef ; loop over all twenty-three
1AFB: C9 RET
; take what is currently showing at one character cell -- its glyph byte
; and the colour byte of the same cell -- and lay the two down side by
; side as a two-byte record. One pointer reaches both planes because they
; hold the same grid at the same offset and are told apart by a single
; address bit. The cell itself is not touched, so what the caller gets is
; a reading and not a reservation
sampleCellGlyphAndColour:
1AFC: 7E LD A,(HL) ; read the cell's glyph
1AFD: 12 LD (DE),A ; store it into the record
1AFE: 13 INC DE
1AFF: CB 94 RES 2,H ; flip the pointer to the colour plane
1B01: 7E LD A,(HL) ; read the same cell's colour
1B02: 12 LD (DE),A ; store it beside the glyph
1B03: C9 RET
; ---- $1B04-$1ED0: data ----
1B04: B1 1B BB 1B C5 1B CF 1B D9 1B E3 1B ED 1B F7 1B
1B14: 01 1C 0B 1C 15 1C 1F 1C 29 1C 33 1C 3D 1C 47 1C
1B24: 51 1C 5B 1C 65 1C 6F 1C 79 1C 83 1C 8D 1C 97 1C
1B34: A1 1C AB 1C B5 1C BF 1C C9 1C D3 1C DD 1C E7 1C
1B44: F1 1C FB 1C 05 1D 0F 1D 19 1D 23 1D 2D 1D 37 1D
1B54: 41 1D 4B 1D 55 1D 5F 1D 69 1D 73 1D 7D 1D 87 1D
1B64: 91 1D 9B 1D A5 1D AF 1D B9 1D C3 1D CD 1D D7 1D
1B74: E1 1D EB 1D F5 1D FF 1D 09 1E 13 1E 1D 1E 27 1E
1B84: 31 1E 3B 1E 45 1E 4F 1E 59 1E 63 1E 6D 1E 77 1E
1B94: 81 1E 8B 1E 95 1E 9F 1E A9 1E B3 1E BD 1E C7 1E
1BA4: 5F A5 13 00 D7 34 34 F1 88 57 A5 BF B9 00 20 50
1BB4: 3C 04 50 00 50 18 5A 01 20 4E 3C 04 50 00 4E 18
1BC4: 54 01 28 4C 32 05 60 01 4C 1C 4E 02 28 48 28 05
1BD4: 60 01 48 1C 48 02 30 46 1E 06 70 01 46 1C 42 03
1BE4: 30 44 1E 06 70 02 44 20 3C 03 38 42 1E 06 80 02
1BF4: 42 20 36 03 38 40 1E 06 80 02 40 20 30 04 40 3F
1C04: 1E 07 90 03 3F 24 2A 04 40 3E 1E 07 90 03 3E 24
1C14: 24 04 40 3D 1E 07 A0 03 3D 24 1E 04 40 3C 1E 07
1C24: B0 03 3C 28 1E 04 48 3B 1E 07 C0 03 3B 28 1E 04
1C34: 48 3A 1E 07 D0 03 3A 2C 1E 04 48 39 1E 07 E0 03
1C44: 39 30 1E 04 48 38 19 07 F0 03 38 30 19 01 28 48
1C54: 32 05 50 01 5C 00 1E 01 28 48 28 05 50 01 5A 00
1C64: 1E 02 30 48 1E 05 60 01 58 00 1E 02 30 48 1E 06
1C74: 60 01 56 00 1E 02 30 48 1E 06 70 02 54 00 1E 03
1C84: 38 40 1E 06 70 02 52 00 1E 03 38 40 1E 06 80 02
1C94: 50 00 1E 03 38 40 1E 06 80 02 4C 00 1E 04 40 40
1CA4: 1E 07 90 02 4C 00 1E 04 40 40 1E 07 90 02 48 00
1CB4: 1E 04 48 38 1E 07 A0 02 48 00 1E 04 48 38 1E 07
1CC4: B0 02 48 00 1E 04 48 38 1E 07 C0 02 48 00 1E 04
1CD4: 48 38 1E 07 D0 02 48 00 1E 04 50 38 1E 07 E0 02
1CE4: 48 00 1E 04 58 30 19 07 F0 02 48 00 19 01 20 50
1CF4: 32 03 50 01 50 08 1E 01 20 50 28 04 50 01 50 08
1D04: 1E 01 20 50 1E 04 60 01 50 0C 1E 01 28 50 1E 04
1D14: 60 02 50 0C 1E 01 28 48 1E 05 70 02 48 10 1E 01
1D24: 28 48 1E 05 80 02 48 10 1E 01 30 48 1E 05 90 03
1D34: 48 14 1E 01 30 48 1E 06 A0 03 48 14 1E 02 30 40
1D44: 1E 06 B0 03 40 18 1E 02 38 40 1E 06 C0 03 40 18
1D54: 1E 02 38 40 1E 06 D0 03 40 18 1E 02 38 40 1E 06
1D64: D0 03 40 18 1E 02 40 38 1E 06 E0 03 38 18 1E 02
1D74: 48 38 1E 06 E0 03 38 18 1E 02 50 38 1E 06 F0 03
1D84: 38 18 1E 03 58 30 19 07 F0 03 30 18 19 01 20 50
1D94: 1E 04 60 01 50 00 1E 01 20 50 1E 04 70 01 50 00
1DA4: 1E 01 28 50 1E 04 80 01 50 00 1E 01 28 50 1E 05
1DB4: 90 02 50 00 1E 01 30 48 1E 05 A0 02 48 00 1E 01
1DC4: 30 48 1E 05 B0 02 48 00 1E 01 38 48 1E 05 C0 03
1DD4: 48 00 1E 01 38 48 1E 06 D0 03 48 00 1E 01 40 40
1DE4: 1E 06 E0 03 40 00 1E 01 40 40 1E 06 F0 03 40 00
1DF4: 1E 01 48 40 1E 06 F0 03 40 00 1E 01 48 40 1E 06
1E04: F0 03 40 00 1E 01 50 38 1E 06 F0 03 38 00 1E 01
1E14: 50 38 1E 06 F0 03 38 00 1E 01 58 38 1E 06 F0 03
1E24: 38 00 1E 01 58 30 19 06 F0 03 30 00 19 01 20 50
1E34: 5A 03 00 01 58 3C 64 01 20 50 5A 03 10 01 54 46
1E44: 5A 01 28 50 50 04 20 01 52 50 50 01 28 50 46 04
1E54: 30 02 50 5A 46 01 30 48 46 04 40 02 4E 64 46 01
1E64: 30 48 3C 05 50 02 4B 6E 3C 01 38 48 3C 05 60 03
1E74: 48 78 3C 01 38 40 32 05 70 03 46 82 3C 01 40 40
1E84: 32 05 80 03 44 8C 32 01 40 40 28 05 90 03 44 96
1E94: 32 01 48 40 28 05 A0 03 42 A0 32 01 48 3C 1E 05
1EA4: B0 03 42 AA 28 01 50 3C 1E 05 C0 03 40 B4 28 01
1EB4: 50 3C 1E 05 D0 03 3C BE 28 01 58 38 1E 05 E0 03
1EC4: 38 C8 1E 01 58 30 19 05 F0 03 34 D2 19
; hand back the control word of whichever cabinet panel currently faces
; the picture
readPlayerControls:
1ED1: 3A 87 A9 LD A,(workRam+187) ; read the screen-orientation flag
1ED4: A7 AND A
1ED5: 21 AF A9 LD HL,$A9AF ; point at the first panel's control mirror
1ED8: 20 03 JR NZ,loc_1edd ; unflipped: use that panel
1EDA: 21 B0 A9 LD HL,$A9B0 ; flipped: use the second panel's mirror
loc_1edd:
1EDD: 7E LD A,(HL) ; hand back the chosen panel's control word
1EDE: C9 RET
; seat the player record (ix=0xa800) and its paired sprite entry
; (iy=0xaa10), then branch on the player-state byte 0xa800: return while
; it is 0, run the tile-animation step (0x2010) while it is any other
; non-0xff value, and once it is 0xff either fly the attract demo pilot
; (0x214b when PLAY_ACTIVE 0xad30 is 0), turn the ship toward the read
; control stick (0x1f01 when the low control nibble is nonzero), or just
; scroll the world (0x1f42) when the stick is centred
dispatchPlayerFrameByState:
1EDF: DD 21 00 A8 LD IX,$A800 ; point at the player record
1EE3: FD 21 10 AA LD IY,$AA10 ; and its paired sprite entry
1EE7: 3A 00 A8 LD A,(workRam) ; read the player state
1EEA: A7 AND A
1EEB: C8 RET Z ; state clear: nothing to do this frame
1EEC: 3C INC A
1EED: C2 10 20 JP NZ,advancePlayerAnimationStrip; mid-count: run the multi-frame animation
1EF0: 3A 30 AD LD A,(workRam+530) ; read the play-active flag
1EF3: A7 AND A
1EF4: CA 4B 21 JP Z,flyDemoShipByScript; not in play: fly the attract-demo pilot
1EF7: CD D1 1E CALL readPlayerControls ; read the panel controls
1EFA: E6 0F AND $0F ; keep the stick nibble
1EFC: 20 03 JR NZ,turnShipTowardTargetHeading; stick pushed: turn the ship toward it
1EFE: C3 42 1F JP scrollWorldAtTheEraPace; stick centred: just scroll the world
; steer the ship one notch toward the wanted heading a table selects
; (leave it when already there, snap on when within one notch, else step
; the short way round the compass by three notches — four once the era's
; low digit reaches three), then fall into the shared world-scroll tail
turnShipTowardTargetHeading:
1F01: 21 2E 1F LD HL,$1F2E ; point at the wanted-heading table
1F04: CF RST $08 ; look up the heading the stick asks for
1F05: 47 LD B,A ; hold that wanted heading
1F06: 3A 02 A8 LD A,(workRam+2) ; take the ship's current heading
1F09: 90 SUB B ; subtract it -- how far from the target
1F0A: CA 42 1F JP Z,scrollWorldAtTheEraPace; already there: just scroll the world
1F0D: 4F LD C,A ; hold the difference
1F0E: 3A 04 AD LD A,(workRam+504) ; read the era number
1F11: E6 0F AND $0F
1F13: FE 03 CP $03 ; from the third era on, turns step faster
1F15: 30 04 JR NC,loc_1f1b ;
1F17: 16 03 LD D,$03 ; three notches a turn
1F19: 18 02 JR loc_1f1d ;
loc_1f1b:
1F1B: 16 04 LD D,$04 ; four notches a turn
loc_1f1d:
1F1D: 79 LD A,C
1F1E: C6 01 ADD A,$01
1F20: FE 03 CP $03
1F22: DA 3E 1F JP C,snapHeadingOntoTheTurnTarget; within one notch: snap straight onto the target
1F25: 79 LD A,C
1F26: FE 80 CP $80 ; is the target the short way ahead?
1F28: D2 6F 1F JP NC,loc_1f6f ; ahead: step the heading up a notch
1F2B: C3 68 1F JP loc_1f68 ; behind: step the heading down a notch
loc_1f2e:
1F2E: 00 NOP
1F2F: 00 NOP
1F30: 80 ADD A,B
1F31: 00 NOP
1F32: C0 RET NZ
1F33: E0 RET PO
1F34: A0 AND B
1F35: 00 NOP
1F36: 40 LD B,B
1F37: 20 60 JR NZ,loc_1f99 ;
1F39: 00 NOP
1F3A: 00 NOP
1F3B: 00 NOP
1F3C: 00 NOP
1F3D: 00 NOP
; end a turn by writing the heading the turn was steering toward straight
; into the player's heading cell, then fall into the world scroll every
; arm of the turn reaches; the target arrives in a register and nothing is
; read, so the whole of the entry is that one store
snapHeadingOntoTheTurnTarget:
1F3E: 78 LD A,B ; take the heading the turn was steering toward
1F3F: 32 02 A8 LD (workRam+2),A ; write it straight onto the ship's heading
; move the world past the ship at the pace the era sets, READING the
; heading rather than deciding it -- some paths in write it first, others
; arrive with whatever is already there: one of three fixed sample tables
; is picked from ERA_INDEX alone -- the opening era its own, the next two
; sharing a second, everything from the third era up sharing a third --
; and the pair that table gives for the ship's heading is handed on to be
; negated into the world scroll cells. Choosing the table is the whole of
; what this entry decides
scrollWorldAtTheEraPace:
1F42: 21 55 1F LD HL,$1F55 ; the negate-into-scroll tail
1F45: E5 PUSH HL ; push it as the return address
1F46: 3A 04 AD LD A,(workRam+504) ; read the era number
1F49: A7 AND A
1F4A: CA 4E 59 JP Z,loc_594e ; opening era: use its own velocity table
1F4D: FE 03 CP $03
1F4F: DA 65 59 JP C,loc_5965 ; the next two eras share a second table
1F52: C3 6B 59 JP loc_596b ; the third era on shares the last table
; ---- $1F55-$1F67: data ----
1F55: AF 67 6F ED 52 22 08 A8 AF 67 6F ED 42 22 0A A8
1F65: C3 AF 20
loc_1f68:
1F68: 92 SUB D
1F69: 80 ADD A,B
1F6A: 32 02 A8 LD (workRam+2),A ; store the heading stepped one notch down
1F6D: 18 D3 JR scrollWorldAtTheEraPace; re-run the world scroll
loc_1f6f:
1F6F: 82 ADD A,D
1F70: 80 ADD A,B
1F71: 32 02 A8 LD (workRam+2),A ; store the heading stepped one notch up
1F74: 18 CC JR scrollWorldAtTheEraPace; re-run the world scroll
; ---- $1F76-$1F98: data ----
1F76: F1 F1 F1 F1 F1 F1 F1 DD F1 F1 F1 F1 F0 F1 F1 F1
1F86: F1 C3 F1 F1 F1 F1 EA F1 F1 F1 F1 F1 F1 F1 F1 F1
1F96: B7 F1 F1
loc_1f99:
1F99: F1 POP AF
1F9A: F1 POP AF
1F9B: 4D LD C,L
1F9C: F1 POP AF
1F9D: F1 POP AF
1F9E: F1 POP AF
1F9F: E5 PUSH HL
1FA0: 2D DEC L
1FA1: 6E LD L,(HL)
1FA2: F1 POP AF
1FA3: F1 POP AF
1FA4: 5E LD E,(HL)
1FA5: 61 LD H,C
1FA6: E6 F1 AND $F1
1FA8: F1 POP AF
1FA9: F1 POP AF
1FAA: B2 OR D
1FAB: F1 POP AF
; ---- $1FAC-$200B: data ----
1FAC: F1 F1 F1 53 F1 F1 F1 F1 95 F1 F1 F1 45 CA F1 F1
1FBC: F1 C6 2C 97 F1 F1 81 69 1E F1 F1 BC A1 60 F1 F1
1FCC: F4 EB F1 F1 F1 F1 48 F1 F1 F1 E0 63 35 F1 F1 AA
1FDC: B4 8A F1 F1 51 E9 F6 F1 F1 82 92 98 F1 F1 F1 46
1FEC: F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1
1FFC: F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1 F1
; put the byte the caller has been carrying where a result is read from,
; so the verdict of an image check can be taken; on the way it walks an
; address forward twice, by a wide step and then by that same byte, and
; the address it lands on is never dereferenced by anything downstream. It
; reads and writes no memory, so the walk is arithmetic and not a fetch
presentChecksumForTamperTest:
200C: 19 ADD HL,DE ; walk the address on by a wide step
200D: DF RST $18 ; walk it on again by the byte in hand
200E: 78 LD A,B ; put the carried count where the tamper check reads its verdict
200F: C9 RET
; advance a phase-byte-driven tile animation: on the first frame
; (phase>=0xb4) clamp the phase, flag the paired entry, and cue sounds
; (56d2 always, 5679 past level 2) unless two game-state cells divert to
; $1F2E; else step the phase down and, on one of seven keyframe values,
; blit a 5x6 shape strip into video+colour RAM
advancePlayerAnimationStrip:
2010: DD 7E 00 LD A,(IX+$00) ; read the animation phase from the record
2013: FE B4 CP $B4 ; at or past the opening frame?
2015: 38 29 JR C,loc_2040 ; mid-animation: step the phase down
2017: DD 36 00 B4 LD (IX+$00),$B4 ; clamp the phase to the opening frame
201B: FD 36 01 FF LD (IY+$01),$FF ; flag the paired sprite entry
201F: 3A 04 AD LD A,(workRam+504) ; read the era number
2022: FE 02 CP $02
2024: D4 79 56 CALL NC,requestLateEraProgressSound; past the second era: cue the extra progress sound
2027: CD D2 56 CALL requestRoundIntroSoundBurst; cue the round-intro sound burst
202A: 3A FE AB LD A,(workRam+3FE) ; read a game-state cell
202D: FE A5 CP $A5 ; is it the running value?
202F: C2 63 20 JP NZ,loc_2063 ; no: divert to the shared exit
2032: 11 FF AB LD DE,$ABFF
2035: 1A LD A,(DE) ; read the next state cell
2036: FE 05 CP $05
2038: CA 40 20 JP Z,loc_2040 ; one accepted value: go straight to the draw
203B: FE 10 CP $10
203D: C2 63 20 JP NZ,loc_2063 ; anything but the two accepted values: divert
loc_2040:
2040: DD 35 00 DEC (IX+$00) ; step the phase down one
2043: DD 7E 00 LD A,(IX+$00) ; read the stepped phase
2046: FE B3 CP $B3 ; a keyframe value? -- pick its shape strip
2048: 28 1C JR Z,loc_2066 ;
204A: FE AB CP $AB ; another keyframe value
204C: 28 1D JR Z,loc_206b ;
204E: FE A3 CP $A3 ; another keyframe value
2050: 28 1E JR Z,loc_2070 ;
2052: FE 9B CP $9B ; another keyframe value
2054: 28 1F JR Z,loc_2075 ;
2056: FE 93 CP $93 ; another keyframe value
2058: 28 20 JR Z,loc_207a ;
205A: FE 8B CP $8B ; another keyframe value
205C: 28 21 JR Z,loc_207f ;
205E: FE 83 CP $83 ; the last keyframe value
2060: 28 22 JR Z,loc_2084 ;
2062: C9 RET ; none matched -- between keyframes, draw nothing
loc_2063:
2063: C3 2E 1F JP loc_1f2e ; divert to the shared exit
loc_2066:
2066: 11 76 1F LD DE,$1F76 ; select this keyframe's shape strip
2069: 18 1E JR loc_2089 ;
loc_206b:
206B: 11 94 1F LD DE,$1F94 ; select this keyframe's shape strip
206E: 18 19 JR loc_2089 ;
loc_2070:
2070: 11 B2 1F LD DE,$1FB2 ; select this keyframe's shape strip
2073: 18 14 JR loc_2089 ;
loc_2075:
2075: 11 D0 1F LD DE,$1FD0 ; select this keyframe's shape strip
2078: 18 0F JR loc_2089 ;
loc_207a:
207A: 11 D0 1F LD DE,$1FD0 ; select this keyframe's shape strip
207D: 18 0A JR loc_2089 ;
loc_207f:
207F: 11 B2 1F LD DE,$1FB2 ; select this keyframe's shape strip
2082: 18 05 JR loc_2089 ;
loc_2084:
2084: 11 EE 1F LD DE,$1FEE ; select this keyframe's shape strip
2087: 18 00 JR loc_2089 ;
loc_2089:
2089: 21 AF A5 LD HL,$A5AF ; the strip's first cell in video memory
208C: 06 C1 LD B,$C1 ; the colour-attribute bias
208E: 3A 04 AD LD A,(workRam+504) ;
2091: 80 ADD A,B ; the era offsets the strip's colour
2092: 4F LD C,A
2093: D9 EXX
2094: 3A 7A 33 LD A,(rom+337A) ; the strip's row count
2097: 47 LD B,A
loc_2098:
2098: D9 EXX
2099: 3A 02 49 LD A,(rom+4902) ; the tiles-per-row count
209C: 47 LD B,A
loc_209d:
209D: 1A LD A,(DE) ; read a tile from the strip
209E: 77 LD (HL),A ; write it into video memory
209F: CB 94 RES 2,H ; aim at the colour plane
20A1: 71 LD (HL),C ; lay its colour attribute alongside in colour memory
20A2: CB D4 SET 2,H
20A4: 23 INC HL
20A5: 13 INC DE
20A6: 10 F5 DJNZ loc_209d ; across the row's tiles
20A8: 3E 1B LD A,$1B ; the step from a row's end to the next row's start
20AA: DF RST $18 ; carry the cursor on to the next row
20AB: D9 EXX
20AC: 10 EA DJNZ loc_2098 ; down all the rows
20AE: C9 RET
; dress the player's own sprite entry to face the way the ship is heading:
; round the heading byte to the nearest of thirty-two equal sectors and
; write the shape and the byte beside it straight into the entry, from two
; parallel thirty-two-entry tables in the program image. The entry and
; both tables are fixed here, so nothing about which object this is comes
; from the caller
dressPlayerSpriteForHeading:
20AF: DD 21 00 A8 LD IX,$A800
20B3: 11 20 00 LD DE,$0020 ; the stride to the paired attribute table
20B6: 3A 02 A8 LD A,(workRam+2) ; take the ship's heading
20B9: C6 04 ADD A,$04 ; round it to the nearest of thirty-two sectors
20BB: 0F RRCA
20BC: 0F RRCA
20BD: 0F RRCA
20BE: E6 1F AND $1F ; keep the five-bit sector number
20C0: 21 CE 20 LD HL,$20CE ; point at the shape-by-sector table
20C3: DF RST $18 ; index it by the sector
20C4: 7E LD A,(HL)
20C5: 32 11 AA LD (workRam+211),A ; write the sprite shape for this heading
20C8: 19 ADD HL,DE ; step to the paired attribute table
20C9: 7E LD A,(HL)
20CA: 32 40 AA LD (workRam+240),A ; write the attribute byte beside it
20CD: C9 RET
; ---- $20CE-$210D: data ----
20CE: F0 F1 F2 F3 F4 F5 F6 F7 E8 F7 F6 F5 F4 F3 F2 F1
20DE: F0 EF EE ED EC EB EA E9 E8 E9 EA EB EC ED EE EF
20EE: 40 40 40 40 40 40 40 40 80 C0 C0 C0 C0 C0 C0 C0
20FE: C0 C0 C0 C0 C0 C0 C0 C0 40 40 40 40 40 40 40 40
; seeds the attract-demo autopilot: picks a heading-command script by the
; demo selector (0xad14), writes its dwell counter to 0xadf2 and little-
; endian pointer to 0xadf3/4, then on a failed tile-image tamper readback
; (0xadfb/0xadfc) tail-jumps into the trap
seedDemoAutopilotScript:
210E: 21 F3 AD LD HL,$ADF3 ; point at the script-pointer cell
2111: EB EX DE,HL
2112: 3A 14 AD LD A,(workRam+514) ; read the demo selector
2115: A7 AND A
2116: 28 28 JR Z,loc_2140 ; selector 0: take the first script
2118: FE 03 CP $03
211A: 28 24 JR Z,loc_2140 ; selector 3: take the first script too
211C: FE 01 CP $01
211E: 28 25 JR Z,loc_2145 ; selector 1: take the second script
2120: 21 FA 22 LD HL,$22FA ; otherwise take the third script
loc_2123:
2123: 7E LD A,(HL) ; read the script's leading byte
2124: 3C INC A
2125: 32 F2 AD LD (workRam+5F2),A ; seat the dwell counter to one past it
2128: EB EX DE,HL
2129: 73 LD (HL),E ; store the script pointer -- low byte
212A: 2C INC L
212B: 72 LD (HL),D ; -- then high byte
212C: 21 FB AD LD HL,$ADFB ; point at the glyph tamper-readback cell
212F: 7E LD A,(HL)
2130: FE FD CP $FD ; does the glyph readback look tampered?
2132: C2 3D 21 JP NZ,loc_213d ; yes: drop into the trap
2135: 23 INC HL
2136: 7E LD A,(HL)
2137: FE 10 CP $10 ; an untampered colour readback returns
2139: C8 RET Z
213A: FE 05 CP $05 ; the other accepted colour returns
213C: C8 RET Z
loc_213d:
213D: C3 51 22 JP loc_2251 ; otherwise drop into the trap
loc_2140:
2140: 21 8C 21 LD HL,$218C ; the first script's address
2143: 18 DE JR loc_2123 ;
loc_2145:
2145: 21 51 22 LD HL,$2251 ; the second script's address
2148: 18 D9 JR loc_2123 ;
; ---- $214A-$214A: data ----
214A: C9
; attract demo auto-pilot step: ticks the packed dwell/turn countdown at
; 0xadf2, steps the heading-command script at 0xadf3/4 when the dwell
; expires, turns PLAYER_HEADING (0xa802) by the 2-bit command, then tail-
; jumps to the mover at 0x1f42
flyDemoShipByScript:
214B: 21 F2 AD LD HL,$ADF2 ; point at the demo pilot's countdown byte -- low six bits are a dwell, the top two a turn command
214E: 7E LD A,(HL) ; read it
214F: 47 LD B,A ; keep the whole byte so the turn command in the top bits survives the mask
2150: E6 3F AND $3F ; isolate the dwell in the low six bits
2152: 28 07 JR Z,loc_215b ; dwell spent -- step to the next script entry
2154: 3D DEC A
2155: 28 04 JR Z,loc_215b ; a single remaining tick counts as spent too -- step the script
2157: 05 DEC B ; tick the dwell down one frame
2158: 70 LD (HL),B ; store the counted-down command byte back
2159: 18 0F JR loc_216a ; act on this frame's turn command
loc_215b:
215B: 23 INC HL ; step to the script pointer that follows the countdown byte
215C: 5E LD E,(HL) ; read the script pointer low
215D: 23 INC HL
215E: 56 LD D,(HL) ; read the script pointer high
215F: 13 INC DE ; advance the pointer one entry along the script
2160: 72 LD (HL),D ; store the pointer high back
2161: 2B DEC HL
2162: 73 LD (HL),E ; store the pointer low back
2163: EB EX DE,HL
2164: 7E LD A,(HL) ; read the next script entry
2165: 1B DEC DE
2166: 3C INC A ; bias it up by one so a fully-spent entry loops around again
2167: 12 LD (DE),A ; write it into the countdown byte
2168: 18 E1 JR flyDemoShipByScript ; re-examine the countdown
loc_216a:
216A: 78 LD A,B ; take the command byte
216B: D9 EXX ; switch to the alternate register bank the world-scroll mover runs on
216C: 07 RLCA
216D: 07 RLCA ; rotate the two turn-command bits down to the bottom
216E: E6 03 AND $03 ; isolate the turn command
2170: CA 42 1F JP Z,scrollWorldAtTheEraPace; no turn this frame -- hand straight to the world-scroll mover
2173: 3D DEC A
2174: 28 0B JR Z,loc_2181 ; turn command one steers one way -- branch off to turn the heading down
2176: 3A 02 A8 LD A,(workRam+2) ; otherwise steer the other way: read the ship heading
2179: C6 03 ADD A,$03 ; turn it by three
217B: 32 02 A8 LD (workRam+2),A ; store the new heading
217E: C3 42 1F JP scrollWorldAtTheEraPace; hand to the world-scroll mover
loc_2181:
2181: 3A 02 A8 LD A,(workRam+2) ; read the ship heading
2184: D6 03 SUB $03 ; turn it three the other way
2186: 32 02 A8 LD (workRam+2),A ; store the new heading
2189: C3 42 1F JP scrollWorldAtTheEraPace; hand to the world-scroll mover
; ---- $218C-$2249: data ----
218C: 3C 3C 3C 3C 0B 95 03 66 95 7C 59 8D 4B 8E 4A 02
219C: 8B 1A 55 0E 8A 7C 4E 05 8A 0B 86 46 03 4A 0D 7C
21AC: 5A 36 AB 08 55 08 56 01 4A 05 56 03 7C 4D BC 83
21BC: 0A 4B 07 BC 81 72 02 56 02 6A 01 95 3B 88 53 03
21CC: BC 95 46 0B 95 04 A0 0C 4A 02 56 03 55 01 95 03
21DC: 4A 04 8A 02 4A 02 8A 29 8B 06 4B 16 4A 01 95 0D
21EC: 88 53 01 6A 0F 8A 08 8B 0D 4B 08 8B 07 55 02 69
21FC: 89 03 4B 01 7C 6F 05 8B 4B 0D 8B 01 4E 83 01 8B
220C: 0F 55 05 A2 42 10 60 26 4B 02 8B 08 4B 05 8F 4F
221C: 01 95 17 4A 0E 8A 04 A0 1B 8B 11 4B 0A 52 97 4D
222C: 8F 47 06 8B 02 55 03 9D 67 8A 0A 56 05 8B 02 48
223C: 88 03 55 09 60 03 76 13 8B 24 4B 2F 8B 05
loc_224a:
224A: 8B ADC A,E
224B: 08 EX AF,AF'
224C: 8A ADC A,D
224D: 15 DEC D
224E: 96 SUB (HL)
224F: 3C INC A
2250: 3C INC A
loc_2251:
2251: 3C INC A
2252: 3C INC A
2253: 3C INC A
2254: 3C INC A
2255: 0A LD A,(BC)
2256: 95 SUB L
2257: 60 LD H,B
2258: 04 INC B
2259: 9E SBC A,(HL)
225A: 53 LD D,E
225B: 0D DEC C
225C: 8B ADC A,E
225D: 02 LD (BC),A
225E: 4B LD C,E
225F: 0F RRCA
2260: 93 SUB E
2261: 53 LD D,E
2262: 07 RLCA
2263: A9 XOR C
2264: 54 LD D,H
2265: 0A LD A,(BC)
2266: 96 SUB (HL)
2267: 03 INC BC
2268: 60 LD H,B
2269: 0F RRCA
226A: 8A ADC A,D
226B: 23 INC HL
226C: 48 LD C,B
226D: B9 CP C
226E: 02 LD (BC),A
226F: 82 ADD A,D
2270: 59 LD E,C
2271: 9F SBC A,A
2272: 59 LD E,C
2273: 01 8B 22 LD BC,$228B
2276: AB XOR E
2277: 02 LD (BC),A
2278: 4B LD C,E
2279: 02 LD (BC),A
227A: 8B ADC A,E
227B: 07 RLCA
227C: 55 LD D,L
227D: AC XOR H
227E: 42 LD B,D
227F: 01 50 90 LD BC,$9050
2282: 02 LD (BC),A
2283: 55 LD D,L
2284: 35 DEC (HL)
2285: 90 SUB B
2286: 50 LD D,B
2287: 04 INC B
2288: 92 SUB D
2289: 5B LD E,E
228A: 89 ADC A,C
228B: 1F RRA
228C: 48 LD C,B
228D: 88 ADC A,B
228E: 05 DEC B
228F: 8C ADC A,H
2290: 42 LD B,D
2291: 05 DEC B
2292: 4A LD C,D
2293: 3C INC A
2294: 0C INC C
2295: 46 LD B,(HL)
2296: 86 ADD A,(HL)
2297: 3C INC A
2298: 04 INC B
2299: 93 SUB E
229A: 5E LD E,(HL)
229B: 06 4B LD B,$4B
229D: 09 ADD HL,BC
229E: 4A LD C,D
229F: 0A LD A,(BC)
22A0: 7C LD A,H
22A1: 7C LD A,H
22A2: 6F LD L,A
22A3: BC CP H
22A4: 01 8B 07 LD BC,$078B
22A7: 92 SUB D
22A8: 48 LD C,B
22A9: 07 RLCA
22AA: 88 ADC A,B
22AB: 7C LD A,H
22AC: 7C LD A,H
22AD: 45 LD B,L
22AE: 11 90 50 LD DE,$5090
22B1: 01 8B 07 LD BC,$078B
22B4: 4B LD C,E
22B5: 0C INC C
22B6: 8B ADC A,E
22B7: 0A LD A,(BC)
22B8: 76 HALT
22B9: AB XOR E
22BA: 12 LD (DE),A
22BB: 87 ADD A,A
22BC: 47 LD B,A
22BD: 18 8B JR loc_224a ;
; ---- $22BF-$23E2: data ----
22BF: 03 8A 02 96 08 4B 02 8B 07 95 3C 3C 17 55 3C 05
22CF: 56 20 7C 44 06 67 BC 4D 8E 0C 56 02 4A 1A 4B 39
22DF: 55 25 56 20 55 0B 4B 03 60 06 4A 03 41 01 BC 9F
22EF: 50 04 96 0F 4B 07 8B 3C 3C 3C 3C 3C 3C 3C 3C 02
22FF: 90 45 02 4B 02 48 88 07 8A 55 01 4A 01 58 82 03
230F: 8A 5F 01 60 07 B2 52 03 46 86 1E 49 89 08 4B 01
231F: 94 49 05 8A 4A 3C 3C 0A BC 84 11 53 88 01 4A 0B
232F: 6B 06 4B 24 4A 11 56 08 4A 0E 4B 07 55 07 4B 07
233F: 7C 72 8E 01 AF 44 02 56 8B 04 5A 85 02 8A 02 90
234F: 45 09 8B 01 48 89 41 02 4B 05 B5 10 4D 83 03 B5
235F: 4B 03 A0 07 72 88 08 4B 01 50 85 03 8B 02 55 05
236F: 95 06 60 06 55 01 4B 09 48 8F 47 03 4B 01 96 07
237F: 8A 05 6A 18 4B 0A 8B 06 8A 02 44 84 06 8B 08 8B
238F: 14 BC 84 03 59 83 02 8B 03 60 08 8B 05 7C 5A 01
239F: B6 0A 48 95 4D 01 8A 09 51 BC 85 65 2D 6B 01 95
23AF: 4D 83 02 8A 4A 01 8B 02 72 85 53 01 95 02 8B 06
23BF: 95 03 8B 01 8A 01 4A 07 95 01 6B 03 97 41 05 4B
23CF: 0B 48 88 05 60 3C 3C 3C 3C 73 A6 14 7E 29 F8 9B
23DF: 13 13 96 B9
; fire and sweep the player's shots: on a fire-button rising edge arm and
; seed one shot into a free slot of the six-slot shot bank at 0xaa80 aimed
; along PLAYER_HEADING; then advance every live shot by the world scroll,
; queue its character-cell tiles, and cull any that leaves the field or
; holds a stale head
fireAndSweepPlayerShots:
23E3: 3A 00 A8 LD A,(workRam) ; read the player-state marker
23E6: 3C INC A
23E7: C2 96 24 JP NZ,loc_2496 ; not alive -- skip firing and just sweep the live shots
23EA: 3A C6 AC LD A,(workRam+4C6) ; read the round-transition hold
23ED: A7 AND A
23EE: C2 96 24 JP NZ,loc_2496 ; mid-transition -- skip firing and just sweep
23F1: CD D1 1E CALL readPlayerControls ; read the player controls
23F4: 07 RLCA ; rotate the fire-button bit out into carry -- four turns bring it around
23F5: 07 RLCA
23F6: 07 RLCA
23F7: 07 RLCA
23F8: 21 8E A9 LD HL,$A98E ; point at the fire-button edge history
23FB: CB 16 RL (HL) ; shift the fire bit into the two-frame history
23FD: 7E LD A,(HL)
23FE: E6 03 AND $03 ; keep just the last two frames of it
2400: FE 01 CP $01 ; a value of one is a fresh press -- up last frame, down now
2402: 21 81 AA LD HL,$AA81 ; point at the pending-shot-burst count
2405: 20 02 JR NZ,loc_2409 ; no fresh press -- skip arming a burst
2407: 36 03 LD (HL),$03 ; fire pressed -- arm a burst of three shots
loc_2409:
2409: 3A 30 AD LD A,(workRam+530) ; read the play-active flag
240C: A7 AND A
240D: 28 05 JR Z,loc_2414 ; attract/idle -- spawn a shot regardless
240F: 7E LD A,(HL) ; in play: read the pending-burst count
2410: A7 AND A
2411: CA 96 24 JP Z,loc_2496 ; nothing pending -- just sweep the live shots
loc_2414:
2414: 23 INC HL ; step to the spawn cooldown
2415: 7E LD A,(HL)
2416: A7 AND A
2417: C2 96 24 JP NZ,loc_2496 ; still cooling down -- just sweep
241A: DD 21 80 AA LD IX,$AA80 ; point at the six-slot shot bank
241E: 06 06 LD B,$06 ; six slots to scan
loc_2420:
2420: DD 7E 00 LD A,(IX+$00) ; read this slot's occupancy
2423: A7 AND A
2424: 28 23 JR Z,loc_2449 ; free slot found -- seed a shot into it
2426: ED 5B 46 0D LD DE,(rom+D46) ; take the slot stride from the program image
242A: DD 19 ADD IX,DE ; step to the next slot
242C: 10 F2 DJNZ loc_2420 ; scan all six
242E: C3 96 24 JP loc_2496 ; none free -- just sweep
; ---- $2431-$2448: data ----
2431: 16 A7 13 96 ED DC F1 8C 68 3B 0D ED F1 96 13 13
2441: 13 13 F1 88 DC ED 11 B9
loc_2449:
2449: CD 7E 56 CALL requestPlayerShotSound; ask for the player-shot sound
244C: AF XOR A
244D: 67 LD H,A
244E: 6F LD L,A
244F: ED 4B 08 A8 LD BC,(workRam+8) ; read the world scroll along one axis
2453: ED 42 SBC HL,BC ; negate it
2455: 29 ADD HL,HL
2456: 29 ADD HL,HL ; scale it up four times
2457: DD 75 0A LD (IX+$0A),L ; seed the shot's sub-position from it
245A: DD 74 0B LD (IX+$0B),H
245D: AF XOR A
245E: 67 LD H,A
245F: 6F LD L,A
2460: ED 4B 0A A8 LD BC,(workRam+A) ; read the world scroll along the other axis
2464: ED 42 SBC HL,BC ; negate it
2466: 29 ADD HL,HL
2467: 29 ADD HL,HL ; scale it up four times
2468: DD 75 0C LD (IX+$0C),L ; seed the shot's other sub-position
246B: DD 74 0D LD (IX+$0D),H
246E: 3A 02 A8 LD A,(workRam+2) ; read the ship heading
2471: C6 04 ADD A,$04 ; round the heading
2473: 0F RRCA ; shift it down toward a direction index
2474: 0F RRCA
2475: 0F RRCA
2476: E6 1F AND $1F ; keep five bits -- one of thirty-two directions
2478: 21 71 27 LD HL,$2771 ; point at the shot-velocity table
247B: CD 8C 01 CALL fetchWideTableWord ; fetch the velocity word for this direction
247E: DD 35 00 DEC (IX+$00) ; mark the slot occupied
2481: DD 36 03 00 LD (IX+$03),$00
2485: DD 73 04 LD (IX+$04),E ; set the shot's speed along one axis
2488: DD 36 05 00 LD (IX+$05),$00
248C: DD 72 06 LD (IX+$06),D ; set the shot's speed along the other axis
248F: 21 81 AA LD HL,$AA81 ; point at the pending-burst count
2492: 35 DEC (HL) ; one shot fired -- count the burst down
2493: 23 INC HL
2494: 36 06 LD (HL),$06 ; reset the spawn cooldown to six frames
loc_2496:
2496: 3A 82 AA LD A,(workRam+282) ; read the spawn cooldown
2499: A7 AND A
249A: 28 04 JR Z,loc_24a0 ; already zero -- skip the tick
249C: 3D DEC A ; count the cooldown down one frame
249D: 32 82 AA LD (workRam+282),A ;
loc_24a0:
24A0: DD 21 80 AA LD IX,$AA80 ; point at the six-slot shot bank
24A4: 06 06 LD B,$06 ; six slots to sweep
loc_24a6:
24A6: D9 EXX
24A7: DD 7E 00 LD A,(IX+$00) ; read the slot's head byte
24AA: A7 AND A
24AB: 28 46 JR Z,loc_24f3 ; empty slot -- on to the next
24AD: 3C INC A
24AE: 20 4C JR NZ,loc_24fc ; head is stale, not the live marker -- cull this shot
24B0: DD 6E 0A LD L,(IX+$0A) ; read one coordinate of the shot
24B3: DD 66 0B LD H,(IX+$0B)
24B6: ED 5B 08 A8 LD DE,(workRam+8) ; add the world scroll to it
24BA: 19 ADD HL,DE
24BB: DD 56 04 LD D,(IX+$04) ; add the shot's stored offset
24BE: DD 5E 03 LD E,(IX+$03)
24C1: 19 ADD HL,DE
24C2: 7C LD A,H
24C3: C6 10 ADD A,$10 ; bias the high byte for the edge test
24C5: FE 10 CP $10
24C7: DA FC 24 JP C,loc_24fc ; off the field edge -- cull this shot
24CA: DD 74 04 LD (IX+$04),H ; store the advanced coordinate back
24CD: DD 75 03 LD (IX+$03),L
24D0: DD 6E 0C LD L,(IX+$0C) ; read the other coordinate of the shot
24D3: DD 66 0D LD H,(IX+$0D)
24D6: ED 5B 0A A8 LD DE,(workRam+A) ; add the world scroll to it
24DA: 19 ADD HL,DE
24DB: DD 56 06 LD D,(IX+$06) ; add the shot's stored offset
24DE: DD 5E 05 LD E,(IX+$05)
24E1: 19 ADD HL,DE
24E2: 7C LD A,H
24E3: C6 08 ADD A,$08 ; bias for the edge test
24E5: FE 18 CP $18
24E7: DA FC 24 JP C,loc_24fc ; off the field edge -- cull this shot
24EA: DD 74 06 LD (IX+$06),H ; store the advanced coordinate back
24ED: DD 75 05 LD (IX+$05),L
24F0: CD 37 53 CALL queueTileStampForObject; queue this shot's sprite tiles
loc_24f3:
24F3: 11 10 00 LD DE,$0010
24F6: DD 19 ADD IX,DE ; step to the next slot
24F8: D9 EXX
24F9: 10 AB DJNZ loc_24a6 ; sweep all six
24FB: C9 RET
loc_24fc:
24FC: AF XOR A
24FD: DD 77 00 LD (IX+$00),A ; clear the slot's head byte
2500: DD 77 04 LD (IX+$04),A ; clear its step along one axis
2503: DD 77 06 LD (IX+$06),A ; clear its step along the other axis
2506: C3 F3 24 JP loc_24f3 ; on to the next slot
; ---- $2509-$2510: data ----
2509: E0 A4 14 9B 10 0D 88 B9
; cold-boot init: paints a 64-byte work-RAM block all-ones, seeds RNG /
; loads default high scores / empties the deferred lists (watchdog-kicking
; after each), then tail-jumps into the settings + cold-start chain
initColdStartRamThenSeedConfig:
2511: 21 00 AC LD HL,$AC00 ; point at a 64-byte work-RAM block
2514: 06 40 LD B,$40 ; sixty-four bytes to paint
loc_2516:
2516: 36 FF LD (HL),$FF ; set this byte all-ones
2518: 23 INC HL
2519: 10 FB DJNZ loc_2516 ; over all sixty-four
251B: CD 67 4B CALL seedRandomRegister ; seed the random register
251E: 32 00 C2 LD (dsw1),A ; kick the watchdog
2521: CD A5 4B CALL loadDefaultHighScores; load the default high-score table
2524: 32 00 C2 LD (dsw1),A ; kick the watchdog
2527: CD 6A 52 CALL emptyBothDeferredCellLists; empty both deferred-cell lists
252A: 32 00 C2 LD (dsw1),A ; kick the watchdog
252D: C3 AA 52 JP seedGameConfigFromDipSwitches; on into the settings and cold-start chain
loc_2530:
2530: 19 ADD HL,DE
2531: 01 18 01 LD BC,$0118
2534: 17 RLA
2535: 01 16 01 LD BC,$0116
2538: 15 DEC D
2539: 01 14 01 LD BC,$0114
253C: 13 INC DE
253D: 01 10 01 LD BC,$0110
2540: 0E 01 LD C,$01
2542: 0C INC C
2543: 01 0A 01 LD BC,$010A
2546: 08 EX AF,AF'
2547: 01 04 01 LD BC,$0104
254A: 01 01 FF LD BC,$FF01
254D: 00 NOP
254E: FB EI
254F: 00 NOP
2550: F8 RET M
2551: 00 NOP
2552: F5 PUSH AF
2553: 00 NOP
2554: F2 00 EE JP P,$EE00
2557: 00 NOP
2558: EB EX DE,HL
2559: 00 NOP
255A: E8 RET PE
255B: 00 NOP
255C: E4 00 E1 CALL PO,$E100
255F: 00 NOP
2560: DE 00 SBC A,$00
2562: DA 00 D7 JP C,$D700
2565: 00 NOP
2566: D4 00 D1 CALL NC,$D100
2569: 00 NOP
256A: CD 00 CA CALL $CA00
256D: 00 NOP
256E: C7 RST $00
256F: 00 NOP
2570: C3 00 C0 JP scanline ;
; ---- $2573-$272F: data ----
2573: 00 BC 00 B8 00 B5 00 B1 00 AC 00 A8 00 A5 00 A0
2583: 00 9A 00 94 00 8F 00 87 00 84 00 7D 00 76 00 70
2593: 00 69 00 61 00 5B 00 53 00 4B 00 44 00 3B 00 33
25A3: 00 2C 00 23 00 1A 00 11 00 08 00 00 00 00 00 F8
25B3: FF EF FF 00 00 DD FF D4 FF CD FF C5 FF BC FF B5
25C3: FF AD FF A5 FF 9F FF 97 FF 90 FF 8A FF 83 FF 7C
25D3: FF 79 FF 7C FF 6C FF 66 FF 60 FF 5B FF 58 FF 54
25E3: FF 4F FF 4B FF 48 FF 44 FF 40 FF 3D FF 39 FF 36
25F3: FF 33 FF 33 FF 2C FF 29 FF 26 FF 22 FF 1F FF 1C
2603: FF 18 FF 15 FF 12 FF 0E FF 0B FF 08 FF 05 FF 01
2613: FF FF FE FC FE F8 FE F6 FE F4 FE F2 FE F0 FE ED
2623: FE EC FE EB FE EA FE E9 FE E8 FE E7 FE E7 FE E8
2633: FE E9 FE EA FE EB FE EC FE ED FE F0 FE F2 FE F4
2643: FE F6 FE F8 FE FC FE FF FE 01 FF 05 FF 08 FF 0B
2653: FF 0E FF 12 FF 15 FF 18 FF 1C FF 1F FF 22 FF 26
2663: FF 29 FF 2C FF 2F FF 33 FF 36 FF 39 FF 3D FF 40
2673: FF 44 FF 48 FF 4B FF 4F FF 54 FF 58 FF 5B FF 60
2683: FF 66 FF 6C FF 71 FF 79 FF 7C FF 83 FF 8A FF 90
2693: FF 97 FF 9F FF A5 FF AD FF B5 FF BC FF C5 FF CD
26A3: FF D4 FF DD FF E6 FF EF FF F8 FF 00 00 00 00 08
26B3: 00 11 00 1A 00 23 00 2C 00 33 00 3B 00 44 00 4B
26C3: 00 53 00 5B 00 61 00 69 00 70 00 76 00 7D 00 84
26D3: 00 87 00 87 00 94 00 9A 00 A0 00 A5 00 A8 00 AC
26E3: 00 B1 00 B5 00 B8 00 BC 00 C0 00 C3 00 C7 00 CA
26F3: 00 CD 00 CA 00 D4 00 D7 00 DA 00 DE 00 E1 00 E4
2703: 00 E8 00 EB 00 EE 00 F2 00 F5 00 F8 00 FB 00 FF
2713: 00 01 01 FB 00 08 01 0A 01 0C 01 0E 01 10 01 13
2723: 01 14 01 15 01 16 01 17 01 18 01 19 01
loc_2730:
2730: 3A 6F AA LD A,(workRam+26F) ;
2733: FE 76 CP $76
2735: C2 30 25 JP NZ,loc_2530 ;
2738: CD 2B 0B CALL hideCaptionSprites ;
273B: CD 0E 21 CALL seedDemoAutopilotScript;
273E: AF XOR A
273F: 32 31 AD LD (workRam+531),A ;
2742: 32 20 AD LD (workRam+520),A ;
2745: 32 30 AD LD (workRam+530),A ;
2748: 32 AC A9 LD (workRam+1AC),A ;
274B: 3C INC A
274C: 32 10 AD LD (workRam+510),A ;
274F: 3E 03 LD A,$03
2751: 32 AB A9 LD (workRam+1AB),A ;
2754: C9 RET
; free all six of the player's shot slots, zeroing each record's occupancy
; byte and its second-axis coordinate but not its first; the fill byte and
; the record stride are both fetched from program space rather than
; written as immediates
freeAllShotSlots:
2755: DD 21 80 AA LD IX,$AA80 ; point at the first of the six shot slots
2759: 21 6E 27 LD HL,$276E
275C: 3A 61 08 LD A,(rom+861) ; take the slot stride's low byte from the program image
275F: 5F LD E,A
2760: 3A 01 5C LD A,(rom+5C01) ; take the fill byte from the program image -- zero on this build
2763: 57 LD D,A ; the fill byte doubles as the stride's high byte
2764: 06 06 LD B,$06 ; six slots to clear
loc_2766:
2766: DD 77 00 LD (IX+$00),A ; clear the slot's occupancy byte
2769: DD 77 04 LD (IX+$04),A ; clear the slot's second-axis byte
276C: DD 19 ADD IX,DE ; step to the next slot
276E: 10 F6 DJNZ loc_2766 ; clear all six
2770: C9 RET
; ---- $2771-$27B0: data ----
2771: 7E 84 7E 85 7E 86 7D 87 7C 88 7B 89 7A 8A 79 8A
2781: 78 8A 77 8A 76 8A 75 89 74 88 73 87 72 86 72 85
2791: 72 84 72 83 72 82 73 81 74 80 75 7F 76 7E 77 7E
27A1: 78 7E 79 7E 7A 7E 7B 7F 7C 80 7D 81 7E 82 7E 83
; round-start sequence arm: seat two player-object records (0xAD0C-0xAD2E)
; and position seeds (0xAC64=0x78,0xAC65=0x84), request a sound and load
; the difficulty record, then split on PLAY_ACTIVE(0xAD30) -- mid-game it
; queues command de=0x0400 and folds a +1 XOR checksum of 256 program
; bytes at 0x1550 into control latch 0xC308 (0xA9EB=0x96); on a fresh
; round it cycles the 1..3 stage counter at 0xA9D0, reseeds the random
; register, clears 0xAA80-0xAADF and 0xA800-0xA97F, SUB-checksums 256
; bytes at 0x3310 into 0xA9AB (xor 0x90) and paints star field
; 0xAC74-0xAC83 with 0x80 (0xA9EB=0x5A); both arms tail-advance the
; sequence sub-step
armRoundStartThenStepSequence:
27B1: CD 34 58 CALL requestRoundStartSound; ask for the round-start jingle
27B4: 3E 78 LD A,$78
27B6: 32 64 AC LD (workRam+464),A ; seed the enemy aim anchor
27B9: 3E 84 LD A,$84
27BB: 32 65 AC LD (workRam+465),A ; seed the enemy aim point
27BE: 21 00 00 LD HL,$0000
27C1: 22 16 AD LD (workRam+516),HL ; clear player 1's life-tick counter
27C4: 22 26 AD LD (workRam+526),HL ; clear player 2's life-tick counter
27C7: 3A CD A9 LD A,(workRam+1CD) ; take the per-round kill quota
27CA: 32 12 AD LD (workRam+512),A ; set player 1's kills-remaining
27CD: 32 22 AD LD (workRam+522),A ; set player 2's kills-remaining
27D0: AF XOR A
27D1: 32 14 AD LD (workRam+514),A ; clear player 1's era index
27D4: 32 24 AD LD (workRam+524),A ; clear player 2's era index
27D7: 32 32 AD LD (workRam+532),A ; clear a per-round cell
27DA: 32 13 AD LD (workRam+513),A ; clear player 1's bonus-life latch
27DD: 32 23 AD LD (workRam+523),A ; clear player 2's bonus-life latch
27E0: 32 1D AD LD (workRam+51D),A ; clear player 1's mother-ship-armed cell
27E3: 32 2D AD LD (workRam+52D),A ; clear player 2's mother-ship-armed cell
27E6: 32 0C AD LD (workRam+50C),A ; clear the pen colour
27E9: 3C INC A
27EA: 32 11 AD LD (workRam+511),A ; set player 1's round number to one
27ED: 32 21 AD LD (workRam+521),A ; set player 2's round number to one
27F0: 32 1E AD LD (workRam+51E),A ; arm player 1's round
27F3: 32 2E AD LD (workRam+52E),A ; arm player 2's round
27F6: 3A 30 AD LD A,(workRam+530) ; read the play-active flag
27F9: A7 AND A
27FA: 28 39 JR Z,loc_2835 ; fresh round -- take the full setup path
27FC: AF XOR A
27FD: 67 LD H,A
27FE: 6F LD L,A
27FF: 32 33 AD LD (workRam+533),A ; clear player 1's score low byte
2802: 22 34 AD LD (workRam+534),HL ; clear player 1's score mid and high bytes
2805: 32 36 AD LD (workRam+536),A ; clear player 2's score low byte
2808: 22 37 AD LD (workRam+537),HL ; clear player 2's score mid and high bytes
280B: 11 00 04 LD DE,$0400 ; load command word $0400
280E: FF RST $38 ; post it
280F: 3A C4 A9 LD A,(workRam+1C4) ; read the difficulty selector
2812: CD 7B 0F CALL loadDifficultyRecord; load the difficulty record in force
2815: 06 00 LD B,$00 ; two hundred fifty-six bytes to fold
2817: 21 50 15 LD HL,$1550 ; point at the checked program-image block
281A: 97 SUB A ; seed the checksum to zero
loc_281b:
281B: AE XOR (HL) ; fold each byte in with exclusive-or
281C: 23 INC HL
281D: 10 FC DJNZ loc_281b ; over all two hundred fifty-six
281F: C6 01 ADD A,$01 ; bias the sum by one
2821: 32 08 C3 LD ($C308),A ; drive the result into a control latch
2824: 3A D3 A9 LD A,(workRam+1D3) ; take the start rung
2827: 32 1A AD LD (workRam+51A),A ; set player 1's start rung
282A: 32 2A AD LD (workRam+52A),A ; set player 2's start rung
282D: 3E 96 LD A,$96
282F: 32 EB A9 LD (workRam+1EB),A ; mark the sequence mode
2832: C3 1A 0F JP advanceSequenceSubStep; step the attract/round sequence
loc_2835:
2835: 21 D0 A9 LD HL,$A9D0 ; point at the 1..3 stage counter
2838: 7E LD A,(HL) ; read it
2839: 3C INC A ; advance it
283A: FE 04 CP $04 ; past three?
283C: 38 02 JR C,loc_2840 ; no -- keep it
283E: 3E 01 LD A,$01 ; wrapped -- back to one
loc_2840:
2840: 77 LD (HL),A ; store the stage
2841: 32 14 AD LD (workRam+514),A ; set player 1's era index from the stage
2844: 3C INC A
2845: 32 11 AD LD (workRam+511),A ; set player 1's round number to stage plus one
2848: AF XOR A
2849: 32 80 A9 LD (workRam+180),A ; clear a game-state cell
284C: 32 CE A9 LD (workRam+1CE),A ; clear the frame counter
284F: 32 CF A9 LD (workRam+1CF),A ; clear the script step counter
2852: CD 67 4B CALL seedRandomRegister ; reseed the random register
2855: 21 80 AA LD HL,$AA80 ; point at the object bank
2858: 11 81 AA LD DE,$AA81
285B: 36 00 LD (HL),$00
285D: 01 5F 00 LD BC,$005F
2860: ED B0 LDIR ; clear it up through 0xAADF
2862: 21 00 A8 LD HL,$A800 ; point at the main work-RAM bank
2865: 11 01 A8 LD DE,$A801
2868: 36 00 LD (HL),$00
286A: 01 7F 01 LD BC,$017F
286D: ED B0 LDIR ; clear it up through 0xA97F
286F: 3E 02 LD A,$02 ; select difficulty record two
2871: CD 7B 0F CALL loadDifficultyRecord; load the difficulty record
2874: 3A D3 A9 LD A,(workRam+1D3) ; take the start rung
2877: 32 1A AD LD (workRam+51A),A ; set player 1's start rung
287A: 32 2A AD LD (workRam+52A),A ; set player 2's start rung
287D: 0E 00 LD C,$00 ; two hundred fifty-six bytes to fold
287F: 21 10 33 LD HL,$3310 ; point at the checked program-image block
2882: 3A AB A9 LD A,(workRam+1AB) ; seed the checksum
loc_2885:
2885: 96 SUB (HL) ; subtract each byte in turn
2886: 23 INC HL
2887: 0D DEC C
2888: 20 FB JR NZ,loc_2885 ; over all two hundred fifty-six
288A: EE 90 XOR $90 ; scramble the sum
288C: 32 AB A9 LD (workRam+1AB),A ; store the checksum
288F: 21 74 AC LD HL,$AC74 ; point at the star-field block
2892: 06 10 LD B,$10 ; sixteen cells to paint
loc_2894:
2894: 36 80 LD (HL),$80 ; paint a star cell
2896: 23 INC HL
2897: 10 FB DJNZ loc_2894 ; all sixteen
2899: 3E 5A LD A,$5A
289B: 32 EB A9 LD (workRam+1EB),A ; mark the sequence mode
289E: C3 1A 0F JP advanceSequenceSubStep; step the attract/round sequence
; work seven fixed object slots in one fixed order, each through the entry
; that seats its own pair of cursors; the order is the whole of what this
; entry decides, and nothing here reads or writes a slot itself. Seven is
; the SET's size and not the per-frame count: the last two slots stand
; down while MOTHER_SHIP_ARMED is set, so on that arm only FIVE slots
; step. A resume value is laid down for each slot that will reach an arm,
; and not for the two that stand down, which reach none
stepSevenCraftSlots:
28A1: CD B7 28 CALL seatCraftSlot0ThenDispatchByEra; service object slot 0
28A4: CD C2 28 CALL seatCraftSlot1ThenDispatchByEra; service object slot 1
28A7: CD CD 28 CALL seatCraftSlot2ThenDispatchByEra; service object slot 2
28AA: CD D8 28 CALL seatCraftSlot3ThenDispatchByEra; service object slot 3
28AD: CD E3 28 CALL seatCraftSlot4ThenDispatchByEra; service object slot 4
28B0: CD EE 28 CALL seatMotherShipSlotThenDispatchByEraUnlessArmed; service the mother-ship slot -- skipped while its cell is armed
28B3: CD FE 28 CALL seatCraftSlot6ThenDispatchByEraUnlessArmed; service object slot 6 -- skipped while the mother-ship cell is armed
28B6: C9 RET
; seat the record cursor and the sprite-entry cursor on one fixed object
; slot, then run the era-keyed dispatch over it; the pair of immediates is
; the whole of what distinguishes this entry from the four siblings that
; share its shape -- the two gated ones later in the chain differ by more
seatCraftSlot0ThenDispatchByEra:
28B7: DD 21 50 A8 LD IX,$A850 ; point at slot 0's object record
28BB: FD 21 1A AA LD IY,$AA1A ; point at slot 0's sprite entry
28BF: C3 0E 29 JP dispatchSeatedSlotByEraIndex; run the era-keyed handler over it
; seat the record cursor and the sprite-entry cursor on one fixed object
; slot, then run the era-keyed dispatch over it; the pair of immediates is
; the whole of what distinguishes this entry from the four siblings that
; share its shape -- the two gated ones later in the chain differ by more
seatCraftSlot1ThenDispatchByEra:
28C2: DD 21 60 A8 LD IX,$A860 ; point at slot 1's object record
28C6: FD 21 1C AA LD IY,$AA1C ; point at slot 1's sprite entry
28CA: C3 0E 29 JP dispatchSeatedSlotByEraIndex; run the era-keyed handler over it
; seat the record cursor and the sprite-entry cursor on one fixed object
; slot, then run the era-keyed dispatch over it; the pair of immediates is
; the whole of what distinguishes this entry from the four siblings that
; share its shape -- the two gated ones later in the chain differ by more
seatCraftSlot2ThenDispatchByEra:
28CD: DD 21 70 A8 LD IX,$A870 ; point at slot 2's object record
28D1: FD 21 1E AA LD IY,$AA1E ; point at slot 2's sprite entry
28D5: C3 0E 29 JP dispatchSeatedSlotByEraIndex; run the era-keyed handler over it
; seat the record cursor and the sprite-entry cursor on one fixed object
; slot, then run the era-keyed dispatch over it; the pair of immediates is
; the whole of what distinguishes this entry from the four siblings that
; share its shape -- the two gated ones later in the chain differ by more
seatCraftSlot3ThenDispatchByEra:
28D8: DD 21 80 A8 LD IX,$A880 ; point at slot 3's object record
28DC: FD 21 20 AA LD IY,$AA20 ; point at slot 3's sprite entry
28E0: C3 0E 29 JP dispatchSeatedSlotByEraIndex; run the era-keyed handler over it
; seat the record cursor and the sprite-entry cursor on one fixed object
; slot, then run the era-keyed dispatch over it, with no gate in front of
; it
seatCraftSlot4ThenDispatchByEra:
28E3: DD 21 90 A8 LD IX,$A890 ; point at slot 4's object record
28E7: FD 21 22 AA LD IY,$AA22 ; point at slot 4's sprite entry
28EB: C3 0E 29 JP dispatchSeatedSlotByEraIndex; run the era-keyed handler over it
; run the era-keyed dispatch over the mother ship's slot, but only while
; the armed cell is clear -- a set cell returns at once, leaving the slot
; unserviced for the frame
seatMotherShipSlotThenDispatchByEraUnlessArmed:
28EE: 3A 0D AD LD A,(workRam+50D) ; read the mother-ship armed cell
28F1: A7 AND A
28F2: C0 RET NZ ; armed -- leave this slot unserviced this frame
28F3: DD 21 A0 A8 LD IX,$A8A0 ; point at the mother-ship object record
28F7: FD 21 24 AA LD IY,$AA24 ; point at its sprite entry
28FB: C3 0E 29 JP dispatchSeatedSlotByEraIndex; run the era-keyed handler over it
; run the era-keyed dispatch over one fixed object slot, but only while
; the mother ship's armed cell is clear -- a set cell returns at once,
; leaving the slot unserviced for the frame
seatCraftSlot6ThenDispatchByEraUnlessArmed:
28FE: 3A 0D AD LD A,(workRam+50D) ; read the mother-ship armed cell
2901: A7 AND A
2902: C0 RET NZ ; armed -- leave this slot unserviced this frame
2903: DD 21 B0 A8 LD IX,$A8B0 ; point at slot 6's object record
2907: FD 21 26 AA LD IY,$AA26 ; point at its sprite entry
290B: C3 0E 29 JP dispatchSeatedSlotByEraIndex; run the era-keyed handler over it
; run the arm the LOW THREE BITS of the ERA INDEX select out of a word
; table laid down inline just behind this entry; the arm is entered as a
; transfer with no place parked for it to come back to, so it returns past
; this entry and nothing here runs after it
dispatchSeatedSlotByEraIndex:
290E: 3A 04 AD LD A,(workRam+504) ; read the active era index
2911: E6 07 AND $07 ; keep the low three bits
2913: F7 RST $30 ; jump through the era table just below into the matching handler
; ---- $2914-$291D: jump table ----
2914: 27 29 4C 29 84 29 B0 29 D5 29
; fold a run of image bytes into a total the caller has already seeded,
; walking a SECOND pointer alongside it in lockstep. The second walk adds
; nothing: each step overwrites the same byte-wide holder, so only the
; last byte it passes survives, and on a genuine image its leftover went
; unread by every RAM signature the pass sampled. A count of zero means a
; full 256 bytes, the total wraps at eight bits, and no memory is written
foldBlockIntoTotal:
291E: 86 ADD A,(HL) ; fold the next image byte into the running total
291F: EB EX DE,HL
2920: 4E LD C,(HL) ; read the byte the second pointer walks past
2921: EB EX DE,HL
2922: 23 INC HL ; step the total pointer
2923: 13 INC DE ; step the second pointer alongside it
2924: 10 F8 DJNZ foldBlockIntoTotal ; over the whole block -- a count of zero means a full 256
2926: C9 RET
; era-0 per-object update dispatched by index 0 of the rst-0x30 era table
; at 0x2914: on the object status byte at (ix+0) it leaves an empty slot
; (0), releases a held object (0xFE), steps a dying one (any other value),
; or steers/flies/refreshes an active craft (0xFF) and lets it spawn,
; retiring it the frame it reaches the line
serviceEra0EnemyCraftSlot:
2927: DD 7E 00 LD A,(IX+$00) ; read this slot's status byte
292A: A7 AND A
292B: C8 RET Z ; empty slot -- nothing to do
292C: 3C INC A
292D: 28 07 JR Z,loc_2936 ; a live craft -- fly it
292F: 3C INC A
2930: CA 52 2B JP Z,releaseHeldObject ; a held object -- release it
2933: C3 93 2B JP stepDyingObjectState; otherwise step the dying object
loc_2936:
2936: CD EF 2B CALL steerTowardAimHeading; steer toward the aim heading
2939: CD 40 58 CALL flyAtSlowestSpeed ; fly at the slowest speed
293C: CD 83 2B CALL hasReachedRetireLine; reached the retire line?
293F: DA DE 2B JP C,retireSlotAndSubPixel; yes -- retire the slot
2942: CD D6 3E CALL launchBankEnemyWhenAimedNearPlayer; try a bank-enemy launch when aimed near the player
2945: CD 3C 2A CALL refreshSpriteFromHeading; refresh the sprite from the heading
2948: CD 43 42 CALL launchAttackerIntoFreeSlot; try to launch an attacker into a free slot
294B: C9 RET
loc_294c:
294C: DD 7E 00 LD A,(IX+$00) ; read this slot's status byte
294F: A7 AND A
2950: C8 RET Z ; empty slot -- nothing to do
2951: 3C INC A
2952: 28 07 JR Z,loc_295b ; a live craft -- fly it
2954: 3C INC A
2955: CA 52 2B JP Z,releaseHeldObject ; a held object -- release it
2958: C3 93 2B JP stepDyingObjectState; otherwise step the dying object
loc_295b:
295B: CD EF 2B CALL steerTowardAimHeading; steer toward the aim heading
295E: CD 54 58 CALL loc_5854 ; fly at this era's speed
2961: CD 83 2B CALL hasReachedRetireLine; reached the retire line?
2964: DA DE 2B JP C,retireSlotAndSubPixel; yes -- retire the slot
2967: CD D6 3E CALL launchBankEnemyWhenAimedNearPlayer; try a bank-enemy launch when aimed near the player
296A: CD 47 2A CALL refreshSecondEraSpriteFromHeading; refresh the sprite from the heading -- second-era sprite set
296D: C9 RET
; ---- $296E-$2983: data ----
296E: 09 A7 32 82 6E 58 B5 77 E4 E8 EC 9D CB 4F 55 FE
297E: A3 31 81 5B 9A B9
; era-2 per-slot object handler (index 2 of the 0x2914 rst-0x30 era table,
; ERA_INDEX 0xad04 low three bits == 2), dispatched on the slot's state
; byte (ix+0): 0x00 idle returns; 0xFF active steers toward its aim 3
; frames in 4, flies at the slowest speed, retires the slot once it
; reaches a retire line, else dresses its sprite and runs two gated enemy-
; launch attempts; 0xFE releases the held object; any other value steps
; the dying-object state
serviceEra2EnemyCraftSlot:
2984: DD 7E 00 LD A,(IX+$00) ; read this slot's status byte
2987: A7 AND A
2988: C8 RET Z ; empty slot -- nothing to do
2989: 3C INC A
298A: 28 07 JR Z,loc_2993 ; a live craft -- fly it
298C: 3C INC A
298D: CA 52 2B JP Z,releaseHeldObject ; a held object -- release it
2990: C3 93 2B JP stepDyingObjectState; otherwise step the dying object
loc_2993:
2993: 3A 80 A9 LD A,(workRam+180) ; read the frame phase counter
2996: E6 03 AND $03 ; keep its low two bits
2998: FE 03 CP $03
299A: DC EF 2B CALL C,steerTowardAimHeading; steer toward the aim heading -- on three frames of every four
299D: CD 40 58 CALL flyAtSlowestSpeed ; fly at the slowest speed
29A0: CD 83 2B CALL hasReachedRetireLine; reached the retire line?
29A3: DA DE 2B JP C,retireSlotAndSubPixel; yes -- retire the slot
29A6: CD D6 3E CALL launchBankEnemyWhenAimedNearPlayer; try a bank-enemy launch when aimed near the player
29A9: CD 97 2A CALL dressSpriteForFineHeading; dress the sprite for its fine heading
29AC: CD 43 42 CALL launchAttackerIntoFreeSlot; try to launch an attacker into a free slot
29AF: C9 RET
; era-3 per-object-slot step, dispatched on the slot's lifecycle byte at
; ix+0: idle does nothing; a live slot (0xff) is steered, dressed, then
; retired at the line or flown on and given a spawn attempt; 0xfe releases
; a held slot; a lower value is a death-countdown step
serviceEra3EnemyCraftSlot:
29B0: DD 7E 00 LD A,(IX+$00) ; read the slot's lifecycle byte
29B3: A7 AND A ; is the slot free?
29B4: C8 RET Z ; free: nothing to do this frame
29B5: 3C INC A ; bump -- the live code 0xff wraps to zero here
29B6: 28 07 JR Z,loc_29bf ; live: steer and service this slot
29B8: 3C INC A ; bump again -- the held code 0xfe wraps to zero here
29B9: CA 52 2B JP Z,releaseHeldObject ; held: run its release-delay countdown
29BC: C3 93 2B JP stepDyingObjectState; any other value: step its dying animation
loc_29bf:
29BF: CD EF 2B CALL steerTowardAimHeading; turn its heading one step toward its aim
29C2: CD A4 58 CALL loc_58a4 ; fly the slot a step along its heading
29C5: CD 83 2B CALL hasReachedRetireLine; has it drifted onto a retire line?
29C8: DA DE 2B JP C,retireSlotAndSubPixel; yes: take the slot out of play
29CB: CD D6 3E CALL launchBankEnemyWhenAimedNearPlayer; when aimed near the ship, try the bank launch
29CE: CD FC 2A CALL dressSpriteForCoarseHeading; dress the sprite to face its heading
29D1: CD 43 42 CALL launchAttackerIntoFreeSlot; launch an attacker into a free slot
29D4: C9 RET
; era-4 (ERA_INDEX 0xad04=4) per-object slot service, index 4 of the
; 0x2914 rst-0x30 table: on the slot's lifecycle byte at (ix+0) it returns
; when free (0), releases when held (0xfe), steps the dying animation for
; any other value, and when live (0xff) steers the slot toward the ship
; then either retires it once it reaches a retire line or animates its
; shape, runs the gated launch attempt, and launches an attacker into a
; free slot
serviceEra4EnemyCraftSlot:
29D5: DD 7E 00 LD A,(IX+$00) ; read the slot's lifecycle byte
29D8: A7 AND A ; is the slot free?
29D9: C8 RET Z ; free: nothing to do this frame
29DA: 3C INC A ; bump -- the live code 0xff wraps to zero here
29DB: 28 07 JR Z,loc_29e4 ; live: steer and service this slot
29DD: 3C INC A ; bump again -- the held code 0xfe wraps to zero here
29DE: CA 52 2B JP Z,releaseHeldObject ; held: run its release-delay countdown
29E1: C3 93 2B JP stepDyingObjectState; any other value: step its dying animation
loc_29e4:
29E4: CD F7 29 CALL steerEnemyTowardShip; steer toward the ship, then fly a step
29E7: CD 83 2B CALL hasReachedRetireLine; has it drifted onto a retire line?
29EA: DA DE 2B JP C,retireSlotAndSubPixel; yes: take the slot out of play
29ED: CD 38 2B CALL animateSelectedShapeCycle; advance its shape's animation cycle
29F0: CD D6 3E CALL launchBankEnemyWhenAimedNearPlayer; when aimed near the ship, try the bank launch
29F3: CD 43 42 CALL launchAttackerIntoFreeSlot; launch an attacker into a free slot
29F6: C9 RET
; steer one live slot toward its aim heading then fly it a step; when the
; slot's probe cell (iy+0x31) lies within a fixed window of either
; reference point the turn runs with the shared turn-rate index forced to
; zero then reseated to four, else at the standing index, and the step
; alternates a double- and a single-velocity mover on bit 1 of the frame
; tick
steerEnemyTowardShip:
29F7: 3E 78 LD A,$78 ; first reference point on the screen (0x78)
29F9: FD 96 31 SUB (IY+$31) ; distance from the slot's screen-position probe
29FC: C6 48 ADD A,$48 ; bias by half the window
29FE: FE 90 CP $90 ; is the probe within the window of it?
2A00: 38 1A JR C,loc_2a1c ; near it: turn with the rate index forced low
2A02: 3E 84 LD A,$84 ; second reference point (0x84)
2A04: FD 96 31 SUB (IY+$31) ; distance from the probe
2A07: C6 48 ADD A,$48 ; bias by half the window
2A09: FE 90 CP $90 ; within the window of it?
2A0B: 38 0F JR C,loc_2a1c ; near it: turn with the rate index forced low
2A0D: CD EF 2B CALL steerTowardAimHeading; otherwise turn toward aim at the standing rate
loc_2a10:
2A10: 3A 80 A9 LD A,(workRam+180) ; read the frame tick counter
2A13: 0F RRCA
2A14: E6 01 AND $01 ; isolate bit 1 of it
2A16: CA AA 58 JP Z,loc_58aa ; that bit clear: fly the slot at double velocity
2A19: C3 60 58 JP loc_5860 ; otherwise fly it at single velocity
loc_2a1c:
2A1C: AF XOR A
2A1D: 32 04 AD LD (workRam+504),A ; force the shared turn-rate index to zero
2A20: CD EF 2B CALL steerTowardAimHeading; turn toward aim at that forced rate
2A23: 3E 04 LD A,$04
2A25: 32 04 AD LD (workRam+504),A ; reseat the shared turn-rate index to four
2A28: 18 E6 JR loc_2a10 ; go fly the step
; ---- $2A2A-$2A3B: data ----
2A2A: DD 7E 04 3D CA 93 2B DD 77 04 DD 36 00 FF CD BA
2A3A: 2B C9
; store the shape byte and the attribute byte that show an object pointing
; the way it is heading into that object's own sprite entry
refreshSpriteFromHeading:
2A3C: CD 57 2A CALL spriteForHeading ; pick the shape and its mirror byte for this heading
2A3F: FD 71 30 LD (IY+$30),C ; store the mirror byte into the sprite entry
2A42: 78 LD A,B
2A43: FD 77 01 LD (IY+$01),A ; store the shape byte into the sprite entry
2A46: C9 RET
; show one of the second era's enemy craft pointing the way it is heading:
; the shared heading lookup picks a shape and the byte beside it, and each
; is stored into the object's own sprite entry shifted by a fixed bias --
; sixteen on the shape, fifty-three on the attribute -- so this era's
; craft is drawn from its own block of the sprite ROM in its own colour.
; The attribute's two flip bits survive the addition because every entry
; of the lookup's attribute table carries the same low colour field, so
; the bias moves the colour and leaves the facing alone
refreshSecondEraSpriteFromHeading:
2A47: CD 57 2A CALL spriteForHeading ; pick the shape and its mirror byte for this heading
2A4A: 79 LD A,C ; take the mirror/attribute byte
2A4B: C6 35 ADD A,$35 ; bias its colour by 53 into this era's block
2A4D: FD 77 30 LD (IY+$30),A ; store it into the sprite entry
2A50: 78 LD A,B ; take the shape byte
2A51: C6 10 ADD A,$10 ; bias it by 16 into this era's sprite block
2A53: FD 77 01 LD (IY+$01),A ; store it into the sprite entry
2A56: C9 RET
; pick the sprite shape, and the byte beside it, that show an object
; pointing the way it is heading, alternating between two shape banks as a
; frame counter's bit turns over
spriteForHeading:
2A57: 11 10 00 LD DE,$0010 ; the mirror table sits sixteen past the shape table
2A5A: DD 7E 02 LD A,(IX+$02) ; read the object's heading
2A5D: C6 08 ADD A,$08 ; add half a sector to round to nearest
2A5F: 0F RRCA
2A60: 0F RRCA
2A61: 0F RRCA
2A62: 0F RRCA
2A63: E6 0F AND $0F ; top nibble: heading snapped to one of sixteen sectors
2A65: 21 77 2A LD HL,$2A77 ; point at the shape-by-sector table
2A68: DF RST $18 ; index it by the sector
2A69: 46 LD B,(HL) ; take the shape byte
2A6A: 19 ADD HL,DE ; step to the parallel mirror table
2A6B: 4E LD C,(HL) ; take the mirror byte beside it
2A6C: 3A 80 A9 LD A,(workRam+180) ; read the frame tick counter
2A6F: CB 4F BIT 1,A ; test bit 1
2A71: C8 RET Z ; half the frame pairs: keep the base shape
2A72: 78 LD A,B
2A73: C6 08 ADD A,$08 ; otherwise advance the shape by eight
2A75: 47 LD B,A
2A76: C9 RET
; ---- $2A77-$2A96: data ----
2A77: 0C 0D 0E 0F 08 0F 0E 0D 0C 0B 0A 09 08 09 0A 0B
2A87: 41 41 41 41 81 C1 C1 C1 C1 C1 C1 C1 41 41 41 41
; dress one sprite entry to face the way its object is heading, resolving
; the heading to thirty-two sectors and writing the shape code and the
; attribute beside it directly into the entry, alternating between two
; shape banks as a frame counter's bit turns over
dressSpriteForFineHeading:
2A97: DD 7E 02 LD A,(IX+$02) ; read the object's heading
2A9A: C6 04 ADD A,$04 ; add half a sector (of thirty-two) to round to nearest
2A9C: E6 F8 AND $F8 ; drop the low three bits
2A9E: 0F RRCA
2A9F: 0F RRCA
2AA0: E6 3F AND $3F ; form the sector as a doubled index -- two-byte entries
2AA2: 21 BC 2A LD HL,$2ABC ; point at the shape/attribute table
2AA5: DF RST $18 ; index it by the doubled sector
2AA6: 46 LD B,(HL) ; take the shape byte
2AA7: 3A 80 A9 LD A,(workRam+180) ; read the frame tick counter
2AAA: E6 02 AND $02 ; take bit 1
2AAC: 20 0A JR NZ,loc_2ab8 ; set: add eight to the shape
loc_2aae:
2AAE: 80 ADD A,B ; combine with the base shape -- zero or eight added
2AAF: FD 77 01 LD (IY+$01),A ; store the shape into the sprite entry
2AB2: 23 INC HL
2AB3: 7E LD A,(HL) ; take the attribute byte from the next table cell
2AB4: FD 77 30 LD (IY+$30),A ; store it into the sprite entry
2AB7: C9 RET
loc_2ab8:
2AB8: 3E 08 LD A,$08 ; the eight to add
2ABA: 18 F2 JR loc_2aae ; join the store path
; ---- $2ABC-$2AFB: data ----
2ABC: 80 DC 80 DC 80 DC 80 DC 81 DC 81 DC 82 DC 83 DC
2ACC: 84 5C 84 5C 83 5C 82 5C 81 5C 81 5C 80 5C 80 5C
2ADC: 80 5C 80 5C 80 5C 80 5C 81 5C 81 5C 82 5C 83 5C
2AEC: 84 DC 84 DC 83 DC 82 DC 81 DC 81 DC 80 DC 80 DC
; point an object's sprite the way it is heading, by rounding its heading
; byte to the nearest of sixteen sectors and taking a shape pair from two
; parallel tables
dressSpriteForCoarseHeading:
2AFC: 11 10 00 LD DE,$0010 ; the second table sits sixteen past the first
2AFF: DD 7E 02 LD A,(IX+$02) ; read the object's heading
2B02: C6 08 ADD A,$08 ; add half a sector to round to nearest
2B04: 0F RRCA
2B05: 0F RRCA
2B06: 0F RRCA
2B07: 0F RRCA
2B08: E6 0F AND $0F ; top nibble: heading snapped to one of sixteen sectors
2B0A: 21 18 2B LD HL,$2B18 ; point at the shape table
2B0D: DF RST $18 ; index it by the sector
2B0E: 7E LD A,(HL) ; take the shape byte
2B0F: FD 77 01 LD (IY+$01),A ; store it into the sprite entry
2B12: 19 ADD HL,DE ; step to the parallel attribute table
2B13: 7E LD A,(HL) ; take the attribute byte
2B14: FD 77 30 LD (IY+$30),A ; store it into the sprite entry
2B17: C9 RET
; ---- $2B18-$2B37: data ----
2B18: 2C 2D 2E 2F 28 2F 2E 2D 2C 2B 2A 29 28 29 2A 2B
2B28: 5B 5B 5B 5B 9B DB DB DB DB DB DB DB 5B 5B 5B 5B
; give one sprite entry the current frame of a four-frame shape cycle,
; from the block a record byte selects, and one fixed attribute beside it
animateSelectedShapeCycle:
2B38: 3A 80 A9 LD A,(workRam+180) ; read the frame tick counter
2B3B: 0F RRCA
2B3C: 0F RRCA
2B3D: E6 03 AND $03 ; two middle bits: the animation phase, turning over every fourth frame
2B3F: C6 D8 ADD A,$D8 ; offset to the first shape in the block (0xd8)
2B41: 47 LD B,A
2B42: DD 7E 04 LD A,(IX+$04) ; read the object's shape-block selector
2B45: D6 01 SUB $01 ; count it from one
2B47: 87 ADD A,A
2B48: 87 ADD A,A ; shift up by two -- times four, a block of four shapes per selector
2B49: 80 ADD A,B ; add the phased base shape
2B4A: FD 77 01 LD (IY+$01),A ; store the shape into the sprite entry
2B4D: FD 36 30 61 LD (IY+$30),$61 ; stamp the fixed attribute (0x61) beside it
2B51: C9 RET
; count a held object's release delay down and, when it expires, step its
; state code to the live one and re-arm the delay
releaseHeldObject:
2B52: DD 35 0E DEC (IX+$0E) ; count the release delay down one
2B55: 28 01 JR Z,loc_2b58 ; reached zero: release it
2B57: C9 RET
loc_2b58:
2B58: DD 34 00 INC (IX+$00) ; step the state code on to the live one
2B5B: DD 36 0E 80 LD (IX+$0E),$80 ; reload the delay with 128
2B5F: C9 RET
; add the frame's world-scroll displacement to one object's two split
; 16-bit coordinates
driftWithWorldScroll:
2B60: FD 66 31 LD H,(IY+$31) ; the object's whole coordinate byte, first axis, from its sprite entry
2B63: DD 6E 03 LD L,(IX+$03) ; its fraction from the record
2B66: ED 5B 08 A8 LD DE,(workRam+8) ; the frame's world-scroll step for this axis
2B6A: 19 ADD HL,DE ; add it into the 16-bit coordinate
2B6B: FD 74 31 LD (IY+$31),H ; store the whole byte back
2B6E: DD 75 03 LD (IX+$03),L ; store the fraction back
2B71: FD 66 00 LD H,(IY+$00) ; the object's whole coordinate byte, second axis
2B74: DD 6E 05 LD L,(IX+$05) ; its fraction from the record
2B77: ED 5B 0A A8 LD DE,(workRam+A) ; the frame's world-scroll step for this axis
2B7B: 19 ADD HL,DE ; add it into the 16-bit coordinate
2B7C: FD 74 00 LD (IY+$00),H ; store the whole byte back
2B7F: DD 75 05 LD (IX+$05),L ; store the fraction back
2B82: C9 RET
; answer whether an actor has drifted onto either of two fixed retire
; lines, within a narrow wrapped window, which is what makes its caller
; free the slot
hasReachedRetireLine:
2B83: FD 7E 31 LD A,(IY+$31) ; the object's screen row byte
2B86: C6 09 ADD A,$09 ; line up the retire row with a three-wide window
2B88: FE 03 CP $03 ; within a pixel of the retire row (0xf8)?
2B8A: D8 RET C ; yes: report reached, carry set
2B8B: FD 7E 00 LD A,(IY+$00) ; the object's screen column byte
2B8E: D6 03 SUB $03 ; line up the retire column with the window
2B90: FE 03 CP $03 ; within a pixel of the retire column (4)? -- carry now carries the answer
2B92: C9 RET
; per-object state-machine step: dispatch on the object's state byte —
; 0xf0 re-arms it to 0x3b and begins its death, 0x3c begins the death then
; flies it on, above 0x3c flies it on, below 0x3c counts the byte down,
; retiring the slot at zero else moving the object for the frame
stepDyingObjectState:
2B93: DD 7E 00 LD A,(IX+$00) ; read the object's state byte
2B96: FE F0 CP $F0 ; the re-arm value 0xf0?
2B98: CA AC 2B JP Z,loc_2bac ; yes: re-seat it and begin the death
2B9B: FE 3C CP $3C ; at the death-begins threshold 0x3c?
2B9D: CC BA 2B CALL Z,countTheKillAndGrantTheSharedToken; exactly there: count the kill and grant the token
2BA0: D2 B4 2B JP NC,decrementObjectStateThenFlyAtSlowestSpeed; at or above the threshold: fly it on
2BA3: DD 35 00 DEC (IX+$00) ; below it: count the state byte down
2BA6: 28 36 JR Z,retireSlotAndSubPixel; hit zero: take the slot out of play
2BA8: CD 22 2C CALL moveObjectByStateByteThenRunAppearance; else move it for the frame and run its appearance
; ---- $2BAB-$2BAB: data ----
2BAB: C9
loc_2bac:
2BAC: DD 36 00 3B LD (IX+$00),$3B ; re-seat the state byte to 0x3b
2BB0: CD BA 2B CALL countTheKillAndGrantTheSharedToken; count the kill and grant the token
2BB3: C9 RET
; count an object's state byte down by one and let it fly on at the
; slowest of the velocity-table speeds; the countdown wraps at a byte and
; nothing here tests it, so reaching zero is the caller's business. Both
; entries into it are on the path a slot takes once its state byte is
; neither free, live nor held
decrementObjectStateThenFlyAtSlowestSpeed:
2BB4: DD 35 00 DEC (IX+$00) ; count the object's state byte down one
2BB7: C3 40 58 JP flyAtSlowestSpeed ; fly it on at the slowest table speed
; the tick a hit object's death begins: ask for the pair of death sounds
; and take one off the round's kill quota -- both UNCONDITIONAL -- and
; then, only past three guards, grant this record the single-holder token
; at 0xA821, its own slot ordinal marked with a top bit. The guards are
; the record's cooldown byte carrying its top bit, the shared arming cell
; being set, and the shared countdown beside it reaching zero on this
; step; the countdown is spent whenever the first two pass, so every
; claimant spends a tick and not only the one that wins. The quota is
; floored rather than wrapped -- a count already at zero is left alone
countTheKillAndGrantTheSharedToken:
2BBA: CD 83 56 CALL requestTwoSounds ; ask for the pair of death sounds
2BBD: 21 02 AD LD HL,$AD02 ; point at the round's kill quota
2BC0: 7E LD A,(HL)
2BC1: A7 AND A ; already at zero?
2BC2: 28 01 JR Z,loc_2bc5 ; yes: leave it -- floor, do not wrap
2BC4: 35 DEC (HL) ; else take one kill off the quota
loc_2bc5:
2BC5: DD 7E 0E LD A,(IX+$0E) ; read this record's cooldown byte
2BC8: CB 7F BIT 7,A ; is its top claim bit set?
2BCA: C8 RET Z ; no: not a claimant, done
2BCB: 3A 12 A8 LD A,(workRam+12) ; read the shared arming cell
2BCE: A7 AND A
2BCF: C8 RET Z ; not set: done
2BD0: 21 11 A8 LD HL,$A811 ; point at the shared kill countdown
2BD3: 35 DEC (HL) ; spend a tick of it
2BD4: C0 RET NZ ; not zero yet: done -- every claimant spends a tick
2BD5: DD 7E 0F LD A,(IX+$0F) ; this record's slot ordinal
2BD8: C6 80 ADD A,$80 ; mark it with the top bit
2BDA: 32 21 A8 LD (workRam+21),A ; grant it the single-holder token
2BDD: C9 RET
; take an object out of play, zeroing each coordinate WHOLE — occupancy
; byte, both sub-pixel remainders, and both sprite-entry coordinates
retireSlotAndSubPixel:
2BDE: AF XOR A ; zero, to clear five cells
2BDF: DD 77 00 LD (IX+$00),A ; clear the occupancy byte -- free the slot
2BE2: DD 77 03 LD (IX+$03),A ; clear the row sub-pixel remainder
2BE5: DD 77 05 LD (IX+$05),A ; clear the column sub-pixel remainder
2BE8: FD 77 00 LD (IY+$00),A ; clear one sprite-entry coordinate
2BEB: FD 77 31 LD (IY+$31),A ; clear the other sprite-entry coordinate
2BEE: C9 RET
; turn an object's heading one step toward the heading it aims at, the
; short way round, at a rate a small table supplies for the current mode
; cell
steerTowardAimHeading:
2BEF: DD 7E 01 LD A,(IX+$01) ; the heading the object is turning toward
2BF2: DD 96 02 SUB (IX+$02) ; minus its current heading -- how far round the aim lies
2BF5: 4F LD C,A ; keep that wrapped difference
2BF6: C6 02 ADD A,$02 ; bias for the arrival test
2BF8: FE 04 CP $04 ; within one step ahead or two behind? already on the aim
2BFA: D8 RET C ; yes: stop turning
2BFB: DD 46 02 LD B,(IX+$02) ; hold the current heading
2BFE: 79 LD A,C ; the difference again
2BFF: FE 80 CP $80 ; is the aim more than half a turn away?
2C01: 30 0C JR NC,loc_2c0f ; yes: the short way round is backward
2C03: 21 1D 2C LD HL,$2C1D ; point at the turn-rate table
2C06: 3A 04 AD LD A,(workRam+504) ; the shared turn-rate index -- the current mode
2C09: CF RST $08 ; fetch this mode's step size
2C0A: 80 ADD A,B ; add the step to the current heading -- turn forward
2C0B: DD 77 02 LD (IX+$02),A ; write the new heading
2C0E: C9 RET
loc_2c0f:
2C0F: 21 1D 2C LD HL,$2C1D ; point at the turn-rate table
2C12: 3A 04 AD LD A,(workRam+504) ; the shared turn-rate index
2C15: CF RST $08 ; fetch this mode's step size
2C16: 90 SUB B
2C17: ED 44 NEG ; current heading minus the step -- turn backward
2C19: DD 77 02 LD (IX+$02),A ; write the new heading
2C1C: C9 RET
; ---- $2C1D-$2C21: data ----
2C1D: 01 01 02 02 05
; move one object for the frame according to its state byte, then run the
; shared appearance step over that same object: from thirty-two up it
; counts the state byte down and flies on at the slowest table speed,
; below thirty-two it only drifts with the world and the state byte is
; left alone; the appearance step runs on both paths
moveObjectByStateByteThenRunAppearance:
2C22: 21 31 2C LD HL,$2C31 ; point at the shared appearance step and...
2C25: E5 PUSH HL ; ...stack it as the return, so whichever move runs next falls straight on into it
2C26: DD 7E 00 LD A,(IX+$00) ; read this object's state byte
2C29: FE 20 CP $20 ; is it thirty-two or more?
2C2B: D2 B4 2B JP NC,decrementObjectStateThenFlyAtSlowestSpeed; yes -- count the state byte down one frame and fly on at the slowest table speed
2C2E: C3 60 2B JP driftWithWorldScroll; no -- only drift with the world scroll, leaving the state byte alone
; ---- $2C31-$2CBB: data ----
2C31: DD 7E 00 FE 2A D2 71 2C FE 0A 30 45 3A 21 A8 CB
2C41: 7F CA DE 2B 3A 21 A8 CB BF DD BE 0F C2 DE 2B 3A
2C51: 80 A9 E6 07 28 03 DD 34 00 FD 36 01 FC FD 36 30
2C61: 6C DD 7E 00 FE 01 C0 11 0C 04 FF AF 32 21 A8 C9
2C71: FD 7E 30 4F E6 C0 47 3A 80 A9 E6 0F 80 FD 77 30
2C81: C9 D6 0A 0F E6 0F 47 21 94 2C CF FD 77 01 FD 36
2C91: 30 3C C9 FF FF 7D 7D 7E 7E 7D 7D 5B 5B 5A 5A 59
2CA1: 59 58 58 18 A7 13 A5 3B 87 F1 34 0E 34 D7 BF F1
2CB1: 65 13 13 13 13 F1 88 DC ED 11 B9
; seat the record cursor and the sprite-entry cursor on the first scenery
; slot, then run one of three fixed lists of parallax wrappers, chosen by
; the era index
runSceneryForEra:
2CBC: DD 21 00 A9 LD IX,$A900 ; seat the scenery record cursor on the first slot
2CC0: FD 21 30 AA LD IY,$AA30 ; seat the sprite-entry cursor on the first slot
2CC4: 3A 04 AD LD A,(workRam+504) ; read the era index
2CC7: A7 AND A ; is it the first era?
2CC8: 28 2B JR Z,loc_2cf5 ; yes -- run the opening scenery order
2CCA: FE 04 CP $04 ; is it the fifth era?
2CCC: 28 34 JR Z,loc_2d02 ; yes -- run the closing scenery order
2CCE: CD 21 2D CALL driftNearestSceneryTriTile; middle order: drift a diagonally-cornered scenery object at five quarters
2CD1: CD 36 2D CALL driftTwoTileSceneryAtThreeQuarters; drift a two-tile scenery object at three quarters
2CD4: CD 36 2D CALL driftTwoTileSceneryAtThreeQuarters; and another two-tile object at three quarters
2CD7: CD 68 2D CALL driftOneTileSceneryAtHalf; drift a one-tile object at half -- the farthest, slowest layer
2CDA: C9 RET
; one turn of the line wipe, and on the turn that finishes it, one tamper
; test: a single line is blanked per turn and the turn ends there while
; lines are still owed; the turn that clears the last one folds a fixed
; 1024-byte span of the program image together with exclusive-or into an
; eight-bit total and compares it against the total an untampered image
; gives. Matching steps the sequence's INNER index, so the sequence
; carries on; not matching steps the OUTER phase instead, restarting the
; inner index somewhere else entirely -- derailing the sequence rather
; than halting it
blankOneLineThenGuardBlockOrDerailSequence:
2CDB: CD C2 01 CALL blankNextLine ; blank the next line of the wipe; comes back saying whether any lines are still owed
2CDE: C0 RET NZ ; lines still owed -- end the turn here
2CDF: 01 04 00 LD BC,$0004 ; four passes of...
2CE2: 21 80 49 LD HL,$4980 ; ...the fixed 1024-byte program-image block at $4980
2CE5: 97 SUB A ; clear the running total
loc_2ce6:
2CE6: AE XOR (HL) ; fold this byte into the total with exclusive-or
2CE7: 23 INC HL ; next byte
2CE8: 10 FC DJNZ loc_2ce6 ; 256 bytes per pass
2CEA: 0D DEC C ; next pass
2CEB: 20 F9 JR NZ,loc_2ce6 ; 1024 bytes folded in all
2CED: C6 BD ADD A,$BD ; add $BD -- lands on zero only when the fold matches an untampered image
2CEF: C2 11 0F JP NZ,advanceSequencePhase; mismatch (image tampered) -- step the outer sequence phase, derailing the sequence
2CF2: C3 1A 0F JP advanceSequenceSubStep; match -- step the sequence's inner index so it carries on
loc_2cf5:
2CF5: CD 15 2D CALL driftThreeTileSceneryAtFiveQuarters; opening order: drift a three-tile scenery strip at five quarters
2CF8: CD 36 2D CALL driftTwoTileSceneryAtThreeQuarters; drift a two-tile object at three quarters
2CFB: CD 36 2D CALL driftTwoTileSceneryAtThreeQuarters; and another two-tile object at three quarters
2CFE: CD 68 2D CALL driftOneTileSceneryAtHalf; drift a one-tile object at half -- the farthest layer
2D01: C9 RET
loc_2d02:
2D02: CD 2D 2D CALL stepTwoTileSceneryAtFiveQuarters; closing order: advance a two-tile scenery object at five quarters
2D05: CD 2D 2D CALL stepTwoTileSceneryAtFiveQuarters; and another two-tile object at five quarters
2D08: CD 62 2D CALL driftOneTileSceneryAtThreeQuarters; drift a one-tile object at three quarters
2D0B: CD 62 2D CALL driftOneTileSceneryAtThreeQuarters; and another one-tile object at three quarters
2D0E: CD 68 2D CALL driftOneTileSceneryAtHalf; drift a one-tile object at half
2D11: CD 68 2D CALL driftOneTileSceneryAtHalf; and another one-tile object at half
2D14: C9 RET
; drift one scenery object at five quarters of the frame's world scroll,
; lay two further tiles flush against it in a straight strip, and step
; both cursors past the object so the caller lands on the next slot
driftThreeTileSceneryAtFiveQuarters:
2D15: CD 6E 2D CALL driftAtFiveQuartersWorldScroll; drift this scenery object at five quarters of the world scroll
2D18: CD 58 30 CALL placeAbuttingTile ; lay a tile flush against it
2D1B: CD 58 30 CALL placeAbuttingTile ; lay a second tile flush, extending the strip
2D1E: C3 9B 30 JP advanceToNextSlot ; step both cursors onto the next slot
; drift one scenery object with the world scroll over-travelled by a
; quarter, then lay the tile abutting it and the one cornering it
; diagonally (three corners of a square) and step both cursors one slot
; past
driftNearestSceneryTriTile:
2D21: CD 6E 2D CALL driftAtFiveQuartersWorldScroll; drift this scenery object at five quarters of the world scroll
2D24: CD 58 30 CALL placeAbuttingTile ; lay the tile abutting it
2D27: CD 8A 30 CALL placeDiagonallyAbuttingTile; lay the tile cornering it diagonally -- three corners of a square
2D2A: C3 9B 30 JP advanceToNextSlot ; step both cursors onto the next slot
; advance one two-tile scenery object: drift it at five quarters of the
; world scroll, lay its second tile flush against the first, and step both
; cursors past the pair
stepTwoTileSceneryAtFiveQuarters:
2D2D: CD 6E 2D CALL driftAtFiveQuartersWorldScroll; drift this scenery object at five quarters of the world scroll
2D30: CD 58 30 CALL placeAbuttingTile ; lay its second tile flush against the first
2D33: C3 9B 30 JP advanceToNextSlot ; step both cursors past the pair
; drift one scenery object at three quarters of the frame's world scroll,
; place a second tile flush against it, and step both cursors past the
; object so the caller lands on the next slot
driftTwoTileSceneryAtThreeQuarters:
2D36: CD 93 2D CALL driftAtThreeQuartersWorldScroll; drift this scenery object at three quarters of the world scroll
2D39: CD 58 30 CALL placeAbuttingTile ; lay a second tile flush against it
2D3C: C3 9B 30 JP advanceToNextSlot ; step both cursors onto the next slot
; one sequence step that puts the credit line up: while FREE_PLAY is set
; it does nothing but move the sequence's inner index on; otherwise it
; repaints the panel field from the packed-decimal credit count at 0xA986,
; queues caption record 8 -- whose glyph run reads CREDIT -- and then
; reads a guard byte that decides everything after. Anything but zero
; transfers to 0x2E3E, which carries no routine, so that transfer RAISES
; rather than running; zero stamps the copyright strip into the display
; list, asks for its line in this frame's colour, and folds the twenty-
; byte run at 0x086B into a total for the chain that judges it. What
; writes the guard byte is not established here
showCreditLine:
2D3F: 3A C0 A9 LD A,(workRam+1C0) ; read the free-play switch
2D42: A7 AND A ; is it set?
2D43: C2 1A 0F JP NZ,advanceSequenceSubStep; free play -- just step the sequence's inner index and leave
2D46: CD FB 4A CALL paintCreditCountPanel; repaint the credit-count panel
2D49: 11 08 01 LD DE,$0108 ; caption command 1, record 8 -- the CREDIT line
2D4C: FF RST $38 ; post it to the command ring
2D4D: 3A 17 A8 LD A,(workRam+17) ; read the guard byte
2D50: A7 AND A ; is it set?
2D51: C2 3E 2E JP NZ,loc_2e3e ; set -- jump to $2E3E, which holds table data, not a routine
2D54: CD 06 0B CALL stampCopyrightStrip ; stamp the copyright/caption strip into the display list
2D57: CD 39 0B CALL flashCopyrightLine ; request the copyright line flashed in this frame's colour
2D5A: 21 6B 08 LD HL,$086B ; point at the twenty-byte image run at $086B...
2D5D: 06 14 LD B,$14 ; ...twenty bytes...
2D5F: C3 E8 43 JP sumImageBlockForTheTamperCheck; ...and fold it into the tamper-check total
; drift one scenery object at three quarters of the frame's world scroll,
; lay no further tile, and step both cursors onto the next slot
driftOneTileSceneryAtThreeQuarters:
2D62: CD 93 2D CALL driftAtThreeQuartersWorldScroll; drift this scenery object at three quarters of the world scroll
2D65: C3 9B 30 JP advanceToNextSlot ; step both cursors onto the next slot -- no further tile
; drift one scenery object at half the frame's world-scroll displacement,
; lay no further tile, and step both cursors onto the next slot -- the
; one-tile member of the parallax family, and the slowest rung, so what it
; moves reads as the farthest layer
driftOneTileSceneryAtHalf:
2D68: CD F4 2D CALL driftAtHalfWorldScroll; drift this scenery object at half the world scroll -- the farthest, slowest layer
2D6B: C3 9B 30 JP advanceToNextSlot ; step both cursors onto the next slot
; move one object by the frame's world-scroll displacement and a further
; quarter of it, so it over-travels the world; applied to both of its
; split coordinates, whole part in the sprite entry and fraction in the
; object record
driftAtFiveQuartersWorldScroll:
2D6E: FD 56 31 LD D,(IY+$31) ; whole part of the first coordinate
2D71: DD 5E 03 LD E,(IX+$03) ; its fraction -- together one sixteen-bit coordinate
2D74: 2A 08 A8 LD HL,(workRam+8) ; this frame's vertical world-scroll
2D77: CD 31 2E CALL displaceByFiveQuarters; move the coordinate by five quarters of the scroll
2D7A: FD 74 31 LD (IY+$31),H ; store the whole part back
2D7D: DD 75 03 LD (IX+$03),L ; and its fraction
2D80: FD 56 00 LD D,(IY+$00) ; whole part of the second coordinate
2D83: DD 5E 05 LD E,(IX+$05) ; its fraction
2D86: 2A 0A A8 LD HL,(workRam+A) ; this frame's horizontal world-scroll
2D89: CD 31 2E CALL displaceByFiveQuarters; move the coordinate by five quarters of the scroll
2D8C: FD 74 00 LD (IY+$00),H ; store the whole part back
2D8F: DD 75 05 LD (IX+$05),L ; and its fraction
2D92: C9 RET
; move one object by three quarters of the frame's world-scroll
; displacement, applied to both of its split coordinates, whole part in
; the sprite entry and fraction in the object record
driftAtThreeQuartersWorldScroll:
2D93: FD 56 31 LD D,(IY+$31) ; whole part of the first coordinate
2D96: DD 5E 03 LD E,(IX+$03) ; its fraction
2D99: 2A 08 A8 LD HL,(workRam+8) ; this frame's vertical world-scroll
2D9C: CD 3E 30 CALL displaceByThreeQuarters; move the coordinate by three quarters of the scroll
2D9F: FD 74 31 LD (IY+$31),H ; store the whole part back
2DA2: DD 75 03 LD (IX+$03),L ; and its fraction
2DA5: FD 56 00 LD D,(IY+$00) ; whole part of the second coordinate
2DA8: DD 5E 05 LD E,(IX+$05) ; its fraction
2DAB: 2A 0A A8 LD HL,(workRam+A) ; this frame's horizontal world-scroll
2DAE: CD 3E 30 CALL displaceByThreeQuarters; move the coordinate by three quarters of the scroll
2DB1: FD 74 00 LD (IY+$00),H ; store the whole part back
2DB4: DD 75 05 LD (IX+$05),L ; and its fraction
2DB7: C9 RET
; start the next round: step the round number, roll the era on and wrap it
; after the fifth, set the round's difficulty byte from one of three
; sources by round bracket, refill the kill quota, and clear two flags
; while arming a third
startNextRound:
2DB8: 21 01 AD LD HL,$AD01 ; point at the round counter
2DBB: 34 INC (HL) ; step to the next round
2DBC: 21 04 AD LD HL,$AD04 ; point at the era index
2DBF: 7E LD A,(HL) ; take it
2DC0: 3C INC A ; roll it forward
2DC1: FE 05 CP $05 ; past the fifth era?
2DC3: 38 01 JR C,loc_2dc6 ; no -- keep it
2DC5: AF XOR A ; yes -- wrap back to the first era
loc_2dc6:
2DC6: 77 LD (HL),A ; store the era index
2DC7: 3A 01 AD LD A,(workRam+501) ; read the round number
2DCA: FE 06 CP $06 ; rounds 1..5?
2DCC: 38 09 JR C,loc_2dd7 ; yes -- take the low bracket's starting rung
2DCE: FE 0B CP $0B ; rounds 6..10?
2DD0: 38 0A JR C,loc_2ddc ; yes -- take the middle bracket's starting rung
2DD2: 3A D5 A9 LD A,(workRam+1D5) ; rounds 11 and up -- take the high bracket's starting rung
2DD5: 18 08 JR loc_2ddf ;
loc_2dd7:
2DD7: 3A D3 A9 LD A,(workRam+1D3) ; rounds 1..5 -- the low bracket's starting rung
2DDA: 18 03 JR loc_2ddf ;
loc_2ddc:
2DDC: 3A D4 A9 LD A,(workRam+1D4) ; rounds 6..10 -- the middle bracket's starting rung
loc_2ddf:
2DDF: 32 0A AD LD (workRam+50A),A ; set this round's starting rung
2DE2: 3A CD A9 LD A,(workRam+1CD) ; take the kill quota
2DE5: 32 02 AD LD (workRam+502),A ; refill kills-remaining from it -- the same every round
2DE8: AF XOR A ; clear the accumulator
2DE9: 32 0D AD LD (workRam+50D),A ; clear the mother-ship-armed flag
2DEC: 32 C6 AC LD (workRam+4C6),A ; clear the round-transition hold
2DEF: 3D DEC A ; flip to all-ones
2DF0: 32 0E AD LD (workRam+50E),A ; arm the round with it
2DF3: C9 RET
; move one object by half the frame's world-scroll displacement, applied
; to both of its split coordinates, whole part in the sprite entry and
; fraction in the object record
driftAtHalfWorldScroll:
2DF4: FD 56 31 LD D,(IY+$31) ; whole part of the first coordinate
2DF7: DD 5E 03 LD E,(IX+$03) ; its fraction
2DFA: 2A 08 A8 LD HL,(workRam+8) ; this frame's vertical world-scroll
2DFD: CD 4D 30 CALL displaceByHalf ; move the coordinate by half the scroll
2E00: FD 74 31 LD (IY+$31),H ; store the whole part back
2E03: DD 75 03 LD (IX+$03),L ; and its fraction
2E06: FD 56 00 LD D,(IY+$00) ; whole part of the second coordinate
2E09: DD 5E 05 LD E,(IX+$05) ; its fraction
2E0C: 2A 0A A8 LD HL,(workRam+A) ; this frame's horizontal world-scroll
2E0F: CD 4D 30 CALL displaceByHalf ; move the coordinate by half the scroll
2E12: FD 74 00 LD (IY+$00),H ; store the whole part back
2E15: DD 75 05 LD (IX+$05),L ; and its fraction
2E18: C9 RET
; open the settings block: store, whole, the byte the caller has already
; worked out from the switch port's low two bits, then peel the next two
; switch bits into a cell each, one bit per cell and nothing else in it,
; and hand the byte on rotated so the last bit spent sits lowest -- twice
; over, in both registers that carry it -- to the continuation that peels
; the rest. Nothing is read from memory and control never comes back.
; 'Switch settings' is established at the CALLER and at the three cells'
; readers, not inside a routine that reads no memory at all: the caller at
; 0x52C0-0x52CF is "ld a,(0xc200) / cpl / ld c,a / and 0x03 / add a,0x03 /
; cp 0x06 / jr nz,+2 / ld a,0xff / jp 0x2e19", so C arrives as the
; complemented DSW port and A as 3, 4, 5 or the folded 0xFF
unpackTheFirstThreeSwitchSettings:
2E19: 32 C1 A9 LD (workRam+1C1),A ; store the starting-lives setting the caller worked out
2E1C: 79 LD A,C ; take the packed switch byte
2E1D: 0F RRCA ; rotate it...
2E1E: 0F RRCA ; ...down two bits
2E1F: 4F LD C,A ; keep the rotated byte
2E20: E6 01 AND $01 ; isolate one bit -- the cabinet type (upright/cocktail)
2E22: 32 C2 A9 LD (workRam+1C2),A ; store it in its own cell
2E25: 79 LD A,C ; take the rotated byte again
2E26: 0F RRCA ; rotate down one more bit
2E27: 4F LD C,A ; keep it
2E28: E6 01 AND $01 ; isolate the next bit -- the bonus-life setting
2E2A: 32 C3 A9 LD (workRam+1C3),A ; store it in its own cell
2E2D: 79 LD A,C ; hand the remaining bits on...
2E2E: C3 A8 49 JP finishBootSelfTestAndColdStart; ...to the continuation that peels the rest and cold-starts
; move a coordinate by a displacement and a further quarter of it, so what
; it carries leads what moves by the whole of it; the quarter rounds down
; rather than toward zero
displaceByFiveQuarters:
2E31: 44 LD B,H ; copy the displacement aside to take a quarter of it
2E32: 4D LD C,L
2E33: CB 28 SRA B ; halve it, keeping its sign...
2E35: CB 19 RR C
2E37: CB 28 SRA B ; ...and halve again: a quarter of the displacement
2E39: CB 19 RR C
2E3B: 09 ADD HL,BC ; displacement plus its quarter -- five quarters
2E3C: 19 ADD HL,DE ; add that to the coordinate
2E3D: C9 RET
loc_2e3e:
2E3E: 32 01 31 LD (rom+3101),A ;
2E41: 01 30 01 LD BC,$0130
2E44: 2F CPL
2E45: 01 2E 01 LD BC,$012E
2E48: 2D DEC L
2E49: 01 2C 01 LD BC,$012C
2E4C: 28 01 JR Z,rom+2E4F ;
2E4E: 26 01 LD H,$01
2E50: 24 INC H
2E51: 01 22 01 LD BC,$0122
2E54: 20 01 JR NZ,loc_2e57 ;
2E56: 1B DEC DE
loc_2e57:
2E57: 01 18 01 LD BC,$0118
2E5A: 16 01 LD D,$01
2E5C: 11 01 0E LD DE,$0E01
2E5F: 01 0B 01 LD BC,$010B
2E62: 08 EX AF,AF'
2E63: 01 03 01 LD BC,$0103
2E66: 00 NOP
2E67: 01 FD 00 LD BC,$00FD
2E6A: F8 RET M
2E6B: 00 NOP
2E6C: F5 PUSH AF
2E6D: 00 NOP
2E6E: F2 00 ED JP P,$ED00
2E71: 00 NOP
2E72: EA 00 E7 JP PE,$E700
2E75: 00 NOP
2E76: E4 00 DF CALL PO,$DF00
2E79: 00 NOP
2E7A: DC 00 D9 CALL C,$D900
2E7D: 00 NOP
2E7E: D4 00 D1 CALL NC,$D100
2E81: 00 NOP
2E82: CD 00 C8 CALL $C800
2E85: 00 NOP
2E86: C5 PUSH BC
2E87: 00 NOP
2E88: C1 POP BC
2E89: 00 NOP
2E8A: BB CP E
2E8B: 00 NOP
2E8C: B7 OR A
2E8D: 00 NOP
2E8E: B4 OR H
2E8F: 00 NOP
2E90: AE XOR (HL)
2E91: 00 NOP
2E92: A8 XOR B
2E93: 00 NOP
2E94: A1 AND C
2E95: 00 NOP
2E96: 9C SBC A,H
2E97: 00 NOP
2E98: 93 SUB E
2E99: 00 NOP
2E9A: 90 SUB B
2E9B: 00 NOP
2E9C: 88 ADC A,B
2E9D: 00 NOP
2E9E: 80 ADD A,B
2E9F: 00 NOP
2EA0: 7A LD A,D
2EA1: 00 NOP
2EA2: 72 LD (HL),D
2EA3: 00 NOP
2EA4: 69 LD L,C
2EA5: 00 NOP
2EA6: 63 LD H,E
2EA7: 00 NOP
2EA8: 5A LD E,D
2EA9: 00 NOP
2EAA: 51 LD D,C
2EAB: 00 NOP
2EAC: 4A LD C,D
2EAD: 00 NOP
2EAE: 40 LD B,B
2EAF: 00 NOP
2EB0: 37 SCF
2EB1: 00 NOP
2EB2: 30 00 JR NC,loc_2eb4 ;
loc_2eb4:
2EB4: 26 00 LD H,$00
2EB6: 1C INC E
2EB7: 00 NOP
2EB8: 12 LD (DE),A
2EB9: 00 NOP
2EBA: 08 EX AF,AF'
2EBB: 00 NOP
2EBC: 00 NOP
2EBD: 00 NOP
2EBE: 00 NOP
2EBF: 00 NOP
2EC0: F8 RET M
2EC1: FF RST $38
2EC2: EE FF XOR $FF
2EC4: 00 NOP
2EC5: 00 NOP
2EC6: DA FF D0 JP C,$D0FF
2EC9: FF RST $38
2ECA: C9 RET
; ---- $2ECB-$303D: data ----
2ECB: FF C0 FF B6 FF AF FF A6 FF 9D FF 97 FF 8E FF 86
2EDB: FF 80 FF 78 FF 70 FF 6D FF 70 FF 5F FF 58 FF 52
2EEB: FF 4C FF 49 FF 45 FF 3F FF 3B FF 38 FF 33 FF 2F
2EFB: FF 2C FF 27 FF 24 FF 21 FF 21 FF 19 FF 16 FF 13
2F0B: FF 0E FF 0B FF 08 FF 03 FF 00 FF FD FE F8 FE F5
2F1B: FE F2 FE EF FE EA FE E8 FE E5 FE E0 FE DE FE DC
2F2B: FE DA FE D8 FE D4 FE D3 FE D2 FE D1 FE D0 FE CF
2F3B: FE CE FE CE FE CF FE D0 FE D1 FE D2 FE D3 FE D4
2F4B: FE D8 FE DA FE DC FE DE FE E0 FE E5 FE E8 FE EA
2F5B: FE EF FE F2 FE F5 FE F8 FE FD FE 00 FF 03 FF 08
2F6B: FF 0B FF 0E FF 13 FF 16 FF 19 FF 1C FF 21 FF 24
2F7B: FF 27 FF 2C FF 2F FF 33 FF 38 FF 3B FF 3F FF 45
2F8B: FF 49 FF 4C FF 52 FF 58 FF 5F FF 64 FF 6D FF 70
2F9B: FF 78 FF 80 FF 86 FF 8E FF 97 FF 9D FF A6 FF AF
2FAB: FF B6 FF C0 FF C9 FF D0 FF DA FF E4 FF EE FF F8
2FBB: FF 00 00 00 00 08 00 12 00 1C 00 26 00 30 00 37
2FCB: 00 40 00 4A 00 51 00 5A 00 63 00 69 00 72 00 7A
2FDB: 00 80 00 88 00 90 00 93 00 93 00 A1 00 A8 00 AE
2FEB: 00 B4 00 B7 00 BB 00 C1 00 C5 00 C8 00 CD 00 D1
2FFB: 00 D4 00 D9 00 DC 00 DF 00 DC 00 E7 00 EA 00 ED
300B: 00 F2 00 F5 00 F8 00 FD 00 00 01 03 01 08 01 0B
301B: 01 0E 01 11 01 16 01 18 01 11 01 20 01 22 01 24
302B: 01 26 01 28 01 2C 01 2D 01 2E 01 2F 01 30 01 31
303B: 01 32 01
; move a coordinate by three quarters of a displacement, so what it
; carries trails what moves by the whole of it
displaceByThreeQuarters:
303E: 44 LD B,H ; copy the displacement aside to take a quarter of it
303F: 4D LD C,L
3040: CB 28 SRA B ; halve it, keeping its sign...
3042: CB 19 RR C
3044: CB 28 SRA B ; ...and halve again: a quarter of the displacement
3046: CB 19 RR C
3048: A7 AND A ; clear the carry for the subtract
3049: ED 42 SBC HL,BC ; displacement minus its quarter -- three quarters
304B: 19 ADD HL,DE ; add that to the coordinate
304C: C9 RET
; move a coordinate by half a displacement, so what it carries keeps half
; the pace of what moves by the whole of it
displaceByHalf:
304D: 44 LD B,H ; copy the displacement into a scratch pair to halve it
304E: 4D LD C,L
304F: CB 28 SRA B ; arithmetic-shift the copy right one, halving it and keeping its sign
3051: CB 19 RR C
3053: A7 AND A ; clear carry before the subtract
3054: ED 42 SBC HL,BC ; displacement minus its half leaves the complementary half
3056: 19 ADD HL,DE ; add that to the coordinate, so it advances at half the pace, the fraction carrying up into the whole
3057: C9 RET
; place an object's next sprite tile flush against the current one and
; step both cursors onto it
placeAbuttingTile:
3058: FD 46 31 LD B,(IY+$31) ; read one coordinate byte of the current sprite entry
305B: FD 4E 00 LD C,(IY+$00) ; read the other coordinate byte
305E: 3E 10 LD A,$10 ; a sprite's width in pixels...
3060: 80 ADD A,B ; ...advances the first coordinate one tile on
3061: FD 77 33 LD (IY+$33),A ; write it into the next sprite entry
3064: FD 71 02 LD (IY+$02),C ; copy the other coordinate into the next entry unchanged
3067: C3 9B 30 JP advanceToNextSlot ; step both cursors onto the entry just written
; ---- $306A-$3073: data ----
306A: FD 46 31 FD 4E 00 26 08 2E 6E
loc_3074:
3074: 7E LD A,(HL) ; read a byte at the pointer
3075: 81 ADD A,C ; add the running coordinate
3076: FD 70 33 LD (IY+$33),B ; write one coordinate byte into the next sprite entry
3079: FD 77 02 LD (IY+$02),A ; and the summed one into its other field
307C: C3 9B 30 JP advanceToNextSlot ; step both cursors onto the entry just written
loc_307f:
307F: 73 LD (HL),E ; store the coordinate through the pointer
3080: A6 AND (HL) ; fold it into the accumulator
3081: 10 F1 DJNZ loc_3074 ; more slots to place: loop back
3083: D7 RST $10 ; fetch a two-byte entry from the following table
3084: 34 INC (HL) ; step the byte past the entry
3085: A5 AND L
3086: 87 ADD A,A
3087: BF CP A
3088: F1 POP AF ; drop a word off the stack into the accumulator and flags
3089: B9 CP C
; carry an object diagonally onto one more sprite entry, cornering off the
; one it already occupies: a pitch back along the high axis and a pitch on
; along the low one, in one 16-bit add so the low axis's wrap borrows
placeDiagonallyAbuttingTile:
308A: FD 46 31 LD B,(IY+$31) ; read the current entry's high-axis coordinate
308D: FD 4E 00 LD C,(IY+$00) ; read its low-axis coordinate
3090: 26 F0 LD H,$F0 ; a pitch back along the high axis (-16 in the high byte)...
3092: 2E 10 LD L,$10 ; ...and a pitch on along the low axis (+16)
3094: 09 ADD HL,BC ; apply both in one add, so a low-axis wrap borrows into the high axis
3095: FD 74 33 LD (IY+$33),H ; write the high-axis coordinate into the next entry
3098: FD 75 02 LD (IY+$02),L ; write the low-axis coordinate into the next entry
; step the record cursor and the parallel sprite-entry cursor on to the
; next object slot
advanceToNextSlot:
309B: 11 10 00 LD DE,$0010 ; one record stride...
309E: DD 19 ADD IX,DE ; ...steps the record cursor onto the next slot
30A0: FD 23 INC IY ; step the entry cursor...
30A2: FD 23 INC IY ; ...by its two-byte stride onto the next slot
30A4: C9 RET
; sum a fixed 16-byte run against a constant as a discarded tamper
; tripwire, copy eight bytes of the ERA_INDEX-keyed row from the 0x3176
; table into the stride-two run at 0xAA31, then tail into the scenery
; clear+run carrying the era in C and the fill byte 0x28 at era four else
; 0xCC
seatEraSceneryRowThenClearAndRunScenery:
30A5: 21 6B 08 LD HL,$086B ; point at a fixed program run to checksum
30A8: 0E 22 LD C,$22 ; the value it must sum to -- a tamper tripwire
30AA: 06 10 LD B,$10 ; sixteen bytes to fold
30AC: CD 4C 0B CALL sumByteRunAndCompareToExpected; fold the run and compare -- the answer is discarded here
30AF: 3A 04 AD LD A,(workRam+504) ; read the current era
30B2: 87 ADD A,A ; scale the era up by eight -- one row of eight bytes per era
30B3: 87 ADD A,A
30B4: 87 ADD A,A
30B5: 4F LD C,A
30B6: 21 76 31 LD HL,$3176 ; point at the era-keyed scenery-row table
30B9: DF RST $18 ; index it by the era offset -- pointer at the row start
30BA: 11 31 AA LD DE,$AA31 ; point at the object-code cells to seat (stride two)
30BD: 06 08 LD B,$08 ; eight bytes to copy
loc_30bf:
30BF: 7E LD A,(HL) ; take a row byte
30C0: 12 LD (DE),A ; seat it into the object cell
30C1: 23 INC HL
30C2: 13 INC DE ; advance to the next object cell -- two apart
30C3: 13 INC DE
30C4: 10 F9 DJNZ loc_30bf ; repeat for all eight
30C6: 3A 04 AD LD A,(workRam+504) ; read the era again
30C9: FE 04 CP $04 ; is it era four?
30CB: 4F LD C,A ; keep the era for the tail
30CC: CA 56 31 JP Z,seatSceneryFillByte0x28ThenClearEraScenery; era four: hand on with the 0x28 fill byte
30CF: 3E CC LD A,$CC ; otherwise use fill byte 0xCC
; clear a stride-two run of eight object cells to the fill byte carried in
; A, then branch on the era in C: below four, seat and run the frame's
; scenery through the four-object seat path; at four and up, when two
; work-RAM guards read their expected values seat eight entries from a
; packed table before running the scenery, and on a wrong guard transfer
; into a data table and fault
clearSceneryEntriesThenRunEraScenery:
30D1: 21 60 AA LD HL,$AA60 ; point at the object attribute cells
30D4: 11 02 00 LD DE,$0002 ; stride of two
30D7: 06 08 LD B,$08 ; eight cells
loc_30d9:
30D9: 77 LD (HL),A ; clear this cell to the fill byte
30DA: 19 ADD HL,DE ; step two on
30DB: 10 FC DJNZ loc_30d9 ; repeat for all eight
30DD: 79 LD A,C ; recall the era
30DE: FE 04 CP $04 ; below era four?
30E0: DA 17 31 JP C,seedSceneryEntriesThenRunScenery; yes: seed four objects and run the scenery
30E3: 21 C7 AC LD HL,$ACC7 ; point at the first runtime guard cell
30E6: 7E LD A,(HL) ; read it
30E7: FE 3B CP $3B ; is it the expected value?
30E9: C2 5B 31 JP NZ,loc_315b ; no: derail into the data table
30EC: 23 INC HL ; step to the second guard byte
30ED: 7E LD A,(HL) ; read it
30EE: FE 05 CP $05
30F0: CA F8 30 JP Z,loc_30f8 ; value 5 is allowed: continue
30F3: FE 10 CP $10
30F5: C2 5B 31 JP NZ,loc_315b ; not 5 or 16: derail into the data table
loc_30f8:
30F8: 06 08 LD B,$08 ; eight entries to seat
30FA: FD 21 30 AA LD IY,$AA30 ; point at the entry-bank cells
30FE: 21 5E 31 LD HL,$315E ; point at the packed entry table
loc_3101:
3101: 7E LD A,(HL) ; take a table byte
3102: FD 77 31 LD (IY+$31),A ; seat it into the entry's code field
3105: 23 INC HL
3106: 7E LD A,(HL) ; take the next table byte
3107: FD 77 00 LD (IY+$00),A ; seat it into the entry's shadow field
310A: 23 INC HL
310B: FD 23 INC IY ; step to the next entry...
310D: FD 23 INC IY ; ...two bytes on
310F: 10 F0 DJNZ loc_3101 ; repeat for all eight
3111: C3 BC 2C JP runSceneryForEra ; run the frame's scenery
; a bare transfer to 0x307F and no return; no cell is read or written and
; no register moves
trampolineToLoc_307f:
3114: C3 7F 30 JP loc_307f ; hand off to the fill path at 0x307F
; when a sentinel pair reads 0x68 then 0x10-or-0x05, seat four objects
; from a packed table into the sprite cell and shadow of the first four
; entry-bank slots and hand on to the frame's scenery run; otherwise
; transfer to the caption path
seedSceneryEntriesThenRunScenery:
3117: 21 39 AD LD HL,$AD39 ; point at the first sentinel byte
311A: 7E LD A,(HL) ; read it
311B: FE 68 CP $68 ; is it the expected 0x68?
311D: C2 14 31 JP NZ,trampolineToLoc_307f; no: seat nothing, hand off to the derail path
3120: 23 INC HL ; step to the second sentinel byte
3121: 7E LD A,(HL) ; read it
3122: FE 10 CP $10 ; is it 16?
3124: CA 2C 31 JP Z,loc_312c ; yes: seat the objects
3127: FE 05 CP $05 ; or 5?
3129: C2 14 31 JP NZ,trampolineToLoc_307f; neither: seat nothing, hand off to the derail path
loc_312c:
312C: 21 6E 31 LD HL,$316E ; point at the packed four-object table
312F: 06 04 LD B,$04 ; four objects
3131: FD 21 30 AA LD IY,$AA30 ; point at the entry-bank cells
loc_3135:
3135: 7E LD A,(HL) ; take the object's tint byte
3136: FD 77 31 LD (IY+$31),A ; seat it into the entry's code field
3139: C6 10 ADD A,$10 ; one tile on...
313B: FD 77 33 LD (IY+$33),A ; ...into the abutting code field
313E: 23 INC HL
313F: 7E LD A,(HL) ; take the object's shape byte
3140: FD 77 00 LD (IY+$00),A ; seat it into the entry's shadow field
3143: FD 77 02 LD (IY+$02),A ; and its neighbour
3146: 23 INC HL
3147: 11 10 00 LD DE,$0010 ; one record stride...
314A: DD 19 ADD IX,DE ; ...steps the record cursor
314C: 11 04 00 LD DE,$0004 ; four-byte entry stride...
314F: FD 19 ADD IY,DE ; ...steps the entry cursor a row
3151: 10 E2 DJNZ loc_3135 ; repeat for all four
3153: C3 BC 2C JP runSceneryForEra ; run the frame's scenery
; fix the fill byte and transfer to 0x30D1 without returning; choosing
; that one constant is the entire content of the entry, so whatever the
; caller carried in its place is discarded
seatSceneryFillByte0x28ThenClearEraScenery:
3156: 3E 28 LD A,$28 ; force the fill byte to 0x28
3158: C3 D1 30 JP clearSceneryEntriesThenRunEraScenery; hand on to clear the entries and run the era's scenery
loc_315b:
315B: C3 76 31 JP loc_3176 ; derail into the scenery-row data table when a guard reads wrong
; ---- $315E-$3175: data ----
315E: 40 68 38 62 60 70 68 D8 88 58 99 B0 37 43 CF 78
316E: 20 D0 50 60 A0 A0 D0 60
loc_3176:
3176: 60 LD H,B
3177: 68 LD L,B
3178: 61 LD H,C
3179: 60 LD H,B
317A: 61 LD H,C
317B: 62 LD H,D
317C: 63 LD H,E
317D: 5C LD E,H
317E: 74 LD (HL),H
317F: 75 LD (HL),L
3180: 76 HALT
3181: 60 LD H,B
3182: 61 LD H,C
3183: 64 LD H,H
3184: 65 LD H,L
3185: 5D LD E,L
3186: 77 LD (HL),A
3187: 78 LD A,B
3188: 79 LD A,C
3189: 66 LD H,(HL)
318A: 67 LD H,A
318B: 64 LD H,H
318C: 65 LD H,L
318D: 5E LD E,(HL)
318E: 7A LD A,D
318F: 7B LD A,E
3190: 7C LD A,H
3191: 60 LD H,B
3192: 61 LD H,C
3193: 62 LD H,D
3194: 63 LD H,E
3195: 5F LD E,A
3196: 31 30 33 LD SP,$3330
3199: 32 85 86 LD ($8685),A
319C: 87 ADD A,A
319D: 85 ADD A,L
319E: 08 EX AF,AF'
319F: A7 AND A
31A0: 32 CA 7E LD ($7ECA),A
31A3: C8 RET Z
31A4: FF RST $38
31A5: 5F LD E,A
31A6: 93 SUB E
31A7: FB EI
31A8: C4 AF D8 CALL NZ,$D8AF
31AB: 2A 6C E1 LD HL,($E16C)
31AE: 7A LD A,D
31AF: 42 LD B,D
31B0: BD CP L
31B1: B0 OR B
31B2: 5A LD E,D
31B3: B9 CP C
; on the 00s and 30s tenths of the packed-decimal life counter 0xad05,
; service enemy-craft slot (units digit, only slots 0-6 whose record head
; at 0xa850 reads 0xff): advance that record's shape animation, then
; unless the state byte at ix+8 is 0x10 re-aim its heading toward a point
; the state byte indexes out of the aim table at 0xac65 -- state 0x11 aims
; at the table base, stores heading+0x80 into ix+1 and resets the record
; to state 0x10, every other state stores the heading straight into ix+1;
; on every other tenth hand off to layOutEnemyAimPointsFromScrollAngle
reaimAndAnimateEnemyCraftOnPhaseTick:
31B4: 3A 05 AD LD A,(workRam+505) ; read the packed-decimal life-tick
31B7: 4F LD C,A ; keep it
31B8: E6 F0 AND $F0 ; look at the tens digit
31BA: 28 0D JR Z,loc_31c9 ; tens 0: service a craft slot
31BC: FE 30 CP $30 ; tens 3?
31BE: C2 6C 32 JP NZ,layOutEnemyAimPointsFromScrollAngle; any other tens: lay out the aim points instead
31C1: 3A 03 49 LD A,(rom+4903) ; read a fixed program-image byte as a guard
31C4: FE 30 CP $30 ; expect it to read 0x30
31C6: C2 C9 31 JP NZ,loc_31c9 ; on to servicing the slot either way
loc_31c9:
31C9: 79 LD A,C ; recall the life-tick
31CA: E6 0F AND $0F ; take the units digit as a slot number
31CC: FE 07 CP $07 ; seven or more?
31CE: D0 RET NC ; no such craft slot: leave
31CF: DD 21 50 A8 LD IX,$A850 ; point at the craft records
31D3: FD 21 1A AA LD IY,$AA1A ; point at the parallel craft entries
31D7: 87 ADD A,A ; double the slot for the two-byte entry stride
31D8: 4F LD C,A
31D9: 06 00 LD B,$00
31DB: FD 09 ADD IY,BC ; step the entry cursor onto this slot
31DD: 87 ADD A,A ; double three more times for the sixteen-byte record stride
31DE: 87 ADD A,A
31DF: 87 ADD A,A
31E0: 4F LD C,A
31E1: DD 09 ADD IX,BC ; step the record cursor onto this slot
31E3: DD 7E 00 LD A,(IX+$00) ; read the record's head byte
31E6: 3C INC A ; is it 0xFF -- occupied?
31E7: C0 RET NZ ; empty slot: leave
31E8: CD 3A 32 CALL stepShapeAnimation ; advance the craft's shape animation
31EB: DD 7E 08 LD A,(IX+$08) ; read its state byte
31EE: FE 10 CP $10 ; held?
31F0: C8 RET Z ; held: nothing more to do
31F1: FE 11 CP $11 ; the re-aim-then-hold state?
31F3: 28 0C JR Z,loc_3201 ; yes: aim at the table base and latch to held
31F5: 87 ADD A,A ; state x 2 to index the aim table
31F6: 21 65 AC LD HL,$AC65 ; point at the aim-point table
31F9: DF RST $18 ; index it by the state
31FA: CD B8 33 CALL headingToward ; compute the heading toward that aim point
31FD: DD 77 01 LD (IX+$01),A ; store it as the craft's heading
3200: C9 RET
loc_3201:
3201: 21 65 AC LD HL,$AC65 ; point at the aim-point table base
3204: CD B8 33 CALL headingToward ; compute the heading toward it
3207: C6 80 ADD A,$80 ; turn it a half-circle
3209: DD 77 01 LD (IX+$01),A ; store as the craft's heading
320C: DD 36 08 10 LD (IX+$08),$10 ; set the record to the held state
3210: DD 36 09 00 LD (IX+$09),$00 ; clear its step timer
3214: C9 RET
; stock the machine for a game with only the FIRST player's context filled
; in: park the caption sprites, raise PLAY_ACTIVE, clear PLAYER_TWO_LIVES
; and the flag beside PLAY_ACTIVE, load PLAYER_ONE_LIVES from the settings
; cell carrying the starting count, TAKE ONE CREDIT off the packed-decimal
; count at 0xA986 and repaint the panel field from it, copy a fixed set of
; tilemap cells into their keeps, and send the sequence machine to its
; last phase. The subtract is decimal-corrected the way the hardware does
; it, so a byte that was never valid packed decimal still lands where the
; hardware would put it
startOnePlayerGame:
3215: CD 2B 0B CALL hideCaptionSprites ; park the caption sprites off-screen
3218: AF XOR A ; zero
3219: 32 31 AD LD (workRam+531),A ; clear the two-player-game flag
321C: 32 20 AD LD (workRam+520),A ; clear player two's life count
321F: 3D DEC A ; all bits set
3220: 32 30 AD LD (workRam+530),A ; raise the play-active flag
3223: 3A C1 A9 LD A,(workRam+1C1) ; read the starting life count from the settings
3226: 32 10 AD LD (workRam+510),A ; seat it as player one's lives
3229: 21 86 A9 LD HL,$A986 ; point at the packed-decimal credit count
322C: 7E LD A,(HL) ; read it
322D: D6 01 SUB $01 ; take one credit
322F: 27 DAA ; decimal-adjust so it stays valid packed decimal
3230: 77 LD (HL),A ; store the reduced count
3231: CD FB 4A CALL paintCreditCountPanel; repaint the credit count on the panel
3234: CD 30 4B CALL copyThreeTilemapCellsFromBothPlanes; copy three tilemap cells from both planes into their keeps
3237: C3 2A 17 JP seatSequencePhase3AndResetSubStep; send the sequence machine to its last phase
; count one record's step timer down and refresh that record's shape byte
; from the entry the NEW count selects, in the run its own selector byte
; points at; a timer already at zero is left alone
stepShapeAnimation:
323A: DD 7E 09 LD A,(IX+$09) ; read the record's step timer
323D: A7 AND A ; is it already zero?
323E: C8 RET Z ; yes: leave the animation stopped
323F: 3D DEC A ; count the timer down one
3240: DD 77 09 LD (IX+$09),A ; store it
3243: 4F LD C,A ; hold the new count as the step index
3244: DD 7E 0A LD A,(IX+$0A) ; read the record's run selector
3247: 21 38 34 LD HL,$3438 ; point at the table of shape-run pointers
324A: D7 RST $10 ; fetch this record's run pointer
324B: EB EX DE,HL
324C: 79 LD A,C ; recall the step index
324D: CF RST $08 ; fetch the shape byte for this step
324E: DD 77 08 LD (IX+$08),A ; store it as the record's shape
3251: C9 RET
; fold a fixed span of the program image and let the sequence's inner step
; go on if it still adds up; a span that does not fold to the expected
; value throws the sequence a whole phase forward instead, which derails
; it rather than halting it
guardBlockOrDerailSequence:
3252: 01 00 03 LD BC,$0300 ; 768 bytes to fold
3255: 21 08 00 LD HL,$0008 ; starting at the program image
3258: 1E 00 LD E,$00 ; clear the running exclusive-or
loc_325a:
325A: 7B LD A,E
325B: AE XOR (HL) ; fold the next byte into the running exclusive-or
325C: 23 INC HL
325D: 0B DEC BC ; count it off
325E: 5F LD E,A
325F: 79 LD A,C
3260: B0 OR B ; loop until the whole span is folded
3261: 20 F7 JR NZ,loc_325a ;
3263: 3E 52 LD A,$52 ; the expected complement
3265: 83 ADD A,E ; add it to the fold
3266: C2 11 0F JP NZ,advanceSequencePhase; a tampered span nets non-zero: throw the sequence a phase forward
3269: C3 1A 0F JP advanceSequenceSubStep; otherwise step the sequence's sub-step
; when the mode byte in C selects sub-mode 7 (low nibble == 7), fill
; sprite object 0xac64's twelve coordinate fields (0x10-0x1b) with six XY
; pairs around centre (0x78 across, 0x84 down): the scroll angle +0x40 and
; the scroll angle itself, each drawn through the velocity table (via
; 0x59d1) at x8 and x16 radii, the +0x40 direction also mirrored to its
; negatives; other sub-modes return without writing
layOutEnemyAimPointsFromScrollAngle:
326C: 79 LD A,C ; recall the mode byte
326D: E6 0F AND $0F ; its sub-mode nibble
326F: FE 07 CP $07 ; sub-mode 7?
3271: C0 RET NZ ; any other sub-mode: leave without writing
3272: DD 21 64 AC LD IX,$AC64 ; point at the sprite object to fill
3276: 3A 02 A8 LD A,(workRam+2) ; read the scroll angle
3279: C6 40 ADD A,$40 ; turn it a quarter-circle
327B: CD D1 59 CALL loc_59d1 ; look up the velocity vector for that direction
327E: EB EX DE,HL
327F: 29 ADD HL,HL ; scale the vector left three places to an x8 radius
3280: 29 ADD HL,HL
3281: 29 ADD HL,HL
3282: 7C LD A,H
3283: C6 78 ADD A,$78 ; offset the across component from screen centre
3285: DD 77 10 LD (IX+$10),A ; store the first aim point's X
3288: 7C LD A,H
3289: ED 44 NEG ; flip the component's sign to mirror it...
328B: C6 78 ADD A,$78 ; ...the same distance the other side of centre
328D: DD 77 14 LD (IX+$14),A ; store the mirrored aim point's X
3290: 29 ADD HL,HL ; double again to an x16 radius
3291: 7C LD A,H
3292: C6 78 ADD A,$78 ; offset from centre
3294: DD 77 12 LD (IX+$12),A ; store the second aim point's X
3297: 7C LD A,H
3298: ED 44 NEG ; mirror across centre...
329A: C6 78 ADD A,$78 ; ...back from centre
329C: DD 77 16 LD (IX+$16),A ; store its mirror's X
329F: 60 LD H,B ; take the down component of the vector
32A0: 69 LD L,C
32A1: 29 ADD HL,HL ; scale left three places to an x8 radius
32A2: 29 ADD HL,HL
32A3: 29 ADD HL,HL
32A4: 7C LD A,H
32A5: C6 84 ADD A,$84 ; offset from screen centre down
32A7: DD 77 11 LD (IX+$11),A ; store the first aim point's Y
32AA: 7C LD A,H
32AB: ED 44 NEG ; mirror across centre...
32AD: C6 84 ADD A,$84 ; ...back from centre
32AF: DD 77 15 LD (IX+$15),A ; store the mirrored Y
32B2: 29 ADD HL,HL ; double to an x16 radius
32B3: 7C LD A,H
32B4: C6 84 ADD A,$84 ; offset from centre
32B6: DD 77 13 LD (IX+$13),A ; store the second aim point's Y
32B9: 7C LD A,H
32BA: ED 44 NEG ; mirror across centre...
32BC: C6 84 ADD A,$84 ; ...back from centre
32BE: DD 77 17 LD (IX+$17),A ; store its mirror's Y
32C1: 3A 02 A8 LD A,(workRam+2) ; read the scroll angle again -- not turned this time
32C4: CD D1 59 CALL loc_59d1 ; look up its velocity vector
32C7: EB EX DE,HL
32C8: 29 ADD HL,HL ; scale left three places to an x8 radius
32C9: 29 ADD HL,HL
32CA: 29 ADD HL,HL
32CB: 7C LD A,H
32CC: C6 78 ADD A,$78 ; offset from centre across
32CE: DD 77 18 LD (IX+$18),A ; store the fifth aim point's X
32D1: 29 ADD HL,HL ; double to an x16 radius
32D2: 7C LD A,H
32D3: C6 78 ADD A,$78 ; offset from centre
32D5: DD 77 1A LD (IX+$1A),A ; store the sixth aim point's X
32D8: 60 LD H,B ; take the down component
32D9: 69 LD L,C
32DA: 29 ADD HL,HL ; scale left three places to an x8 radius
32DB: 29 ADD HL,HL
32DC: 29 ADD HL,HL
32DD: 7C LD A,H
32DE: C6 84 ADD A,$84 ; offset from centre down
32E0: DD 77 19 LD (IX+$19),A ; store the fifth aim point's Y
32E3: 29 ADD HL,HL ; double to an x16 radius
32E4: 7C LD A,H
32E5: C6 84 ADD A,$84 ; offset from centre
32E7: DD 77 1B LD (IX+$1B),A ; store the sixth aim point's Y
32EA: C9 RET
; hold the machine still at power-on and then hand it over: count twelve
; passes down in a work-RAM cell, petting the watchdog 256 times inside
; each so the board is never reset while nothing happens, leave the cell
; and the two counting registers at zero and the pointer on the cell, tell
; the audio processor to go quiet, pick up the byte that decides the
; interrupt-enable bit, and fall into the routine that starts the machine
petWatchdogThroughStartupDelayThenStartMachine:
32EB: 32 00 C2 LD (dsw1),A ; kick the watchdog
32EE: 21 EB A9 LD HL,$A9EB ; point at the startup-delay cell
32F1: 36 0C LD (HL),$0C ; set it to twelve passes
loc_32f3:
32F3: 01 00 00 LD BC,$0000 ; reset the inner and outer tick counters
loc_32f6:
32F6: 10 FE DJNZ loc_32f6 ; spin the inner counter down -- a pure time delay
32F8: 32 00 C2 LD (dsw1),A ; kick the watchdog
32FB: 0D DEC C ; count the outer loop
32FC: 20 F8 JR NZ,loc_32f6 ; 256 kicks per pass
32FE: 35 DEC (HL) ; count one pass off the startup-delay cell
32FF: 20 F2 JR NZ,loc_32f3 ; twelve passes in all
3301: AF XOR A ; command 0...
3302: CD F8 55 CALL sendSoundCommand ; ...tell the audio processor to go quiet
3305: 3A 87 4C LD A,(rom+4C87) ; pick up the byte that sets the interrupt-enable bit
3308: C3 A8 00 JP enableInterruptAndEnterForegroundLoop; enable interrupts and drop into the foreground loop
loc_330b:
330B: 21 EB A9 LD HL,$A9EB ; point at the delay cell
330E: 35 DEC (HL) ; count it down
330F: C0 RET NZ ; not yet elapsed: leave
3310: CD C3 4C CALL fileScoreIntoHighScoreTable; try to file the score into the high-score table
3313: D2 26 33 JP NC,loc_3326 ; it did not place: branch away
3316: 11 09 03 LD DE,$0309
3319: FF RST $38 ; queue ring command 3 / argument 9
331A: 1E 0B LD E,$0B
331C: FF RST $38 ; queue ring command 3 / argument 11
331D: 3A 43 08 LD A,(rom+843) ; take a fixed byte from the program image
3320: 32 AC A9 LD (workRam+1AC),A ; seat it
3323: C3 E7 12 JP passTurnToOtherPlayerIfLivesElseStepSequence; pass the turn to the other player if lives remain, else step the sequence
loc_3326:
3326: CD 3A 58 CALL loc_583a ;
3329: 3E 00 LD A,$00 ; zero...
332B: 32 0C AD LD (workRam+50C),A ; ...clear the pen colour
332E: 3E F1 LD A,$F1 ; 0xF1...
3330: 32 0B AD LD (workRam+50B),A ; ...set the pen glyph
3333: CD E1 01 CALL armThePenRouteThenColdStartOnATamperedImage; re-arm the pen route
3336: 06 00 LD B,$00 ; 256 bytes to fold
3338: 21 F1 01 LD HL,$01F1 ; starting at this program run
333B: AF XOR A ; clear the running sum
loc_333c:
333C: 86 ADD A,(HL) ; fold the next byte in
333D: 23 INC HL
333E: 10 FC DJNZ loc_333c ; over all 256 bytes
3340: D6 19 SUB $19 ; subtract the expected sum
3342: C4 11 0F CALL NZ,advanceSequencePhase; a tampered run: throw the sequence a phase forward
3345: C3 1A 0F JP advanceSequenceSubStep; then step the sequence's sub-step
; ---- $3348-$335D: data ----
3348: 11 A7 13 68 3B 34 F1 68 D7 F1 DC 0F 68 F1 88 57
3358: A5 BF 34 D7 ED B9
; sequence-machine arm: fold a fixed image run into the sequence-phase
; cell as a tamper tripwire (net-zero on a genuine image), then seat the
; caption pen (glyph 0xAD0B / colour 0xAD0C, and the active player's save
; block) from a two-byte glyph/colour record indexed by that player's era;
; steps the sub-step an extra time if the pen colour was unchanged, re-
; arms the pen route, then steps the sub-step again as a tail
seatCaptionPenFromEraFoldingTamperIntoPhase:
335E: 3A AB A9 LD A,(workRam+1AB) ; read the sequence-phase cell -- the tamper accumulator
3361: 21 8C 17 LD HL,$178C ; point at the image block to fold
3364: 06 1E LD B,$1E ; thirty bytes
loc_3366:
3366: 86 ADD A,(HL) ; fold the next byte in
3367: 23 INC HL
3368: 10 FC DJNZ loc_3366 ; over all thirty
336A: C6 2C ADD A,$2C ; add the genuine-image bias
336C: 32 AB A9 LD (workRam+1AB),A ; store back into the phase cell -- nets to leave the phase standing on a genuine image
336F: 3A 32 AD LD A,(workRam+532) ; read which player is up
3372: A7 AND A ; is it player one?
3373: 11 1B AD LD DE,$AD1B ; point at player one's saved-pen record
3376: 3A 14 AD LD A,(workRam+514) ; read player one's era
3379: 28 06 JR Z,loc_3381 ; player one: use those
337B: 11 2B AD LD DE,$AD2B ; otherwise point at player two's saved-pen record
337E: 3A 24 AD LD A,(workRam+524) ; read player two's era
loc_3381:
3381: 87 ADD A,A ; era x 2 to index the glyph/colour table
3382: 21 8D 0F LD HL,$0F8D ; point at the glyph/colour table
3385: CF RST $08 ; fetch the era's glyph
3386: 12 LD (DE),A ; write it into the saved-pen record
3387: 32 0B AD LD (workRam+50B),A ; and onto the live pen glyph
338A: 23 INC HL
338B: 13 INC DE
338C: 7E LD A,(HL) ; fetch the era's colour
338D: 12 LD (DE),A ; write it into the saved-pen record
338E: 21 0C AD LD HL,$AD0C ; point at the live pen colour
3391: BE CP (HL) ; did the pen colour already hold this?
3392: 77 LD (HL),A ; set the live pen colour
3393: CC 1A 0F CALL Z,advanceSequenceSubStep; if it was unchanged, step the sub-step an extra time
3396: CD E1 01 CALL armThePenRouteThenColdStartOnATamperedImage; re-arm the pen route
3399: C3 1A 0F JP advanceSequenceSubStep; step the sub-step as a tail
; seed the pen the active player's SAVED context block will hand back —
; the glyph and the colour a caption is stamped in — from the era recorded
; in that same block, both halves coming as one two-byte record out of an
; inline table the era indexes; the live pen is left alone, where the
; nearer arm at 0x335E sets it too, sums a run of image bytes into a
; tamper cell before doing any of it, and can repaint
setSavedPenFromEra:
339C: 3A 32 AD LD A,(workRam+532) ; read which player is up
339F: A7 AND A ; player one?
33A0: 11 1B AD LD DE,$AD1B ; point at player one's saved-pen record
33A3: 3A 14 AD LD A,(workRam+514) ; read player one's round index
33A6: 28 06 JR Z,loc_33ae ; player one: use those
33A8: 11 2B AD LD DE,$AD2B ; otherwise player two's saved-pen record
33AB: 3A 24 AD LD A,(workRam+524) ; read player two's round index
loc_33ae:
33AE: 87 ADD A,A ; round x 2 to index the table
33AF: 21 8D 0F LD HL,$0F8D ; point at the glyph/colour table
33B2: DF RST $18 ; index it by the round
33B3: ED A0 LDI ; copy the glyph into the saved-pen record
33B5: ED A0 LDI ; copy the colour after it
33B7: C9 RET
; return the heading from an object to a point as a byte of a 256-step
; circle: the signs and relative sizes of the two axis differences pick
; one of eight octants, and the shorter leg over the longer places the
; answer at one of thirty-two rungs inside it
headingToward:
33B8: 0E 00 LD C,$00 ; start the sector code at zero
33BA: FD 46 31 LD B,(IY+$31) ; the object's second-axis position
33BD: 5E LD E,(HL) ; the point's first-axis coordinate -- kept for later
33BE: 2D DEC L ; step to the point's second-axis coordinate
33BF: 7E LD A,(HL) ; read it
33C0: 90 SUB B ; reach to the point along the second axis
33C1: 30 04 JR NC,loc_33c7 ; already positive
33C3: ED 44 NEG ; take its magnitude
33C5: CB C1 SET 0,C ; mark the second axis as running negative
loc_33c7:
33C7: 57 LD D,A ; hold the second-axis distance
33C8: FD 46 00 LD B,(IY+$00) ; the object's first-axis position
33CB: 7B LD A,E ; recall the point's first-axis coordinate
33CC: 90 SUB B ; reach along the first axis
33CD: 30 04 JR NC,loc_33d3 ; already positive
33CF: ED 44 NEG ; take its magnitude
33D1: CB C9 SET 1,C ; mark the first axis as running negative
loc_33d3:
33D3: 5F LD E,A ; hold the first-axis distance
33D4: 08 EX AF,AF'
33D5: 7B LD A,E
33D6: 08 EX AF,AF'
33D7: 92 SUB D ; compare the two distances
33D8: 28 35 JR Z,loc_340f ; equal: read a fixed diagonal heading
33DA: 30 02 JR NC,loc_33de ; first axis is the longer
33DC: CB D1 SET 2,C ; first axis is the shorter
loc_33de:
33DE: 2E 00 LD L,$00 ; clear the low byte of the dividend
33E0: CB 51 BIT 2,C ; which leg is the shorter?
33E2: 20 03 JR NZ,loc_33e7 ;
33E4: 62 LD H,D ; shorter = second-axis distance
33E5: 18 02 JR loc_33e9 ;
loc_33e7:
33E7: 63 LD H,E ; shorter = first-axis distance
33E8: 5A LD E,D ; longer = second-axis distance, the divisor
loc_33e9:
33E9: 06 08 LD B,$08 ; eight quotient bits
33EB: AF XOR A ; clear the remainder
loc_33ec:
33EC: ED 6A ADC HL,HL ; shift the dividend up, carrying a quotient bit
33EE: 7C LD A,H
33EF: 38 03 JR C,loc_33f4 ;
33F1: BB CP E ; does the divisor go into the running remainder?
33F2: 38 03 JR C,loc_33f7 ;
loc_33f4:
33F4: 93 SUB E ; subtract the divisor
33F5: 67 LD H,A
33F6: AF XOR A
loc_33f7:
33F7: 3F CCF ; shift the quotient bit in
33F8: 10 F2 DJNZ loc_33ec ; eight bits in all
33FA: 45 LD B,L ; the quotient is the rung within the sector
33FB: 79 LD A,C ; the sector code
33FC: 21 15 34 LD HL,$3415 ; point at the sector-heading table
33FF: DF RST $18 ; index it by the sector
3400: 78 LD A,B ; the rung...
3401: 0F RRCA ; ...scaled down to one of thirty-two rungs
3402: 0F RRCA
3403: E6 1F AND $1F
3405: CB 6E BIT 5,(HL) ; does this sector count its rungs backwards?
3407: 28 04 JR Z,loc_340d ;
3409: 47 LD B,A
340A: 3E 1F LD A,$1F
340C: 90 SUB B ; reverse the rung within the sector
loc_340d:
340D: 86 ADD A,(HL) ; add the sector's base heading
340E: C9 RET
loc_340f:
340F: 21 1D 34 LD HL,$341D ; point at the diagonal-heading table
3412: 79 LD A,C ; the sector code
3413: CF RST $08 ; read the fixed heading for this diagonal
3414: C9 RET
; ---- $3415-$36AE: data ----
3415: 20 40 C0 A0 00 60 E0 80 20 60 E0 A0 21 50 0C CD
3425: 8C 01 EB 5E 23 56 23 23 3A 0C AD C6 05 E6 0F 4F
3435: C3 FF 0B 6F 34 8F 34 AF 34 CF 34 EF 34 0F 35 2F
3445: 35 4F 35 6F 35 8F 35 AF 35 CF 35 EF 35 0F 36 2F
3455: 36 4F 36 6F 36 8F 36 11 A7 13 68 3B 34 F1 88 57
3465: A5 BF 34 D7 F1 68 3B 57 BF B9 11 09 09 09 09 09
3475: 09 09 09 09 09 09 09 09 09 09 09 09 09 09 09 09
3485: 09 09 09 09 09 09 09 09 09 09 11 08 08 08 08 08
3495: 08 08 08 08 08 08 08 08 08 08 09 08 08 08 08 08
34A5: 08 08 08 08 08 08 08 08 08 08 11 00 00 00 00 00
34B5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
34C5: 00 00 00 00 00 00 00 00 00 00 11 0A 0A 0A 0A 0A
34D5: 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A
34E5: 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 11 0B 0B 0B 0B 0B
34F5: 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B
3505: 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 11 10 10 10 10 10
3515: 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
3525: 10 10 10 10 10 10 10 10 10 10 11 10 10 10 10 10
3535: 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
3545: 10 10 10 10 10 10 10 10 10 0D 11 10 10 10 10 10
3555: 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
3565: 10 10 10 10 10 10 10 10 0C 0D 11 10 10 10 10 10
3575: 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
3585: 10 10 10 10 10 10 10 0C 0D 0D 11 09 09 09 09 09
3595: 09 09 09 09 09 09 09 09 09 09 09 09 09 09 09 09
35A5: 09 09 09 09 09 09 09 10 10 10 11 08 08 08 08 08
35B5: 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08
35C5: 08 08 08 08 08 08 08 10 10 10 11 00 00 00 00 00
35D5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
35E5: 00 00 00 00 00 00 00 10 10 10 11 0A 0A 0A 0A 0A
35F5: 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A
3605: 0A 0A 0A 0A 0A 0A 0A 10 10 10 11 0B 0B 0B 0B 0B
3615: 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B
3625: 0B 0B 0B 0B 0B 0B 0B 10 10 10 11 10 10 10 10 10
3635: 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
3645: 10 10 10 10 10 10 10 10 10 10 11 10 10 10 10 10
3655: 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
3665: 10 10 10 10 10 10 0D 10 10 10 11 10 10 10 10 10
3675: 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
3685: 10 10 10 10 10 10 10 10 10 10 11 10 10 10 10 10
3695: 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
36A5: 10 10 10 10 10 10 0D 10 10 10
; enemy-wave substep: while the wave-hold cell 0xacc6 is clear, dispatch
; by era and life-phase -- era 4 to spawnEnemyWaveIntoFreeSlots, phase 7
; to stopFiveSlotAnimations, phase below 7 to
; gateTheFreeSlotSearchAndPickItsRun, phase 8 to
; spawnEnemyCraftWhenBandUnderTwo; at phase 9+ with the low life-tick
; 0xad05 spent, spawn a fresh wave inline across the 0xa850/0xaa1a craft
; band from a heading-biased shape run, then request a sound once enough
; of the five slots filled
driveEnemyWaveForLifePhase:
36AF: 3A C6 AC LD A,(workRam+4C6) ; read the wave-hold cell
36B2: A7 AND A ; test it
36B3: C0 RET NZ ; a wave is being held: nothing to do this frame
36B4: 3A 04 AD LD A,(workRam+504) ; read the era number
36B7: FE 04 CP $04 ; is this era 4?
36B9: CA 6E 38 JP Z,spawnEnemyWaveIntoFreeSlots; era 4: spawn the wave straight into the free slots
36BC: 21 05 AD LD HL,$AD05 ; point at the low life-tick byte -- the phase tails all read it
36BF: 3A 06 AD LD A,(workRam+506) ; read the life-phase byte
36C2: E6 0F AND $0F ; keep its low nibble -- the phase
36C4: FE 07 CP $07 ; phase 7?
36C6: CA 55 38 JP Z,stopFiveSlotAnimations; phase 7: settle the five slot animations
36C9: DA BD 37 JP C,gateTheFreeSlotSearchAndPickItsRun; below 7: gate the free-slot search and pick its run
36CC: FE 09 CP $09 ; phase 8?
36CE: DA 9F 37 JP C,spawnEnemyCraftWhenBandUnderTwo; phase 8: spawn a craft while the band is under two
36D1: 7E LD A,(HL) ; read the low life-tick
36D2: A7 AND A ; test it
36D3: C0 RET NZ ; not spent yet: wait
36D4: CD 4B 4B CALL drawRandomByte ; draw a random byte
36D7: 0F RRCA ; roll its low bit into carry
36D8: 3A 04 AD LD A,(workRam+504) ; read the era number
36DB: 8F ADC A,A ; double it and fold the random bit in as the low bit -- the wave descriptor index
36DC: 21 C2 AC LD HL,$ACC2
36DF: 36 FF LD (HL),$FF ; raise the wave mark
36E1: 23 INC HL
36E2: 77 LD (HL),A ; store the wave descriptor index
36E3: 3A 02 A8 LD A,(workRam+2) ; read the player heading
36E6: C6 08 ADD A,$08 ; bias it by 8 -- round to the nearest sector
36E8: 0F RRCA ; shift the biased heading down four to a 0-15 sector index
36E9: 0F RRCA
36EA: 0F RRCA
36EB: 0F RRCA
36EC: E6 0F AND $0F ; mask to the sixteen-sector index
36EE: 21 D9 38 LD HL,$38D9 ; point at the heading-bias table
36F1: DF RST $18 ; index it by the sector
36F2: 4E LD C,(HL) ; take the shape bias for this heading
36F3: 3A C3 AC LD A,(workRam+4C3) ; read the wave descriptor index
36F6: 87 ADD A,A ; times sixteen -- a sixteen-byte descriptor entry
36F7: 87 ADD A,A
36F8: 87 ADD A,A
36F9: 87 ADD A,A
36FA: 21 7B 39 LD HL,$397B ; point at the wave-descriptor table
36FD: DF RST $18 ; index it by the descriptor number
36FE: EB EX DE,HL ; keep the descriptor pointer aside
36FF: 3A C1 AC LD A,(workRam+4C1) ; read the round's craft count
3702: 47 LD B,A ; use it as the slot loop count
3703: 3A 02 AD LD A,(workRam+502) ; read the kills still owed
3706: A7 AND A ; test it
3707: 20 02 JR NZ,loc_370b ; some owed: keep the craft count
3709: 06 05 LD B,$05 ; none owed: fill a fixed five slots instead
loc_370b:
370B: AF XOR A
370C: 32 11 A8 LD (workRam+11),A ; zero the filled-slot counter
370F: DD 21 50 A8 LD IX,$A850 ; point at the first craft record
3713: FD 21 1A AA LD IY,$AA1A ; point at its sprite entry
loc_3717:
3717: DD 7E 00 LD A,(IX+$00) ; read this slot's head byte
371A: A7 AND A ; test it
371B: C2 68 37 JP NZ,loc_3768 ; slot busy: step to the next one
371E: 1A LD A,(DE) ; read the descriptor's shape offset
371F: 81 ADD A,C ; add the heading bias
3720: 87 ADD A,A ; double it -- two-byte shape entries
3721: 21 E9 38 LD HL,$38E9 ; point at the shape-run table
3724: CF RST $08 ; fetch the shape byte
3725: FD 77 31 LD (IY+$31),A ; write the shape into the sprite entry
3728: 23 INC HL
3729: 7E LD A,(HL) ; read the paired attribute byte
372A: FD 77 00 LD (IY+$00),A ; write it to the entry head
372D: 3A 02 A8 LD A,(workRam+2) ; read the player heading
3730: C6 80 ADD A,$80 ; flip it half a turn
3732: DD 77 01 LD (IX+$01),A ; store it as the craft's heading
3735: DD 77 02 LD (IX+$02),A ; and as its facing
3738: CD 2D 38 CALL pickScriptAtRandomOrInTurn; pick a script at random or in turn
373B: C6 09 ADD A,$09 ; offset the script number by nine
373D: DD 77 0A LD (IX+$0A),A ; store the script index
3740: 13 INC DE ; advance to the descriptor's second field
3741: 1A LD A,(DE) ; read the descriptor tail byte
3742: DD 77 0E LD (IX+$0E),A ; store it as the slot's tail
3745: 13 INC DE ; advance the descriptor pointer for the next slot
3746: DD 36 03 00 LD (IX+$03),$00 ; clear the sub-position
374A: DD 36 05 00 LD (IX+$05),$00 ; clear the second sub-position
374E: DD 36 09 20 LD (IX+$09),$20 ; prime the step counter
3752: D9 EXX ; set the working registers aside across the animation step
3753: CD 3A 32 CALL stepShapeAnimation ; step the shape animation once
3756: D9 EXX ; restore them
3757: DD 36 00 FE LD (IX+$00),$FE ; mark the slot live (0xFE)
375B: DD 7E 0E LD A,(IX+$0E) ; read the slot's tail byte
375E: A7 AND A ; test it
375F: 20 03 JR NZ,loc_3764 ; non-zero tail: leave the slot at 0xFE
3761: DD 34 00 INC (IX+$00) ; clear tail: bump the head to 0xFF -- fully settled
loc_3764:
3764: 21 11 A8 LD HL,$A811 ; point at the filled-slot counter
3767: 34 INC (HL) ; count this filled slot
loc_3768:
3768: EB EX DE,HL
3769: 11 10 00 LD DE,$0010 ; a sixteen-byte record stride
376C: DD 19 ADD IX,DE ; advance to the next craft record
376E: FD 23 INC IY ; advance to the next two-byte sprite entry
3770: FD 23 INC IY
3772: EB EX DE,HL
3773: 10 A2 DJNZ loc_3717 ; loop over the slots
3775: AF XOR A
3776: 32 C2 AC LD (workRam+4C2),A ; clear the wave mark
3779: 3E E4 LD A,$E4
377B: 32 12 A8 LD (workRam+12),A ; stamp the wave-claim timer ready (0xE4)
377E: 21 11 A8 LD HL,$A811 ; point at the filled-slot counter
3781: 7E LD A,(HL) ; read how many filled
3782: FE 05 CP $05 ; five or more?
3784: D2 17 58 JP NC,requestEnemyWaveSound; enough filled: request the enemy-wave sound
3787: 21 C1 AC LD HL,$ACC1 ; point at the round's craft count
378A: BE CP (HL) ; compare the filled count against it
378B: 7E LD A,(HL) ; take the craft count
378C: 32 11 A8 LD (workRam+11),A ; store it as the filled count
378F: D2 17 58 JP NC,requestEnemyWaveSound; filled met the count: request the enemy-wave sound
3792: C9 RET
loc_3793:
3793: 06 05 LD B,$05 ; a fixed run of five slots
3795: DD 21 90 A8 LD IX,$A890 ; point at the fifth craft record
3799: FD 21 22 AA LD IY,$AA22 ; point at its sprite entry
379D: 18 37 JR spawnEnemyIntoFreeSlotElseStepSearch; fill the first free one
; gate a spawn tick on the packed-decimal phase byte the caller points at
; (return unless it is 0x00 or 0x30), count the busy heads across the
; seven-record enemy-craft band at 0xa850, and while fewer than two are
; busy run the free-slot search -- the cleared run via $3793 when the
; owed-kills cell 0xad02 is zero, else the owed run (b from the round's
; craft count 0xacc1, seated at 0xa8b0/0xaa26) via
; spawnEnemyIntoFreeSlotElseStepSearch; stages nothing when the gate is
; shut or two heads are busy
spawnEnemyCraftWhenBandUnderTwo:
379F: 7E LD A,(HL) ; read the phase byte the caller points at
37A0: A7 AND A ; test it
37A1: 28 03 JR Z,loc_37a6 ; zero: an idle tick, go count the band
37A3: FE 30 CP $30 ; the open phase?
37A5: C0 RET NZ ; any other value: not a spawning tick
loc_37a6:
37A6: 21 50 A8 LD HL,$A850 ; point at the first craft record
37A9: 11 10 00 LD DE,$0010 ; the record stride
37AC: 01 00 07 LD BC,$0700 ; seven slots to scan, busy count starts at zero
loc_37af:
37AF: 7E LD A,(HL) ; read this slot's head byte
37B0: A7 AND A ; test it
37B1: 28 01 JR Z,loc_37b4 ; free: skip
37B3: 0C INC C ; busy: count it
loc_37b4:
37B4: 19 ADD HL,DE ; step to the next record
37B5: 10 F8 DJNZ loc_37af ; scan all seven
37B7: 79 LD A,C ; take the busy count
37B8: FE 02 CP $02 ; two or more busy?
37BA: D0 RET NC ; band already full enough: stage nothing
37BB: 18 07 JR loc_37c4 ; fewer than two: pick the search run
; decide whether this is a spawning tick and, if it is, choose which run
; of object slots the free-slot search walks: the caller points at a
; counter cell and only two of its values open the gate, every other value
; ending the entry with nothing staged; past the gate the count of kills
; still owed picks between two runs of the one slot file -- while any are
; owed the run starts two records higher and is as long as the round's
; craft count asks, and once none are owed a fixed run of five starts
; lower -- and control leaves for the search without coming back. The role
; names no address for the gate byte on purpose: it is read through a
; pointer, so the routine itself cannot know what it is
gateTheFreeSlotSearchAndPickItsRun:
37BD: 7E LD A,(HL) ; read the gate byte the caller points at
37BE: A7 AND A ; test it
37BF: 28 03 JR Z,loc_37c4 ; zero: a spawning tick
37C1: FE 30 CP $30 ; the other open value?
37C3: C0 RET NZ ; anything else: gate shut, stage nothing
loc_37c4:
37C4: 3A 02 AD LD A,(workRam+502) ; read the kills still owed
37C7: A7 AND A ; test it
37C8: 28 C9 JR Z,loc_3793 ; none owed: run the fixed five-slot search
37CA: 3A C1 AC LD A,(workRam+4C1) ; read the round's craft count
37CD: 47 LD B,A ; use it as the search length
37CE: DD 21 B0 A8 LD IX,$A8B0 ; point at the seventh craft record
37D2: FD 21 26 AA LD IY,$AA26 ; point at its sprite entry
; work one slot in a downward free-slot search: a busy slot passes the
; turn to the search tail, a free slot is claimed and stocked with a
; random heading-derived velocity, facing, script and fresh animation (at
; most one slot filled per turn); this fills the green enemy-craft band
; (0xA850) one slot at a time
spawnEnemyIntoFreeSlotElseStepSearch:
37D6: DD 7E 00 LD A,(IX+$00) ; read this slot's head byte
37D9: A7 AND A ; test it
37DA: C2 47 38 JP NZ,closeOneTurnOfTheFreeSlotSearch; busy: hand the turn to the search tail
37DD: DD 35 00 DEC (IX+$00) ; claim the free slot -- head to 0xFF
37E0: 3A 02 A8 LD A,(workRam+2) ; read the scroll angle
37E3: 0F RRCA ; shift it down two
37E4: 0F RRCA
37E5: E6 3F AND $3F ; keep a 0-63 heading base
37E7: 4F LD C,A ; hold the base
37E8: CD 4B 4B CALL drawRandomByte ; draw a random byte
37EB: E6 0F AND $0F ; keep a 0-15 jitter
37ED: D6 08 SUB $08 ; centre it about zero
37EF: 81 ADD A,C ; jitter the heading base
37F0: E6 3F AND $3F ; wrap to 0-63
37F2: 21 FB 39 LD HL,$39FB ; point at the heading table
37F5: CF RST $08 ; fetch the heading's velocity index
37F6: 87 ADD A,A ; times four -- four-byte velocity entries
37F7: 87 ADD A,A
37F8: 21 3B 3A LD HL,$3A3B ; point at the velocity table
37FB: CF RST $08 ; fetch the first velocity byte
37FC: FD 77 31 LD (IY+$31),A ; write it into the sprite entry
37FF: 23 INC HL
3800: 7E LD A,(HL) ; read the paired velocity byte
3801: FD 77 00 LD (IY+$00),A ; write it to the entry head
3804: 3A 02 A8 LD A,(workRam+2) ; read the scroll angle
3807: C6 80 ADD A,$80 ; flip it half a turn -- the craft's heading
3809: DD 77 01 LD (IX+$01),A ; store the heading
380C: DD 77 02 LD (IX+$02),A ; and the facing
380F: CD 2D 38 CALL pickScriptAtRandomOrInTurn; pick a script at random or in turn
3812: DD 77 0A LD (IX+$0A),A ; store the script index
3815: AF XOR A
3816: 32 C5 AC LD (workRam+4C5),A ; clear the shared zero cell
3819: DD 36 03 00 LD (IX+$03),$00 ; clear the sub-position
381D: DD 36 05 00 LD (IX+$05),$00 ; clear the second sub-position
3821: DD 36 09 20 LD (IX+$09),$20 ; prime the step counter
3825: CD 3A 32 CALL stepShapeAnimation ; step the shape animation once
3828: DD 36 0E 00 LD (IX+$0E),$00 ; clear the slot's tail byte
382C: C9 RET
; draw a byte and let one comparison against a threshold cell decide which
; of two entirely different answers the caller gets: a draw at or above
; the threshold is folded down to one of four values and handed straight
; back, writing nothing; a draw below it ignores the drawn byte completely
; and instead steps a five-long cycle counter on, wrapping it to zero once
; it would leave the cycle, stores it and hands THAT back. The two arms
; draw from DISJOINT halves rather than sampling one pool two ways: the
; random arm can only answer 5 through 8, the rotation only 0 through 4,
; so which arm ran is recoverable from the answer alone
pickScriptAtRandomOrInTurn:
382D: CD 4B 4B CALL drawRandomByte ; draw a random byte
3830: 21 C4 AC LD HL,$ACC4 ; point at the script-pick threshold
3833: BE CP (HL) ; compare the draw against it
3834: 30 0C JR NC,loc_3842 ; at or above: take the random arm
3836: 21 CF A9 LD HL,$A9CF ; below: point at the script cycle counter
3839: 7E LD A,(HL) ; read it
383A: 3C INC A ; step it on
383B: FE 05 CP $05 ; past the five-long cycle?
383D: 38 01 JR C,loc_3840 ; still in range: keep it
383F: AF XOR A ; wrapped: back to zero
loc_3840:
3840: 77 LD (HL),A ; store the stepped counter
3841: C9 RET ; hand it back
loc_3842:
3842: E6 03 AND $03 ; fold the random draw to 0-3
3844: C6 05 ADD A,$05 ; lift it into the 5-8 band
3846: C9 RET ; hand it back
; close one turn of the search for a free object slot and decide whether
; there is another: step the record cursor back one whole sixteen-byte
; record and the sprite-entry cursor back one two-byte entry, so the
; search walks its bank downward, strike one off the turn count, and while
; any remain transfer back to the body that tries one slot; when the last
; is struck off the search ends having filled nothing and this entry
; simply returns. The wide scratch pair the backward step is built from is
; left standing on the way out
closeOneTurnOfTheFreeSlotSearch:
3847: 11 F0 FF LD DE,$FFF0 ; a backward record stride (minus sixteen)
384A: DD 19 ADD IX,DE ; step the record cursor back one slot
384C: FD 2B DEC IY ; step the entry cursor back one two-byte entry
384E: FD 2B DEC IY
3850: 05 DEC B ; strike one off the turn count
3851: C2 D6 37 JP NZ,spawnEnemyIntoFreeSlotElseStepSearch; turns left: try the next slot
3854: C9 RET ; last turn: the search ends, nothing filled
; leave five consecutive object records standing on the shape a finished
; animation ends on, with their step bytes cleared so nothing walks them
; again — but only while the byte the caller points at still reads zero,
; so it is a guarded settling and not a step
stopFiveSlotAnimations:
3855: 7E LD A,(HL) ; read the guard byte the caller points at
3856: A7 AND A ; test it
3857: C0 RET NZ ; non-zero: touch nothing
3858: DD 21 50 A8 LD IX,$A850 ; point at the first craft record
385C: 11 10 00 LD DE,$0010 ; the record stride
385F: 06 05 LD B,$05 ; five records to settle
loc_3861:
3861: DD 36 08 11 LD (IX+$08),$11 ; set the resting shape
3865: DD 36 09 00 LD (IX+$09),$00 ; clear the step timer -- freeze the animation
3869: DD 19 ADD IX,DE ; step to the next record
386B: 10 F4 DJNZ loc_3861 ; settle all five
386D: C9 RET
; spawn a wave across a fixed bank of object slots: fill each free slot
; from a randomly-drawn shape record (shape index + two fields), prime its
; step counter, step its animation once, mark it live; store a fixed
; status byte when the pass ends
spawnEnemyWaveIntoFreeSlots:
386E: DD 21 50 A8 LD IX,$A850 ; point at the first craft record
3872: FD 21 1A AA LD IY,$AA1A ; point at its sprite entry
3876: 3A C1 AC LD A,(workRam+4C1) ; read the round's craft count
3879: 47 LD B,A ; the wave size
387A: 3A 0D AD LD A,(workRam+50D) ; read the boss-craft flag
387D: A7 AND A ; test it
387E: 28 02 JR Z,loc_3882 ; clear: keep the configured size
3880: 06 05 LD B,$05 ; boss present: fill a fixed five
loc_3882:
3882: C5 PUSH BC ; save the count and the ordinal
3883: DD 7E 00 LD A,(IX+$00) ; read this slot's head byte
3886: A7 AND A ; test it
3887: C2 C0 38 JP NZ,loc_38c0 ; busy: skip to the next slot
388A: CD 4B 4B CALL drawRandomByte ; draw a random byte
388D: E6 FC AND $FC ; mask it to a four-byte shape record
388F: 21 3B 3A LD HL,$3A3B ; point at the shape-record table
3892: CF RST $08 ; fetch the shape index
3893: FD 77 31 LD (IY+$31),A ; write it into the sprite entry
3896: 23 INC HL
3897: 7E LD A,(HL) ; read the entry attribute byte
3898: FD 77 00 LD (IY+$00),A ; write it to the entry head
389B: 23 INC HL
389C: 7E LD A,(HL) ; read the slot field byte
389D: DD 77 01 LD (IX+$01),A ; store it as the heading
38A0: DD 77 02 LD (IX+$02),A ; and as the facing
38A3: 3A C1 AC LD A,(workRam+4C1) ; read the round's craft count
38A6: 90 SUB B ; subtract the remaining count -- the ordinal within the pass
38A7: 21 D2 38 LD HL,$38D2 ; point at the ordinal table
38AA: CF RST $08 ; fetch this slot's per-slot byte
38AB: DD 77 0A LD (IX+$0A),A ; store the script index
38AE: DD 36 09 20 LD (IX+$09),$20 ; prime the step counter
38B2: CD 3A 32 CALL stepShapeAnimation ; step the shape animation once
38B5: DD 36 04 01 LD (IX+$04),$01 ; set the slot's active flag
38B9: DD 36 0E 00 LD (IX+$0E),$00 ; clear the slot's tail byte
38BD: DD 35 00 DEC (IX+$00) ; mark the slot live -- head to 0xFF
loc_38c0:
38C0: 11 10 00 LD DE,$0010 ; the record stride
38C3: DD 19 ADD IX,DE ; step to the next record
38C5: FD 23 INC IY ; step to the next sprite entry
38C7: FD 23 INC IY
38C9: C1 POP BC ; restore the count and ordinal
38CA: 10 B6 DJNZ loc_3882 ; walk the whole bank
38CC: 3E E4 LD A,$E4
38CE: 32 12 A8 LD (workRam+12),A ; stamp the wave-claim timer ready (0xE4)
38D1: C9 RET
; ---- $38D2-$3B5E: data ----
38D2: 0A 0B 0D 0E 0F 09 0C 08 0C 0F 13 16 1A 1D 21 24
38E2: 28 2B 2F 33 37 3A 3D F0 10 F0 20 F0 30 F0 40 F0
38F2: 50 F0 60 F0 70 F0 80 F0 90 F0 A0 F0 B0 F0 C0 F0
3902: D0 F0 E0 F0 F0 E0 F8 D0 F8 C0 F8 B0 F8 A0 F8 90
3912: F8 80 F8 70 F8 60 F8 50 F8 40 F8 30 F8 20 F8 10
3922: F8 00 F0 00 E0 00 D0 00 C0 00 B0 00 A0 00 90 00
3932: 80 00 70 00 60 00 50 00 40 00 30 00 20 00 10 10
3942: 10 20 10 30 10 40 10 50 10 60 10 70 10 80 10 90
3952: 10 A0 10 B0 10 C0 10 D0 10 E0 10 F0 10 F0 20 F0
3962: 30 F0 40 F0 50 F0 60 F0 70 F0 80 F0 90 F0 A0 F0
3972: B0 F0 C0 F0 D0 F0 E0 F0 F0 00 01 01 11 FF 11 02
3982: 21 FE 21 03 31 FD 31 00 00 00 11 01 01 FF 01 02
3992: 11 FE 11 03 21 FD 21 00 00 00 01 02 11 FE 11 03
39A2: 21 FD 21 04 31 FC 31 00 00 00 31 03 01 FD 01 04
39B2: 11 FC 11 03 11 FD 11 00 00 00 01 03 01 FD 01 04
39C2: 11 FC 11 05 21 FB 21 00 00 00 01 03 11 FD 11 00
39D2: 21 03 21 FD 21 00 31 00 00 03 01 FD 01 03 11 FD
39E2: 11 05 11 FB 11 00 29 00 00 00 01 03 11 FD 11 05
39F2: 21 FB 21 03 31 FD 31 00 00 08 09 0A 0B 0C 0D 0D
3A02: 0E 0F 10 11 12 13 14 14 15 16 17 18 19 1A 1B 1B
3A12: 1C 1D 1E 1F 20 21 22 22 23 24 25 26 27 28 29 29
3A22: 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 38
3A32: 39 00 01 02 03 04 05 06 07 F0 10 60 00 F0 20 80
3A42: 00 F0 30 80 00 F0 40 80 00 F0 50 80 00 F0 60 80
3A52: 00 F0 70 80 00 F0 80 80 00 F0 90 80 00 F0 A0 80
3A62: 00 F0 B0 80 00 F0 C0 80 00 F0 D0 80 00 F0 E0 80
3A72: 00 F0 F0 A0 00 E0 F8 C0 00 D0 F8 C0 00 C0 F8 C0
3A82: 00 B0 F8 C0 00 A0 F8 C0 00 90 F8 C0 00 80 F8 C0
3A92: 00 70 F8 C0 00 60 F8 C0 00 50 F8 C0 00 40 F8 C0
3AA2: 00 30 F8 C0 00 20 F8 C0 00 10 F8 C0 00 00 F0 E0
3AB2: 00 00 E0 00 00 00 D0 00 00 00 C0 00 00 00 B0 00
3AC2: 00 00 A0 00 00 00 90 00 00 00 80 00 00 00 70 00
3AD2: 00 00 60 00 00 00 50 00 00 00 40 00 00 00 30 00
3AE2: 00 00 20 00 00 00 10 20 00 10 10 40 00 20 10 40
3AF2: 00 30 10 40 00 40 10 40 00 50 10 40 00 60 10 40
3B02: 00 70 10 40 00 80 10 40 00 90 10 40 00 A0 10 40
3B12: 00 B0 10 40 00 C0 10 40 00 D0 10 40 00 E0 10 40
3B22: 00 F0 10 60 00 F0 20 80 00 F0 30 80 00 F0 40 80
3B32: 00 F0 50 80 00 F0 60 80 00 F0 70 80 00 F0 80 80
3B42: 00 F0 90 80 00 F0 A0 80 00 F0 B0 80 00 F0 C0 80
3B52: 00 F0 D0 80 00 F0 E0 80 00 F0 F0 A0 00
; era-1 only: dispatch the single object at record 0xa8c0 by its head byte
; -- 0 arms its fire timer (armBomberSlotWhenTimerFires), 0xff runs the
; two-tile move (advanceTwoTileObjectThenTryAimedSpawn), any other value
; advances a hit-soaking object toward death
; (advanceHitSoakingObjectThenAnimateDeath); returns untouched outside era
; 1
serviceEra1BomberObject:
3B5F: 3A 04 AD LD A,(workRam+504) ; read the era number
3B62: 3D DEC A ; is it era 1?
3B63: C0 RET NZ ; outside era 1: leave the object untouched
3B64: DD 21 C0 A8 LD IX,$A8C0 ; point at the era-1 object record
3B68: FD 21 28 AA LD IY,$AA28 ; point at its sprite entry
3B6C: DD 7E 00 LD A,(IX+$00) ; read the record head byte
3B6F: A7 AND A ; test it
3B70: CA 25 3C JP Z,armBomberSlotWhenTimerFires; empty: arm its fire timer
3B73: 3C INC A ; is the head 0xFF (fully live)?
3B74: C2 94 3B JP NZ,advanceHitSoakingObjectThenAnimateDeath; a partial count: soak hits toward death -- 0xFF runs the two-tile move
; advance a two-tile object one frame: fly it along its stored velocity,
; then seat its second tile directly under the first (same X, Y+0x10); if
; hasReachedBoundaryBandSelectedByHeading answers it has reached a
; boundary retire it, otherwise dress the pair by heading and run the
; aimed-spawn attempt
advanceTwoTileObjectThenTryAimedSpawn:
3B77: CD 05 3E CALL flyAlongStoredVelocity; fly the object along its stored velocity
3B7A: FD 7E 31 LD A,(IY+$31) ; read the top tile's Y
3B7D: C6 10 ADD A,$10 ; drop sixteen -- one tile down
3B7F: FD 77 33 LD (IY+$33),A ; place the second tile's Y under it
3B82: FD 7E 00 LD A,(IY+$00) ; read the top tile's X
3B85: FD 77 02 LD (IY+$02),A ; give the second tile the same X
3B88: CD C4 3C CALL hasReachedBoundaryBandSelectedByHeading; has it reached a heading-selected boundary?
3B8B: DA 0D 3C JP C,retireObjectAndHold; reached: retire it and hold the slot
3B8E: CD E9 3C CALL mirrorTwoTileObjectByHeading; dress the pair by heading
3B91: C3 25 3D JP spawnAimedEnemyIntoEraBankWhenInWindow; run the aimed-spawn attempt
; advance one hit-soaking object: while HITS_REMAINING (0xa8dc) is left,
; spend one, force the record head live (0xff) and re-request its sound
; pair before the ordinary two-tile move; once no hits remain, run the
; record head down (capped at 0x61) toward a retire-and-hold at 0, drift
; it with the world scroll, and at head 0x40 post a command / on 8-step
; boundaries above 0x40 reseat the sprite shape from the 0x3c09 table
advanceHitSoakingObjectThenAnimateDeath:
3B94: 3D DEC A ; recover the record head byte
3B95: 4F LD C,A
3B96: 21 DC A8 LD HL,$A8DC ; point at the hits-remaining count
3B99: 7E LD A,(HL) ; read it
3B9A: A7 AND A ; test it
3B9B: CA A9 3B JP Z,loc_3ba9 ; no hits left: begin the death sequence
3B9E: 35 DEC (HL) ; spend one hit
3B9F: DD 36 00 FF LD (IX+$00),$FF ; force the record head live
3BA3: CD 83 56 CALL requestTwoSounds ; re-request the craft's two sounds
3BA6: C3 77 3B JP advanceTwoTileObjectThenTryAimedSpawn; run the ordinary two-tile move
loc_3ba9:
3BA9: 79 LD A,C ; take the record head
3BAA: FE 61 CP $61 ; at or past the death cap?
3BAC: 38 0F JR C,loc_3bbd ; below: run it down
3BAE: DD 36 00 61 LD (IX+$00),$61 ; cap the head at 0x61
3BB2: CD 83 56 CALL requestTwoSounds ; request the craft's two sounds
3BB5: FD 36 30 3D LD (IY+$30),$3D ; set the death attribute on tile one
3BB9: FD 36 32 3D LD (IY+$32),$3D ; and on tile two
loc_3bbd:
3BBD: DD 35 00 DEC (IX+$00) ; count the death animation down one
3BC0: 28 4B JR Z,retireObjectAndHold; reached zero: retire and hold the slot
3BC2: CD 60 2B CALL driftWithWorldScroll; drift the object with the world scroll
3BC5: FD 7E 31 LD A,(IY+$31) ; read tile one's Y
3BC8: C6 10 ADD A,$10 ; drop sixteen
3BCA: FD 77 33 LD (IY+$33),A ; place tile two's Y under it
3BCD: FD 7E 00 LD A,(IY+$00) ; read tile one's X
3BD0: FD 77 02 LD (IY+$02),A ; give tile two the same X
3BD3: DD 7E 00 LD A,(IX+$00) ; read the record head
3BD6: D6 40 SUB $40 ; measure it past 0x40
3BD8: CA F1 3B JP Z,loc_3bf1 ; exactly at 0x40: post the burst command
3BDB: D8 RET C ; below 0x40: nothing more this frame
3BDC: 4F LD C,A
3BDD: E6 07 AND $07 ; on an eight-step boundary?
3BDF: C0 RET NZ ; not a boundary: wait
3BE0: 79 LD A,C ; take the distance past 0x40
3BE1: 0F RRCA ; divide it by eight
3BE2: 0F RRCA
3BE3: 0F RRCA
3BE4: 3D DEC A ; step back one -- the death-shape index
3BE5: 21 09 3C LD HL,$3C09 ; point at the death-shape table
3BE8: CF RST $08 ; fetch the shape byte
3BE9: FD 77 03 LD (IY+$03),A ; write it to tile two
3BEC: 3C INC A ; the next shape
3BED: FD 77 01 LD (IY+$01),A ; write it to tile one
3BF0: C9 RET
loc_3bf1:
3BF1: 11 0B 04 LD DE,$040B ; burst command 4, argument 0x0B
3BF4: FF RST $38 ; queue the sound-ring burst command
3BF5: FD 36 03 FA LD (IY+$03),$FA ; set the burst shape on tile two
3BF9: FD 36 01 FB LD (IY+$01),$FB ; and on tile one
3BFD: FD 36 30 6C LD (IY+$30),$6C ; set the burst attribute on tile one
3C01: FD 36 32 6C LD (IY+$32),$6C ; and on tile two
3C05: DD 35 00 DEC (IX+$00) ; step the head past 0x40
3C08: C9 RET
; ---- $3C09-$3C0C: data ----
3C09: 96 94 92 90
; take an object and the slot one stride on out of play -- both record
; heads, both coordinates of the caller's sprite entry and of one fixed
; entry -- then set a further byte of the caller's record to a non-zero
; constant instead of clearing it
retireObjectAndHold:
3C0D: AF XOR A
3C0E: DD 77 00 LD (IX+$00),A ; clear this record's head
3C11: DD 77 10 LD (IX+$10),A ; clear the next record's head too
3C14: FD 77 00 LD (IY+$00),A ; clear the sprite entry's first coordinate
3C17: FD 77 31 LD (IY+$31),A ; clear its second coordinate
3C1A: 32 5B AA LD (workRam+25B),A ; clear a fixed entry's second coordinate
3C1D: 32 2A AA LD (workRam+22A),A ; clear that fixed entry's first coordinate
3C20: DD 36 0E 80 LD (IX+$0E),$80 ; hold the slot -- set its hold byte non-zero
3C24: C9 RET
; on even frames tick a slot's arming countdown at ix+0x0e; when it fires
; and MOTHER_SHIP_ARMED (0xad0d) is clear, arm the slot -- pick a shape
; record from PLAYER_HEADING (0xa802) via the table at 0x3c84, snap the
; heading to a facing bit, look up the velocity pair, write
; shape/facing/velocity into the record, set HITS_REMAINING (0xa8dc)=3,
; and mark the slot live (ix+0=0xff); the era-1 large multi-hit craft
; (removed by a negative control). mechanisms.md identifies this counter-3
; era-1 craft as the 1940 bomber (absorbs three, dies on the fourth hit)
; -- NOT the counter-7 Mother-Ship; the MOTHER_SHIP_ARMED gate names the
; 0xAD0D boss class, and 3c25's sole caller serviceEra1BomberObject
; dispatches it only in era 1
armBomberSlotWhenTimerFires:
3C25: 3A 80 A9 LD A,(workRam+180) ; read the frame counter
3C28: E6 01 AND $01 ; odd frame?
3C2A: C0 RET NZ ; odd frame: only tick on even ones
3C2B: DD 35 0E DEC (IX+$0E) ; count the arming timer down
3C2E: CA 32 3C JP Z,loc_3c32 ; fired: arm the slot
3C31: C9 RET ; not yet
loc_3c32:
3C32: 3A 0D AD LD A,(workRam+50D) ; read the boss-craft flag
3C35: A7 AND A ; test it
3C36: C0 RET NZ ; boss already present: do not arm
3C37: 3A 02 A8 LD A,(workRam+2) ; read the player heading
3C3A: 47 LD B,A
3C3B: C6 08 ADD A,$08 ; bias by 8
3C3D: E6 7F AND $7F ; within the half-circle
3C3F: FE 10 CP $10 ; near a quadrant edge?
3C41: 38 32 JR C,loc_3c75 ; near the edge: nudge the heading toward the axis
3C43: 78 LD A,B ; take the heading
loc_3c44:
3C44: 0F RRCA ; rotate the heading down two
3C45: 0F RRCA
3C46: E6 3E AND $3E ; to an even shape-table offset
3C48: 21 84 3C LD HL,$3C84 ; point at the bomber shape/velocity table
3C4B: CF RST $08 ; fetch the shape byte
3C4C: FD 77 31 LD (IY+$31),A ; write the shape into the sprite entry
3C4F: 23 INC HL
3C50: 7E LD A,(HL) ; read the paired attribute byte
3C51: FD 77 00 LD (IY+$00),A ; write it to the entry head
3C54: 78 LD A,B ; take the heading again
3C55: C6 C0 ADD A,$C0 ; rotate three quarters
3C57: E6 80 AND $80 ; keep the facing bit
3C59: DD 77 02 LD (IX+$02),A ; store the facing
3C5C: CD 42 59 CALL loc_5942 ; look up the velocity pair for this facing
3C5F: DD 73 0A LD (IX+$0A),E ; store a velocity byte
3C62: DD 72 0B LD (IX+$0B),D ; store a velocity byte
3C65: DD 71 0C LD (IX+$0C),C ; store a velocity byte
3C68: DD 70 0D LD (IX+$0D),B ; store a velocity byte
3C6B: 3E 03 LD A,$03
3C6D: 32 DC A8 LD (workRam+DC),A ; set the hits-remaining count to three
3C70: DD 36 00 FF LD (IX+$00),$FF ; mark the slot live
3C74: C9 RET
loc_3c75:
3C75: 3A 80 A9 LD A,(workRam+180) ; read the frame counter
3C78: 4F LD C,A
3C79: 3E 10 LD A,$10 ; a quarter-step nudge
3C7B: CB 59 BIT 3,C ; pick the nudge direction from the frame counter
3C7D: 20 02 JR NZ,loc_3c81 ; one way: keep +0x10
3C7F: ED 44 NEG ; the other way: negate to -0x10
loc_3c81:
3C81: 80 ADD A,B ; nudge the heading
3C82: 18 C0 JR loc_3c44 ; back to the shape lookup
; ---- $3C84-$3CC3: data ----
3C84: EC 80 EC 88 EC 90 EC A0 EC B0 EC C0 EC D0 EC E0
3C94: F0 EC F0 EC F0 E0 F0 D0 F0 C0 F0 B0 F0 A0 F0 90
3CA4: F0 80 F0 78 F0 70 F0 60 F0 50 F0 40 F0 30 F0 28
3CB4: F0 20 EC 20 EC 30 EC 40 EC 50 EC 60 EC 70 EC 78
; answer, in the carry flag, whether an object has reached a boundary, the
; heading choosing which of two adjacent and disjoint three-wide bands is
; the one tested
hasReachedBoundaryBandSelectedByHeading:
3CC4: DD 7E 02 LD A,(IX+$02) ; read the object's heading
3CC7: C6 40 ADD A,$40 ; rotate a quarter turn
3CC9: CB 7F BIT 7,A ; which half of the compass?
3CCB: C2 D9 3C JP NZ,hasDriftedOffTheField; one half: test the vertical drift band
3CCE: FD 7E 31 LD A,(IY+$31) ; read the entry's second coordinate
3CD1: C6 13 ADD A,$13 ; bias toward the wrap
3CD3: FE 03 CP $03 ; inside the three-wide band?
3CD5: D8 RET C ; inside: reached the boundary -- carry set
3CD6: C3 E1 3C JP hasReachedHorizontalEdgeWindow; outside: test the horizontal edge window
; answer whether an object has drifted onto the boundary its caller frees
; the slot at: the vertical window this arm owns is tested here, and when
; it is not met the same question is handed on to the horizontal one, so
; the answer is an OR of two windows on two axes and only the first is
; decided here
hasDriftedOffTheField:
3CD9: FD 7E 31 LD A,(IY+$31) ; read the entry's second coordinate
3CDC: C6 10 ADD A,$10 ; bias toward the wrap
3CDE: FE 03 CP $03 ; inside the three-wide band?
3CE0: D8 RET C ; inside: reached -- carry set; else fall to the horizontal test
; answer whether the byte at the head of a sprite entry has reached its
; wrap point, testing a four-wide window that straddles zero -- so it
; measures a wrapped distance rather than bounding a range, which is what
; lets a byte stepping several units at a time land inside the window
; instead of over it
hasReachedHorizontalEdgeWindow:
3CE1: FD 7E 00 LD A,(IY+$00) ; read the entry's head coordinate
3CE4: C6 02 ADD A,$02 ; bias across the wrap
3CE6: FE 04 CP $04 ; inside the four-wide window straddling zero?
3CE8: C9 RET ; carry answers whether it reached the edge
; dress two adjacent sprite entries with a consecutive pair of shape codes
; from the block HITS_REMAINING selects, so the object wears its damage,
; and mirror the pair -- swapping which entry takes the lower code, and
; flipping both -- on whichever half of the heading circle it is in
mirrorTwoTileObjectByHeading:
3CE9: 3A 80 A9 LD A,(workRam+180) ; read the frame counter
3CEC: E6 02 AND $02 ; take one alternating bit -- flicker between two shapes
3CEE: 47 LD B,A
3CEF: 3A DC A8 LD A,(workRam+DC) ; read the hits remaining
3CF2: 4F LD C,A
3CF3: 3E 03 LD A,$03 ; the most hits it can take
3CF5: 91 SUB C ; subtract the hits left -- the damage taken
3CF6: 87 ADD A,A ; times four -- four shapes per damage step
3CF7: 87 ADD A,A
3CF8: C6 A0 ADD A,$A0 ; the first damage shape
3CFA: 80 ADD A,B ; plus the flicker bit
3CFB: 4F LD C,A ; the pair's lower shape code
3CFC: DD 7E 02 LD A,(IX+$02) ; read the object's heading
3CFF: C6 40 ADD A,$40 ; rotate a quarter turn
3D01: FE 80 CP $80 ; which half of the compass?
3D03: 38 10 JR C,loc_3d15 ; one half: swap which entry takes the lower code
3D05: FD 71 01 LD (IY+$01),C ; one entry takes the lower shape
3D08: 0C INC C ; the next shape
3D09: FD 71 03 LD (IY+$03),C ; the other entry takes the upper shape
3D0C: FD 36 30 ED LD (IY+$30),$ED ; set the forward attribute on tile one
3D10: FD 36 32 ED LD (IY+$32),$ED ; and on tile two
3D14: C9 RET
loc_3d15:
3D15: FD 71 03 LD (IY+$03),C ; the other entry takes the lower shape
3D18: 0C INC C ; the next shape
3D19: FD 71 01 LD (IY+$01),C ; one entry takes the upper shape
3D1C: FD 36 30 6D LD (IY+$30),$6D ; set the reversed attribute on tile one
3D20: FD 36 32 6D LD (IY+$32),$6D ; and on tile two
3D24: C9 RET
; spawn one aimed enemy when the spawn slot is free, the cooldown at
; 0xa8f4 is clear, the era count at 0xa8c6 is live, and an object in the
; caller's two-slot bank sits inside a doubled window: seat the found
; slot's coords, the doubled velocity pair aimed toward the player at
; 0xac7f (aim side alternated each spawn via 0xa8d4), a script and a shape
; into the era's fixed record+sprite bank (0xa840/0xaa18 or
; 0xa8e0/0xaa2c), decrement the new record head, and reload the cooldown
; from 0xa8f6
spawnAimedEnemyIntoEraBankWhenInWindow:
3D25: DD 7E 00 LD A,(IX+$00) ; read the candidate spawn slot's head byte
3D28: 3C INC A
3D29: C0 RET NZ ; return unless that slot is free ($FF)
3D2A: 3A F4 A8 LD A,(workRam+F4) ; read the attacker-spawn cooldown timer
3D2D: A7 AND A
3D2E: C0 RET NZ ; return while the cooldown is still counting
3D2F: 3A C6 A8 LD A,(workRam+C6) ; read how many attackers this era still owes
3D32: A7 AND A
3D33: C8 RET Z ; return if none are due
3D34: FE 01 CP $01 ; is exactly one attacker due?
3D36: CA 40 3D JP Z,loc_3d40 ; one due -- require the primary attacker record free
3D39: 3A E0 A8 LD A,(workRam+E0) ; read the second era-object record's head
3D3C: A7 AND A
3D3D: CA 45 3D JP Z,loc_3d45 ; free -- go find a windowed object to launch from
loc_3d40:
3D40: 3A 40 A8 LD A,(workRam+40) ; read the primary attacker record's head
3D43: A7 AND A
3D44: C0 RET NZ ; return unless it too is free
loc_3d45:
3D45: 06 02 LD B,$02 ; two bank slots to scan
3D47: 3A D6 A8 LD A,(workRam+D6) ; read the spawn-window half-width
3D4A: 57 LD D,A
3D4B: 87 ADD A,A ; double it -- the full window width
3D4C: 5F LD E,A
loc_3d4d:
3D4D: 3E 84 LD A,$84 ; screen X reference ($84)
3D4F: FD 96 00 SUB (IY+$00) ; minus this object's X
3D52: 82 ADD A,D ; re-centre by the half-width
3D53: BB CP E ; within the doubled X window?
3D54: D2 6F 3D JP NC,loc_3d6f ; object reaches the launch window on X -- go launch from it
3D57: 3E 78 LD A,$78 ; screen Y reference ($78)
3D59: FD 96 31 SUB (IY+$31) ; minus this object's Y
3D5C: 82 ADD A,D ; re-centre by the half-width
3D5D: BB CP E ; within the doubled Y window?
3D5E: D2 6F 3D JP NC,loc_3d6f ; object reaches the launch window on Y -- go launch from it
3D61: D9 EXX
3D62: 11 10 00 LD DE,$0010 ; record stride ($10)
3D65: DD 19 ADD IX,DE ; step to the next bank record
3D67: FD 23 INC IY ; step to the next sprite entry (two bytes)
3D69: FD 23 INC IY
3D6B: D9 EXX
3D6C: 10 DF DJNZ loc_3d4d ; try the other slot
3D6E: C9 RET ; none in the window -- nothing to spawn
loc_3d6f:
3D6F: CD 5F 56 CALL requestEnemyLaunchSound; request the enemy-launch sound
3D72: 21 7F AC LD HL,$AC7F ; point at the player's aim reference cell
3D75: CD B8 33 CALL headingToward ; find the heading that points at the player
3D78: 67 LD H,A
3D79: 3E 18 LD A,$18 ; prepare the turn offset ($18)
3D7B: EB EX DE,HL
3D7C: 21 D4 A8 LD HL,$A8D4 ; point at the aim-side toggle
3D7F: 34 INC (HL) ; flip the aim side for this spawn
3D80: 46 LD B,(HL) ; read the toggle
3D81: CB 40 BIT 0,B ; test its low bit
3D83: 20 02 JR NZ,loc_3d87 ; odd -- turn one way
3D85: ED 44 NEG ; even -- turn the other way (negate)
loc_3d87:
3D87: EB EX DE,HL
3D88: 84 ADD A,H ; add the turn onto the heading -- the aimed angle
3D89: 08 EX AF,AF' ; stash the aimed angle
3D8A: FD 46 31 LD B,(IY+$31) ; take the object's Y
3D8D: FD 4E 00 LD C,(IY+$00) ; take the object's X
3D90: 3A C6 A8 LD A,(workRam+C6) ; read the attackers-due count again
3D93: FE 01 CP $01 ; exactly one due?
3D95: CA 9F 3D JP Z,loc_3d9f ; then seat into the primary era bank
3D98: 3A E0 A8 LD A,(workRam+E0) ; read the second era-object record's head
3D9B: A7 AND A
3D9C: CA CF 3D JP Z,loc_3dcf ; free -- seat into the second era bank
loc_3d9f:
3D9F: DD 21 40 A8 LD IX,$A840 ; point at the primary attacker record
3DA3: FD 21 18 AA LD IY,$AA18 ; and its sprite entry
loc_3da7:
3DA7: FD 70 31 LD (IY+$31),B ; seat the object's Y into the new entry
3DAA: FD 71 00 LD (IY+$00),C ; seat the object's X
3DAD: 08 EX AF,AF' ; recover the aimed angle
3DAE: CD C5 59 CALL loc_59c5 ; look up the doubled velocity pair for that angle
3DB1: DD 73 0A LD (IX+$0A),E ; stock the record with the aimed velocity pair -- four bytes
3DB4: DD 72 0B LD (IX+$0B),D
3DB7: DD 71 0C LD (IX+$0C),C
3DBA: DD 70 0D LD (IX+$0D),B
3DBD: FD 36 01 4D LD (IY+$01),$4D ; set the launch script/animation code
3DC1: FD 36 30 62 LD (IY+$30),$62 ; set the launch sprite shape
3DC5: DD 35 00 DEC (IX+$00) ; count the record head down one -- mark it live
3DC8: 3A F6 A8 LD A,(workRam+F6) ; read the cooldown reload period
3DCB: 32 F4 A8 LD (workRam+F4),A ; reload the attacker-spawn cooldown
3DCE: C9 RET
loc_3dcf:
3DCF: DD 21 E0 A8 LD IX,$A8E0 ; point at the second era-object record
3DD3: FD 21 2C AA LD IY,$AA2C ; and its sprite entry
3DD7: C3 A7 3D JP loc_3da7 ; seat and stock it the same way
; guard on the era index and, when it passes, hand two fixed bases to the
; shared slot servicer; the guard is the whole of the decision, and the
; bases are constants rather than anything a caller chose
serviceFixedSlotInEra1:
3DDA: 3A 04 AD LD A,(workRam+504) ; read the era index
3DDD: 3D DEC A
3DDE: C0 RET NZ ; service only in era 1
3DDF: DD 21 E0 A8 LD IX,$A8E0 ; point at the fixed era-object record
3DE3: FD 21 2C AA LD IY,$AA2C ; and its sprite entry
3DE7: CD EB 3D CALL serviceSlotByHeadByte; service that slot by its head byte
3DEA: C9 RET
; service one slot, splitting three ways on the head byte of its record:
; zero does nothing at all, all-ones flies the object one step along the
; velocity it carries and retires it into the shared cooldown only once
; that step has put it on a retire line, and any OTHER value retires it on
; the spot without moving it first
serviceSlotByHeadByte:
3DEB: DD 7E 00 LD A,(IX+$00) ; read the slot's head byte
3DEE: A7 AND A
3DEF: C8 RET Z ; empty slot -- nothing to do
3DF0: 3C INC A
3DF1: C2 FB 3D JP NZ,retireSlotIntoSharedCooldown; any value but $FF -- retire the slot on the spot
3DF4: CD 05 3E CALL flyAlongStoredVelocity; $FF -- fly the object one step along its velocity
3DF7: CD 83 2B CALL hasReachedRetireLine; has it reached a retire line?
3DFA: D0 RET NC ; not yet -- leave it flying
; retire a slot the way retireSlot does and then arm its delay byte from
; one shared address instead of leaving it clear, so every slot retired
; here goes out holding the same value
retireSlotIntoSharedCooldown:
3DFB: CD AB 40 CALL retireSlot ; take the slot out of play
3DFE: 3A F6 A8 LD A,(workRam+F6) ; read the shared cooldown period
3E01: DD 77 0E LD (IX+$0E),A ; stock the retired record's delay byte with it
3E04: C9 RET
; fly one object a single step along the velocity held in its own record,
; and in the same add carry it with the world; each coordinate gains its
; stored word plus the shared per-frame scroll
flyAlongStoredVelocity:
3E05: DD 66 0B LD H,(IX+$0B) ; take the object's stored Y velocity, high byte
3E08: DD 6E 0A LD L,(IX+$0A) ; and its low byte
3E0B: ED 5B 08 A8 LD DE,(workRam+8) ; read the shared per-frame world scroll (Y)
3E0F: 19 ADD HL,DE ; add the scroll onto the velocity
3E10: FD 56 31 LD D,(IY+$31) ; take the object's whole Y
3E13: DD 5E 03 LD E,(IX+$03) ; and its Y fraction
3E16: 19 ADD HL,DE ; advance the split Y coordinate
3E17: FD 74 31 LD (IY+$31),H ; store the new whole Y
3E1A: DD 75 03 LD (IX+$03),L ; store the new Y fraction
3E1D: DD 66 0D LD H,(IX+$0D) ; take the object's stored X velocity, high byte
3E20: DD 6E 0C LD L,(IX+$0C) ; and its low byte
3E23: ED 5B 0A A8 LD DE,(workRam+A) ; read the shared per-frame world scroll (X)
3E27: 19 ADD HL,DE ; add the scroll onto the velocity
3E28: FD 56 00 LD D,(IY+$00) ; take the object's whole X
3E2B: DD 5E 05 LD E,(IX+$05) ; and its X fraction
3E2E: 19 ADD HL,DE ; advance the split X coordinate
3E2F: FD 74 00 LD (IY+$00),H ; store the new whole X
3E32: DD 75 05 LD (IX+$05),L ; store the new X fraction
3E35: C9 RET
; put four named actor slots through the shared per-slot step, in a fixed
; order, one after another, without asking first whether any of them holds
; anything — so the four are serviced as a group and the group's
; membership is fixed here rather than by the caller
stepFourActorSlots:
3E36: DD 21 10 A8 LD IX,$A810 ; point at actor record slot 0
3E3A: FD 21 12 AA LD IY,$AA12 ; and its sprite entry
3E3E: CD 63 3E CALL dispatchObjectSlotByHeadByte; step that slot by its head byte
3E41: DD 21 20 A8 LD IX,$A820 ; actor record slot 1
3E45: FD 21 14 AA LD IY,$AA14 ; and its sprite entry
3E49: CD 63 3E CALL dispatchObjectSlotByHeadByte; step it
3E4C: DD 21 30 A8 LD IX,$A830 ; actor record slot 2
3E50: FD 21 16 AA LD IY,$AA16 ; and its sprite entry
3E54: CD 63 3E CALL dispatchObjectSlotByHeadByte; step it
3E57: DD 21 40 A8 LD IX,$A840 ; actor record slot 3
3E5B: FD 21 18 AA LD IY,$AA18 ; and its sprite entry
3E5F: CD 63 3E CALL dispatchObjectSlotByHeadByte; step it
3E62: C9 RET
; split three ways on the head byte of the record an index register points
; at: zero returns with nothing done, all-ones hands over to one
; continuation and every other value to another. One byte read, nothing
; written, and neither continuation is given anything this entry computed
dispatchObjectSlotByHeadByte:
3E63: DD 7E 00 LD A,(IX+$00) ; read the slot's head byte
3E66: A7 AND A
3E67: C8 RET Z ; empty slot -- nothing to do
3E68: 3C INC A
3E69: C2 8E 3E JP NZ,runSlotCountdownDriftAndAnimateElseRetire; any value but $FF -- run its countdown/animate branch
; fly one object a step along the velocity it carries and retire its slot
; once that step has put it on a retire line; in one era of the game, and
; only that one, the object is also given the next frame of a fixed shape
; cycle before it moves, and the retire is last so a shape written this
; tick may go out in the same breath
flyAndRetireSlotCyclingShapeInEra4:
3E6C: 3A 04 AD LD A,(workRam+504) ; read the era index
3E6F: FE 04 CP $04 ; is it the last era (4)?
3E71: CC 7E 3E CALL Z,animateFixedShapeCycle; in that era, cycle the object's shape first
3E74: CD 05 3E CALL flyAlongStoredVelocity; fly the object one step along its velocity
3E77: CD 83 2B CALL hasReachedRetireLine; has it reached a retire line?
3E7A: D0 RET NC ; not yet -- leave it flying
3E7B: C3 AB 40 JP retireSlot ; reached the line -- retire the slot
; give a sprite entry the next frame of an eight-frame cycle from a fixed
; shape base, and one fixed control byte beside it; nothing of the object
; is read, so two entries written in one tick get the same shape
animateFixedShapeCycle:
3E7E: 3A 80 A9 LD A,(workRam+180) ; read the free-running frame tick
3E81: 0F RRCA ; halve it -- advance every other tick
3E82: E6 07 AND $07 ; take it modulo eight -- one of eight frames
3E84: C6 40 ADD A,$40 ; offset to the shape-code base ($40)
3E86: FD 77 01 LD (IY+$01),A ; write the shape into the sprite entry
3E89: FD 36 30 44 LD (IY+$30),$44 ; set the fixed control byte beside it
3E8D: C9 RET
; run one slot's counter down for a frame and take the slot out of play as
; soon as it has nothing left to run; the era cell not standing at the
; last era, or the counter already sitting one above the floor, ends it
; outright, and otherwise the counter drops by one and the slot drifts
; with the world
runSlotCountdownDriftAndAnimateElseRetire:
3E8E: 3A 04 AD LD A,(workRam+504) ; read the era index
3E91: FE 04 CP $04 ; the last era (4)?
3E93: 28 03 JR Z,loc_3e98 ; yes -- run the countdown
3E95: C3 AB 40 JP retireSlot ; any other era -- retire the slot at once
loc_3e98:
3E98: DD 7E 00 LD A,(IX+$00) ; read the slot's countdown
3E9B: FE 01 CP $01 ; down to one above the floor?
3E9D: CA AB 40 JP Z,retireSlot ; then retire the slot
3EA0: DD 35 00 DEC (IX+$00) ; otherwise drop the countdown by one
3EA3: FE 3C CP $3C ; was the count at or above $3C?
3EA5: D4 CB 3E CALL NC,stampObjectStateByte3bThenRequestTwoSounds; if so, clamp the slot's state and ask for two sounds
3EA8: CD 60 2B CALL driftWithWorldScroll; drift the object with the world scroll
3EAB: DD 7E 00 LD A,(IX+$00) ; re-read the countdown -- the clamp may have moved it
3EAE: FE 1C CP $1C ; below the animate threshold ($1C)?
3EB0: D8 RET C ; yes -- leave the shape as it is
3EB1: D6 1C SUB $1C ; offset above the threshold
3EB3: 0F RRCA ; shift down by two -- four counts per shape
3EB4: 0F RRCA
3EB5: E6 07 AND $07 ; one of eight shapes
3EB7: 21 C3 3E LD HL,$3EC3 ; point at the shape table
3EBA: CF RST $08 ; fetch the shape byte for this count
3EBB: FD 77 01 LD (IY+$01),A ; write it into the sprite entry
3EBE: FD 36 30 03 LD (IY+$30),$03 ; set the control byte beside it
3EC2: C9 RET
; ---- $3EC3-$3ECA: data ----
3EC3: FF E6 E7 E7 E6 E6 E5 E4
; force the head byte of the record the index register points at to one
; fixed value and hand over; what that byte held is discarded unread, so
; this is a clamp and not a step
stampObjectStateByte3bThenRequestTwoSounds:
3ECB: DD 36 00 3B LD (IX+$00),$3B ; force the slot's head byte to $3B
3ECF: C3 83 56 JP requestTwoSounds ; request the two sounds
; ---- $3ED2-$3ED5: data ----
3ED2: 92 A6 14 B9
; one gated attempt to launch an enemy into the object bank: past a phase-
; key match, an arm flag, a non-empty flight count, and a strided scan for
; a free record, three margin windows must place the aim point near the
; player entry and the scroll; only then does it request the launch sound,
; copy the entry's two coordinates into the found record's paired entry,
; look up a doubled velocity pair from the heading via one of two tables
; chosen by a select cell, stock the record with that velocity, stamp two
; entry constants, re-arm the flag from its source, and count the record
; head down one
launchBankEnemyWhenAimedNearPlayer:
3ED6: 3A 80 A9 LD A,(workRam+180) ; read the free-running frame tick
3ED9: E6 07 AND $07 ; take its low three bits
3EDB: C6 05 ADD A,$05 ; form this frame's bank-phase key
3EDD: DD BE 0F CP (IX+$0F) ; does it match this bank's phase key?
3EE0: C0 RET NZ ; wrong phase this frame -- nothing to do
3EE1: 3A 17 A8 LD A,(workRam+17) ; read the launch arm/cooldown flag
3EE4: A7 AND A
3EE5: C0 RET NZ ; return while a launch is still armed
3EE6: 21 10 A8 LD HL,$A810 ; point at the first actor record
3EE9: 11 12 AA LD DE,$AA12 ; and its sprite entry
3EEC: 3A 44 A8 LD A,(workRam+44) ; read the count of craft in flight
3EEF: A7 AND A
3EF0: C8 RET Z ; return if none are flying
3EF1: 47 LD B,A ; that count bounds the free-slot scan
loc_3ef2:
3EF2: 7E LD A,(HL) ; read this record's head
3EF3: A7 AND A
3EF4: 28 09 JR Z,loc_3eff ; free record found -- launch into it
3EF6: 7D LD A,L
3EF7: C6 10 ADD A,$10 ; advance to the next record (stride $10)
3EF9: 6F LD L,A
3EFA: 1C INC E ; advance the sprite entry (two bytes)
3EFB: 1C INC E
3EFC: 10 F4 DJNZ loc_3ef2 ; keep scanning the bank
3EFE: C9 RET ; bank full -- nothing to launch
loc_3eff:
3EFF: 22 91 A9 LD (workRam+191),HL ; remember the free record pointer
3F02: ED 53 93 A9 LD (workRam+193),DE ; and its sprite entry pointer
3F06: 3A 27 A8 LD A,(workRam+27) ; read the near half-width for Y
3F09: 47 LD B,A
3F0A: 87 ADD A,A ; double it -- the full near band
3F0B: 4F LD C,A
3F0C: 3E 78 LD A,$78 ; screen Y reference ($78)
3F0E: FD 96 31 SUB (IY+$31) ; minus the player entry's Y
3F11: 80 ADD A,B ; re-centre by the half-width
3F12: B9 CP C ; player entry within the near Y band?
3F13: 30 08 JR NC,loc_3f1d ; outside it -- this launch clears, go on
3F15: 3E 84 LD A,$84 ; within Y -- also test X: screen X reference ($84)
3F17: FD 96 00 SUB (IY+$00) ; minus the player entry's X
3F1A: 80 ADD A,B ; re-centre by the half-width
3F1B: B9 CP C ; within the near X band too?
3F1C: D8 RET C ; too close on both axes -- reject this launch
loc_3f1d:
3F1D: 3A 37 A8 LD A,(workRam+37) ; read the near half-width for the scroll axis
3F20: 47 LD B,A
3F21: 87 ADD A,A ; double it -- the full band
3F22: 4F LD C,A
3F23: 3A 02 A8 LD A,(workRam+2) ; read the scroll reference
3F26: DD 96 02 SUB (IX+$02) ; minus the object's X
3F29: 80 ADD A,B ; re-centre by the half-width
3F2A: B9 CP C ; within the scroll band?
3F2B: D0 RET NC ; outside it -- reject this launch
3F2C: 7A LD A,D ; read the found entry's page byte
3F2D: FE 02 CP $02 ; is the entry page $02?
3F2F: CA 9E 3F JP Z,loc_3f9e ; if so, take the extra window check
loc_3f32:
3F32: 21 7F AC LD HL,$AC7F ; point at the player's aim reference
3F35: CD B8 33 CALL headingToward ; compute the heading toward the player
3F38: 4F LD C,A ; keep the heading
3F39: DD 96 02 SUB (IX+$02) ; measure it against the object's own heading
3F3C: C6 10 ADD A,$10 ; centre the difference
3F3E: FE 20 CP $20 ; within a sector either way?
3F40: D0 RET NC ; not aligned -- give up this frame
3F41: CD 93 3F CALL requestEraKeyedLaunchSound; request the era-keyed launch sound
3F44: DD E5 PUSH IX
3F46: FD E5 PUSH IY
3F48: FD 56 31 LD D,(IY+$31) ; take the player entry's Y
3F4B: FD 5E 00 LD E,(IY+$00) ; and its X
3F4E: DD 2A 91 A9 LD IX,(workRam+191) ; point at the free record found earlier
3F52: FD 2A 93 A9 LD IY,(workRam+193) ; and its sprite entry
3F56: FD 72 31 LD (IY+$31),D ; seat the player entry's Y into the new entry
3F59: FD 73 00 LD (IY+$00),E ; seat its X
3F5C: 3A 04 AD LD A,(workRam+504) ; read the era index
3F5F: A7 AND A
3F60: 79 LD A,C ; load the heading for the velocity lookup
3F61: 20 05 JR NZ,loc_3f68 ; past era 0 -- use the later-era velocity table
3F63: CD CB 59 CALL loc_59cb ; era 0 -- look up the velocity pair for the heading
3F66: 18 03 JR loc_3f6b ;
loc_3f68:
3F68: CD D1 59 CALL loc_59d1 ; later eras -- look up the velocity pair for the heading
loc_3f6b:
3F6B: DD 73 0A LD (IX+$0A),E ; stock the record with the velocity pair -- four bytes
3F6E: DD 72 0B LD (IX+$0B),D
3F71: DD 71 0C LD (IX+$0C),C
3F74: DD 70 0D LD (IX+$0D),B
3F77: FD 7E 31 LD A,(IY+$31)
3F7A: FD 7E 00 LD A,(IY+$00)
3F7D: FD 36 01 4D LD (IY+$01),$4D ; set the launch script/animation code
3F81: FD 36 30 62 LD (IY+$30),$62 ; set the launch sprite shape
3F85: 3A 14 A8 LD A,(workRam+14) ; read the arm-flag reload source
3F88: 32 17 A8 LD (workRam+17),A ; re-arm the launch flag
3F8B: DD 35 00 DEC (IX+$00) ; count the new record head down one -- mark it live
3F8E: FD E1 POP IY
3F90: DD E1 POP IX
3F92: C9 RET
; request the sound of a craft launching, taking the code from one of two
; program bytes according to whether the era has reached the fourth; both
; go through the play-gated door, so the attract demo stays silent
requestEraKeyedLaunchSound:
3F93: 3A 04 AD LD A,(workRam+504) ; read the era index
3F96: FE 03 CP $03 ; before the third era?
3F98: DA 5F 56 JP C,requestEnemyLaunchSound; yes -- request the early-era launch sound
3F9B: C3 69 56 JP requestEnemyLaunchSoundLateEra; third era on -- request the late-era launch sound
loc_3f9e:
3F9E: 3A E6 A8 LD A,(workRam+E6) ; read a near half-width
3FA1: 47 LD B,A
3FA2: 87 ADD A,A ; double it -- the full band
3FA3: 4F LD C,A
3FA4: 3E 84 LD A,$84 ; screen X reference ($84)
3FA6: FD 96 00 SUB (IY+$00) ; minus the player entry's X
3FA9: 80 ADD A,B ; re-centre by the half-width
3FAA: B9 CP C ; within the band?
3FAB: D0 RET NC ; outside it -- reject this launch
3FAC: C3 32 3F JP loc_3f32 ; within it -- carry on to the aim check
; point an object's sprite the way it is heading, from a different pair of
; sector tables to the sibling that does the same rounding
dressSpriteShapeAndAttributeForHeadingSector:
3FAF: DD 7E 02 LD A,(IX+$02) ; read the object's heading byte
3FB2: C6 08 ADD A,$08 ; add half a sector -- round to the nearest
3FB4: 0F RRCA
3FB5: 0F RRCA
3FB6: 0F RRCA
3FB7: 0F RRCA ; shift the top nibble down -- one of sixteen heading sectors
3FB8: E6 0F AND $0F ; keep the sector index
3FBA: 21 CA 3F LD HL,$3FCA ; point at the shape table
3FBD: CF RST $08 ; fetch the shape for this sector
3FBE: FD 77 01 LD (IY+$01),A ; write the shape into the sprite entry
3FC1: 11 10 00 LD DE,$0010 ; step sixteen entries on --
3FC4: 19 ADD HL,DE ; to the parallel attribute table
3FC5: 7E LD A,(HL) ; fetch the attribute byte for this sector
3FC6: FD 77 30 LD (IY+$30),A ; write it beside the shape
3FC9: C9 RET
; ---- $3FCA-$3FE9: data ----
3FCA: 48 49 4A 4B 4C 4B 4A 49 48 49 4A 4B 4C 4B 4A 49
3FDA: F4 B4 B4 B4 B4 34 34 34 34 74 74 74 74 F4 F4 F4
; era-zero-gated top-of-frame entry to the three-slot ballistic-object
; bank (dispatched as serviceRoundThenResolvePlayerState's substep 7):
; returns at once unless ERA_INDEX 0xad04 is 0, else seats the cursors
; (record ix=0xa8c0, sprite iy=0xaa28, count b=3) and routes the first
; slot by its marker byte -- step an empty slot via
; advanceSlotThenSweepObjectBankByHead, fly a ballistic (0xFF) slot then
; step it, else hand any other marker to
; sweepObjectSlotBankServicingFirstSlot
serviceEra0BallisticObjectBank:
3FEA: 3A 04 AD LD A,(workRam+504) ; read the era index
3FED: A7 AND A
3FEE: C0 RET NZ ; run only in era 0
3FEF: DD 21 C0 A8 LD IX,$A8C0 ; point at the ballistic bank's first record
3FF3: FD 21 28 AA LD IY,$AA28 ; and its first sprite entry
3FF7: 06 03 LD B,$03 ; three slots in the bank
; sweep a fixed bank of object slots for a frame, servicing each by its
; head byte -- fly a ballistic slot (0xFF) a frame along its arc, run the
; shape-cycle countdown service on any other nonzero, skip an empty (0) --
; striding one 0x10 record and two sprite-entry bytes per slot for the
; caller's count
sweepObjectSlotBankByHead:
3FF9: DD 7E 00 LD A,(IX+$00) ; read this slot's head byte
3FFC: A7 AND A
3FFD: CA 0B 40 JP Z,advanceSlotThenSweepObjectBankByHead; empty slot -- step over it
4000: 3C INC A
4001: 20 05 JR NZ,sweepObjectSlotBankServicingFirstSlot; any value but $FF -- run its animate service
4003: CD 17 40 CALL flyAlongBallisticArc; $FF -- fly the ballistic object a frame along its arc
4006: 18 03 JR advanceSlotThenSweepObjectBankByHead; then step to the next slot
; sweep the fixed three-slot object bank for one frame from the seated
; cursors (record cursor +0x10, sprite cursor +2 per slot, count bounding
; the pass): service the first slot's shape-cycle unconditionally, then
; route each following slot by its marker byte -- skip an empty (0x00)
; slot, fly a ballistic (0xFF) slot a step, and service any other marker's
; shape-cycle
sweepObjectSlotBankServicingFirstSlot:
4008: CD 6C 40 CALL runOneShotAnimatedObjectSlot; service the first slot's shape-cycle
; advance-step entry of the object-bank sweep: stride one slot forward
; (record +0x10, sprite entry +2) and return when the count runs out; step
; over an empty slot, fly a ballistic (0xFF) slot a frame and step over
; it, and hand the first slot bearing any other marker to the servicing
; sweep for the rest of the bank
advanceSlotThenSweepObjectBankByHead:
400B: 11 10 00 LD DE,$0010 ; the record stride -- sixteen bytes per slot
400E: DD 19 ADD IX,DE ; step the record cursor to the next slot
4010: FD 23 INC IY ; step the sprite-entry cursor two bytes to match
4012: FD 23 INC IY
4014: 10 E3 DJNZ sweepObjectSlotBankByHead; strike one off the slot count and, while slots remain, loop back to the sweep head
4016: C9 RET
; fly one object a frame along a ballistic arc -- a constant sideways step
; whose sign the record's own flag fixes, and a stored velocity on the
; other axis that gains a fixed amount every frame -- carrying it with the
; world scroll in both axes, and retiring the slot outright once it leaves
; the field on either
flyAlongBallisticArc:
4017: FD 56 31 LD D,(IY+$31) ; take the whole part of the object's position from the sprite entry
401A: DD 5E 03 LD E,(IX+$03) ; and its fraction from the record -- one 16-bit coordinate
401D: DD 7E 01 LD A,(IX+$01) ; read the record's across-direction flag
4020: A7 AND A
4021: 28 05 JR Z,loc_4028 ; flag zero: step this axis the positive way
4023: 21 80 FE LD HL,$FE80 ; non-zero: a fixed negative step across
4026: 18 03 JR loc_402b ;
loc_4028:
4028: 21 80 01 LD HL,$0180 ; a fixed positive step across
loc_402b:
402B: 19 ADD HL,DE ; add the step onto the object's position
402C: ED 5B 08 A8 LD DE,(workRam+8) ; the frame's world scroll on this axis
4030: 19 ADD HL,DE ; carry the object along with the world too
4031: FD 74 31 LD (IY+$31),H ; store the new position back -- whole to the entry, fraction to the record
4034: DD 75 03 LD (IX+$03),L
4037: DD 6E 07 LD L,(IX+$07) ; take the object's accumulated speed's low byte from the record
403A: DD 66 08 LD H,(IX+$08) ; and its high byte -- a 16-bit speed
403D: 11 09 00 LD DE,$0009 ; the per-frame speed gain
4040: 19 ADD HL,DE ; the object accelerates: add nine to its stored speed
4041: DD 75 07 LD (IX+$07),L ; keep the grown speed in the record for next frame
4044: DD 74 08 LD (IX+$08),H
4047: FD 56 00 LD D,(IY+$00) ; the whole part of the object's other-axis position
404A: DD 5E 05 LD E,(IX+$05) ; and its fraction
404D: 19 ADD HL,DE ; move that axis by the just-grown speed
404E: ED 5B 0A A8 LD DE,(workRam+A) ; the world scroll on that axis
4052: 19 ADD HL,DE ; carry it along with the world as well
4053: FD 74 00 LD (IY+$00),H ; store the new position back to entry and record
4056: DD 75 05 LD (IX+$05),L
4059: FD 7E 31 LD A,(IY+$31) ; read the first axis's whole part
405C: C6 10 ADD A,$10 ; shift the wrap point into view
405E: FE 20 CP $20 ; inside a 32-wide band at the edge?
4060: DA AB 40 JP C,retireSlot ; yes -- it has left the field: retire the slot
4063: FD 7E 00 LD A,(IY+$00) ; read the other axis's whole part
4066: FE F8 CP $F8 ; past the far limit (248)?
4068: D2 AB 40 JP NC,retireSlot ; yes: retire the slot
406B: C9 RET
; service one animated slot for a frame: rearm it (stamp the countdown to
; 59 and request the paired sound) when the countdown at (ix+0) is >=0x3c,
; count the countdown down, retire the sprite (zero iy+0 and iy+0x31) when
; it reaches zero, otherwise drift the object with the world scroll and,
; once the countdown is >=0x1c, drive the sprite shape (iy+1) from the
; 9-byte table at 0x4094 indexed by (countdown-0x1c)>>2 and set its
; attribute (iy+0x30) to 0x0e
runOneShotAnimatedObjectSlot:
406C: DD 7E 00 LD A,(IX+$00) ; read the slot's countdown
406F: FE 3C CP $3C ; at or above the reset mark (60)?
4071: D4 9D 40 CALL NC,stampObjectStateByte3bThenRequestSound; yes -- re-stamp its state and ask for the paired sound
4074: DD 35 00 DEC (IX+$00) ; count the slot down one frame
4077: 28 32 JR Z,retireSlot ; reached zero: retire the slot
4079: CD 60 2B CALL driftWithWorldScroll; drift the object with the world scroll
407C: DD 7E 00 LD A,(IX+$00) ; read the countdown again
407F: FE 1C CP $1C ; below the animation window floor (28)?
4081: D8 RET C ; yes: nothing more to draw this frame
4082: D6 1C SUB $1C ; measure how far into the window it is
4084: 0F RRCA ; divide that distance by four
4085: 0F RRCA
4086: E6 0F AND $0F ; keep the low nibble as a shape-table index
4088: 21 94 40 LD HL,$4094 ; point at the shape-cycle table
408B: CF RST $08 ; fetch the shape byte for this frame
408C: FD 77 01 LD (IY+$01),A ; write it as the sprite's shape code
408F: FD 36 30 0E LD (IY+$30),$0E ; set the sprite's attribute
4093: C9 RET
; ---- $4094-$409C: data ----
4094: FF 9A 99 98 98 99 99 9A 9B
; stamp one object's state byte to fifty-nine and ask for the sound that
; goes with it; the stamp is unconditional -- nothing here reads the byte
; first, and the ROM's test at this entry sends both of its answers to the
; same address
stampObjectStateByte3bThenRequestSound:
409D: DD 36 00 3B LD (IX+$00),$3B ; stamp the object's state byte to fifty-nine
40A1: 3A 04 AD LD A,(workRam+504) ; read the era index
40A4: A7 AND A
40A5: CA 8E 56 JP Z,loc_568e ; ask for the accompanying sound
40A8: C3 8E 56 JP loc_568e ; the other arm asks for it too -- the test above changes nothing
; retire an object, zeroing only the INTEGER halves — occupancy byte and
; both sprite-entry coordinates — leaving the sub-pixel remainders
; standing
retireSlot:
40AB: DD 36 00 00 LD (IX+$00),$00 ; clear the slot's occupancy byte
40AF: FD 36 00 00 LD (IY+$00),$00 ; zero one whole coordinate in the sprite entry
40B3: FD 36 31 00 LD (IY+$31),$00 ; and the other -- the object leaves the screen
40B7: C9 RET
; ask for one sound on every thirty-second frame from the third era on,
; and only while none of the three records at 0xA8C0, 0xA8D0 and 0xA8E0 is
; live; any one of those four tests failing ends the entry having done
; nothing at all
askForSoundWhileTheGroupIsClear:
40B8: 3A 04 AD LD A,(workRam+504) ; read the era index
40BB: FE 02 CP $02 ; below the third era (index 2)?
40BD: D8 RET C ; yes: ask for nothing
40BE: 3A 80 A9 LD A,(workRam+180) ; read the free-running frame counter
40C1: E6 1F AND $1F ; keep its low five bits
40C3: C0 RET NZ ; not the one frame in thirty-two: do nothing
40C4: 3A C0 A8 LD A,(workRam+C0) ; first of the three era-object records
40C7: 3C INC A
40C8: C8 RET Z ; holds a live object (0xFF): ask for nothing
40C9: 3A D0 A8 LD A,(workRam+D0) ; second era-object record
40CC: 3C INC A
40CD: C8 RET Z ; holds a live object: ask for nothing
40CE: 3A E0 A8 LD A,(workRam+E0) ; third era-object record
40D1: 3C INC A
40D2: C8 RET Z ; holds a live object: ask for nothing
40D3: C3 79 56 JP requestLateEraProgressSound; all clear from the third era on: ask for the progress sound
; entry to the per-slot sweep over an object bank: return early below era
; 2 (ERA_INDEX 0xad04) or when the bank's slot count (0xa8c6) is zero,
; else seat the record cursor (0xa8c0), the sprite-entry cursor (0xaa28)
; and the turn count, and run the sweep body at 0x40ea
sweepEra2PlusObjectBank:
40D6: 3A 04 AD LD A,(workRam+504) ; read the era index
40D9: FE 02 CP $02 ; below the third era (index 2)?
40DB: D8 RET C ; yes: this bank is idle before then
40DC: DD 21 C0 A8 LD IX,$A8C0 ; seat the record cursor at the first bank slot
40E0: FD 21 28 AA LD IY,$AA28 ; seat the sprite-entry cursor to match
40E4: 3A C6 A8 LD A,(workRam+C6) ; read the bank's slot count
40E7: A7 AND A
40E8: C8 RET Z ; no slots: nothing to do
40E9: 47 LD B,A ; use the count as the sweep's turn count
loc_40ea:
40EA: DD 7E 00 LD A,(IX+$00) ; read this slot's marker byte
40ED: A7 AND A
40EE: CA 0B 41 JP Z,closeOneTurnOfTheSlotSweep; empty: skip to the next slot
40F1: 3C INC A ; test for the ballistic marker (0xFF)
40F2: 20 14 JR NZ,loc_4108 ; any other marker: handle the drifting-countdown object
40F4: 3A 04 AD LD A,(workRam+504) ; ballistic slot -- read the era index
40F7: FE 04 CP $04 ; the final era (index 4)?
40F9: CA 94 41 JP Z,stepSlotApproachThenBreakawayRetire; yes: run the approach-then-breakaway handler
40FC: DD 7E 0E LD A,(IX+$0E) ; read the slot's own countdown at +0x0e
40FF: A7 AND A
4100: C2 8B 41 JP NZ,flyLiveSlotAndTickCountdown; still running: fly the live slot and tick that countdown
4103: CD 17 41 CALL chaseOneAimPointAndRetireAtTheLine; else chase the aim point and retire at the line
4106: 18 03 JR closeOneTurnOfTheSlotSweep; then close this turn of the sweep
loc_4108:
4108: CD 3C 41 CALL stepDriftingCountdownObjectByEraFrames; otherwise step the drifting countdown object for its era
; close one turn of the per-slot sweep over an object bank: step the
; record cursor on one whole sixteen-byte record and the sprite-entry
; cursor on one two-byte entry, strike one off the turn count and go round
; again while any remain, ending the sweep when the count runs out;
; several arms of the sweep's body converge here rather than one, and the
; record stride is left standing in the wide scratch pair on the way out
closeOneTurnOfTheSlotSweep:
410B: 11 10 00 LD DE,$0010 ; the record stride -- sixteen bytes
410E: DD 19 ADD IX,DE ; step the record cursor to the next slot
4110: FD 23 INC IY ; step the sprite-entry cursor two bytes to match
4112: FD 23 INC IY
4114: 10 D4 DJNZ loc_40ea ; strike one off the turn count and loop to the sweep head while slots remain
4116: C9 RET
; run one object through a whole frame of chasing: re-aim it, turn it,
; move it, dress its sprite, and retire it once it has drifted onto a
; retire line. Re-aiming is RATIONED rather than done every frame -- the
; object carries a phase byte and the aim is recomputed only on the frames
; whose low four bits match it, which spreads a crowd across sixteen
; frames and leaves each object a stale aim in between; a phase byte above
; 15 can never match FRAME_TICK's low four bits at all, so such an object
; is never re-aimed. The point is neither the only one nor a constant: it
; is one of SIX two-byte points packed at 0xAC74-0xAC7F, and those twelve
; bytes are rewritten as a block. The turn, the move and the dressing run
; every frame regardless, and the counter pair the caller holds is put
; back before the retire test
chaseOneAimPointAndRetireAtTheLine:
4117: C5 PUSH BC ; save the sweep's turn count across the object work
4118: 3A 80 A9 LD A,(workRam+180) ; read the free-running frame counter
411B: E6 0F AND $0F ; its low four bits
411D: DD BE 0F CP (IX+$0F) ; do they match this object's phase byte?
4120: 20 09 JR NZ,loc_412b ; no: keep last frame's aim, just turn and move
4122: 21 7F AC LD HL,$AC7F ; point at the shared aim point
4125: CD B8 33 CALL headingToward ; compute the heading toward it
4128: DD 77 01 LD (IX+$01),A ; store it as the heading to turn toward
loc_412b:
412B: CD 01 42 CALL steerTowardAimOneUnitAFrame; turn one step toward the aim
412E: CD AA 58 CALL loc_58aa ; move the object a frame
4131: CD AF 3F CALL dressSpriteShapeAndAttributeForHeadingSector; dress the sprite's shape and attribute for its heading
4134: C1 POP BC ; restore the sweep's turn count
4135: CD 83 2B CALL hasReachedRetireLine; has it drifted onto a retire line?
4138: D0 RET NC ; no: keep it in play
4139: C3 AB 40 JP retireSlot ; yes: retire the slot
; advance one countdown-driven object per frame: re-stamp+sound at the
; reset cap, drift with world scroll, decrement, retire the slot at zero,
; else animate the sprite from an era-selected frame table above the
; window floor
stepDriftingCountdownObjectByEraFrames:
413C: DD 7E 00 LD A,(IX+$00) ; read the object's countdown
413F: FE 3C CP $3C ; at or above the reset mark (60)?
4141: D4 9D 40 CALL NC,stampObjectStateByte3bThenRequestSound; yes: re-stamp its state and ask for the paired sound
4144: CD 60 2B CALL driftWithWorldScroll; drift the object with the world scroll
4147: DD 35 00 DEC (IX+$00) ; count it down one frame
414A: CA AB 40 JP Z,retireSlot ; reached zero: retire the slot
414D: DD 7E 00 LD A,(IX+$00) ; read the countdown again
4150: FE 1C CP $1C ; below the animation window floor (28)?
4152: D8 RET C ; yes: draw nothing more this frame
4153: D6 1C SUB $1C ; measure how far into the window it is
4155: 0F RRCA ; divide that distance by four
4156: 0F RRCA
4157: E6 07 AND $07 ; keep three bits -- a frame index 0..7
4159: 57 LD D,A ; hold the frame index
415A: 3A 04 AD LD A,(workRam+504) ; read the era index
415D: FE 04 CP $04 ; the final era (index 4)?
415F: 30 15 JR NC,loc_4176 ; yes: use the far-era frame table
4161: 21 6E 41 LD HL,$416E ; the near-era frame table
4164: 7A LD A,D ; the frame index
4165: CF RST $08 ; fetch the frame's shape byte
4166: FD 77 01 LD (IY+$01),A ; write it as the sprite's shape code
4169: FD 36 30 0D LD (IY+$30),$0D ; set the sprite's attribute (near)
416D: C9 RET
; ---- $416E-$4175: data ----
416E: FF 9E 9F 9F 9E 9E 9D 9C
loc_4176:
4176: 21 83 41 LD HL,$4183 ; the far-era frame table
4179: 7A LD A,D ; the frame index
417A: CF RST $08 ; fetch the frame's shape byte
417B: FD 77 01 LD (IY+$01),A ; write it as the sprite's shape code
417E: FD 36 30 02 LD (IY+$30),$02 ; set the sprite's attribute (far)
4182: C9 RET
; ---- $4183-$418A: data ----
4183: FF E2 E3 E3 E2 E2 E1 E0
; service one live slot of the per-slot object sweep: fly the slot's
; object a step along its stored velocity (retiring it once it crosses a
; retire line), tick down the slot's own countdown at record offset 0x0e,
; then close the turn of the sweep; reached only for a slot whose marker
; byte reads 0xFF with a nonzero countdown, outside the fourth era
flyLiveSlotAndTickCountdown:
418B: CD 6C 3E CALL flyAndRetireSlotCyclingShapeInEra4; fly the object a step and retire it if it crossed the retire line
418E: DD 35 0E DEC (IX+$0E) ; tick this slot's countdown at +0x0e down one
4191: C3 0B 41 JP closeOneTurnOfTheSlotSweep; close this turn of the sweep
; one slot's per-frame handler in an object sweep: while the record's
; approach countdown at +4 runs, decrement it and drive the object through
; its chased-object frame; the tick it hits zero, fly the object at double
; velocity, animate its shape cycle, and retire the slot only if it has
; reached a retire line, then step the sweep onto the next slot
stepSlotApproachThenBreakawayRetire:
4194: DD 7E 04 LD A,(IX+$04) ; read the slot's approach countdown at +0x04
4197: A7 AND A
4198: CA A4 41 JP Z,loc_41a4 ; it has expired: break away
419B: DD 35 04 DEC (IX+$04) ; still approaching: count it down one
419E: CD B8 41 CALL flyTowardShipStandoffThenEndApproach; fly it toward the standoff point
41A1: C3 0B 41 JP closeOneTurnOfTheSlotSweep; close this turn of the sweep
loc_41a4:
41A4: C5 PUSH BC ; save the sweep's turn count
41A5: CD B6 58 CALL loc_58b6 ; move the object
41A8: CD F1 41 CALL animateFixedShapeCycleAtHalfRate; animate its shape from the frame counter
41AB: CD 83 2B CALL hasReachedRetireLine; has it reached a retire line?
41AE: C1 POP BC ; restore the turn count
41AF: D2 0B 41 JP NC,closeOneTurnOfTheSlotSweep; no: leave it and close the turn
41B2: CD AB 40 CALL retireSlot ; yes: retire the slot
41B5: C3 0B 41 JP closeOneTurnOfTheSlotSweep; close the turn
; run one chased object through a frame: every sixteenth frame re-aim it
; at one of two fixed points a record bit selects, cut its approach
; countdown to zero once both axis gaps to that point fall under sixteen,
; then turn, move and dress it every frame; the carry answers whether it
; reached a retire line
flyTowardShipStandoffThenEndApproach:
41B8: C5 PUSH BC ; save the sweep's turn count
41B9: 3A 80 A9 LD A,(workRam+180) ; read the free-running frame counter
41BC: E6 0F AND $0F ; its low four bits
41BE: 20 1F JR NZ,loc_41df ; not the sixteenth frame: skip re-aiming
41C0: 21 75 AC LD HL,$AC75 ; the default aim point
41C3: DD CB 0F 46 BIT 0,(IX+$0F) ; test bit 0 of the slot's identity byte
41C7: 20 03 JR NZ,loc_41cc ; set: keep that point
41C9: 21 79 AC LD HL,$AC79 ; clear: the other aim point
loc_41cc:
41CC: CD B8 33 CALL headingToward ; compute the heading toward the chosen point
41CF: 47 LD B,A ; hold the heading
41D0: 7A LD A,D ; the first axis's distance to the aim point
41D1: FE 10 CP $10 ; within sixteen of it?
41D3: 30 07 JR NC,loc_41dc ; no: still approaching, just store the heading
41D5: 08 EX AF,AF' ; recover the second axis's distance
41D6: FE 10 CP $10 ; within sixteen on that axis too?
41D8: DC EC 41 CALL C,endApproachNow ; both close: it has arrived -- cut the approach countdown to zero
41DB: 08 EX AF,AF'
loc_41dc:
41DC: DD 70 01 LD (IX+$01),B ; store the new heading to turn toward
loc_41df:
41DF: CD 1F 42 CALL steerTowardAimAtFixedRate; turn toward the aim at the fixed rate
41E2: CD B6 58 CALL loc_58b6 ; move the object
41E5: CD F1 41 CALL animateFixedShapeCycleAtHalfRate; animate its shape
41E8: C1 POP BC
41E9: C3 83 2B JP hasReachedRetireLine; answer whether it reached a retire line
; make the countdown at +0x04 of the record a caller points at read zero,
; so that record's handler takes its expired arm on the next frame instead
; of counting the rest of the delay down; one store and nothing else
endApproachNow:
41EC: DD 36 04 00 LD (IX+$04),$00 ; zero the approach countdown at +0x04, so the slot retires next frame
41F0: C9 RET
; give one sprite entry the current frame of an eight-frame shape cycle
; from a fixed base, and one fixed byte beside it; the frame is picked
; from bits one to three of the free-running counter, so the cycle turns
; over once every sixteen counts. Nothing about the object is read, so two
; entries written in one tick get the same shape
animateFixedShapeCycleAtHalfRate:
41F1: 3A 80 A9 LD A,(workRam+180) ; read the free-running frame counter
41F4: 0F RRCA ; halve it
41F5: E6 07 AND $07 ; keep three bits -- a frame 0..7
41F7: C6 50 ADD A,$50 ; offset into the eight-shape run at 0x50
41F9: FD 77 01 LD (IY+$01),A ; write it as the sprite's shape code
41FC: FD 36 30 0A LD (IY+$30),$0A ; set the sprite's attribute
4200: C9 RET
; turn an object's heading one unit toward the heading it aims at, on
; every dispatch, standing still once the heading sits on the aim or one
; unit past it; the direction test is taken on the gap PLUS ONE, so a gap
; of exactly 127 turns the LONG way round and the standing band is off
; centre
steerTowardAimOneUnitAFrame:
4201: DD 7E 01 LD A,(IX+$01) ; read the heading the object aims toward
4204: DD 96 02 SUB (IX+$02) ; subtract its current heading -- the gap around the circle
4207: C6 01 ADD A,$01 ; bias the gap by one
4209: FE 02 CP $02 ; within one step of the aim either side?
420B: D8 RET C ; yes: hold the heading still
420C: FE 80 CP $80 ; is the shorter way round to increase or decrease?
420E: DD 7E 02 LD A,(IX+$02) ; the current heading
4211: 30 06 JR NC,loc_4219 ; turn the decreasing way
4213: C6 01 ADD A,$01 ; turn one step up toward the aim
4215: DD 77 02 LD (IX+$02),A ; store the new heading
4218: C9 RET
loc_4219:
4219: D6 01 SUB $01 ; turn one step down toward the aim
421B: DD 77 02 LD (IX+$02),A ; store the new heading
421E: C9 RET
; turn an object's heading two units toward the heading it aims at, on the
; three frames in four when the frame counter's low two bits are not both
; clear; a fixed step, where its sibling steerTowardAimHeading takes its
; rate from a table
steerTowardAimAtFixedRate:
421F: 3A 80 A9 LD A,(workRam+180) ; read the free-running frame counter
4222: E6 03 AND $03 ; its low two bits
4224: C8 RET Z ; one frame in four: do not turn
4225: DD 7E 01 LD A,(IX+$01) ; the heading the object aims toward
4228: DD 96 02 SUB (IX+$02) ; minus its current heading -- the gap
422B: C6 01 ADD A,$01 ; bias the gap by one
422D: FE 02 CP $02 ; within one step of the aim?
422F: D8 RET C ; yes: hold still
4230: FE 80 CP $80 ; shorter way up or down?
4232: DD 7E 02 LD A,(IX+$02) ; the current heading
4235: 30 06 JR NC,loc_423d ; turn the decreasing way
4237: C6 02 ADD A,$02 ; turn two steps up toward the aim
4239: DD 77 02 LD (IX+$02),A ; store the new heading
423C: C9 RET
loc_423d:
423D: D6 02 SUB $02 ; turn two steps down toward the aim
423F: DD 77 02 LD (IX+$02),A ; store the new heading
4242: C9 RET
; on this object's turn of the eight-frame round, once the shared spawn
; cooldown (0xA8F4) has expired, walk the object-record bank for a free
; slot, stash its record/entry pointers at 0xA991/0xA993, and if the new
; object clears the two fixed lines hand the caller's facing (C=IX+0x02)
; to the era-0 aim launcher (0x429C) or the heading-follows launcher
; (0x42B7); otherwise tick the cooldown down or leave everything untouched
launchAttackerIntoFreeSlot:
4243: 3A 80 A9 LD A,(workRam+180) ; read the frame counter
4246: E6 07 AND $07 ; keep its low three bits -- which of the eight frames of the round this is
4248: C6 05 ADD A,$05 ; bias it by five to form this object's spawn turn
424A: DD BE 0F CP (IX+$0F) ; is it this object's turn to try a spawn?
424D: C0 RET NZ ; not its turn: leave
424E: 21 F4 A8 LD HL,$A8F4 ; point at the shared spawn cooldown
4251: 7E LD A,(HL) ; read the cooldown
4252: A7 AND A ; is it still running?
4253: 28 02 JR Z,loc_4257 ; cooldown spent: go hunt a free slot
4255: 35 DEC (HL) ; still cooling down: tick it down one
4256: C9 RET
loc_4257:
4257: 21 C0 A8 LD HL,$A8C0 ; point at the first record in the spawn bank
425A: 11 28 AA LD DE,$AA28 ; point at its paired sprite entry
425D: 3A C6 A8 LD A,(workRam+C6) ; how many slots the bank holds
4260: A7 AND A ; any at all?
4261: C8 RET Z ; none: leave
4262: 47 LD B,A ; set the loop count to the slot total
loc_4263:
4263: 7E LD A,(HL) ; read this record's occupancy byte
4264: A7 AND A ; is the slot free?
4265: CA 71 42 JP Z,loc_4271 ; free: take it
4268: 7D LD A,L
4269: C6 10 ADD A,$10 ; step on to the next record -- records lie 0x10 apart
426B: 6F LD L,A
426C: 13 INC DE ; step the paired sprite-entry pointer along too
426D: 13 INC DE
426E: 10 F3 DJNZ loc_4263 ; try the next slot
4270: C9 RET
loc_4271:
4271: 22 91 A9 LD (workRam+191),HL ; stash the free record pointer
4274: ED 53 93 A9 LD (workRam+193),DE ; stash its paired entry pointer
4278: 3A D6 A8 LD A,(workRam+D6) ; read the spawn-window half-width
427B: 57 LD D,A
427C: 87 ADD A,A ; double it to the full window width
427D: 4F LD C,A
427E: 3E 78 LD A,$78 ; how far the spawner sits from the fixed line 0x78, offset into the window
4280: FD 96 31 SUB (IY+$31)
4283: 82 ADD A,D
4284: B9 CP C ; inside the window on this axis?
4285: 30 08 JR NC,loc_428f ; far enough on the first axis: go ahead and launch
4287: 3E 84 LD A,$84 ; else measure its distance from the fixed line 0x84 on the other axis
4289: FD 96 00 SUB (IY+$00)
428C: 82 ADD A,D
428D: B9 CP C ; inside that window too?
428E: D8 RET C ; too close on both axes: abandon the launch
loc_428f:
428F: DD 4E 02 LD C,(IX+$02) ; hand the spawner's facing to the launcher
4292: 3A 04 AD LD A,(workRam+504) ; read the era index
4295: A7 AND A ; era zero?
4296: CA 9C 42 JP Z,setTheLaunchFacingInsideOneAimWindow; era 0: launch on the aligned-facing path
4299: C3 B7 42 JP commissionStagedAttackerByEra; else launch with the heading-follows path
; the last gate in front of a launch, and the one thing the launcher is
; told: on one of the two coordinates the sprite entry carries, the firing
; object must lie inside a window centred on a fixed line whose half-width
; is READ FROM 0xA8E6 rather than baked in, and outside it this entry ends
; and nothing is launched; inside it the OTHER coordinate is compared
; against a second fixed line, and which side it falls on is handed to the
; launcher at 0x42B7 in the narrow scratch byte as a plain zero or one,
; which that routine turns into a mirroring of the NEW object's sprite
; rather than of the firing one's. 0xA8E6 is one of the two aim windows
; applyEraRungSettings scatters, which is why the name says 'one' and not
; 'the'; the cell also has a NON-WINDOW reader at 0x43AE ("ld a,(0xa8e6) /
; ld (ix+0x04),a", seeding a record countdown), and mechanisms.md marks
; what each of those twelve scattered cells governs as not fully settled
setTheLaunchFacingInsideOneAimWindow:
429C: 3A E6 A8 LD A,(workRam+E6) ; read the aim-window half-width
429F: 57 LD D,A
42A0: 87 ADD A,A ; double it to the full window width
42A1: 4F LD C,A
42A2: 3E 84 LD A,$84 ; how far the spawner sits from the fixed line 0x84, offset into the window
42A4: FD 96 00 SUB (IY+$00)
42A7: 82 ADD A,D
42A8: B9 CP C ; inside the window on this axis?
42A9: D0 RET NC ; outside: abandon the launch
42AA: 3E 78 LD A,$78 ; measure its distance from the fixed line 0x78 on the other axis
42AC: FD 96 31 SUB (IY+$31)
42AF: 38 04 JR C,loc_42b5 ; past the line one way: mark the mirror flag
42B1: 0E 00 LD C,$00 ; the other way: mirror flag clear
42B3: 18 02 JR commissionStagedAttackerByEra; go commission the launch
loc_42b5:
42B5: 0E 01 LD C,$01 ; mirror flag set -- the new sprite is drawn mirrored
; commission the object the free-slot finder staged, whose record/entry
; pointers wait at 0xA991/0xA993: copy the spawner's two coordinate pairs
; and the caller's facing (C) into the new slot, then fit it out one of
; four ways chosen by the era cell 0xAD04 -- era 0 an unaimed drift with a
; mirror flag (IY+0x01=0x4F) and slow-fall marker; eras 1-2 a heading
; toward the fixed point 0xAC7F skewed by a stored half-turn from
; (IX+0x0F); era 3 a doubled velocity vector for a heading offset +/-0x1A
; from the facing; era 4 a straight aim at 0xAC7F plus a seeded (IX+0x04);
; each way winds the new slot's active count (IX+0x00) down, re-arms the
; spawn cooldown (0xA8F4 from 0xA8F6), restores the spawner's own IX/IY,
; and hands off to one era-specific sound request
commissionStagedAttackerByEra:
42B7: FD 56 31 LD D,(IY+$31) ; grab the spawner's first coordinate
42BA: DD 5E 03 LD E,(IX+$03) ; and that axis's low byte
42BD: FD 66 00 LD H,(IY+$00) ; grab its second coordinate
42C0: DD 6E 05 LD L,(IX+$05) ; and that axis's low byte
42C3: D9 EXX ; tuck the copied coordinates into the alternate registers
42C4: DD E5 PUSH IX ; save the spawner's record pointer
42C6: FD E5 PUSH IY ; save the spawner's entry pointer
42C8: DD 2A 91 A9 LD IX,(workRam+191) ; point at the staged free record
42CC: FD 2A 93 A9 LD IY,(workRam+193) ; point at its paired entry
42D0: D9 EXX ; bring the copied coordinates back
42D1: DD 73 03 LD (IX+$03),E ; seat the first coordinate's low byte in the new record
42D4: FD 72 31 LD (IY+$31),D ; and its high byte in the new entry
42D7: DD 75 05 LD (IX+$05),L ; seat the second coordinate's low byte
42DA: FD 74 00 LD (IY+$00),H ; and its high byte
42DD: DD 71 01 LD (IX+$01),C ; store the facing in the new record
42E0: 3A 04 AD LD A,(workRam+504) ; read the era index
42E3: FE 04 CP $04 ; the final era?
42E5: CA AE 43 JP Z,loc_43ae ; era 4: seed the extra byte first, then aim
42E8: A7 AND A ; era zero?
42E9: C2 13 43 JP NZ,loc_4313 ; other eras: the aimed / heading paths
42EC: FD 36 01 4F LD (IY+$01),$4F ; era 0: give the new sprite its fixed shape code
42F0: 79 LD A,C ; take the facing
42F1: 0F RRCA ; spread its low bit up into the top bits
42F2: CB 2F SRA A
42F4: E6 C0 AND $C0 ; keep the two top bits
42F6: C6 0B ADD A,$0B ; form the sprite's attribute code
42F8: FD 77 30 LD (IY+$30),A ; store it
42FB: DD 36 07 00 LD (IX+$07),$00 ; clear the sub-pixel remainder
42FF: DD 36 08 FF LD (IX+$08),$FF ; seed the slow-fall marker
4303: DD 35 00 DEC (IX+$00) ; wind the new slot's active count down -- brings it live
4306: 3A F6 A8 LD A,(workRam+F6) ; read the spawn-cooldown reload
4309: 32 F4 A8 LD (workRam+F4),A ; re-arm the shared spawn cooldown
430C: FD E1 POP IY ; restore the spawner's entry pointer
430E: DD E1 POP IX ; and its record pointer
4310: C3 64 56 JP requestAttackerSpawnSoundEra0; ask for the era-0 spawn sound and return
loc_4313:
4313: 3A 04 AD LD A,(workRam+504) ; read the era index again
4316: FE 03 CP $03 ; era three?
4318: CA 6F 43 JP Z,loc_436f ; era 3: the doubled-velocity offset path
431B: D2 4C 43 JP NC,loc_434c ; era 4: the straight-aim path
431E: 21 7F AC LD HL,$AC7F ; point at the fixed aim point -- eras 1-2
4321: CD B8 33 CALL headingToward ; work out the heading toward it
4324: DD 77 01 LD (IX+$01),A ; store that heading
4327: DD 7E 0F LD A,(IX+$0F) ; take the stored half-turn phase
432A: 0F RRCA ; keep its sign
432B: E6 80 AND $80
432D: C6 40 ADD A,$40 ; skew the heading by a half-turn either way
432F: DD 86 01 ADD A,(IX+$01)
4332: DD 77 02 LD (IX+$02),A ; store the skewed heading as the object's facing
4335: CD AF 3F CALL dressSpriteShapeAndAttributeForHeadingSector; dress the sprite's shape and colour for that heading
4338: DD 35 00 DEC (IX+$00) ; wind the new slot live
433B: 3A F6 A8 LD A,(workRam+F6) ; read the spawn-cooldown reload
433E: 32 F4 A8 LD (workRam+F4),A ; re-arm the shared spawn cooldown
4341: DD 36 0E 00 LD (IX+$0E),$00 ; clear the slot's delay byte
4345: FD E1 POP IY ; restore the spawner's entry pointer
4347: DD E1 POP IX ; and its record pointer
4349: C3 6E 56 JP requestTwoSoundsWhilePlaying; ask for the in-play spawn sounds and return
loc_434c:
434C: 21 7F AC LD HL,$AC7F ; point at the fixed aim point
434F: CD B8 33 CALL headingToward ; work out the heading toward it
4352: DD 77 01 LD (IX+$01),A ; store it as the object's heading
4355: DD 77 02 LD (IX+$02),A ; and as its facing, unskewed
4358: CD AF 3F CALL dressSpriteShapeAndAttributeForHeadingSector; dress the sprite for that heading
435B: DD 35 00 DEC (IX+$00) ; wind the new slot live
435E: 3A F6 A8 LD A,(workRam+F6) ; read the spawn-cooldown reload
4361: 32 F4 A8 LD (workRam+F4),A ; re-arm the shared spawn cooldown
4364: DD 36 0E 00 LD (IX+$0E),$00 ; clear the slot's delay byte
4368: FD E1 POP IY ; restore the spawner's entry pointer
436A: DD E1 POP IX ; and its record pointer
436C: C3 74 56 JP requestAttackerSpawnSoundLateEra; ask for the late-era spawn sound and return
loc_436f:
436F: C5 PUSH BC ; save the facing
4370: 79 LD A,C ; which half of the compass the facing lies in
4371: C6 40 ADD A,$40
4373: E6 80 AND $80
4375: 79 LD A,C ; reload the facing
4376: 20 07 JR NZ,loc_437f ; back half: subtract the offset
4378: C6 1A ADD A,$1A ; front half: add a fixed heading offset
437A: DD 77 02 LD (IX+$02),A ; store the offset heading
437D: 18 05 JR loc_4384 ; go build its velocity
loc_437f:
437F: D6 1A SUB $1A ; subtract the fixed heading offset
4381: DD 77 02 LD (IX+$02),A ; store the offset heading
loc_4384:
4384: CD 8E 59 CALL loc_598e ; look up the doubled velocity vector for that heading
4387: DD 73 0A LD (IX+$0A),E ; file the first velocity word
438A: DD 72 0B LD (IX+$0B),D
438D: DD 71 0C LD (IX+$0C),C ; file the second velocity word
4390: DD 70 0D LD (IX+$0D),B
4393: C1 POP BC ; recover the facing
4394: DD 71 02 LD (IX+$02),C ; restore the true facing over the offset one
4397: CD AF 3F CALL dressSpriteShapeAndAttributeForHeadingSector; dress the sprite for the heading
439A: DD 36 0E 20 LD (IX+$0E),$20 ; seed the slot's delay byte
439E: DD 35 00 DEC (IX+$00) ; wind the new slot live
43A1: 3A F6 A8 LD A,(workRam+F6) ; read the spawn-cooldown reload
43A4: 32 F4 A8 LD (workRam+F4),A ; re-arm the shared spawn cooldown
43A7: FD E1 POP IY ; restore the spawner's entry pointer
43A9: DD E1 POP IX ; and its record pointer
43AB: C3 6E 56 JP requestTwoSoundsWhilePlaying; ask for the in-play spawn sounds and return
loc_43ae:
43AE: 3A E6 A8 LD A,(workRam+E6) ; read the aim-window half-width
43B1: DD 77 04 LD (IX+$04),A ; seed it as the new slot's countdown byte
43B4: C3 13 43 JP loc_4313 ; then take the aim path
; once-in-eight-frames gate for the Mother-Ship: while the wave-hold flag
; 0xacc6 is clear, defer to the deep-state stepper (stepMotherShip) if it
; is already live (MOTHER_SHIP_ARMED 0xad0d != 0), else -- only when the
; kill quota (KILLS_REMAINING 0xad02) is spent and both records of its
; two-slot bank (0xa8a0/0xa8b0) read empty -- arm it (0xad0d=0xff), seed
; the lead record's seven-hit counter (ix+0x04=0x07), and retire the
; matching entry pair into cooldown to spawn it
armMotherShipOrStep:
43B7: 3A C6 AC LD A,(workRam+4C6) ; read the round-transition hold flag
43BA: 3C INC A ; is it holding?
43BB: C8 RET Z ; held between rounds: do nothing
43BC: 3A 0D AD LD A,(workRam+50D) ; read the mother-ship armed flag
43BF: A7 AND A ; already active?
43C0: 20 2E JR NZ,loc_43f0 ; yes: step its live sequence
43C2: 3A 80 A9 LD A,(workRam+180) ; which frame of the eight-frame round
43C5: E6 07 AND $07
43C7: FE 05 CP $05 ; only on frame five
43C9: C0 RET NZ ; other frames: leave
43CA: DD 21 A0 A8 LD IX,$A8A0 ; point at the mother-ship record bank
43CE: FD 21 24 AA LD IY,$AA24 ; and its sprite entry
43D2: 3A 02 AD LD A,(workRam+502) ; read the kills-remaining quota
43D5: DD B6 00 OR (IX+$00) ; fold in the first bank record
43D8: DD B6 10 OR (IX+$10) ; and the second record
43DB: C0 RET NZ ; quota unspent or a slot still busy: not yet
43DC: 3E FF LD A,$FF ; raise the mother-ship armed flag
43DE: 32 0D AD LD (workRam+50D),A ;
43E1: DD 36 04 07 LD (IX+$04),$07 ; seed its seven-hit counter
43E5: C3 DB 46 JP retireEntryPairIntoCooldown; clear its entry pair and set its spawn cooldown to bring it on
; add a run of program-image bytes into one eight-bit total and hand it
; down the tail chain that compares it against the value a genuine image
; gives, so the machine leaves either on the ordinary path or into the
; trap; a length of zero means a full 256 bytes and the total wraps
sumImageBlockForTheTamperCheck:
43E8: AF XOR A ; start the running total at zero
loc_43e9:
43E9: 86 ADD A,(HL) ; add this program-image byte into the total
43EA: 23 INC HL ; step to the next byte
43EB: 10 FC DJNZ loc_43e9 ; sum the whole block -- a length of zero means all 256 bytes
43ED: C3 AD 07 JP parkTheImageTotalForTheTamperVerdict; hand the total to the tamper-verdict check
loc_43f0:
43F0: DD 21 A0 A8 LD IX,$A8A0 ; point at the mother-ship record
43F4: FD 21 24 AA LD IY,$AA24 ; and its sprite entry
43F8: DD 7E 00 LD A,(IX+$00) ; read its state byte
43FB: A7 AND A ; in the idle / placement state?
43FC: CA 35 45 JP Z,loc_4535 ; state zero: tick its spawn delay or place it
43FF: 3C INC A ; is the state 0xFF -- in flight?
4400: C2 40 45 JP NZ,loc_4540 ; other states: the hit-countdown handling
loc_4403:
4403: DD 66 0C LD H,(IX+$0C) ; take the object's first velocity word
4406: DD 6E 0D LD L,(IX+$0D)
4409: ED 5B 08 A8 LD DE,(workRam+8) ; read the world-scroll for that axis
440D: 19 ADD HL,DE ; add the scroll to the velocity
440E: FD 56 31 LD D,(IY+$31) ; take the object's current coordinate
4411: DD 5E 03 LD E,(IX+$03)
4414: 19 ADD HL,DE ; advance it
4415: FD 74 31 LD (IY+$31),H ; store the new coordinate back
4418: DD 75 03 LD (IX+$03),L
441B: DD 66 1C LD H,(IX+$1C) ; take the second velocity word
441E: DD 6E 1D LD L,(IX+$1D)
4421: ED 5B 0A A8 LD DE,(workRam+A) ; read the world-scroll for the other axis
4425: 19 ADD HL,DE ; add the scroll
4426: FD 56 00 LD D,(IY+$00) ; take the other coordinate
4429: DD 5E 05 LD E,(IX+$05)
442C: 19 ADD HL,DE ; advance it
442D: FD 74 00 LD (IY+$00),H ; store it back
4430: DD 75 05 LD (IX+$05),L
4433: FD 7E 31 LD A,(IY+$31) ; take the first coordinate
4436: C6 10 ADD A,$10 ; bias it for the hardware sprite
4438: FD 77 33 LD (IY+$33),A ; write it to the hardware sprite slot
443B: FD 7E 00 LD A,(IY+$00) ; take the second coordinate
443E: FD 77 02 LD (IY+$02),A ; copy it to the hardware sprite slot
4441: CD 47 44 CALL dressSpriteForHeadingOrRetireAtEdge; dress the sprite for its heading, or retire it at the edge
4444: C3 F0 46 JP loc_46f0 ; then try to fire a mother-ship shot at the player
; dress an object's sprite entry to face its heading (heading-quadrant
; picks a shape pair, era picks a colour, one heading half swaps the pair
; and the other biases the colour by half a page), unless the object has
; reached the field edge, in which case retire the entry pair; on the
; flutter era instead give a two-frame flutter and step/cap/close-out the
; wind-down counter
dressSpriteForHeadingOrRetireAtEdge:
4447: CD C4 3C CALL hasReachedBoundaryBandSelectedByHeading; has the object reached the field-edge band its heading faces?
444A: DA DB 46 JP C,retireEntryPairIntoCooldown; at the edge: retire its entry pair into cooldown
444D: 3A 04 AD LD A,(workRam+504) ; read the era index
4450: 57 LD D,A
4451: FE 04 CP $04 ; the flutter era?
4453: CA A2 44 JP Z,loc_44a2 ; era 4: give it the flutter animation instead
4456: 7A LD A,D ; multiply the era by sixteen -- the shape-table row base
4457: 87 ADD A,A
4458: 87 ADD A,A
4459: 87 ADD A,A
445A: 87 ADD A,A
445B: 47 LD B,A
445C: 3A 80 A9 LD A,(workRam+180) ; take one bit of the frame counter -- the two-frame animation tick
445F: E6 02 AND $02
4461: 80 ADD A,B ; fold it into the row base
4462: 47 LD B,A
4463: 3E 07 LD A,$07 ; seven minus the object's phase byte
4465: DD 96 04 SUB (IX+$04)
4468: 0F RRCA ; fold that to a two-bit heading quadrant
4469: E6 03 AND $03
446B: 5F LD E,A
446C: 87 ADD A,A ; index = quadrant*4 + row base
446D: 87 ADD A,A
446E: 80 ADD A,B
446F: 21 F1 44 LD HL,$44F1 ; point at the shape-pair table
4472: DF RST $18 ; offset into it by the index
4473: 46 LD B,(HL) ; read the pair's two shape codes
4474: 23 INC HL
4475: 4E LD C,(HL)
4476: 21 31 45 LD HL,$4531 ; point at the per-era colour table
4479: 7A LD A,D ; index by era
447A: DF RST $18 ; offset into it
447B: 56 LD D,(HL) ; read this era's colour code
447C: DD 7E 02 LD A,(IX+$02) ; which half of the compass the heading lies in
447F: C6 40 ADD A,$40
4481: FE 80 CP $80
4483: 38 10 JR C,loc_4495 ; one half: swap the pair, keep the plain colour
4485: FD 70 01 LD (IY+$01),B ; seat the shape codes in order
4488: FD 71 03 LD (IY+$03),C
448B: 7A LD A,D ; take the colour
448C: C6 80 ADD A,$80 ; bias it by half a page
448E: FD 77 30 LD (IY+$30),A ; seat the biased colour in both sprite slots
4491: FD 77 32 LD (IY+$32),A
4494: C9 RET
loc_4495:
4495: FD 71 01 LD (IY+$01),C ; seat the shape codes swapped
4498: FD 70 03 LD (IY+$03),B
449B: FD 72 30 LD (IY+$30),D ; seat the plain colour in both slots
449E: FD 72 32 LD (IY+$32),D
44A1: C9 RET
loc_44a2:
44A2: DD 7E 04 LD A,(IX+$04) ; read the object's wind-down seed
44A5: 5F LD E,A
44A6: FE 07 CP $07 ; already settled?
44A8: CA BF 44 JP Z,loc_44bf ; settled: just dress the flutter
44AB: DD 34 06 INC (IX+$06) ; step the wind-down counter
44AE: DD 4E 06 LD C,(IX+$06) ; read it
44B1: CB 79 BIT 7,C ; has it wrapped past the top?
44B3: 20 14 JR NZ,restartAnimationCounterThenDressFlutterSprite; overrun: restart the counter and dress the flutter
44B5: 7B LD A,E ; has the counter reached the seed plus two?
44B6: C6 02 ADD A,$02
44B8: B9 CP C
44B9: 30 04 JR NC,loc_44bf ; not yet: dress the flutter
44BB: DD 36 06 80 LD (IX+$06),$80 ; mark the counter closed out
loc_44bf:
44BF: FD 36 30 70 LD (IY+$30),$70 ; set the flutter colour code in both slots
44C3: FD 36 32 70 LD (IY+$32),$70
44C7: 18 13 JR dressSpriteFlutterShapesByFrameTickBit; go dress the flutter's two shapes
; close out one object's animation and dress its sprite entry: the counter
; the caller carries is read without the top bit that selected this path,
; and once what is left has reached three the counter cell in the object's
; record is put back to zero -- below three it is left alone. Either way
; both attribute slots of the sprite entry take the one code fixed here,
; and the two shape codes are then chosen by the flutter this entry hands
; on to
restartAnimationCounterThenDressFlutterSprite:
44C9: 79 LD A,C ; drop the top marker bit from the counter
44CA: E6 7F AND $7F
44CC: FE 03 CP $03 ; reached three?
44CE: 38 04 JR C,loc_44d4 ; below three: leave the counter alone
44D0: DD 36 06 00 LD (IX+$06),$00 ; at three or more: restart the wind-down counter
loc_44d4:
44D4: FD 36 30 51 LD (IY+$30),$51 ; set the sprite's attribute code in both slots
44D8: FD 36 32 51 LD (IY+$32),$51
; give an object the two shapes of a two-frame flutter, the pair picked by
; one bit of a counter cell and nothing the object holds
dressSpriteFlutterShapesByFrameTickBit:
44DC: 11 02 02 LD DE,$0202 ; the step between the two flutter shape pairs
44DF: 21 D5 D4 LD HL,$D4D5 ; the first shape pair
44E2: 3A 80 A9 LD A,(workRam+180) ; test one bit of the frame counter -- which flutter frame
44E5: CB 57 BIT 2,A
44E7: 20 01 JR NZ,loc_44ea ; one phase: keep the first pair
44E9: 19 ADD HL,DE ; other phase: take the second pair
loc_44ea:
44EA: FD 75 01 LD (IY+$01),L ; seat the two shape codes in the sprite entry
44ED: FD 74 03 LD (IY+$03),H
44F0: C9 RET
; ---- $44F1-$4534: data ----
44F1: 39 38 39 38 3B 3A 3D 3C 3B 3A 3D 3C 3D 3C 3F 3E
4501: B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
4511: C0 C1 C2 C3 C4 C5 C6 C7 C6 C7 C8 C9 C8 C9 CA CB
4521: CC CD CC CD CE CF D0 D1 CE CF D0 D1 D0 D1 D2 D3
4531: E9 58 6F 6E
loc_4535:
4535: DD 7E 0E LD A,(IX+$0E) ; read the mother-ship's spawn delay
4538: A7 AND A ; counted out?
4539: CA 63 46 JP Z,loc_4663 ; time to place it: go position it
453C: DD 35 0E DEC (IX+$0E) ; else tick the delay down
453F: C9 RET
loc_4540:
4540: 4F LD C,A ; keep the raised state value
4541: DD 7E 04 LD A,(IX+$04) ; read its remaining hit count
4544: A7 AND A ; any hits left?
4545: 28 0D JR Z,loc_4554 ; none: it is destroyed -- run the round-clear
4547: DD 35 04 DEC (IX+$04) ; take one off the hit count
454A: DD 36 00 FF LD (IX+$00),$FF ; keep it in flight
454E: CD 83 56 CALL requestTwoSounds ; play its hit sound
4551: C3 03 44 JP loc_4403 ; and fly it on
loc_4554:
4554: 79 LD A,C ; recall the state value
4555: FE F0 CP $F0 ; is the flash sequence over?
4557: C2 B3 45 JP NZ,stepMotherShipWarpFlashFrame; not yet: step the warp/flash frame
455A: AF XOR A ; clear the mother-ship progress cell
455B: 32 DC A8 LD (workRam+DC),A ;
455E: CD 34 56 CALL enqueueTransitionSoundBurst; quiet the running sound
4561: CD D2 56 CALL requestRoundIntroSoundBurst; sound the round-clear fanfare
4564: 21 10 A8 LD HL,$A810 ; point at the object bank
4567: 11 10 00 LD DE,$0010 ; record stride
456A: 06 0F LD B,$0F ; fifteen records to sweep
456C: 0E 14 LD C,$14 ; first fill value
loc_456e:
456E: 7E LD A,(HL) ; is this record empty?
456F: 3C INC A
4570: 20 22 JR NZ,loc_4594 ; occupied: handle it differently
4572: 71 LD (HL),C ; stamp the record with the sweep value
4573: D9 EXX
4574: 11 02 04 LD DE,$0402
4577: FF RST $38 ; queue a display command for it
4578: D9 EXX
loc_4579:
4579: 19 ADD HL,DE ; step to the next record
457A: 79 LD A,C ; bump the fill value
457B: C6 0A ADD A,$0A
457D: 4F LD C,A
457E: 10 EE DJNZ loc_456e ; sweep the rest of the bank
4580: 0E 3C LD C,$3C
4582: 3E FE LD A,$FE ; set the round-transition hold
4584: 32 C6 AC LD (workRam+4C6),A ;
4587: DD 36 00 E4 LD (IX+$00),$E4 ; set the mother-ship's exit state
458B: FD 36 30 3D LD (IY+$30),$3D ; set its exit colour in both slots
458F: FD 36 32 3D LD (IY+$32),$3D
4593: C9 RET
loc_4594:
4594: 3C INC A ; was the record 0xFE?
4595: 20 E2 JR NZ,loc_4579 ; no: move on
4597: 36 00 LD (HL),$00 ; clear it
4599: 18 DE JR loc_4579 ; move on
; reached only via the "wrong-glyph" derail ($1772) and the loop-back
; ($4660); the bytes run as harmless NOPs and stray-stack POPs. The real
; routine is at $45B3.
; ---- $459B-$45B2: misaligned anti-tamper entry ----
459B: 16 A7 13 96 ED DC F1 8C 68 3B 0D ED F1 9B 13 13
45AB: 13 13 F1 88 DC ED 11 B9
; step one object's timed warp/flash sequence: drift it with the world,
; seed the sprite's heading and shape from angle/Y-gated tables, then
; count a state byte down — the 0xB4 frame flags the sprite, bumps the
; 0xA800 sentinel (which requests the warp sound at 0x580B when it wraps)
; and posts command 0x04/0x0D to the ring, above-trigger frames step an
; eight-shape ROM cycle, and a spent counter resets to idle then loops or
; returns on two program-image gates; reached through a misaligned
; prologue (two POP AF, DEC SP) whose stray carry can fold in a life-loss
stepMotherShipWarpFlashFrame:
45B3: CD 60 2B CALL driftWithWorldScroll; drift the object along with the world scroll
45B6: FD 7E 31 LD A,(IY+$31) ; take its first coordinate
45B9: 47 LD B,A
45BA: C6 13 ADD A,$13 ; is it inside the top screen band?
45BC: FE 03 CP $03
45BE: 38 15 JR C,loc_45d5 ; off the top: blank its shapes
45C0: 78 LD A,B ; bias the coordinate for the hardware sprite
45C1: C6 10 ADD A,$10
45C3: FD 77 33 LD (IY+$33),A ; write it to the hardware sprite slot
45C6: FD 7E 00 LD A,(IY+$00) ; take its second coordinate
45C9: 47 LD B,A
45CA: C6 08 ADD A,$08 ; inside the edge band on that axis?
45CC: FE 28 CP $28
45CE: 38 05 JR C,loc_45d5 ; off that edge: blank its shapes
45D0: FD 70 02 LD (IY+$02),B ; write the second coordinate to the hardware sprite slot
45D3: 18 08 JR loc_45dd ; carry on to the shape step
loc_45d5:
45D5: FD 36 01 FF LD (IY+$01),$FF ; blank the sprite's two shapes -- off-screen
45D9: FD 36 03 FF LD (IY+$03),$FF
loc_45dd:
45DD: DD 7E 00 LD A,(IX+$00) ; read the sequence counter
45E0: FE B4 CP $B4 ; is this the flash-trigger frame?
45E2: 28 3F JR Z,loc_4623 ; the trigger frame: fire the warp/flash
45E4: 38 13 JR C,loc_45f9 ; below the trigger: skip the shape step
45E6: D6 B4 SUB $B4 ; turn the frame number into a 0-7 shape index
45E8: 0F RRCA
45E9: 0F RRCA
45EA: 0F RRCA
45EB: 3D DEC A
45EC: E6 07 AND $07
45EE: 21 1B 46 LD HL,$461B ; point at the eight-frame warp animation
45F1: CF RST $08 ; read this frame's shape
45F2: FD 77 03 LD (IY+$03),A ; seat it as one shape
45F5: 3C INC A ; seat the next code as the other shape
45F6: FD 77 01 LD (IY+$01),A
loc_45f9:
45F9: DD 35 00 DEC (IX+$00) ; count the sequence down one frame
45FC: CA 46 46 JP Z,loc_4646 ; sequence spent: end it
45FF: DD 7E 00 LD A,(IX+$00) ; reached the halfway mark?
4602: FE 5A CP $5A
4604: C0 RET NZ ; not yet: leave
4605: FD 36 01 FF LD (IY+$01),$FF ; blank its shapes at the halfway point
4609: FD 36 03 FF LD (IY+$03),$FF
460D: C9 RET
; two-player-start setup arm (called from 0x189E): when the video cell
; 0xA67C and work cell 0xAB43 disagree, decrement the counter at (IX+0),
; seat 0xFE/0xFD and 0x6C/0x6C into the object slot at
; (IY+1/+3/+0x30/+0x32), request sound 0x580B when 0xA800 is 0xFF, and
; queue ring command 0x04/0x0D; a no-op when the two cells agree
setUpTwoPlayerStartObjectOnce:
460E: 21 7C A6 LD HL,$A67C ; point at the watched video cell
4611: 7E LD A,(HL) ; read it
4612: 4F LD C,A
4613: 3A 43 AB LD A,(workRam+343) ; compare against the mirror cell
4616: 91 SUB C
4617: C2 43 46 JP NZ,loc_4643 ; they disagree: run the start-object setup
461A: C9 RET
loc_461b:
461B: 94 SUB H ; eight warp-flash shape codes -- also run off as harmless register churn when the start-object path jumps here and drops into 0x4623
461C: 96 SUB (HL)
461D: 96 SUB (HL)
461E: 94 SUB H
461F: 92 SUB D
4620: 90 SUB B
4621: 90 SUB B
4622: 94 SUB H
loc_4623:
4623: DD 35 00 DEC (IX+$00) ; step the sequence counter
4626: FD 36 01 FE LD (IY+$01),$FE ; set the flash shapes
462A: FD 36 03 FD LD (IY+$03),$FD
462E: FD 36 30 6C LD (IY+$30),$6C ; set the flash colour in both slots
4632: FD 36 32 6C LD (IY+$32),$6C
4636: 3A 00 A8 LD A,(workRam) ; is the warp sentinel armed?
4639: 3C INC A ; if so, sound the mother-ship warp
463A: CC 0B 58 CALL Z,requestMotherShipWarpSound;
463D: 11 0D 04 LD DE,$040D ; the display command
4640: C3 38 00 JP postCommand ; queue it and return
loc_4643:
4643: C3 1B 46 JP loc_461b ; jump on to the start-object setup
loc_4646:
4646: 3E FF LD A,$FF ; raise the round-transition hold
4648: 32 C6 AC LD (workRam+4C6),A ;
464B: DD 36 00 00 LD (IX+$00),$00 ; reset the object's state to idle
464F: 21 43 AB LD HL,$AB43 ; point at the mode cell
4652: 7E LD A,(HL) ; is it the expected value?
4653: FE 7C CP $7C
4655: C2 60 46 JP NZ,loc_4660 ; no: loop the warp/flash stepper
4658: 23 INC HL ; read the following cell
4659: 7E LD A,(HL)
465A: FE 10 CP $10 ; one accepted value?
465C: C8 RET Z ; yes: end here
465D: FE 05 CP $05 ; the other accepted value?
465F: C8 RET Z ; yes: end here
loc_4660:
4660: C3 9B 45 JP rom+459B ; run the warp/flash stepper again
loc_4663:
4663: 3A C6 AC LD A,(workRam+4C6) ; is a round transition holding?
4666: A7 AND A
4667: C0 RET NZ ; yes: wait
4668: 3A 02 A8 LD A,(workRam+2) ; take the world angle
466B: 47 LD B,A
466C: 3A 80 A9 LD A,(workRam+180) ; take the frame counter
466F: 4F LD C,A
4670: 3E 10 LD A,$10 ; base step
4672: CB 59 BIT 3,C ; test a frame-counter bit
4674: 20 02 JR NZ,loc_4678 ; one way: keep it positive
4676: ED 44 NEG ; other way: negate the step
loc_4678:
4678: 80 ADD A,B ; add it to the angle
4679: 0F RRCA ; fold to an even table index
467A: 0F RRCA
467B: E6 3E AND $3E
467D: 21 84 3C LD HL,$3C84 ; point at the entry-position table
4680: CF RST $08 ; read the first coordinate
4681: FD 77 31 LD (IY+$31),A ; seat it
4684: 23 INC HL ; read the second coordinate
4685: 7E LD A,(HL)
4686: FD 77 00 LD (IY+$00),A ; seat it
4689: 78 LD A,B ; which half of the compass the angle faces
468A: C6 C0 ADD A,$C0
468C: E6 80 AND $80
468E: DD 77 02 LD (IX+$02),A ; store it as the mother-ship's facing
4691: CD BA 46 CALL setMotherShipVelocityFromHeading; give it the velocity its heading picks
4694: DD 7E 04 LD A,(IX+$04) ; is its hit count already high enough?
4697: FE 06 CP $06
4699: 30 04 JR NC,loc_469f ; yes: leave it
469B: DD 36 04 05 LD (IX+$04),$05 ; else seed a minimum hit count
loc_469f:
469F: DD 36 00 FF LD (IX+$00),$FF ; mark the mother ship in flight
46A3: C3 F7 57 JP requestCurrentEraSound; ask for the current-era sound and return
; ---- $46A6-$46B9: data ----
46A6: 3A 80 A9 4F E6 1C CB 41 20 02 ED 44 80 0F 0F E6
46B6: 3E C3 7D 46
; give the Mother-Ship the two velocity words its current heading picks
; out of the velocity table the era selects -- the word at the heading and
; the word a quarter turn behind it -- and park them at +0x0C and +0x1C of
; the record pair, which is where its motion reads them
setMotherShipVelocityFromHeading:
46BA: 21 CE 46 LD HL,$46CE ; set the return to the record-filing step
46BD: E5 PUSH HL ; lay it on the stack for the arm to return through
46BE: 3A 04 AD LD A,(workRam+504) ; the era index picks which velocity arm
46C1: E6 07 AND $07
46C3: F7 RST $30 ; jump through the arm table below
; ---- $46C4-$46CD: jump table ----
46C4: 42 59 4E 59 4E 59 65 59 6B 59
; file two register pairs into an object's record as four bytes, each pair
; high byte first and so stored the opposite way round from a word
fileTwoPairsIntoObjectRecordHighByteFirst:
46CE: DD 72 0C LD (IX+$0C),D ; file the first pair's high byte
46D1: DD 73 0D LD (IX+$0D),E ; and its low byte
46D4: DD 70 1C LD (IX+$1C),B ; file the second pair's high byte
46D7: DD 71 1D LD (IX+$1D),C ; and its low byte
46DA: C9 RET
; clear a record's occupancy byte and both coordinates of TWO neighbouring
; sprite entries, then arm the record's delay byte with a fixed value
; rather than leaving it clear
retireEntryPairIntoCooldown:
46DB: AF XOR A ; zero
46DC: DD 77 00 LD (IX+$00),A ; clear the record's occupancy byte
46DF: FD 77 00 LD (IY+$00),A ; clear the first entry's coordinate
46E2: FD 77 02 LD (IY+$02),A ; and its hardware copy
46E5: FD 77 31 LD (IY+$31),A ; clear the paired entry's coordinate
46E8: FD 77 33 LD (IY+$33),A ; and its hardware copy
46EB: DD 36 0E 5F LD (IX+$0E),$5F ; arm the record's cooldown delay
46EF: C9 RET
loc_46f0:
46F0: DD 7E 00 LD A,(IX+$00) ; is the object fully in flight?
46F3: 3C INC A
46F4: C0 RET NZ ; not yet: leave
46F5: 3A 17 A8 LD A,(workRam+17) ; is a mother-ship shot already out?
46F8: A7 AND A
46F9: C0 RET NZ ; yes: only one at a time
46FA: 06 02 LD B,$02 ; two entry slots to consider
46FC: 3A 27 A8 LD A,(workRam+27) ; read the fire-window half-width
46FF: 57 LD D,A
4700: 87 ADD A,A ; double it to the full window
4701: 5F LD E,A
loc_4702:
4702: FD 7E 00 LD A,(IY+$00) ; is the target within the vertical band?
4705: C6 08 ADD A,$08
4707: FE 28 CP $28
4709: 38 1B JR C,loc_4726 ; off that band: skip this slot
470B: FD 7E 31 LD A,(IY+$31) ; within the horizontal band?
470E: C6 10 ADD A,$10
4710: FE 20 CP $20
4712: 38 12 JR C,loc_4726 ; off it: skip this slot
4714: 3E 84 LD A,$84 ; within the aim window on one axis?
4716: FD 96 00 SUB (IY+$00)
4719: 82 ADD A,D
471A: BB CP E
471B: 30 17 JR NC,loc_4734 ; yes: fire from this slot
471D: 3E 78 LD A,$78 ; within the window on the other axis?
471F: FD 96 31 SUB (IY+$31)
4722: 82 ADD A,D
4723: BB CP E
4724: 30 0E JR NC,loc_4734 ; yes: fire from this slot
loc_4726:
4726: D9 EXX ; step to the next record
4727: 11 10 00 LD DE,$0010
472A: DD 19 ADD IX,DE
472C: FD 23 INC IY ; step its paired entry
472E: FD 23 INC IY
4730: D9 EXX
4731: 10 CF DJNZ loc_4702 ; try the other slot
4733: C9 RET
loc_4734:
4734: 21 30 A8 LD HL,$A830 ; point at the shot record bank
4737: D9 EXX
4738: 21 16 AA LD HL,$AA16 ; and its sprite entries
473B: 06 02 LD B,$02 ; two shot slots to search
loc_473d:
473D: D9 EXX
473E: 7E LD A,(HL) ; is this shot slot free?
473F: A7 AND A
4740: 28 0A JR Z,loc_474c ; free: launch from it
4742: 11 10 00 LD DE,$0010 ; step to the next shot record
4745: 19 ADD HL,DE
4746: D9 EXX
4747: 23 INC HL ; and its entry
4748: 23 INC HL
4749: 10 F2 DJNZ loc_473d ; try the other slot
474B: C9 RET
loc_474c:
474C: 22 91 A9 LD (workRam+191),HL ; stash the free shot record
474F: D9 EXX
4750: 22 93 A9 LD (workRam+193),HL ; and its entry
4753: CD 5F 56 CALL requestEnemyLaunchSound; play the enemy launch sound
4756: 21 7F AC LD HL,$AC7F ; the fixed aim point -- the player
4759: CD B8 33 CALL headingToward ; work out the heading toward it
475C: 67 LD H,A ; keep the heading
475D: EB EX DE,HL
475E: 21 B4 A8 LD HL,$A8B4 ; step the alternating spread counter
4761: 34 INC (HL)
4762: 3E 18 LD A,$18 ; one bit of it picks the spread direction
4764: CB 46 BIT 0,(HL)
4766: 20 02 JR NZ,loc_476a ; one way: keep the spread positive
4768: ED 44 NEG ; other way: negate it
loc_476a:
476A: EB EX DE,HL
476B: 84 ADD A,H ; skew the heading by the spread
476C: FD 46 31 LD B,(IY+$31) ; take the firer's coordinates
476F: FD 4E 00 LD C,(IY+$00)
4772: DD 2A 91 A9 LD IX,(workRam+191) ; point at the new shot's record and entry
4776: FD 2A 93 A9 LD IY,(workRam+193) ;
477A: DD 77 02 LD (IX+$02),A ; store the shot's heading
477D: FD 70 31 LD (IY+$31),B ; place it at the firer's position
4780: FD 71 00 LD (IY+$00),C
4783: 21 95 47 LD HL,$4795 ; set the return to the velocity-store step
4786: E5 PUSH HL
4787: 3A 04 AD LD A,(workRam+504) ; the era index picks the shot's velocity arm
478A: F7 RST $30
; ---- $478B-$4794: jump table ----
478B: 8E 59 8E 59 94 59 94 59 94 59
loc_4795:
4795: DD 73 0A LD (IX+$0A),E ; file the first velocity word
4798: DD 72 0B LD (IX+$0B),D
479B: DD 71 0C LD (IX+$0C),C ; file the second velocity word
479E: DD 70 0D LD (IX+$0D),B
47A1: FD 36 01 4D LD (IY+$01),$4D ; set the shot's shape code
47A5: FD 36 30 62 LD (IY+$30),$62 ; and its colour
47A9: DD 35 00 DEC (IX+$00) ; bring the shot live
47AC: 3A 14 A8 LD A,(workRam+14) ; mark that a mother-ship shot is now out
47AF: 32 17 A8 LD (workRam+17),A ;
47B2: C9 RET
; per-frame manager of the single parachutist slot (record 0xa8f0, sprite
; 0xaa2e): idle in era 4, else branch on the slot's state byte — free
; spawns it at the edge ahead, in-flight (0xff) flies it and retires it
; once it reaches a retire line else steps its shape from the frame tick,
; 0x10 posts its bonus, >=0x3c shows its award, and any lower value drifts
; it with the world then counts down and retires it at zero; the
; parachutist rescue object (canopy + 1000 bonus), removed by a negative
; control
runParachutistSlot:
47B3: 3A 04 AD LD A,(workRam+504) ; read the era index
47B6: FE 04 CP $04 ; the final era, which has no parachutist?
47B8: C8 RET Z ; yes: nothing to run
47B9: DD 21 F0 A8 LD IX,$A8F0 ; point at the parachutist record
47BD: FD 21 2E AA LD IY,$AA2E ; and its sprite entry
47C1: DD 7E 00 LD A,(IX+$00) ; is the slot free?
47C4: A7 AND A
47C5: CA 53 48 JP Z,spawnAtEdgeAhead ; free: spawn one at the edge ahead
47C8: 3C INC A ; is its state in flight -- 0xFF?
47C9: C2 F2 47 JP NZ,loc_47f2 ; other states: the drift / award handling
47CC: CD 05 3E CALL flyAlongStoredVelocity; fly it along its stored velocity
47CF: CD 83 2B CALL hasReachedRetireLine; has it reached the retire line?
47D2: DA AD 48 JP C,retireSlotIntoCooldown; yes: retire the slot into cooldown
47D5: 3A 80 A9 LD A,(workRam+180) ; turn the frame tick into a 0-7 shape index
47D8: 0F RRCA
47D9: 0F RRCA
47DA: 0F RRCA
47DB: 0F RRCA
47DC: E6 07 AND $07
47DE: 21 EA 47 LD HL,$47EA ; point at the canopy-sway shape table
47E1: CF RST $08 ; read this frame's shape
47E2: FD 77 01 LD (IY+$01),A ; seat it
47E5: FD 36 30 75 LD (IY+$30),$75 ; set the canopy colour
47E9: C9 RET
; ---- $47EA-$47F1: data ----
47EA: 00 01 02 03 03 02 01 00
loc_47f2:
47F2: CD 60 2B CALL driftWithWorldScroll; drift the parachutist with the world
47F5: DD 7E 00 LD A,(IX+$00) ; is it at the post-bonus state?
47F8: FE 10 CP $10
47FA: CA 31 48 JP Z,postNextParachutistBonus; yes: post its next bonus value
47FD: FE 3C CP $3C ; is it at or above the award state?
47FF: D2 09 48 JP NC,showParachutistAward; yes: show its award glyph
4802: DD 35 00 DEC (IX+$00) ; else count its dying timer down
4805: C0 RET NZ ; still counting: leave
4806: C3 AD 48 JP retireSlotIntoCooldown; timer spent: retire the slot into cooldown
; start the parachutist slot's exit: put its state byte at the top of the
; dying countdown, ask for the sound that goes with collecting it, and
; swap its sprite tile to the glyph for the award the slot's own rung byte
; selects -- with one fixed glyph once the rung passes the four the table
; holds, so the lookup never reads on past the table
showParachutistAward:
4809: DD 36 00 3B LD (IX+$00),$3B ; put the slot into its award / exit state
480D: CD FF 57 CALL requestParachutistAwardSound; play the parachutist award sound
4810: DD 7E 07 LD A,(IX+$07) ; is the award index within the table?
4813: FE 04 CP $04
4815: D2 24 48 JP NC,loc_4824 ; past the table: use the fixed award glyph
4818: 21 2D 48 LD HL,$482D ; read the award glyph for this rung
481B: CF RST $08
481C: FD 77 01 LD (IY+$01),A ; seat it as the sprite shape
481F: FD 36 30 6C LD (IY+$30),$6C ; set its colour
4823: C9 RET
loc_4824:
4824: FD 36 01 8F LD (IY+$01),$8F ; use the fixed top-award glyph
4828: FD 36 30 6C LD (IY+$30),$6C ; set its colour
482C: C9 RET
; ---- $482D-$4830: data ----
482D: F9 FC 8D 8E
; post the next rung of the rescue award to the command ring and step the
; per-life rung count on; the first four rungs each take their own value
; from a four-entry table and every rung after them takes the same top
; value, so the ladder rises and then caps
postNextParachutistBonus:
4831: DD 35 00 DEC (IX+$00) ; count the slot's timer down
4834: DD 7E 07 LD A,(IX+$07) ; read the rung number
4837: DD 34 07 INC (IX+$07) ; step it on for next time
483A: FE 04 CP $04 ; past the four table rungs?
483C: D2 49 48 JP NC,loc_4849 ; yes: post the fixed top value
483F: 21 4F 48 LD HL,$484F ; index the rung-value table by the rung
4842: DF RST $18
4843: 5E LD E,(HL) ; take this rung's value
4844: 16 04 LD D,$04 ; command 0x04
4846: C3 38 00 JP postCommand ; queue the bonus command and return
loc_4849:
4849: 11 0F 04 LD DE,$040F ; the fixed top command and value
484C: C3 38 00 JP postCommand ; queue it and return
; ---- $484F-$4852: data ----
484F: 0A 0C 0D 0E
; on a cooldown, and only on alternate frames, place a free slot at the
; field-edge position the player's current heading selects, clear its sub-
; pixel remainders and mark it live
spawnAtEdgeAhead:
4853: 3A 0D AD LD A,(workRam+50D) ; is the mother ship armed?
4856: A7 AND A
4857: C0 RET NZ ; yes: hold this spawn off
4858: 3A 80 A9 LD A,(workRam+180) ; only act on alternate frames
485B: E6 01 AND $01
485D: C8 RET Z ; this frame: skip
485E: DD 35 0E DEC (IX+$0E) ; tick the slot's spawn delay down
4861: C0 RET NZ ; not yet zero: wait
4862: 3A 02 A8 LD A,(workRam+2) ; take the player's heading, rounded
4865: C6 08 ADD A,$08
4867: 0F RRCA ; fold it to one of sixteen edge sectors -- an even index
4868: 0F RRCA
4869: 0F RRCA
486A: E6 1E AND $1E
486C: 21 8D 48 LD HL,$488D ; point at the edge-position table
486F: CF RST $08 ; read the sector's first coordinate
4870: FD 77 31 LD (IY+$31),A ; seat it
4873: 23 INC HL ; read the second coordinate
4874: 7E LD A,(HL)
4875: FD 77 00 LD (IY+$00),A ; seat it
4878: DD 36 0A 00 LD (IX+$0A),$00 ; clear its velocity sub-pixel
487C: DD 36 0B 00 LD (IX+$0B),$00
4880: DD 36 0C 40 LD (IX+$0C),$40 ; set its downward drift speed
4884: DD 36 0D 00 LD (IX+$0D),$00
4888: DD 36 00 FF LD (IX+$00),$FF ; mark the slot live last, so it never runs with stale contents
488C: C9 RET
; ---- $488D-$48AC: data ----
488D: F0 40 F0 80 F0 F8 60 F8 80 F8 A0 F8 10 F8 00 80
489D: 00 90 10 10 30 10 60 10 80 10 A0 10 C0 10 F0 28
; take an object out of play -- occupancy byte and both of its sprite
; entry's coordinates -- and then arm the record's delay byte instead of
; leaving it clear, so the slot is held rather than freed
retireSlotIntoCooldown:
48AD: DD 36 00 00 LD (IX+$00),$00 ; clear the record's occupancy byte -- the object leaves play
48B1: FD 36 00 00 LD (IY+$00),$00 ; clear the sprite entry's first coordinate
48B5: FD 36 31 00 LD (IY+$31),$00 ; clear its second coordinate
48B9: DD 36 0E F0 LD (IX+$0E),$F0 ; load the record's delay byte with 0xF0 -- hold the slot on a cooldown rather than freeing it at once
48BD: C9 RET
; one frame of coin-input service: run the two coin-slot debounce/accept
; handlers and the phase-gated credit drip in turn, then pulse each
; mechanical coin counter once per coin still owed; dead unless an input
; edge or a pending debt is present
serviceCoinInputs:
48BE: CD E7 48 CALL awardOneCreditOnDebouncedInputEdge; debounce the service-credit line and award a credit on its edge
48C1: CD 41 49 CALL tallyCoinSlot1AndAwardCredit; run coin slot 1's accept-and-tally
48C4: CD 11 49 CALL meterCoinageTowardCreditOnEdge; run the phase-gated credit drip
48C7: CD 84 49 CALL pulseSlot1CoinCounter; pulse coin slot 1's mechanical counter for each coin still owed
48CA: CD D6 49 CALL pulseSlot2CoinCounter; pulse coin slot 2's mechanical counter for each coin still owed
48CD: C9 RET
; ---- $48CE-$48E6: data ----
48CE: 2C A7 13 FD 3B 88 0D DC F1 BF 68 0D D7 F1 FD 3B
48DE: FD DC FD A5 57 ED F1 52 B9
; per-frame debounce of IN0 bit 2 (port mirror 0xA9AE): rotate that bit
; into the bottom of the rolling history at 0xA983 (rl (hl)), fire only on
; a clean leading edge — the low three history bits reading 001 (idle,
; idle, pressed) — else return; on the edge request a sound (0x57F1) and
; award exactly one credit outright (C=1 into
; awardCoinCreditThenPulseCoinCounter, which folds it into the BCD credit
; count at 0xA986 and pulses the coin counter), a flat-credit path
; distinct from the coinage-metered coin-1 handler at 0x4941
awardOneCreditOnDebouncedInputEdge:
48E7: 3A AE A9 LD A,(workRam+1AE) ; read the input-port mirror
48EA: 0F RRCA ; rotate it right three times, dropping bit 2 -- the service-credit line -- into the carry
48EB: 0F RRCA
48EC: 0F RRCA
48ED: 21 83 A9 LD HL,$A983 ; point at the service-credit debounce history
48F0: CB 16 RL (HL) ; shift that bit into the bottom of the rolling history
48F2: 7E LD A,(HL)
48F3: E6 07 AND $07 ; keep the last three samples
48F5: FE 01 CP $01 ; a clean leading edge reads idle, idle, pressed
48F7: C0 RET NZ ; not an edge -- nothing to do
48F8: CD F1 57 CALL requestCoinSound ; blip the coin sound
48FB: 0E 01 LD C,$01 ; award exactly one credit
48FD: C3 6E 49 JP awardCoinCreditThenPulseCoinCounter; fold it into the credit count and pulse the counter
; ---- $4900-$4910: data ----
4900: BC A6 05 30 F1 7C 68 3B A5 38 FD F1 96 5D 17 9B
4910: B9
; phase-gated credit drip: rotate a selector bit (from 0xA9AE) into the
; phase cell 0xA9CA and act only when its low 3 bits read 1 -- request a
; sound, bump the counter at 0xA982, step the low byte at 0xA9CB up by
; 0x10; once the high byte at 0xA9CC still trails the raised low byte,
; pull the low byte back by (high&0xF0)+0x10 and tail into
; awardCoinCreditThenPulseCoinCounter with C = the high byte
meterCoinageTowardCreditOnEdge:
4911: 3A AE A9 LD A,(workRam+1AE) ; read the input-port mirror
4914: 21 CA A9 LD HL,$A9CA ; point at coin slot 2's debounce history
4917: 0F RRCA ; rotate the slot-2 selector bit toward the carry
4918: 0F RRCA
4919: CB 16 RL (HL)
491B: 7E LD A,(HL) ; shift it into the bottom of the history
491C: E6 07 AND $07 ; keep the last three samples
491E: FE 01 CP $01 ; act only on a clean leading edge
4920: C0 RET NZ ; otherwise nothing this frame
4921: EB EX DE,HL
4922: CD F1 57 CALL requestCoinSound ; blip the coin sound
4925: 21 82 A9 LD HL,$A982
4928: 34 INC (HL) ; bump the slot-2 coins-accepted count
4929: EB EX DE,HL
492A: 23 INC HL
492B: 7E LD A,(HL)
492C: C6 10 ADD A,$10 ; step the coins-inserted accumulator up by one coin's worth (0x10)
492E: 77 LD (HL),A ; store it back
492F: 47 LD B,A
4930: 23 INC HL
4931: 7E LD A,(HL) ; read the coinage ratio byte
4932: 90 SUB B ; compare the raised accumulator against it
4933: D0 RET NC ; ratio not yet reached -- wait for more
4934: 7E LD A,(HL)
4935: 4F LD C,A ; carry the whole ratio byte for crediting
4936: E6 F0 AND $F0 ; take its coins-per-credit high nibble
4938: C6 10 ADD A,$10 ; plus one coin's worth
493A: 2B DEC HL
493B: ED 44 NEG ; negate
493D: 86 ADD A,(HL) ; pull the accumulator back by that much, carrying the overshoot forward
493E: 77 LD (HL),A ; store the reduced accumulator
493F: 18 2D JR awardCoinCreditThenPulseCoinCounter; award the credit and pulse the counter
; one frame of coin slot 1 accounting: clock the raw coin line into a
; debounce shift register and, on a clean rising edge, count the coin --
; blip the coin sound, bump the tally, add a unit to the coins-inserted
; accumulator; once it passes the coinage threshold (coins-per-credit high
; nibble, credits awarded low) carry the overshoot forward and, unless the
; no-credit flag is set, add the low nibble to the packed-decimal credit
; count (saturated at 99) and repaint its panel; either overshoot path
; then pulses the mechanical coin counter
tallyCoinSlot1AndAwardCredit:
4941: 3A AE A9 LD A,(workRam+1AE) ; read the input-port mirror
4944: 21 C7 A9 LD HL,$A9C7 ; point at coin slot 1's debounce shift register
4947: 0F RRCA ; rotate the coin-1 line toward the carry
4948: CB 16 RL (HL) ; clock it into the debounce register
494A: 7E LD A,(HL)
494B: E6 07 AND $07 ; keep the last three samples
494D: FE 01 CP $01 ; a clean rising edge counts as one coin
494F: C0 RET NZ ; no edge -- done
4950: EB EX DE,HL
4951: CD F1 57 CALL requestCoinSound ; blip the coin sound
4954: 21 81 A9 LD HL,$A981
4957: 34 INC (HL) ; bump the count of coins slot 1 still owes its mechanical counter
4958: EB EX DE,HL
4959: 23 INC HL
495A: 7E LD A,(HL)
495B: C6 10 ADD A,$10 ; step the coins-inserted accumulator up by one coin (0x10)
495D: 77 LD (HL),A ; store it
495E: 47 LD B,A
495F: 23 INC HL
4960: 7E LD A,(HL) ; read the coinage ratio byte
4961: 90 SUB B ; compare the raised accumulator against it
4962: D0 RET NC ; still short of a credit
4963: 7E LD A,(HL)
4964: 4F LD C,A ; carry the coinage byte for crediting -- its low nibble is the credits awarded
4965: E6 F0 AND $F0 ; take the coins-per-credit high nibble
4967: C6 10 ADD A,$10 ; plus one coin's worth
4969: 2B DEC HL
496A: ED 44 NEG ; negate
496C: 86 ADD A,(HL) ; pull the accumulator back, carrying the overshoot past the threshold forward
496D: 77 LD (HL),A ; store the reduced accumulator
; outside free play, fold C's low decimal digit into the packed-decimal
; credit count at 0xa986 (decimal add, clamp to 99) and repaint that
; field, then run the coin-counter pulse
awardCoinCreditThenPulseCoinCounter:
496E: 3A C0 A9 LD A,(workRam+1C0) ; read the free-play flag
4971: A7 AND A
4972: 20 10 JR NZ,pulseSlot1CoinCounter; free play -- skip crediting, just pulse the counter
4974: 79 LD A,C ; take the low decimal digit of the award
4975: E6 0F AND $0F
4977: 21 86 A9 LD HL,$A986 ; point at the packed-decimal credit count
497A: 86 ADD A,(HL) ; add the digit in
497B: 27 DAA ; decimal-adjust the sum
497C: 77 LD (HL),A ; store the new credit count
497D: 30 02 JR NC,loc_4981 ; no overflow -- keep it
497F: 36 99 LD (HL),$99 ; clamp the credit count at 99
loc_4981:
4981: CD FB 4A CALL paintCreditCountPanel; repaint the credit panel
; drive coin slot 1's mechanical counter through one pulse for each coin
; the machine still owes it -- energise the line, release it at the half-
; way count, and take one off the debt as the pulse ends -- so a debt of
; two comes out as two separate pulses; with nothing owed it does nothing
pulseSlot1CoinCounter:
4984: 3A 81 A9 LD A,(workRam+181) ; read how many coins slot 1 still owes its mechanical counter
4987: A7 AND A
4988: C8 RET Z ; nothing owed -- do nothing
4989: 21 84 A9 LD HL,$A984 ; point at the pulse timer
498C: 7E LD A,(HL)
498D: A7 AND A
498E: 20 07 JR NZ,loc_4997 ; a pulse already under way -- fall through to its countdown
4990: 36 30 LD (HL),$30 ; arm the pulse for its full length (48 frames)
4992: 3C INC A
4993: 32 0A C3 LD ($C30A),A ; drive the coin-counter line high
4996: C9 RET
loc_4997:
4997: 35 DEC (HL) ; count the pulse timer down one frame
4998: 28 09 JR Z,loc_49a3 ; timer expired -- retire one coin from the debt
499A: 7E LD A,(HL)
499B: FE 18 CP $18 ; at the half-way count (24)
499D: C0 RET NZ ; not yet
499E: AF XOR A
499F: 32 0A C3 LD ($C30A),A ; drop the coin-counter line low again
49A2: C9 RET
loc_49a3:
49A3: 21 81 A9 LD HL,$A981 ; point at the debt count
49A6: 35 DEC (HL) ; take one coin off it so the next pulse can start
49A7: C9 RET
; tail of power-on config decode + self-test: slices two bits of the
; rolled config byte into work-RAM 0xa9c4/0xa9c6, kicks the watchdog,
; drives LS259 line 1 from ROM byte 0x0c3e, tiles the character plane,
; sums the 256-byte ROM block at 0x27de and derails a tampered image into
; the frame handler, else cold-starts
finishBootSelfTestAndColdStart:
49A8: 0F RRCA ; rotate the decoded configuration byte
49A9: 4F LD C,A
49AA: E6 07 AND $07 ; take three configuration bits
49AC: 32 C4 A9 LD (workRam+1C4),A ; store them
49AF: 79 LD A,C
49B0: 0F RRCA ; shift down to the next configuration bit
49B1: 0F RRCA
49B2: 0F RRCA
49B3: E6 01 AND $01
49B5: 32 C6 A9 LD (workRam+1C6),A ; store the attract-sound enable flag
49B8: 32 00 C2 LD (dsw1),A ; kick the watchdog
49BB: 3A 3E 0C LD A,(rom+C3E) ; take a fixed byte from the program image
49BE: 32 02 C3 LD ($C302),A ; drive a control-latch line from it
49C1: CD B1 00 CALL tileCharPlaneWithBoxLattice; tile the character plane with the box lattice
49C4: 06 00 LD B,$00 ; 256 bytes to sum
49C6: 21 DE 27 LD HL,$27DE ; point at the block to checksum
49C9: AF XOR A ; clear the running total
loc_49ca:
49CA: 86 ADD A,(HL) ; add this byte into the sum
49CB: 23 INC HL
49CC: 10 FC DJNZ loc_49ca ; over all 256 bytes
49CE: D6 C5 SUB $C5 ; compare the sum against its expected total (0xC5)
49D0: C4 D8 00 CALL NZ,saveAccumulatorForFrameInterrupt; a tampered image derails into the frame handler
49D3: C3 EB 32 JP petWatchdogThroughStartupDelayThenStartMachine; a good image cold-starts the machine and does not return
; drive one hardware output line as a train of square pulses, one pulse
; per unit of a pending count
pulseSlot2CoinCounter:
49D6: 3A 82 A9 LD A,(workRam+182) ; read how many coins slot 2 still owes its mechanical counter
49D9: A7 AND A
49DA: C8 RET Z ; nothing owed -- do nothing
49DB: 21 85 A9 LD HL,$A985 ; point at the slot-2 pulse timer
49DE: 7E LD A,(HL)
49DF: A7 AND A
49E0: 20 07 JR NZ,loc_49e9 ; a pulse already running -- fall through to its countdown
49E2: 36 30 LD (HL),$30 ; arm the pulse for its full length (48 frames)
49E4: 3C INC A
49E5: 32 0C C3 LD ($C30C),A ; drive the slot-2 coin-counter line high
49E8: C9 RET
loc_49e9:
49E9: 35 DEC (HL) ; count the pulse timer down one frame
49EA: 28 09 JR Z,loc_49f5 ; timer expired -- retire one coin from the debt
49EC: 7E LD A,(HL)
49ED: FE 18 CP $18 ; at the half-way count (24)
49EF: C0 RET NZ ; not yet
49F0: AF XOR A
49F1: 32 0C C3 LD ($C30C),A ; drop the slot-2 line low again
49F4: C9 RET
loc_49f5:
49F5: 21 82 A9 LD HL,$A982 ; point at the slot-2 debt count
49F8: 35 DEC (HL) ; take one coin off it so the next pulse can start
49F9: C9 RET
; reached only via the "wrong-glyph" derail ($19E6); the bytes run as
; harmless NOPs and stray-stack POPs. The real routine is at $4A0F.
; ---- $49FA-$4A0E: misaligned anti-tamper entry ----
49FA: EE A6 14 A5 3B 87 F1 DC D7 BF F1 DC C4 FD ED F1
4A0A: 7D A5 38 34 B9
; lay out one phase of the sequenced intro/self-test screen: stock an
; 8-byte control block at 0xA9F0 (ROM shape byte 0x3213, fixed fields,
; parked ROM pointer 0x56F1), write a fixed attribute run at 0xA400,
; colour three colour-plane rows and a small block by adding the base
; colour at 0xAD0C to fixed offsets, seed the active player's saved pen
; from its era, then tail-step the sequence sub-step; unreached by either
; tape
paintSelfTestScreenPhaseThenStepSequence:
4A0F: 3A 13 32 LD A,(rom+3213) ; take a shape byte from the program image
4A12: 32 F0 A9 LD (workRam+1F0),A ; drop it into the head of an eight-byte display control block
4A15: 3E 00 LD A,$00
4A17: 32 F1 A9 LD (workRam+1F1),A ; clear the next field
4A1A: 3E FF LD A,$FF
4A1C: 32 F2 A9 LD (workRam+1F2),A ; fixed field
4A1F: 3E 04 LD A,$04
4A21: 32 F3 A9 LD (workRam+1F3),A ; fixed field
4A24: 3E FF LD A,$FF
4A26: 32 F4 A9 LD (workRam+1F4),A ; fixed field
4A29: 3E 08 LD A,$08
4A2B: 32 F6 A9 LD (workRam+1F6),A ; a count field
4A2E: 21 F1 56 LD HL,$56F1 ; the script pointer 0x56F1
4A31: 22 F7 A9 LD (workRam+1F7),HL ; park it as the shared glyph-script cursor
4A34: 06 0D LD B,$0D ; thirteen attribute cells to fill
4A36: 21 00 A4 LD HL,$A400 ; point at the attribute run
4A39: 0E 14 LD C,$14 ; the fill value
loc_4a3b:
4A3B: 71 LD (HL),C ; lay one attribute cell
4A3C: 23 INC HL
4A3D: 10 FC DJNZ loc_4a3b ; fill all thirteen
4A3F: 3E 00 LD A,$00
4A41: 77 LD (HL),A ; then one blank cell -- the run continues in the shared band painter
; continue a caption's colour band from the caller's HL cursor: lay the
; caller's A over one cell, a 13-cell run of the caller's C and a 4-cell
; tail (0x0e), then fill two colour-RAM rows and six scattered colour
; cells from the base colour at 0xAD0C (each value base+offset), then seed
; the saved pen from the era and step the sequence sub-step; A/C/HL/DE
; left scratch
paintCaptionColourBandAndStepSequence:
4A42: 23 INC HL
4A43: 77 LD (HL),A ; lay the head colour over one cell
4A44: 23 INC HL
4A45: 06 0D LD B,$0D ; a thirteen-cell run
loc_4a47:
4A47: 71 LD (HL),C ; fill each with the band colour
4A48: 23 INC HL
4A49: 10 FC DJNZ loc_4a47 ;
4A4B: 3E 0E LD A,$0E ; the tail colour
4A4D: 06 04 LD B,$04 ; a four-cell tail
loc_4a4f:
4A4F: 77 LD (HL),A ; lay each tail cell
4A50: 23 INC HL
4A51: 10 FC DJNZ loc_4a4f ;
4A53: 21 B1 A7 LD HL,$A7B1 ; a tilemap address...
4A56: CB 94 RES 2,H ; ...folded down into colour RAM
4A58: 3A 0C AD LD A,(workRam+50C) ; read the base pen colour
4A5B: 4F LD C,A ; keep it
4A5C: 3E A0 LD A,$A0
4A5E: 81 ADD A,C ; base plus a fixed offset
4A5F: CD 19 13 CALL fillCellRun ; fill a colour-RAM row with it
4A62: 21 D1 A5 LD HL,$A5D1
4A65: CB 94 RES 2,H ; fold to colour RAM
4A67: 3E 20 LD A,$20
4A69: 81 ADD A,C ; base plus a fixed offset
4A6A: CD 19 13 CALL fillCellRun ; fill a second colour-RAM row
4A6D: 21 10 A6 LD HL,$A610
4A70: CB 94 RES 2,H ; fold to colour RAM
4A72: 3E A0 LD A,$A0
4A74: 81 ADD A,C ; base plus an offset
4A75: 77 LD (HL),A ; colour one cell
4A76: 19 ADD HL,DE ; step one row up
4A77: 3E 20 LD A,$20
4A79: 81 ADD A,C ; base plus an offset
4A7A: 77 LD (HL),A ; colour the cell above it
4A7B: 21 12 A6 LD HL,$A612
4A7E: CB 94 RES 2,H ; fold to colour RAM
4A80: 3E E0 LD A,$E0
4A82: 81 ADD A,C ; base plus an offset
4A83: 77 LD (HL),A ; colour a cell
4A84: 19 ADD HL,DE ; step one row up
4A85: 3E 60 LD A,$60
4A87: 81 ADD A,C ; base plus an offset
4A88: 77 LD (HL),A ; colour the cell above it
4A89: 21 11 A6 LD HL,$A611
4A8C: CB 94 RES 2,H ; fold to colour RAM
4A8E: 3E A0 LD A,$A0
4A90: 81 ADD A,C ; base plus an offset
4A91: 77 LD (HL),A ; colour a cell
4A92: 19 ADD HL,DE ; step one row up
4A93: 3E 20 LD A,$20
4A95: 81 ADD A,C ; base plus an offset
4A96: 77 LD (HL),A ; colour the cell above it
4A97: CD 9C 33 CALL setSavedPenFromEra ; seed the active player's saved pen from its era
4A9A: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-step on
; step thirteen cells of the character plane on by one shape each, but
; only where a script says so, walking that script through one shared
; cursor cell that is left wherever the walk ended; two bits of one
; incoming byte set the directions independently -- the low bit reads the
; script backwards and steps the shape DOWN, the next bit takes the cells
; a row up instead of a row down
stepThirteenScriptedGlyphCells:
4A9D: 06 0D LD B,$0D ; thirteen cells to step
4A9F: 2A F7 A9 LD HL,(workRam+1F7) ; load the shared glyph-script cursor
loc_4aa2:
4AA2: 7E LD A,(HL) ; read this script byte
4AA3: A7 AND A
4AA4: EB EX DE,HL
4AA5: 28 09 JR Z,loc_4ab0 ; a zero leaves this cell's shape alone
4AA7: 7E LD A,(HL) ; read the plane cell's shape
4AA8: 3C INC A ; step the shape up one
4AA9: CB 41 BIT 0,C ; the low direction bit
4AAB: 28 02 JR Z,loc_4aaf ; forward -- keep the step up
4AAD: 3D DEC A
4AAE: 3D DEC A ; backward -- step the shape down instead
loc_4aaf:
4AAF: 77 LD (HL),A ; write the stepped shape back
loc_4ab0:
4AB0: CB 49 BIT 1,C ; the row-direction bit
4AB2: 11 20 00 LD DE,$0020 ; one row down
4AB5: 28 03 JR Z,loc_4aba ; forward
4AB7: 11 E0 FF LD DE,$FFE0 ; or one row up
loc_4aba:
4ABA: 19 ADD HL,DE ; move to the next plane cell
4ABB: EB EX DE,HL
4ABC: 2A F7 A9 LD HL,(workRam+1F7) ; reload the script cursor
4ABF: 23 INC HL ; step the script forward...
4AC0: CB 41 BIT 0,C ; the low direction bit
4AC2: 28 02 JR Z,loc_4ac6 ;
4AC4: 2B DEC HL ; ...or backward when it is set
4AC5: 2B DEC HL
loc_4ac6:
4AC6: 22 F7 A9 LD (workRam+1F7),HL ; leave the cursor where the walk ended
4AC9: 10 D7 DJNZ loc_4aa2 ; over all thirteen cells
4ACB: C9 RET
; turn the two four-bit coinage settings into the byte each coin slot's
; accept arm works from, and raise the free-play flag when either of them
; reads free play
unpackCoinage:
4ACC: 3A B1 A9 LD A,(workRam+1B1) ; read the packed coinage settings
4ACF: E6 0F AND $0F ; take the low nibble -- coin slot 1's setting
4AD1: FE 0F CP $0F ; is it the free-play code?
4AD3: 20 05 JR NZ,loc_4ada ;
4AD5: 21 C0 A9 LD HL,$A9C0
4AD8: 36 FF LD (HL),$FF ; raise the free-play flag
loc_4ada:
4ADA: 21 95 4B LD HL,$4B95 ; point at the coinage value table
4ADD: CF RST $08 ; look the setting's value up
4ADE: 32 C9 A9 LD (workRam+1C9),A ; store it as coin slot 1's ratio byte
4AE1: 3A B1 A9 LD A,(workRam+1B1) ; re-read the coinage settings
4AE4: 0F RRCA ; rotate the high nibble down -- coin slot 2's setting
4AE5: 0F RRCA
4AE6: 0F RRCA
4AE7: 0F RRCA
4AE8: E6 0F AND $0F ; isolate that nibble
4AEA: FE 0F CP $0F ; free-play code?
4AEC: 20 05 JR NZ,loc_4af3 ;
4AEE: 21 C0 A9 LD HL,$A9C0
4AF1: 36 FF LD (HL),$FF ; raise the same free-play flag
loc_4af3:
4AF3: 21 95 4B LD HL,$4B95 ; point at the coinage value table
4AF6: CF RST $08 ; look the setting's value up
4AF7: 32 CC A9 LD (workRam+1CC),A ; store it as coin slot 2's ratio byte
4AFA: C9 RET
; set the pen colour, the destination cell and the source byte, then paint
; them through the packed-digit painter; every one of the three is fixed
; here, so a caller chooses none of them
paintCreditCountPanel:
4AFB: 0E 10 LD C,$10 ; select the panel pen colour
4AFD: 11 7F A4 LD DE,$A47F ; the cell the first digit lands in
4B00: 21 86 A9 LD HL,$A986 ; read the packed-decimal credit count
4B03: CD 81 0D CALL paintTwoUnsuppressedDigitsFromByte; paint its two digits
4B06: C9 RET
; ---- $4B07-$4B18: data ----
4B07: 2A 41 AB 7D AC 2F 87 87 ED 6A 22 41 AB ED 5F 85
4B17: AC C9
; step the sequence's inner sub-step on, folding a block of the program
; image on the way; a total that does not match advances the outer phase
; instead, which derails the sequence rather than halting it
stepSequenceUnderChecksum:
4B19: 11 CC 0B LD DE,$0BCC ; point at the program block to sum
4B1C: 01 89 00 LD BC,$0089 ; 256 bytes, starting the total at 0x89
4B1F: 3A 50 1A LD A,(rom+1A50) ; read the expected total from the program image
4B22: 67 LD H,A ; keep it
loc_4b23:
4B23: 1A LD A,(DE) ; take a block byte
4B24: 81 ADD A,C ; fold it into the running total
4B25: 4F LD C,A
4B26: 13 INC DE
4B27: 10 FA DJNZ loc_4b23 ; over all 256 bytes
4B29: 94 SUB H ; compare against the expected total
4B2A: C4 11 0F CALL NZ,advanceSequencePhase; a mismatch advances the outer sequence phase -- derailing the sequence
4B2D: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-step on
; copy three tilemap cells into three two-byte keeps, reading each cell
; twice because its two planes sit a fixed distance apart
copyThreeTilemapCellsFromBothPlanes:
4B30: 21 1B 0D LD HL,$0D1B ; point at a table of three address records
4B33: 06 03 LD B,$03 ; three cells to copy
loc_4b35:
4B35: 5E LD E,(HL) ; read the source address low...
4B36: 23 INC HL
4B37: 56 LD D,(HL) ; ...and high
4B38: 23 INC HL
4B39: 1A LD A,(DE) ; read the cell on the first plane
4B3A: 08 EX AF,AF'
4B3B: 3E 04 LD A,$04
4B3D: 82 ADD A,D ; step the source high byte to the other plane -- a fixed distance apart
4B3E: 57 LD D,A
4B3F: 1A LD A,(DE) ; read the same cell on the second plane
4B40: 5E LD E,(HL) ; read the destination keep low...
4B41: 23 INC HL
4B42: 56 LD D,(HL) ; ...and high
4B43: 23 INC HL
4B44: 12 LD (DE),A ; store the second-plane byte
4B45: 1C INC E ; next byte of the keep -- low half only, so it wraps in its own page
4B46: 08 EX AF,AF'
4B47: 12 LD (DE),A ; store the first-plane byte beside it
4B48: 10 EB DJNZ loc_4b35 ; over all three cells
4B4A: C9 RET
; draw the next pseudo-random byte: advance the seventeen-byte shift
; register one place, fill the vacated head with the exclusive-or of two
; taps, and hand back that feedback plus the frame counter, so two draws
; at different moments differ even where the register has not moved
drawRandomByte:
4B4B: D9 EXX
4B4C: 21 3F AB LD HL,$AB3F ; source: the register's second-from-top byte
4B4F: 11 40 AB LD DE,$AB40 ; destination: the top byte
4B52: 01 10 00 LD BC,$0010 ; sixteen bytes to move
4B55: ED B8 LDDR ; shift the whole shift register one place along
4B57: 21 40 AB LD HL,$AB40
4B5A: 3A 37 AB LD A,(workRam+337) ; read one feedback tap
4B5D: AE XOR (HL) ; exclusive-or it with the other tap
4B5E: 32 30 AB LD (workRam+330),A ; drop the feedback into the vacated head
4B61: 21 80 A9 LD HL,$A980
4B64: 86 ADD A,(HL) ; add the free-running frame counter to the result
4B65: D9 EXX
4B66: C9 RET
; copy a fixed seventeen-byte run of program space at 0x4B84 into the
; random register block, then check the image that run came out of: three
; bytes taken from two fixed words of program space are added to one
; constant, and any total but zero means the program space being read is
; not the one the constant was picked for -- on that outcome control
; transfers to 0x6000, outside the image, so it raises rather than
; running. The copy is unconditional and COMPLETE before the check runs,
; so nothing this entry wrote is gated by it
seedRandomRegister:
4B67: 21 84 4B LD HL,$4B84 ; point at the seventeen seed bytes in program space
4B6A: 11 30 AB LD DE,$AB30 ; point at the random register
4B6D: 01 11 00 LD BC,$0011 ; seventeen bytes to copy
4B70: ED B0 LDIR ; seed the random register from the fixed run
4B72: DD 2A 6D 08 LD IX,(rom+86D) ; take a word of program space for the image check
4B76: 2A 70 08 LD HL,(rom+870) ; take a second word of program space
4B79: DD 7D LD A,IXL ; start the check total with the first word's low byte
4B7B: DD 84 ADD A,IXH ; add the first word's high byte
4B7D: 85 ADD A,L ; add the second word's low byte
4B7E: C6 44 ADD A,$44 ; add the fixed bias -- a sound program image brings the total to zero
4B80: C2 00 60 JP NZ,$6000 ; any other total: this is not the image the constant was picked for, so jump outside the image -- never taken on a good board
4B83: C9 RET
; ---- $4B84-$4BA4: data ----
4B84: FF 05 F6 80 32 17 9C C9 DD 21 74 98 FD BF 24 AE
4B94: 46 01 02 03 04 05 06 07 11 13 15 21 22 24 31 33
4BA4: 01
; copy forty bytes of program space into the five-entry high-score table,
; which is the only way that table is ever initialised
loadDefaultHighScores:
4BA5: 21 B1 4B LD HL,$4BB1 ; point at the default high-score block in program space
4BA8: 11 08 AB LD DE,$AB08 ; point at the five-entry high-score table
4BAB: 01 28 00 LD BC,$0028 ; forty bytes -- the whole table
4BAE: ED B0 LDIR ; stamp the default high scores in -- the only thing that ever fills the table
4BB0: C9 RET
; ---- $4BB1-$4BD8: data ----
4BB1: 00 00 00 01 7C 11 68 F1 01 00 88 00 3B 11 A5 F1
4BC1: 02 60 84 00 38 11 FD F1 03 20 65 00 68 11 68 F1
4BD1: 04 00 43 00 BF 11 A5 F1
; a bare transfer to 0x08AE and no return; no cell is read or written and
; no register moves
trampolineToSelectFoldBlock:
4BD9: C3 AE 08 JP selectFoldBlock ; hand on to the routine that picks a program block to fold
; paint five labelled numeric readouts up the tile plane: seat each of
; five source records (0xab08, stride 8), its tile-plane cursor cell
; (0xa711, stride 2) and its pen colour, then hand to the column painter
; paintLabelledNumericReadoutColumn; writes tile/colour cells
; 0xa0f1-0xa719
paintFiveLabelledNumericReadouts:
4BDC: 21 08 AB LD HL,$AB08 ; first score record
4BDF: 11 11 A7 LD DE,$A711 ; its cursor cell in the tile plane
4BE2: 0E 14 LD C,$14 ; pen colour for this readout
4BE4: CD 1F 4C CALL paintLabelledNumericReadoutColumn; paint it as an upward column
4BE7: 21 10 AB LD HL,$AB10 ; second score record
4BEA: 11 13 A7 LD DE,$A713 ; its cursor cell
4BED: 0E 16 LD C,$16 ; pen colour
4BEF: CD 1F 4C CALL paintLabelledNumericReadoutColumn; paint it
4BF2: 21 18 AB LD HL,$AB18 ; third score record
4BF5: 11 15 A7 LD DE,$A715 ; its cursor cell
4BF8: 0E 12 LD C,$12 ; pen colour
4BFA: CD 1F 4C CALL paintLabelledNumericReadoutColumn; paint it
4BFD: 21 20 AB LD HL,$AB20 ; fourth score record
4C00: 11 17 A7 LD DE,$A717 ; its cursor cell
4C03: 0E 15 LD C,$15 ; pen colour
4C05: CD 1F 4C CALL paintLabelledNumericReadoutColumn; paint it
4C08: 21 28 AB LD HL,$AB28 ; fifth score record
4C0B: 11 19 A7 LD DE,$A719 ; its cursor cell
4C0E: 0E 13 LD C,$13 ; pen colour
4C10: CD 1F 4C CALL paintLabelledNumericReadoutColumn; paint it
4C13: C9 RET
; ---- $4C14-$4C1E: data ----
4C14: 73 A6 14 7E 29 F8 96 5D F3 13 B9
; paint a labelled numeric readout as one upward tile-plane column: a
; table-indexed three-tile pictogram (source lead byte x3 into 0x4cb4), a
; six-digit field, then a three-tile suffix, each cell paired into the
; colour plane with the caller's pen colour
paintLabelledNumericReadoutColumn:
4C1F: E5 PUSH HL ; save the source record pointer
4C20: 7E LD A,(HL) ; read the record's lead byte
4C21: 87 ADD A,A ; times two
4C22: 86 ADD A,(HL) ; times three -- three tiles per pictogram, so the lead byte selects the pictogram
4C23: 21 B4 4C LD HL,$4CB4 ; point at the pictogram table
4C26: CF RST $08 ; fetch the indexed pictogram tile
4C27: 12 LD (DE),A ; stamp the tile
4C28: CB 92 RES 2,D ; drop to the paired colour cell
4C2A: 79 LD A,C ; pen colour
4C2B: 12 LD (DE),A ; write the colour
4C2C: CB D2 SET 2,D ; back to the tile cell
4C2E: 23 INC HL ; next pictogram tile
4C2F: E7 RST $20 ; step the cursor up one cell
4C30: 7E LD A,(HL) ; read the tile
4C31: 12 LD (DE),A ; stamp it
4C32: CB 92 RES 2,D ; drop to the colour cell
4C34: 79 LD A,C ; pen colour
4C35: 12 LD (DE),A ; write the colour
4C36: CB D2 SET 2,D ; back to the tile cell
4C38: 23 INC HL ; next pictogram tile
4C39: E7 RST $20 ; step the cursor up one cell
4C3A: 7E LD A,(HL) ; read the third pictogram tile
4C3B: 12 LD (DE),A ; stamp it
4C3C: CB 92 RES 2,D ; drop to the colour cell
4C3E: 79 LD A,C ; pen colour
4C3F: 12 LD (DE),A ; write the colour
4C40: CB D2 SET 2,D ; back to the tile cell
4C42: 21 80 FF LD HL,$FF80 ; drop the cursor 0x80 down to the six-digit field
4C45: 19 ADD HL,DE
4C46: EB EX DE,HL
4C47: E1 POP HL ; restore the source record pointer
4C48: 23 INC HL ; step the source past its lead bytes to the digit field
4C49: 23 INC HL
4C4A: 23 INC HL
4C4B: CD 73 0D CALL paintSixDigitFieldSuppressingLeadingZeros; paint the six-digit score field, leading zeros suppressed
4C4E: E5 PUSH HL ; save the cursor
4C4F: 21 A0 FF LD HL,$FFA0 ; drop the cursor 0x60 down to the suffix
4C52: 19 ADD HL,DE
4C53: EB EX DE,HL
4C54: E1 POP HL ; restore the source pointer
4C55: 23 INC HL ; step the source to the suffix bytes
4C56: 23 INC HL
4C57: 23 INC HL
4C58: 7E LD A,(HL) ; read the first suffix tile
4C59: 12 LD (DE),A ; stamp it
4C5A: CB 92 RES 2,D ; drop to the colour cell
4C5C: 79 LD A,C ; pen colour
4C5D: 12 LD (DE),A ; write the colour
4C5E: CB D2 SET 2,D ; back to the tile cell
4C60: 23 INC HL ; next suffix tile
4C61: E7 RST $20 ; step the cursor up one cell
4C62: 7E LD A,(HL) ; read the tile
4C63: 12 LD (DE),A ; stamp it
4C64: CB 92 RES 2,D ; drop to the colour cell
4C66: 79 LD A,C ; pen colour
4C67: 12 LD (DE),A ; write the colour
4C68: CB D2 SET 2,D ; back to the tile cell
4C6A: 23 INC HL ; next suffix tile
4C6B: E7 RST $20 ; step the cursor up one cell
4C6C: 7E LD A,(HL) ; read the third suffix tile
4C6D: 12 LD (DE),A ; stamp it
4C6E: CB 92 RES 2,D ; drop to the colour cell
4C70: 79 LD A,C ; pen colour
4C71: 12 LD (DE),A ; write the colour
4C72: CB D2 SET 2,D ; back to the tile cell
4C74: C9 RET
; sequence arm (computed-dispatch entry 3 of the table at 0x0F29): blank a
; fixed character-cell run, copy the active player's saved 16-byte context
; block into the live block at 0xAD00, step the sequence sub-index; when
; play is active it also posts the round number (cmd 6) and lives-less-one
; (cmd 5) to the command ring and folds a fixed program span (0x5B50, 256
; bytes) into an XOR whose low bit less one drives the picture-enable
; latch 0xC308 -- a tamper guard
loadActivePlayerContextAndPostRoundHud:
4C75: CD D2 07 CALL blankFourteenCharCells; blank the fixed run of character cells
4C78: 3A 32 AD LD A,(workRam+532) ; read the active-player flag
4C7B: A7 AND A ; test it
4C7C: 21 10 AD LD HL,$AD10 ; default to player one's saved context block
4C7F: 28 03 JR Z,loc_4c84 ; player one: keep it
4C81: 21 20 AD LD HL,$AD20 ; otherwise player two's saved context block
loc_4c84:
4C84: 11 00 AD LD DE,$AD00 ; point at the live context block
4C87: 01 10 00 LD BC,$0010 ; sixteen bytes -- the whole context
4C8A: ED B0 LDIR ; copy the saved context into the live block
4C8C: 3A 30 AD LD A,(workRam+530) ; read the play-active flag
4C8F: A7 AND A ; test it
4C90: CA 1A 0F JP Z,advanceSequenceSubStep; not in play: just step the sequence sub-index and return
4C93: 3A 01 AD LD A,(workRam+501) ; read the round number
4C96: 16 06 LD D,$06 ; command 6 -- the round number
4C98: 5F LD E,A ; as its argument
4C99: FF RST $38 ; post it to the command ring
4C9A: 3A 00 AD LD A,(workRam+500) ; read the lives-remaining count
4C9D: 3D DEC A ; less one
4C9E: 16 05 LD D,$05 ; command 5 -- lives less one
4CA0: 5F LD E,A ; as its argument
4CA1: FF RST $38 ; post it to the command ring
4CA2: 06 00 LD B,$00 ; two hundred fifty-six bytes to fold
4CA4: 21 50 5B LD HL,$5B50 ; point at the fixed program span to fold
4CA7: 97 SUB A ; start the fold at zero
loc_4ca8:
4CA8: AE XOR (HL) ; fold the next byte into the running total
4CA9: 23 INC HL
4CAA: 10 FC DJNZ loc_4ca8 ; loop over the whole span
4CAC: C6 FF ADD A,$FF ; less one
4CAE: 32 08 C3 LD ($C308),A ; drive the picture-enable latch with it -- a tampered image blanks the picture
4CB1: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index and return
; ---- $4CB4-$4CC2: data ----
4CB4: 96 ED DC 9B 3B 87 CD D7 87 F3 DC C4 7F DC C4
; file the active player's finished score into the five-record high-score
; board: walk the standing scores top-down comparing each (isScoreBelow)
; to find the first the new score is not below, slide the records beneath
; down one slot (lddr), write the new score with blank 0xf1 name-cell
; sentinels, look up its initial-glyph row pointer, and renumber the rank
; column 0..4; carry returns clear when filed, set when the score beat
; none
fileScoreIntoHighScoreTable:
4CC3: 21 0B AB LD HL,$AB0B ; point at the top standing score's high byte
4CC6: 06 05 LD B,$05 ; five records to consider
4CC8: 3A 32 AD LD A,(workRam+532) ; read the active-player flag
4CCB: A7 AND A ; test it
4CCC: 11 35 AD LD DE,$AD35 ; default to player one's finished score
4CCF: 28 03 JR Z,loc_4cd4 ; player one: keep it
4CD1: 11 38 AD LD DE,$AD38 ; otherwise player two's finished score
loc_4cd4:
4CD4: E5 PUSH HL ; save the standing pointer
4CD5: D5 PUSH DE ; save the score pointer
4CD6: CD 2B 4D CALL isScoreBelow ; is the new score below this standing score?
4CD9: 30 09 JR NC,loc_4ce4 ; not below: this is its slot
4CDB: D1 POP DE ; restore the score pointer
4CDC: E1 POP HL ; restore the standing pointer
4CDD: 3E 08 LD A,$08 ; record stride is eight
4CDF: CF RST $08 ; walk the standing pointer down one record
4CE0: 10 F2 DJNZ loc_4cd4 ; try the next standing score
4CE2: 37 SCF ; beat none -- set carry to say so
4CE3: C9 RET
loc_4ce4:
4CE4: 05 DEC B ; how many records lie below the slot
4CE5: 28 3F JR Z,loc_4d26 ; slot is the bottom record: nothing to slide
4CE7: 21 27 AB LD HL,$AB27 ; last byte of the block to slide
4CEA: 11 2F AB LD DE,$AB2F ; last byte of its destination one slot down
4CED: 78 LD A,B ; record count
4CEE: 87 ADD A,A ; times two
4CEF: 87 ADD A,A ; times four
4CF0: 87 ADD A,A ; times eight -- eight cells per record
4CF1: 4F LD C,A ; that many bytes
4CF2: 06 00 LD B,$00
4CF4: ED B8 LDDR ; slide the lower records down one slot
4CF6: EB EX DE,HL ; point at the freed slot
loc_4cf7:
4CF7: 2B DEC HL ; back up one cell
4CF8: 36 F1 LD (HL),$F1 ; blank a name cell
4CFA: 2B DEC HL ; back up one cell
4CFB: 36 F1 LD (HL),$F1 ; blank a name cell
4CFD: 2B DEC HL ; back up one cell
4CFE: 36 F1 LD (HL),$F1 ; blank a name cell
4D00: 22 91 A9 LD (workRam+191),HL ; remember where the three name cells start
4D03: 2B DEC HL ; back up to the score field
4D04: D1 POP DE ; restore the finished-score pointer
4D05: 01 03 00 LD BC,$0003 ; three score bytes
4D08: EB EX DE,HL ; make the finished score the copy source
4D09: ED B8 LDDR ; copy the new score into the slot
4D0B: 1A LD A,(DE) ; read the rank byte the copy uncovered
4D0C: E1 POP HL ; discard the saved standing pointer
4D0D: 21 31 A5 LD HL,$A531 ; point at the initial-glyph row table
4D10: 87 ADD A,A ; rank times two -- two bytes per entry
4D11: CF RST $08 ; look up this rank's initial-glyph row pointer
4D12: 22 93 A9 LD (workRam+193),HL ; remember it
4D15: 21 08 AB LD HL,$AB08 ; point at the high-score table base
4D18: 11 08 00 LD DE,$0008 ; record stride is eight
4D1B: 06 05 LD B,$05 ; five records
4D1D: AF XOR A ; start the rank at zero
loc_4d1e:
4D1E: 77 LD (HL),A ; write this record's rank number
4D1F: 19 ADD HL,DE ; next record
4D20: 3C INC A ; next rank
4D21: 10 FB DJNZ loc_4d1e ; renumber all five ranks top to bottom
4D23: 37 SCF ; set carry
4D24: 3F CCF ; then clear it -- carry clear says the score was filed
4D25: C9 RET
loc_4d26:
4D26: 21 2F AB LD HL,$AB2F ; slot is the bottom record: point at its last byte
4D29: 18 CC JR loc_4cf7 ; go blank the name cells and write the score -- nothing to slide
; answer whether one three-byte score is below another, both read most
; significant byte first from the two addresses given and DOWNWARD, all
; three equal counting as not below; nothing is written -- the answer,
; mirrored into carry for the caller to branch on, is the whole product
isScoreBelow:
4D2B: 0E 03 LD C,$03 ; three bytes to compare, most significant first
loc_4d2d:
4D2D: 1A LD A,(DE) ; read the candidate byte
4D2E: BE CP (HL) ; compare with the standing byte
4D2F: D8 RET C ; candidate lower: below -- return with carry set
4D30: 20 05 JR NZ,loc_4d37 ; they differ but candidate is higher: not below
4D32: 1B DEC DE ; same so far: step both down to the next byte
4D33: 2B DEC HL
4D34: 0D DEC C ; one fewer byte
4D35: 20 F6 JR NZ,loc_4d2d ; compare the next byte
loc_4d37:
4D37: 37 SCF ; set carry
4D38: 3F CCF ; then clear it -- carry clear says not below
4D39: C9 RET
; step a three-place base-sixty tick counter at 0xAD05, carrying into the
; next place only while a place rolls over; only on a full roll-over count
; down the reload timer at 0xA9D7, and each time it fires rearm it from
; 0xA9D6, climb the escalation rung at 0xACC0 one step (held at 15), and
; apply that rung's tuning row
escalateDifficultyRungOnCounterWrap:
4D3A: 21 05 AD LD HL,$AD05 ; point at the ones place of the base-sixty tick counter
4D3D: CD 67 4D CALL advanceSexagesimalDigit; step it on by one
4D40: D8 RET C ; no roll-over: the whole pass ends here
4D41: 2C INC L ; move to the next place
4D42: CD 67 4D CALL advanceSexagesimalDigit; step it on by one
4D45: 38 04 JR C,loc_4d4b ; it did not roll over: skip the top place
4D47: 2C INC L ; move to the top place
4D48: CD 67 4D CALL advanceSexagesimalDigit; step it on by one
loc_4d4b:
4D4B: 21 D7 A9 LD HL,$A9D7 ; point at the reload timer
4D4E: 7E LD A,(HL) ; read it
4D4F: A7 AND A ; test it
4D50: C8 RET Z ; timer already spent: nothing more to do
4D51: 35 DEC (HL) ; count the timer down one
4D52: C0 RET NZ ; not yet zero: done
4D53: 3A D6 A9 LD A,(workRam+1D6) ; read the reload value
4D56: 77 LD (HL),A ; rearm the timer with it
4D57: 3A C0 AC LD A,(workRam+4C0) ; read the difficulty-escalation rung
4D5A: 3C INC A ; climb one step
4D5B: FE 10 CP $10 ; past the top rung?
4D5D: 38 02 JR C,loc_4d61 ; no: keep it
4D5F: 3E 0F LD A,$0F ; clamp it to the top rung -- fifteen
loc_4d61:
4D61: 32 C0 AC LD (workRam+4C0),A ; store the escalation rung
4D64: C3 9A 1A JP applyEraRungSettings; apply this rung's tuning row and return
; advance one two-digit packed-decimal place of a base-sixty counter,
; storing the stepped value before testing it and replacing it with zero
; once it reaches sixty; the answer comes back in the carry, inverted, so
; a set carry means it did NOT wrap
advanceSexagesimalDigit:
4D67: 7E LD A,(HL) ; read the packed-decimal place
4D68: C6 01 ADD A,$01 ; add one
4D6A: 27 DAA ; decimal-correct the result
4D6B: 77 LD (HL),A ; store the stepped value
4D6C: FE 60 CP $60 ; reached sixty?
4D6E: D8 RET C ; no: return with carry set -- did not roll over
4D6F: 36 00 LD (HL),$00 ; rolled over: store zero
4D71: C9 RET
; ---- $4D72-$4DDD: data ----
4D72: 4F 3A 30 AD A7 C8 11 83 A7 79 FE 07 38 02 3E 06
4D82: A7 28 0C 06 09 0E 18 08 CD AF 4D 08 3D 20 F8 01
4D92: 10 F1 21 DD 59 19 30 05 CD CF 4D 18 F5 06 00 21
4DA2: 11 07 97 AE 23 10 FC C6 19 C2 B1 4B C9 78 C6 03
4DB2: 12 3D 1B 12 E7 78 12 3C 13 12 21 00 FC 19 E7 71
4DC2: 2B 71 7D C6 20 6F 30 01 24 71 23 71 C9 EB 70 2B
4DD2: 36 F1 CB 94 71 23 71 CB D4 EB E7 C9
; award an extra life when the active player's score reaches one of the
; bonus marks, once per mark. It returns immediately unless PLAY_ACTIVE is
; set; picks one of the two mark tables at ROM 0x4E1B and 0x4E30 on bit 0
; of the settings byte at 0xA9C3; and searches the chosen table with cpir
; for an EXACT match on the top byte of the active player's six-digit
; packed-decimal score -- 0xAD35 or 0xAD38, selected on ACTIVE_PLAYER --
; so only a score standing on a mark matches, never one compared against
; it. Bit 0 of 0xAD03 makes the award one-shot: a match while that bit is
; already set does nothing, and the first call that does not match clears
; it again. On a fresh match it sets the bit, increments LIVES_REMAINING,
; posts ring command 5 with the count from BEFORE the increment, and tail-
; jumps into requestBonusLifeSound for the sound, so
; requestBonusLifeSound's ret returns to this routine's caller. Its one
; call site in the image is serviceRoundThenResolvePlayerState, the round
; engine's straight-line block of calls, which reaches it once per
; dispatch of that block
awardBonusLifeAtScoreMark:
4DDE: 3A 30 AD LD A,(workRam+530) ; read the play-active flag
4DE1: A7 AND A ; test it
4DE2: C8 RET Z ; not in play: award nothing
4DE3: 3A C3 A9 LD A,(workRam+1C3) ; read the bonus-life setting
4DE6: E6 01 AND $01 ; keep bit 0 -- picks which mark list
4DE8: 21 1B 4E LD HL,$4E1B ; default to the first mark list
4DEB: 28 03 JR Z,loc_4df0 ; bit 0 clear: use it
4DED: 21 30 4E LD HL,$4E30 ; otherwise the second mark list
loc_4df0:
4DF0: 4E LD C,(HL) ; read the list length into the count
4DF1: 06 00 LD B,$00
4DF3: 23 INC HL ; step past the length byte to the marks
4DF4: 3A 32 AD LD A,(workRam+532) ; read the active-player flag
4DF7: A7 AND A ; test it
4DF8: 3A 35 AD LD A,(workRam+535) ; default to player one's score top byte
4DFB: 28 03 JR Z,loc_4e00 ; player one: keep it
4DFD: 3A 38 AD LD A,(workRam+538) ; otherwise player two's score top byte
loc_4e00:
4E00: ED B1 CPIR ; scan the list for an exact match on that top byte
4E02: 21 03 AD LD HL,$AD03 ; point at the bonus-life latch
4E05: 20 11 JR NZ,loc_4e18 ; no match: go clear the one-shot latch below
4E07: CB 46 BIT 0,(HL) ; matched: is the latch already set?
4E09: C0 RET NZ ; already awarded this mark: do nothing
4E0A: CB C6 SET 0,(HL) ; set the one-shot latch
4E0C: 21 00 AD LD HL,$AD00 ; point at the lives-remaining count
4E0F: 7E LD A,(HL) ; read it
4E10: 34 INC (HL) ; add one life
4E11: 16 05 LD D,$05 ; command 5 -- the award
4E13: 5F LD E,A ; argument is the count before the increment
4E14: FF RST $38 ; post it to the command ring
4E15: C3 05 58 JP requestBonusLifeSound; request the bonus-life sound and return through it
loc_4e18:
4E18: CB 86 RES 0,(HL) ; no match: clear the one-shot latch
4E1A: C9 RET
; ---- $4E1B-$4E4E: data ----
4E1B: 14 01 06 11 16 21 26 31 36 41 46 51 56 61 66 71
4E2B: 76 81 86 91 96 11 02 08 14 20 26 32 38 44 50 56
4E3B: 62 68 74 80 86 92 98 6F A6 14 88 57 A5 BF 34 D7
4E4B: F1 9B F1 B9
; dispatch one round's per-frame collision pass by ERA_INDEX (0xad04): era
; 4 to dispatchEra4CollisionByFrameParity, era 1 to
; splitCollisionWorkByFrameParity, every other era split on FRAME_TICK's
; (0xa980) low bit to dispatchShotSweepByMotherShipArmed (odd) else
; runAllCollisionSweepsThisFrame (even); reached from the substep-7
; dispatcher 0x1199
dispatchCollisionPassByEra:
4E4F: 3A 04 AD LD A,(workRam+504) ; read the era of play
4E52: FE 04 CP $04 ; era 4?
4E54: CA 2A 4F JP Z,dispatchEra4CollisionByFrameParity; yes: take the era-4 collision path
4E57: 3D DEC A ; era 1? -- test by decrement
4E58: CA BC 4E JP Z,splitCollisionWorkByFrameParity; yes: take the era-1 collision path
4E5B: 3A 80 A9 LD A,(workRam+180) ; read the frame tick
4E5E: E6 01 AND $01 ; its low bit -- frame parity
4E60: C2 35 4F JP NZ,dispatchShotSweepByMotherShipArmed; odd frame: run the shot sweeps; even frame runs the full pass below
; run one round's collision-and-destruction pass: sweep the player's shots
; against targets, then the player against a run of objects, then --
; picked by whether the mother-ship is armed -- either the player-vs-slots
; contact sweep plus the mother-ship mutual-kill box, or a wider player-
; vs-slots sweep; then a three-target attacker sweep and a final mark of
; objects touching the player. The object/slot cursor pair threads through
; DE/IY across the chain, each stage continuing where the last left off
runAllCollisionSweepsThisFrame:
4E63: CD 5D 4F CALL stagePlayerShotSweepAgainstTargetsAndRun; sweep the player's shots against their targets
4E66: 06 04 LD B,$04 ; four objects
4E68: 11 10 A8 LD DE,$A810 ; point the record cursor at the object run
4E6B: FD 21 12 AA LD IY,$AA12 ; point the entry cursor at the object run
4E6F: 2E 05 LD L,$05 ; collision box near bound
4E71: 26 0B LD H,$0B ; collision box far bound
4E73: CD 85 51 CALL destroyPlayerAndObjectsTouchingIt; destroy the player and the objects it is touching
4E76: 3A 0D AD LD A,(workRam+50D) ; read the mother-ship-armed flag
4E79: A7 AND A ; test it
4E7A: 20 1B JR NZ,loc_4e97 ; armed: take the wider mother-ship branch
4E7C: 06 07 LD B,$07 ; seven slots
4E7E: 2E 07 LD L,$07 ; collision box near bound
4E80: 26 0F LD H,$0F ; collision box far bound
4E82: CD 52 51 CALL destroySlotsAndPlayerOnContact; destroy the slots and the player on contact
4E85: 06 03 LD B,$03 ; three targets
4E87: 2E 06 LD L,$06 ; collision box near bound
4E89: 26 0D LD H,$0D ; collision box far bound
4E8B: CD 21 51 CALL destroyTargetsReachedByFixedAttacker; destroy the targets a fixed attacker has reached
4E8E: 06 01 LD B,$01 ; one object
4E90: 2E 08 LD L,$08 ; collision box near bound
4E92: 26 11 LD H,$11 ; collision box far bound
4E94: C3 B3 51 JP markObjectsTouchingPlayer; mark the objects touching the player and return
loc_4e97:
4E97: 06 05 LD B,$05 ; armed branch: five slots
4E99: 2E 07 LD L,$07 ; collision box near bound
4E9B: 26 0F LD H,$0F ; collision box far bound
4E9D: CD 52 51 CALL destroySlotsAndPlayerOnContact; destroy the slots and the player on contact
4EA0: CD B1 50 CALL ramTestPlayerVsMotherShip; the mother-ship mutual-kill box
4EA3: 06 03 LD B,$03 ; three targets
4EA5: 11 C0 A8 LD DE,$A8C0 ; point the record cursor at the era-object run
4EA8: FD 21 28 AA LD IY,$AA28 ; point the entry cursor at the era-object run
4EAC: 2E 06 LD L,$06 ; collision box near bound
4EAE: 26 0D LD H,$0D ; collision box far bound
4EB0: CD 21 51 CALL destroyTargetsReachedByFixedAttacker; destroy the targets a fixed attacker has reached
4EB3: 06 01 LD B,$01 ; one object
4EB5: 2E 08 LD L,$08 ; collision box near bound
4EB7: 26 11 LD H,$11 ; collision box far bound
4EB9: C3 B3 51 JP markObjectsTouchingPlayer; mark the objects touching the player and return
; split the per-frame collision work by frame parity: on odd frames run
; the shot-vs-target sweeps (dispatchShotSweepByMotherShipArmed); on even
; frames run the player-vs-object collision chain, adding the mother-ship
; mutual-kill check (ramTestPlayerVsMotherShip) only while the mother ship
; is armed
splitCollisionWorkByFrameParity:
4EBC: 3A 80 A9 LD A,(workRam+180) ; read the frame tick
4EBF: E6 01 AND $01 ; its low bit -- frame parity
4EC1: C2 35 4F JP NZ,dispatchShotSweepByMotherShipArmed; odd frame: run the shot sweeps and return
4EC4: CD 7E 4F CALL destroyFixedTargetHitByShots; even frame: destroy the fixed target the shots have reached
4EC7: 06 04 LD B,$04 ; four objects
4EC9: 11 10 A8 LD DE,$A810 ; point the record cursor at the object run
4ECC: FD 21 12 AA LD IY,$AA12 ; point the entry cursor at the object run
4ED0: 2E 05 LD L,$05 ; collision box near bound
4ED2: 26 0B LD H,$0B ; collision box far bound
4ED4: CD 85 51 CALL destroyPlayerAndObjectsTouchingIt; destroy the player and the objects it is touching
4ED7: 3A 0D AD LD A,(workRam+50D) ; read the mother-ship-armed flag
4EDA: A7 AND A ; test it
4EDB: 20 25 JR NZ,loc_4f02 ; armed: take the mother-ship branch
4EDD: 06 07 LD B,$07 ; seven slots
4EDF: 2E 07 LD L,$07 ; collision box near bound
4EE1: 26 0F LD H,$0F ; collision box far bound
4EE3: CD 52 51 CALL destroySlotsAndPlayerOnContact; destroy the slots and the player on contact
4EE6: CD 7E 50 CALL destroyFixedTargetReachedByPlayer; destroy the fixed target the player has reached
4EE9: 06 01 LD B,$01 ; one object
4EEB: 11 E0 A8 LD DE,$A8E0 ; point the record cursor at the second era-object run
4EEE: FD 21 2C AA LD IY,$AA2C ; point the entry cursor at the second era-object run
4EF2: 2E 05 LD L,$05 ; collision box near bound
4EF4: 26 0B LD H,$0B ; collision box far bound
4EF6: CD 85 51 CALL destroyPlayerAndObjectsTouchingIt; destroy the player and the objects it is touching
4EF9: 06 01 LD B,$01 ; one object
4EFB: 2E 08 LD L,$08 ; collision box near bound
4EFD: 26 11 LD H,$11 ; collision box far bound
4EFF: C3 B3 51 JP markObjectsTouchingPlayer; mark the objects touching the player and return
loc_4f02:
4F02: 06 05 LD B,$05 ; armed branch: five slots
4F04: 2E 07 LD L,$07 ; collision box near bound
4F06: 26 0F LD H,$0F ; collision box far bound
4F08: CD 52 51 CALL destroySlotsAndPlayerOnContact; destroy the slots and the player on contact
4F0B: CD B1 50 CALL ramTestPlayerVsMotherShip; the mother-ship mutual-kill box
4F0E: CD 7E 50 CALL destroyFixedTargetReachedByPlayer; destroy the fixed target the player has reached
4F11: 06 01 LD B,$01 ; one object
4F13: 11 E0 A8 LD DE,$A8E0 ; point the record cursor at the second era-object run
4F16: FD 21 2C AA LD IY,$AA2C ; point the entry cursor at the second era-object run
4F1A: 2E 05 LD L,$05 ; collision box near bound
4F1C: 26 0B LD H,$0B ; collision box far bound
4F1E: CD 85 51 CALL destroyPlayerAndObjectsTouchingIt; destroy the player and the objects it is touching
4F21: 06 01 LD B,$01 ; one object
4F23: 2E 08 LD L,$08 ; collision box near bound
4F25: 26 11 LD H,$11 ; collision box far bound
4F27: C3 B3 51 JP markObjectsTouchingPlayer; mark the objects touching the player and return
; era-4 (ERA_INDEX 0xad04=4) per-frame collision dispatch split by frame
; parity (FRAME_TICK 0xa980), reached only as dispatchCollisionPassByEra's
; era-4 tail: even frames run the whole player-vs-object collision-and-
; destruction pass; odd frames stage one shot-vs-target sweep over the
; object-slot run at 0xa810/0xaa12 (six shots, box l=7/h=0x0f), restaging
; the shared body's two reload cursors 0xa991/0xa993 first -- while
; MOTHER_SHIP_ARMED (0xad0d) is set the run is nine long and a mother-ship
; mutual-kill pass (0x4fe0) follows, while clear the run is eleven long
; and none does
dispatchEra4CollisionByFrameParity:
4F2A: 3A 80 A9 LD A,(workRam+180) ; read the frame tick
4F2D: E6 01 AND $01 ; its low bit -- frame parity
4F2F: CA 63 4E JP Z,runAllCollisionSweepsThisFrame; even frame: run the whole collision pass
4F32: C3 32 50 JP loc_5032 ; odd frame: run the era-4 shot sweep
; choose between the round's two shot sweeps and, on one of the two arms
; only, stage the full seven-target run: while MOTHER_SHIP_ARMED is set
; the sweep that also covers the standing object runs instead, and that
; sweep stages its own runs, so this entry gives it nothing but the
; branch; while the cell is clear the two cursor cells the shared sweep
; reloads between passes are staged here first, so every pass restarts on
; the run chosen here, and the shared sweep then runs six shots against
; seven targets inside one box. Both counts handed over are seven, so the
; first pass is no shorter than the rest
dispatchShotSweepByMotherShipArmed:
4F35: 3A 0D AD LD A,(workRam+50D) ; read the mother-ship-armed flag
4F38: A7 AND A ; test it
4F39: C2 BF 4F JP NZ,destroyCraftAndMotherShipHitByShots; armed: run the sweep that also covers the standing craft, and return
4F3C: 11 50 A8 LD DE,$A850 ; point the record cursor at the craft run
4F3F: FD 21 1A AA LD IY,$AA1A ; point the entry cursor at the craft run
4F43: DD 21 80 AA LD IX,$AA80 ; point at the player-shot array
4F47: 08 EX AF,AF'
4F48: 3E 07 LD A,$07 ; seven targets
4F4A: 47 LD B,A ; stage the per-pass target count
4F4B: 08 EX AF,AF' ; keep seven as the first-pass count aside
4F4C: 0E 06 LD C,$06 ; six shots
4F4E: ED 53 93 A9 LD (workRam+193),DE ; stage the record cursor the sweep reloads between passes
4F52: FD 22 91 A9 LD (workRam+191),IY ; stage the entry cursor the sweep reloads between passes
4F56: 2E 07 LD L,$07 ; collision box near bound
4F58: 26 0F LD H,$0F ; collision box far bound
4F5A: C3 11 52 JP destroyTargetsHitByShots; run the sweep of shots against the seven targets
; stage the two cursor cells and the eight fixed arguments -- the six-slot
; player shot run, a three-slot target run at a sixteen-byte stride, and a
; box seven by fifteen -- then tail-jump into destroyTargetsHitByShots,
; which does the destroying; choosing the runs is the whole of what this
; entry contributes
stagePlayerShotSweepAgainstTargetsAndRun:
4F5D: 11 C0 A8 LD DE,$A8C0 ; point the record cursor at the era-object run
4F60: FD 21 28 AA LD IY,$AA28 ; point the entry cursor at the era-object run
4F64: DD 21 80 AA LD IX,$AA80 ; point at the player-shot array
4F68: 08 EX AF,AF'
4F69: 3E 03 LD A,$03 ; three targets
4F6B: 47 LD B,A ; stage the per-pass target count
4F6C: 08 EX AF,AF' ; keep three as the first-pass count aside
4F6D: 0E 06 LD C,$06 ; six shots
4F6F: ED 53 93 A9 LD (workRam+193),DE ; stage the record cursor the sweep reloads between passes
4F73: FD 22 91 A9 LD (workRam+191),IY ; stage the entry cursor the sweep reloads between passes
4F77: 2E 07 LD L,$07 ; collision box near bound
4F79: 26 0F LD H,$0F ; collision box far bound
4F7B: C3 11 52 JP destroyTargetsHitByShots; run the sweep of six shots against the three targets
; destroy the one fixed target the player's shots have reached, spending
; each shot that reached it and posting the score for each; the target's
; liveness is tested ONCE, ahead of the sweep, so several shots can be
; spent on it in a single pass
destroyFixedTargetHitByShots:
4F7E: 2E 06 LD L,$06 ; the target's hit window -- first-axis slack
4F80: 26 0D LD H,$0D ; first-axis window width
4F82: 1E 17 LD E,$17 ; second-axis slack
4F84: 16 1F LD D,$1F ; second-axis window width
4F86: FD 21 80 AA LD IY,$AA80 ; point at the player-shot array
4F8A: 06 06 LD B,$06 ; six shot slots to sweep
4F8C: 3A C0 A8 LD A,(workRam+C0) ; read the target's occupancy
4F8F: 3C INC A ; is it live?
4F90: C0 RET NZ ; target already gone: nothing to do
loc_4f91:
4F91: FD 7E 00 LD A,(IY+$00) ; read this shot slot's occupancy
4F94: 3C INC A ; is the shot live?
4F95: 20 1F JR NZ,loc_4fb6 ; empty slot: skip to the next
4F97: 3A 28 AA LD A,(workRam+228) ; target's first-axis position
4F9A: FD 96 06 SUB (IY+$06) ; minus the shot's first-axis position
4F9D: 85 ADD A,L ; plus the first-axis slack
4F9E: BC CP H ; within the first-axis window?
4F9F: 30 15 JR NC,loc_4fb6 ; outside: skip this shot
4FA1: 3A 59 AA LD A,(workRam+259) ; target's second-axis position
4FA4: FD 96 04 SUB (IY+$04) ; minus the shot's second-axis position
4FA7: 83 ADD A,E ; plus the second-axis slack
4FA8: BA CP D ; within the second-axis window?
4FA9: 30 0B JR NC,loc_4fb6 ; outside: skip this shot
4FAB: 3E F0 LD A,$F0 ; hit -- destroyed marker
4FAD: 32 C0 A8 LD (workRam+C0),A ; mark the target destroyed
4FB0: FD 77 00 LD (IY+$00),A ; spend the shot that hit it
4FB3: CD DE 51 CALL postChainedHitScore ; post the score for this hit
loc_4fb6:
4FB6: FD 7D LD A,IYL ; step to the next shot slot -- low half of the cursor only, so a wide array wraps in its page
4FB8: C6 10 ADD A,$10
4FBA: FD 6F LD IYL,A
4FBC: 10 D3 DJNZ loc_4f91 ; loop over all six shots
4FBE: C9 RET
; run the shot sweeps for the stretch of a round in which the Mother-Ship
; is on the field: stage the two cursor cells, sweep the six player shots
; against FIVE ordinary craft rather than the usual seven, then fall
; through into the sweep that runs the same six shots against the Mother-
; Ship's own state byte and screen position. Choosing the shorter craft
; run is the whole of what this entry adds
destroyCraftAndMotherShipHitByShots:
4FBF: 11 50 A8 LD DE,$A850 ; point at the five ordinary craft's state records
4FC2: FD 21 1A AA LD IY,$AA1A ; point at those craft's sprite entries
4FC6: DD 21 80 AA LD IX,$AA80 ; point at the six player-shot records
4FCA: 08 EX AF,AF'
4FCB: 3E 05 LD A,$05 ; five craft to test per shot -- also stashed in the alternate accumulator to reload the count each pass
4FCD: 47 LD B,A
4FCE: 08 EX AF,AF'
4FCF: 0E 06 LD C,$06 ; six shots to sweep through the run
4FD1: ED 53 93 A9 LD (workRam+193),DE ; stash the target-record pointer so each shot pass reloads it
4FD5: FD 22 91 A9 LD (workRam+191),IY ; stash the sprite-entry pointer likewise
4FD9: 2E 07 LD L,$07 ; box half-reach 7 on the first axis
4FDB: 26 0F LD H,$0F ; box span 15 on the first axis
4FDD: CD 11 52 CALL destroyTargetsHitByShots; sweep the six shots against the five craft, destroying and scoring each hit -- then fall into the mother-ship sweep
; sweep the six player-shot slots for one that has reached the single
; fixed two-slot target, mark both destroyed and post the score for each;
; the first-axis window is widened for two of the era values, by a data
; swap rather than a second body
destroyMotherShipAndShotOnMutualHit:
4FE0: 3A 04 AD LD A,(workRam+504) ; read the era index
4FE3: A7 AND A ; era 0?
4FE4: 28 45 JR Z,loc_502b ; era 0: use the wider first-axis box
4FE6: FE 04 CP $04 ; era 4?
4FE8: 28 41 JR Z,loc_502b ; era 4: use the wider box too
4FEA: 2E 06 LD L,$06 ; narrow first-axis half-reach 6
4FEC: 26 0D LD H,$0D ; narrow first-axis span 13
loc_4fee:
4FEE: 1E 17 LD E,$17 ; second-axis half-reach 23
4FF0: 16 1F LD D,$1F ; second-axis span 31
4FF2: FD 21 80 AA LD IY,$AA80 ; point at the six player-shot records
4FF6: 06 06 LD B,$06 ; six shots to sweep
4FF8: 3A A0 A8 LD A,(workRam+A0) ; read the mother ship's state
4FFB: 3C INC A
4FFC: C0 RET NZ ; return unless the mother ship is live (0xFF)
loc_4ffd:
4FFD: FD 7E 00 LD A,(IY+$00) ; read this shot's state
5000: 3C INC A
5001: 20 1F JR NZ,loc_5022 ; dead shot -- skip
5003: 3A 24 AA LD A,(workRam+224) ; the mother ship's first-axis coordinate
5006: FD 96 06 SUB (IY+$06) ; minus this shot's first-axis
5009: 85 ADD A,L ; add the first-axis half-reach
500A: BC CP H ; inside the first-axis span?
500B: 30 15 JR NC,loc_5022 ; outside -- skip
500D: 3A 55 AA LD A,(workRam+255) ; the mother ship's second-axis coordinate
5010: FD 96 04 SUB (IY+$04) ; minus this shot's second-axis
5013: 83 ADD A,E ; add the second-axis half-reach
5014: BA CP D ; inside the second-axis span?
5015: 30 0B JR NC,loc_5022 ; outside -- skip
5017: 3E F0 LD A,$F0 ; the destroyed code
5019: 32 A0 A8 LD (workRam+A0),A ; destroy the mother ship
501C: FD 77 00 LD (IY+$00),A ; destroy this shot
501F: CD DE 51 CALL postChainedHitScore ; post the chained hit score
loc_5022:
5022: FD 7D LD A,IYL
5024: C6 10 ADD A,$10 ; advance to the next shot record -- low byte only, so it wraps inside the page
5026: FD 6F LD IYL,A
5028: 10 D3 DJNZ loc_4ffd ; loop the six shots
502A: C9 RET
loc_502b:
502B: 2E 08 LD L,$08 ; wider first-axis half-reach 8
502D: 26 11 LD H,$11 ; wider first-axis span 17
502F: C3 EE 4F JP loc_4fee ; run the sweep with the wider box
loc_5032:
5032: 3A 0D AD LD A,(workRam+50D) ; read the flag that the mother ship is out
5035: A7 AND A ; set?
5036: C2 5A 50 JP NZ,loc_505a ; mother ship out: run the nine-target sweep, then the mutual-hit pass
5039: 11 10 A8 LD DE,$A810 ; point at the enemy-target state records
503C: FD 21 12 AA LD IY,$AA12 ; point at their sprite entries
5040: DD 21 80 AA LD IX,$AA80 ; point at the six player-shot records
5044: 08 EX AF,AF'
5045: 3E 0B LD A,$0B ; eleven targets per shot -- also stashed in the alternate accumulator to reload each pass
5047: 47 LD B,A
5048: 08 EX AF,AF'
5049: 0E 06 LD C,$06 ; six shots to sweep
504B: ED 53 93 A9 LD (workRam+193),DE ; stash the target-record pointer for reload
504F: FD 22 91 A9 LD (workRam+191),IY ; stash the sprite-entry pointer likewise
5053: 2E 07 LD L,$07 ; box half-reach 7
5055: 26 0F LD H,$0F ; box span 15
5057: C3 11 52 JP destroyTargetsHitByShots; sweep the shots against the eleven targets, destroying and scoring each
loc_505a:
505A: 11 10 A8 LD DE,$A810 ; point at the enemy-target state records
505D: FD 21 12 AA LD IY,$AA12 ; point at their sprite entries
5061: DD 21 80 AA LD IX,$AA80 ; point at the six player-shot records
5065: 08 EX AF,AF'
5066: 3E 09 LD A,$09 ; nine targets per shot -- also stashed in the alternate accumulator to reload each pass
5068: 47 LD B,A
5069: 08 EX AF,AF'
506A: 0E 06 LD C,$06 ; six shots to sweep
506C: ED 53 93 A9 LD (workRam+193),DE ; stash the target-record pointer for reload
5070: FD 22 91 A9 LD (workRam+191),IY ; stash the sprite-entry pointer likewise
5074: 2E 07 LD L,$07 ; box half-reach 7
5076: 26 0F LD H,$0F ; box span 15
5078: CD 11 52 CALL destroyTargetsHitByShots; sweep the shots against the nine targets, destroying and scoring each
507B: C3 E0 4F JP destroyMotherShipAndShotOnMutualHit; then run the mother-ship-and-shot mutual-hit sweep
; destroy one fixed target and the player with it when the two touch, zero
; the target's HITS_REMAINING so the contact kills it outright rather than
; costing it a hit, and tail-transfer to the scoring routine; four tests
; must all pass, so nothing at all is written unless every one of them
; does
destroyFixedTargetReachedByPlayer:
507E: DD 21 10 AA LD IX,$AA10 ; point at the player's sprite entry
5082: 3A 00 A8 LD A,(workRam) ; read the player's state
5085: 3C INC A
5086: C0 RET NZ ; player gone -- nothing to do
5087: 3A C0 A8 LD A,(workRam+C0) ; read the fixed target's state
508A: 3C INC A
508B: C0 RET NZ ; target gone -- nothing to do
508C: 3A 28 AA LD A,(workRam+228) ; the fixed target's first-axis coordinate
508F: DD 96 00 SUB (IX+$00) ; minus the player's first-axis
5092: C6 06 ADD A,$06 ; add the first-axis slack (6)
5094: FE 0D CP $0D ; inside the first-axis window (13)?
5096: D0 RET NC ; outside -- no contact
5097: 3A 59 AA LD A,(workRam+259) ; the fixed target's second-axis coordinate
509A: DD 96 31 SUB (IX+$31) ; minus the player's second-axis
509D: C6 18 ADD A,$18 ; add the second-axis slack (24)
509F: FE 21 CP $21 ; inside the second-axis window (33)?
50A1: D0 RET NC ; outside -- no contact
50A2: 3E F0 LD A,$F0 ; the destroyed code
50A4: 32 00 A8 LD (workRam),A ; destroy the player
50A7: 32 C0 A8 LD (workRam+C0),A ; destroy the fixed target
50AA: AF XOR A
50AB: 32 DC A8 LD (workRam+DC),A ; clear the target's remaining-hits count so the contact kills it outright rather than costing it a hit
50AE: C3 DE 51 JP postChainedHitScore ; post the chained hit score
; select the collision box for the mutual kill of the player and one fixed
; two-slot target by ERA_INDEX: eras 0 and 4 transfer to the wider first-
; axis check (destroyPlayerAndMotherShipOnContact), the rest run the same
; destruction inline with a narrower first-axis window; when both are live
; and their coordinates fall in the box, mark both destroyed, clear the
; cell beside them, and tail-post the chained hit score
ramTestPlayerVsMotherShip:
50B1: 3A 04 AD LD A,(workRam+504) ; read the era index
50B4: A7 AND A ; era 0?
50B5: 28 37 JR Z,destroyPlayerAndMotherShipOnContact; era 0: use the wider-box mutual-kill check
50B7: FE 04 CP $04 ; era 4?
50B9: 28 33 JR Z,destroyPlayerAndMotherShipOnContact; era 4: use the wider-box check too
50BB: DD 21 10 AA LD IX,$AA10 ; point at the player's sprite entry
50BF: 3A 00 A8 LD A,(workRam) ; read the player's state
50C2: 3C INC A
50C3: C0 RET NZ ; player gone -- nothing to do
50C4: 3A A0 A8 LD A,(workRam+A0) ; read the mother ship's state
50C7: 3C INC A
50C8: C0 RET NZ ; mother ship gone -- nothing to do
50C9: 3A 24 AA LD A,(workRam+224) ; the mother ship's first-axis coordinate
50CC: DD 96 00 SUB (IX+$00) ; minus the player's first-axis
50CF: C6 06 ADD A,$06 ; add the narrow first-axis slack (6)
50D1: FE 0D CP $0D ; inside the first-axis window (13)?
50D3: D0 RET NC ; outside -- no contact
50D4: 3A 55 AA LD A,(workRam+255) ; the mother ship's second-axis coordinate
50D7: DD 96 31 SUB (IX+$31) ; minus the player's second-axis
50DA: C6 19 ADD A,$19 ; add the second-axis slack (25)
50DC: FE 23 CP $23 ; inside the second-axis window (35)?
50DE: D0 RET NC ; outside -- no contact
50DF: 3E F0 LD A,$F0 ; the destroyed code
50E1: 32 00 A8 LD (workRam),A ; destroy the player
50E4: 32 A0 A8 LD (workRam+A0),A ; destroy the mother ship
50E7: AF XOR A
50E8: 32 A4 A8 LD (workRam+A4),A ; clear the mother ship's hold counter, the cell beside its state
50EB: C3 DE 51 JP postChainedHitScore ; post the chained hit score
; destroy the player and one fixed two-slot target together when they
; touch, zero that target's hit counter so the contact kills it outright
; instead of costing it one hit, and tail-transfer to the chained hit
; score; this is the wider of two first-axis windows, and the arm its
; caller selects for two of the era values
destroyPlayerAndMotherShipOnContact:
50EE: DD 21 10 AA LD IX,$AA10 ; point at the player's sprite entry
50F2: 3A 00 A8 LD A,(workRam) ; read the player's state
50F5: 3C INC A
50F6: C0 RET NZ ; player gone -- nothing to do
50F7: 3A A0 A8 LD A,(workRam+A0) ; read the mother ship's state
50FA: 3C INC A
50FB: C0 RET NZ ; mother ship gone -- nothing to do
50FC: 3A 24 AA LD A,(workRam+224) ; the mother ship's first-axis coordinate
50FF: DD 96 00 SUB (IX+$00) ; minus the player's first-axis
5102: C6 08 ADD A,$08 ; add the wider first-axis slack (8)
5104: FE 11 CP $11 ; inside the wider first-axis window (17)?
5106: D0 RET NC ; outside -- no contact
5107: 3A 55 AA LD A,(workRam+255) ; the mother ship's second-axis coordinate
510A: DD 96 31 SUB (IX+$31) ; minus the player's second-axis
510D: C6 19 ADD A,$19 ; add the second-axis slack (25)
510F: FE 23 CP $23 ; inside the second-axis window (35)?
5111: D0 RET NC ; outside -- no contact
5112: 3E F0 LD A,$F0 ; the destroyed code
5114: 32 00 A8 LD (workRam),A ; destroy the player
5117: 32 A0 A8 LD (workRam+A0),A ; destroy the mother ship
511A: AF XOR A
511B: 32 A4 A8 LD (workRam+A4),A ; clear the mother ship's hold counter, the cell beside its state
511E: C3 DE 51 JP postChainedHitScore ; post the chained hit score
; destroy every target of a caller's run that one fixed attacker -- the
; player's own ship -- has reached, marking both destroyed and posting the
; chained score for each; the attacker's state is tested once, so one pass
; can take several
destroyTargetsReachedByFixedAttacker:
5121: 3A 00 A8 LD A,(workRam) ; read the player's state -- the fixed attacker
5124: 3C INC A
5125: C0 RET NZ ; player gone -- nothing to do
loc_5126:
5126: 1A LD A,(DE) ; read this target's state
5127: 3C INC A
5128: 20 1D JR NZ,loc_5147 ; dead target -- skip
512A: 3A 10 AA LD A,(workRam+210) ; the player's first-axis coordinate
512D: FD 96 00 SUB (IY+$00) ; minus this target's first-axis
5130: 85 ADD A,L ; add the caller's slack
5131: BC CP H ; inside the caller's window?
5132: 30 13 JR NC,loc_5147 ; outside -- skip
5134: 3A 41 AA LD A,(workRam+241) ; the player's second-axis coordinate
5137: FD 96 31 SUB (IY+$31) ; minus this target's second-axis
513A: 85 ADD A,L ; add the same slack
513B: BC CP H ; inside the same window?
513C: 30 09 JR NC,loc_5147 ; outside -- skip
513E: 3E F0 LD A,$F0 ; the destroyed code
5140: 32 00 A8 LD (workRam),A ; destroy the player
5143: 12 LD (DE),A ; destroy this target
5144: CD DE 51 CALL postChainedHitScore ; post the chained hit score
loc_5147:
5147: 7B LD A,E
5148: C6 10 ADD A,$10 ; advance to the next target record -- low byte only, so it wraps inside the page
514A: 5F LD E,A
514B: FD 23 INC IY ; step to the next sprite entry (two bytes on)
514D: FD 23 INC IY
514F: 10 D5 DJNZ loc_5126 ; loop the run
5151: C9 RET
; sweep a run of slots against the player's own sprite entry and, for
; every overlap, write the destroyed marker into both the slot and the
; player and post the score; the sweep does not stop at the first
destroySlotsAndPlayerOnContact:
5152: 3A 00 A8 LD A,(workRam) ; read the player's state
5155: 3C INC A
5156: C0 RET NZ ; player already hit -- nothing to do
loc_5157:
5157: 1A LD A,(DE) ; read this slot's state
5158: 3C INC A
5159: 20 1F JR NZ,loc_517a ; slot already hit -- skip
515B: 3A 10 AA LD A,(workRam+210) ; the player's first-axis coordinate
515E: FD 96 00 SUB (IY+$00) ; minus this slot's first-axis
5161: 85 ADD A,L ; add the caller's first-axis bias
5162: BC CP H ; inside the caller's first-axis width?
5163: 30 15 JR NC,loc_517a ; outside -- skip
5165: 3A 41 AA LD A,(workRam+241) ; the player's second-axis coordinate
5168: FD 96 31 SUB (IY+$31) ; minus this slot's second-axis
516B: C6 08 ADD A,$08 ; add the fixed second-axis bias (8)
516D: FE 11 CP $11 ; inside the fixed second-axis width (17)?
516F: 30 09 JR NC,loc_517a ; outside -- skip
5171: 3E F0 LD A,$F0 ; the hit code
5173: 32 00 A8 LD (workRam),A ; mark the player hit
5176: 12 LD (DE),A ; mark this slot hit
5177: CD DE 51 CALL postChainedHitScore ; post the chained hit score
loc_517a:
517A: 7B LD A,E
517B: C6 10 ADD A,$10 ; advance to the next slot record -- low byte only, so it wraps inside the page
517D: 5F LD E,A
517E: FD 23 INC IY ; step to the next sprite entry (two bytes on)
5180: FD 23 INC IY
5182: 10 D3 DJNZ loc_5157 ; loop the run
5184: C9 RET
; destroy the player and every object of a caller's run that lies inside a
; wrapped box around the player's sprite entry, while the player is alive;
; one window width serves both axes, nothing is scored, and the sweep runs
; on past the first
destroyPlayerAndObjectsTouchingIt:
5185: 3A 00 A8 LD A,(workRam) ; read the player's state
5188: 3C INC A
5189: C0 RET NZ ; player gone -- nothing to do
loc_518a:
518A: 1A LD A,(DE) ; read this object's state
518B: 3C INC A
518C: 20 1A JR NZ,loc_51a8 ; dead object -- skip
518E: 3A 10 AA LD A,(workRam+210) ; the player's first-axis coordinate
5191: FD 96 00 SUB (IY+$00) ; minus this object's first-axis
5194: 85 ADD A,L ; add the caller's slack
5195: BC CP H ; inside the caller's window?
5196: 30 10 JR NC,loc_51a8 ; outside -- skip
5198: 3A 41 AA LD A,(workRam+241) ; the player's second-axis coordinate
519B: FD 96 31 SUB (IY+$31) ; minus this object's second-axis
519E: 85 ADD A,L ; add the same slack
519F: BC CP H ; inside the same window?
51A0: 30 06 JR NC,loc_51a8 ; outside -- skip
51A2: 3E F0 LD A,$F0 ; the destroyed code
51A4: 32 00 A8 LD (workRam),A ; destroy the player
51A7: 12 LD (DE),A ; destroy this object
loc_51a8:
51A8: 7B LD A,E
51A9: C6 10 ADD A,$10 ; advance to the next object record -- low byte only, so it wraps inside the page
51AB: 5F LD E,A
51AC: FD 23 INC IY ; step to the next sprite entry (two bytes on)
51AE: FD 23 INC IY
51B0: 10 D8 DJNZ loc_518a ; loop the run
51B2: C9 RET
; replace the state byte of every object in a caller's run that lies
; inside a wrapped box around the player's sprite entry, while the player
; is alive; the box is the caller's, the player's own state is untouched
; and nothing is scored
markObjectsTouchingPlayer:
51B3: 3A 00 A8 LD A,(workRam) ; read the player's state
51B6: 3C INC A
51B7: C0 RET NZ ; player gone -- nothing to do
loc_51b8:
51B8: 1A LD A,(DE) ; read this object's state
51B9: 3C INC A
51BA: 20 17 JR NZ,loc_51d3 ; dead object -- skip
51BC: 3A 10 AA LD A,(workRam+210) ; the player's first-axis coordinate
51BF: FD 96 00 SUB (IY+$00) ; minus this object's first-axis
51C2: 85 ADD A,L ; add the caller's offset
51C3: BC CP H ; inside the caller's width?
51C4: 30 0D JR NC,loc_51d3 ; outside -- skip
51C6: 3A 41 AA LD A,(workRam+241) ; the player's second-axis coordinate
51C9: FD 96 31 SUB (IY+$31) ; minus this object's second-axis
51CC: 85 ADD A,L ; add the same offset
51CD: BC CP H ; inside the same width?
51CE: 30 03 JR NC,loc_51d3 ; outside -- skip
51D0: 3E F0 LD A,$F0 ; the marked code
51D2: 12 LD (DE),A ; mark this object -- the only cell written
loc_51d3:
51D3: 7B LD A,E
51D4: C6 10 ADD A,$10 ; advance to the next object state byte -- low byte only, so it wraps inside the page
51D6: 5F LD E,A
51D7: FD 23 INC IY ; step to the next sprite entry (two bytes on)
51D9: FD 23 INC IY
51DB: 10 DB DJNZ loc_51b8 ; loop the run
51DD: C9 RET
; post a scoring command to the ring, stepping the award up while
; consecutive hits keep landing inside the chain window and wrapping back
; round after the eighth
postChainedHitScore:
51DE: D5 PUSH DE ; save the caller's DE across the post
51DF: 3A 9D A9 LD A,(workRam+19D) ; read the chain window
51E2: A7 AND A ; has it run out?
51E3: 28 15 JR Z,loc_51fa ; window closed -- post the first step
51E5: 3A 9E A9 LD A,(workRam+19E) ; read the chain step
51E8: 3C INC A ; climb it one
51E9: 32 9E A9 LD (workRam+19E),A ; store the climbed step
51EC: E6 07 AND $07 ; wrap it within the eight-long chain
51EE: 3C INC A ; the argument is that step plus one
51EF: 5F LD E,A
51F0: 16 04 LD D,$04 ; scoring command group 4
51F2: FF RST $38 ; queue the score request onto the command ring
51F3: D1 POP DE ; restore the caller's DE
51F4: 3E 1E LD A,$1E ; reload value 30
51F6: 32 9D A9 LD (workRam+19D),A ; re-arm the chain window
51F9: C9 RET
loc_51fa:
51FA: 11 01 04 LD DE,$0401 ; scoring command 4, argument 1 -- the first step
51FD: FF RST $38 ; queue it onto the command ring
51FE: D1 POP DE ; restore the caller's DE
51FF: 3E 1E LD A,$1E ; reload value 30
5201: 32 9D A9 LD (workRam+19D),A ; re-arm the chain window
5204: C9 RET
; run the chained-hit window down by one and, on every frame after it has
; reached zero, clear the chain step so the next hit starts the award
; ladder from the bottom again
expireHitChain:
5205: 21 9D A9 LD HL,$A99D ; point at the chain window
5208: 7E LD A,(HL) ; read it
5209: A7 AND A ; already zero?
520A: 28 02 JR Z,loc_520e ; run out -- clear the chain step instead
520C: 35 DEC (HL) ; count the window down one
520D: C9 RET
loc_520e:
520E: 2C INC L ; point at the next byte -- the chain step
520F: 77 LD (HL),A ; clear it, so the next hit starts the award from the bottom
5210: C9 RET
; destroy every target a live shot has reached, spending the shot with
; them, and post the score for each; the sweep does not stop at the first,
; so one shot can take several in a pass
destroyTargetsHitByShots:
5211: DD 7E 00 LD A,(IX+$00) ; read this shot's state
5214: 3C INC A
5215: 20 3D JR NZ,loc_5254 ; dead shot -- on to the next shot
loc_5217:
5217: 1A LD A,(DE) ; read this target's state
5218: 3C INC A
5219: 20 2F JR NZ,loc_524a ; dead target -- skip
521B: FD 7E 00 LD A,(IY+$00) ; the target's first-axis coordinate
521E: C6 08 ADD A,$08 ; shift the low end above zero
5220: FE 19 CP $19 ; in the dead band a blank slot leaves near zero?
5222: 38 26 JR C,loc_524a ; yes -- no real target here, skip
5224: FD 7E 31 LD A,(IY+$31) ; the target's second-axis coordinate
5227: C6 10 ADD A,$10 ; shift the low end above zero
5229: FE 11 CP $11 ; in the near-zero dead band?
522B: 38 1D JR C,loc_524a ; yes -- skip
522D: DD 7E 06 LD A,(IX+$06) ; the shot's first-axis coordinate
5230: FD 96 00 SUB (IY+$00) ; minus the target's first-axis
5233: 85 ADD A,L ; add the half-reach
5234: BC CP H ; inside the span?
5235: 30 13 JR NC,loc_524a ; outside -- skip
5237: DD 7E 04 LD A,(IX+$04) ; the shot's second-axis coordinate
523A: FD 96 31 SUB (IY+$31) ; minus the target's second-axis
523D: 85 ADD A,L ; add the half-reach
523E: BC CP H ; inside the span?
523F: 30 09 JR NC,loc_524a ; outside -- skip
5241: 3E F0 LD A,$F0 ; the destroyed code
5243: DD 77 00 LD (IX+$00),A ; destroy the shot
5246: 12 LD (DE),A ; destroy the target
5247: CD DE 51 CALL postChainedHitScore ; post the chained hit score
loc_524a:
524A: 7B LD A,E
524B: C6 10 ADD A,$10 ; advance to the next target record -- low byte only, so it wraps inside the page
524D: 5F LD E,A
524E: FD 23 INC IY ; step to the next target entry (two bytes on)
5250: FD 23 INC IY
5252: 10 C3 DJNZ loc_5217 ; loop the targets for this shot
loc_5254:
5254: FD 2A 91 A9 LD IY,(workRam+191) ; reload the sprite-entry cursor for the next shot
5258: ED 5B 93 A9 LD DE,(workRam+193) ; reload the target-record cursor for the next shot
525C: 08 EX AF,AF'
525D: 47 LD B,A ; reload the per-shot target count from the alternate accumulator
525E: 08 EX AF,AF'
525F: DD 7D LD A,IXL
5261: C6 10 ADD A,$10 ; advance to the next shot record -- low byte only, so it wraps inside the page
5263: DD 6F LD IXL,A
5265: 0D DEC C ; one shot done
5266: C2 11 52 JP NZ,destroyTargetsHitByShots; loop the shots
5269: C9 RET
; put both deferred character-cell lists back to empty, parking each
; cursor four bytes past its own head
emptyBothDeferredCellLists:
526A: 21 84 AE LD HL,$AE84 ; point at the erase list's first entry
526D: 22 80 AE LD (workRam+680),HL ; park the erase cursor there -- list emptied
5270: 21 04 AE LD HL,$AE04 ; point at the pending list's first entry
5273: 22 00 AE LD (workRam+600),HL ; park the pending cursor there -- list emptied
5276: C9 RET
; ---- $5277-$5285: data ----
5277: 06 00 21 DE 27 AF 86 23 10 FC D6 C5 C4 D4 53
; one pass of the deferred cell machinery: blank the cells the erase list
; names, paint the cells the pending list names, then copy the pending
; list wholesale onto the erase list and park the pending cursor back on
; its own first entry. The copy length is the pending cursor's own byte,
; cursor included, so it lands the pending count on top of the erase
; cursor and the line after replaces that with the same count plus a mark
; in the top bit; where nothing is pending both cursors are parked instead
; and no copy happens; and a cursor of ZERO is not nothing pending -- the
; count is a block-copy length, and a length of zero means the whole
; address space. NOT a double buffer: the copy runs one way, 0xAE00 onto
; 0xAE80, on every pass, and the two lists hold different jobs rather than
; alternating ones
drainBothDeferredCellLists:
5286: CD 0E 53 CALL blankCellsPaintedLastPass; blank the cells the previous pass painted
5289: CD D2 52 CALL paintDeferredCells ; paint the cells now pending
528C: 3A 00 AE LD A,(workRam+600) ; read the pending list's fill count -- its cursor's low byte
528F: FE 04 CP $04 ; still at the first entry -- nothing pending?
5291: 28 D7 JR Z,emptyBothDeferredCellLists; nothing pending -- park both cursors and return
5293: 4F LD C,A ; set the block-copy length to the pending fill count
5294: 06 00 LD B,$00 ; high byte zero -- a length of zero would copy the whole address space
5296: 21 00 AE LD HL,$AE00 ; source: the pending list from its cursor
5299: 11 80 AE LD DE,$AE80 ; destination: the erase list
529C: ED B0 LDIR ; copy the pending list wholesale onto the erase list
529E: C6 80 ADD A,$80 ; flag the copied count with the top bit...
52A0: 32 80 AE LD (workRam+680),A ; ...and write it as the erase cursor, marking the list copied
52A3: 21 04 AE LD HL,$AE04 ; point at the pending list's first entry
52A6: 22 00 AE LD (workRam+600),HL ; park the pending cursor there -- pending list emptied
52A9: C9 RET
; boot-time DIP seed: copy two ROM defaults into their cells
; (0x08c9->0xa98d, 0x0874->KILL_QUOTA), store DSW0 complemented as
; COINAGE_SETTINGS and unpack the coin ratios, then turn DSW1's low two
; bits into a lives count (3/4/5, or 0xff when they fold to none) and
; tail-jump with it plus the whole complemented bank into the switch-
; settings peeler; never returns
seedGameConfigFromDipSwitches:
52AA: 3A C9 08 LD A,(rom+8C9) ; take the boot default for the high-score high byte
52AD: 32 8D A9 LD (workRam+18D),A ; seed it into its cell
52B0: 3A 74 08 LD A,(rom+874) ; take the boot default for the kill quota
52B3: 32 CD A9 LD (workRam+1CD),A ; seed it into its cell
52B6: 3A 60 C3 LD A,(dsw0) ; read the first DIP-switch bank
52B9: 2F CPL ; complement it -- the switches read active-low
52BA: 32 B1 A9 LD (workRam+1B1),A ; store it as the coin settings
52BD: CD CC 4A CALL unpackCoinage ; unpack the coin ratios from it
52C0: 3A 00 C2 LD A,(dsw1) ; read the second DIP-switch bank
52C3: 2F CPL ; complement it
52C4: 4F LD C,A ; keep the whole complemented bank for the peeler
52C5: E6 03 AND $03 ; take the low two switch bits
52C7: C6 03 ADD A,$03 ; turn them into a lives count of 3, 4, 5 or 6
52C9: FE 06 CP $06 ; the setting that would give six...
52CB: 20 02 JR NZ,loc_52cf ; ...otherwise carry the lives count on
52CD: 3E FF LD A,$FF ; ...folds to all-ones (no starting lives)
loc_52cf:
52CF: C3 19 2E JP unpackTheFirstThreeSwitchSettings; hand the lives count and the whole bank to the switch-settings peeler
; paint the deferred cell list into the character plane and its colour
; plane: each four-byte entry gives a colour-plane address, the shape to
; put a plane above it and the colour to put at it, with one shared bias
; added to every colour. How many are pending comes off the low half of
; the list's own write cursor, so the whole list lives inside one page; an
; entry whose colour cell already has the high-priority bit set is passed
; over untouched, and a cursor that scales to a count of zero is not empty
; -- the loop runs 256 times
paintDeferredCells:
52D2: 3A 0C AD LD A,(workRam+50C) ; read the pen colour
52D5: E6 0F AND $0F ; keep its low nibble as the shared tint bias
52D7: 4F LD C,A
52D8: 2A 00 AE LD HL,(workRam+600) ; read the pending list cursor
52DB: 7D LD A,L ; take its low byte
52DC: D6 04 SUB $04 ; drop the four-byte header -- how many bytes are filled
52DE: C8 RET Z ; nothing pending -- done
52DF: 0F RRCA ; divide the byte count by four -- four bytes per entry -- into an entry count
52E0: 0F RRCA
52E1: E6 1F AND $1F ; keep it within one page (at most 31 entries)
52E3: 47 LD B,A
52E4: 21 04 AE LD HL,$AE04 ; point at the list's first entry
loc_52e7:
52E7: 5E LD E,(HL) ; take the entry's colour-plane address low byte
52E8: 2C INC L
52E9: 56 LD D,(HL) ; and its high byte
52EA: 2C INC L
52EB: 1A LD A,(DE) ; read the colour cell there
52EC: E6 10 AND $10 ; is it flagged as drawn above the sprites?
52EE: 20 0E JR NZ,loc_52fe ; yes -- leave this cell alone
52F0: 7E LD A,(HL) ; take the entry's shape
52F1: CB D2 SET 2,D ; aim one plane up -- the character plane
52F3: 12 LD (DE),A ; write the shape into the character plane
52F4: CB 92 RES 2,D
52F6: 2C INC L
52F7: 7E LD A,(HL) ; take the entry's colour
52F8: 2C INC L
52F9: 81 ADD A,C ; add the shared tint bias
52FA: 12 LD (DE),A ; write the colour into the colour cell
52FB: 10 EA DJNZ loc_52e7 ; loop the pending entries
52FD: C9 RET
loc_52fe:
52FE: 2C INC L ; skip this passed-over entry's shape and colour bytes
52FF: 2C INC L
5300: 10 E5 DJNZ loc_52e7 ; loop the pending entries
5302: C9 RET
; run the image-checksum tamper test and relay by its verdict: present the
; carried checksum, step the attract sequence on the one genuine value,
; else spring the tamper trap
advanceSequenceUnlessImageTampered:
5303: CD 0C 20 CALL presentChecksumForTamperTest; run the image-checksum tamper test, returning the checksum
5306: FE 67 CP $67 ; is it the one value a genuine image gives?
5308: C2 8D 0F JP NZ,loc_0f8d ; tampered -- spring the tamper trap
530B: C3 1A 0F JP advanceSequenceSubStep; genuine -- step the attract sequence on
; blank the character-plane cells the previous pass painted: walk the
; second deferred cell list, which the shared caller filled by copying the
; paint list wholesale after draining it, and write the blank shape a
; plane above each entry's address, leaving the colour byte exactly as it
; was. The pending count comes off the masked low half of that list's own
; cursor -- the mask drops the top bit the caller sets when it copies --
; and an entry whose colour cell already has the high-priority bit set is
; passed over
blankCellsPaintedLastPass:
530E: 2A 80 AE LD HL,(workRam+680) ; read the deferred-blank list's write cursor
5311: 7D LD A,L ; take its low byte -- how far the list has filled
5312: E6 7F AND $7F ; mask off the top bit the filler sets
5314: D6 04 SUB $04 ; subtract the four-byte header -- leaves the bytes actually queued
5316: C8 RET Z ; still at the first entry: nothing pending, done
5317: 0F RRCA ; turn the byte count into an entry count -- four bytes each
5318: 0F RRCA
5319: E6 1F AND $1F ; keep it within the list's page
531B: 47 LD B,A ; loop that many entries
531C: 21 84 AE LD HL,$AE84 ; point at the first list entry
loc_531f:
531F: 5E LD E,(HL) ; this entry's target colour cell, low byte of the address
5320: 2C INC L
5321: 56 LD D,(HL) ; and its high byte
5322: 2C INC L
5323: 1A LD A,(DE) ; read that colour cell
5324: E6 10 AND $10 ; test its high-priority bit
5326: 20 0A JR NZ,loc_5332 ; already high-priority: leave this cell, on to the next entry
5328: 2C INC L ; step past this entry's two spare bytes to the next
5329: 2C INC L
532A: CB D2 SET 2,D ; aim the same address at the character plane (+$400)
532C: 3E 20 LD A,$20 ; the blank glyph
532E: 12 LD (DE),A ; stamp it into the character cell, colour left as-is
532F: 10 EE DJNZ loc_531f ; next queued entry
5331: C9 RET
loc_5332:
5332: 2C INC L ; step over the skipped entry's two spare bytes
5333: 2C INC L
5334: 10 E9 DJNZ loc_531f ; on to the next entry
5336: C9 RET
; queue a two-by-two block of character cells for an object's position
; onto the deferred write list, one four-byte entry per cell, skipping a
; pair whose glyph is zero
queueTileStampForObject:
5337: DD 7E 04 LD A,(IX+$04) ; the object's first pixel coordinate
533A: C6 07 ADD A,$07 ; bias by seven -- centres the block on the object
533C: 47 LD B,A ; keep the biased coordinate
533D: 16 28 LD D,$28 ; seed the tile-plane address high byte
533F: 07 RLCA
5340: CB 12 RL D
5342: 07 RLCA
5343: CB 12 RL D ; fold the coordinate's top bits into that high byte
5345: E6 E0 AND $E0 ; its tile-row bits for the address low byte
5347: 5F LD E,A
5348: DD 7E 06 LD A,(IX+$06) ; the object's second pixel coordinate
534B: C6 07 ADD A,$07 ; bias by seven
534D: 4F LD C,A ; keep it
534E: 0F RRCA
534F: 0F RRCA
5350: 0F RRCA
5351: E6 1F AND $1F ; its tile column
5353: 83 ADD A,E ; combine row and column -- the block's top-left cell, low byte
5354: 5F LD E,A
5355: 79 LD A,C ; back to the second coordinate
5356: 07 RLCA
5357: 07 RLCA
5358: 07 RLCA
5359: E6 38 AND $38 ; its low three bits, the sub-cell shift
535B: 4F LD C,A
535C: 78 LD A,B ; the first coordinate again
535D: 06 00 LD B,$00
535F: CB 57 BIT 2,A ; its bit that overflows the record index into the high byte
5361: 28 01 JR Z,loc_5364 ;
5363: 04 INC B ; set that high bit of the index
loc_5364:
5364: 0F RRCA
5365: 0F RRCA
5366: E6 C0 AND $C0 ; the first coordinate's low bits for the index
5368: 81 ADD A,C ; add the second coordinate's -- picks one of sixty-four sub-cell records
5369: 4F LD C,A
536A: 21 D4 53 LD HL,$53D4 ; the table of pre-shifted tile records
536D: 09 ADD HL,BC ; index the chosen record -- four glyph/attribute pairs, one per cell
536E: 7E LD A,(HL) ; first cell's glyph
536F: 23 INC HL
5370: 46 LD B,(HL) ; its colour attribute
5371: 23 INC HL
5372: A7 AND A ; glyph zero?
5373: 28 10 JR Z,loc_5385 ; transparent: skip this cell
5375: E5 PUSH HL
5376: 2A 00 AE LD HL,(workRam+600) ; the deferred-write list's tail
5379: 73 LD (HL),E ; write the cell address low byte
537A: 2C INC L
537B: 72 LD (HL),D ; the high byte
537C: 2C INC L
537D: 77 LD (HL),A ; the glyph
537E: 2C INC L
537F: 70 LD (HL),B ; the attribute
5380: 2C INC L
5381: 22 00 AE LD (workRam+600),HL ; store the advanced tail -- stays inside its page
5384: E1 POP HL
loc_5385:
5385: 13 INC DE ; step the cell across one column
5386: 7E LD A,(HL) ; second cell's glyph
5387: 23 INC HL
5388: 46 LD B,(HL) ; its attribute
5389: 23 INC HL
538A: A7 AND A ; glyph zero?
538B: 28 10 JR Z,loc_539d ; transparent: skip it
538D: E5 PUSH HL
538E: 2A 00 AE LD HL,(workRam+600) ; the list tail
5391: 73 LD (HL),E ; cell address low byte
5392: 2C INC L
5393: 72 LD (HL),D ; high byte
5394: 2C INC L
5395: 77 LD (HL),A ; glyph
5396: 2C INC L
5397: 70 LD (HL),B ; attribute
5398: 2C INC L
5399: 22 00 AE LD (workRam+600),HL ; store the advanced tail
539C: E1 POP HL
loc_539d:
539D: 7B LD A,E
539E: C6 1F ADD A,$1F ; step the cell down one row to the second row's first column
53A0: 5F LD E,A
53A1: 30 01 JR NC,loc_53a4 ;
53A3: 14 INC D
loc_53a4:
53A4: 7E LD A,(HL) ; third cell's glyph
53A5: 23 INC HL
53A6: 46 LD B,(HL) ; its attribute
53A7: 23 INC HL
53A8: A7 AND A ; glyph zero?
53A9: 28 10 JR Z,loc_53bb ; transparent: skip it
53AB: E5 PUSH HL
53AC: 2A 00 AE LD HL,(workRam+600) ; the list tail
53AF: 73 LD (HL),E ; cell address low byte
53B0: 2C INC L
53B1: 72 LD (HL),D ; high byte
53B2: 2C INC L
53B3: 77 LD (HL),A ; glyph
53B4: 2C INC L
53B5: 70 LD (HL),B ; attribute
53B6: 2C INC L
53B7: 22 00 AE LD (workRam+600),HL ; store the advanced tail
53BA: E1 POP HL
loc_53bb:
53BB: 13 INC DE ; step the cell across one column
53BC: 7E LD A,(HL) ; fourth cell's glyph
53BD: 23 INC HL
53BE: 46 LD B,(HL) ; its attribute
53BF: 23 INC HL
53C0: A7 AND A ; glyph zero?
53C1: 28 10 JR Z,loc_53d3 ; transparent: skip it
53C3: E5 PUSH HL
53C4: 2A 00 AE LD HL,(workRam+600) ; the list tail
53C7: 73 LD (HL),E ; cell address low byte
53C8: 2C INC L
53C9: 72 LD (HL),D ; high byte
53CA: 2C INC L
53CB: 77 LD (HL),A ; glyph
53CC: 2C INC L
53CD: 70 LD (HL),B ; attribute
53CE: 2C INC L
53CF: 22 00 AE LD (workRam+600),HL ; store the advanced tail
53D2: E1 POP HL
loc_53d3:
53D3: C9 RET
; ---- $53D4-$55D3: data ----
53D4: 24 20 00 00 00 00 00 00 DD 20 00 00 00 00 00 00
53E4: 61 20 00 00 00 00 00 00 3C 20 00 00 00 00 00 00
53F4: 61 60 00 00 00 00 00 00 DD 60 00 00 00 00 00 00
5404: 24 60 00 00 00 00 00 00 39 20 39 60 00 00 00 00
5414: 30 20 00 00 00 00 00 00 A1 20 00 00 00 00 00 00
5424: B7 20 00 00 00 00 00 00 D0 20 00 00 00 00 00 00
5434: B7 60 00 00 00 00 00 00 A1 60 00 00 00 00 00 00
5444: 30 60 00 00 00 00 00 00 6D 20 6D 60 00 00 00 00
5454: 40 20 00 00 00 00 00 00 34 20 00 00 00 00 00 00
5464: 2B 20 00 00 00 00 00 00 B1 20 00 00 00 00 00 00
5474: 2B 60 00 00 00 00 00 00 34 60 00 00 00 00 00 00
5484: 40 60 00 00 00 00 00 00 8E 20 8E 60 00 00 00 00
5494: 74 20 00 00 00 00 00 00 54 20 00 00 00 00 00 00
54A4: 4C 20 00 00 00 00 00 00 2D 20 00 00 00 00 00 00
54B4: 4C 60 00 00 00 00 00 00 54 60 00 00 00 00 00 00
54C4: 74 60 00 00 00 00 00 00 D5 20 D5 60 00 00 00 00
54D4: 40 A0 00 00 00 00 00 00 34 A0 00 00 00 00 00 00
54E4: 2B A0 00 00 00 00 00 00 B1 A0 00 00 00 00 00 00
54F4: 2B E0 00 00 00 00 00 00 34 E0 00 00 00 00 00 00
5504: 40 E0 00 00 00 00 00 00 8E A0 8E E0 00 00 00 00
5514: 30 A0 00 00 00 00 00 00 A1 A0 00 00 00 00 00 00
5524: B7 A0 00 00 00 00 00 00 D0 A0 00 00 00 00 00 00
5534: B7 E0 00 00 00 00 00 00 A1 E0 00 00 00 00 00 00
5544: 30 E0 00 00 00 00 00 00 6D A0 6D E0 00 00 00 00
5554: 24 A0 00 00 00 00 00 00 DD A0 00 00 00 00 00 00
5564: 61 A0 00 00 00 00 00 00 3C A0 00 00 00 00 00 00
5574: 61 E0 00 00 00 00 00 00 DD E0 00 00 00 00 00 00
5584: 24 E0 00 00 00 00 00 00 39 A0 39 E0 00 00 00 00
5594: 3A 20 00 00 3A A0 00 00 8F 20 00 00 8F A0 00 00
55A4: 70 20 00 00 70 A0 00 00 66 20 00 00 66 A0 00 00
55B4: 70 60 00 00 70 E0 00 00 8F 60 00 00 8F E0 00 00
55C4: 3A 60 00 00 3A E0 00 00 C7 20 C7 60 C7 A0 C7 E0
; send the byte at the head of the pending-sound queue, then close the gap
; it left: a count cell at 0xAC43 says how many bytes are waiting and the
; bytes follow it from 0xAC44, and a count of zero is left untouched with
; nothing going out. Otherwise the count comes down by one, the head byte
; goes out, and every byte still waiting slides one place down so the head
; slot always holds the next one. The send happens whether or not anything
; is left to slide, so emptying the queue costs no slide; and nothing
; bounds the count, so a large one slides bytes in from past the queue's
; own cells
sendOldestQueuedSoundCommand:
55D4: 21 43 AC LD HL,$AC43 ; point at the pending-sound queue's count
55D7: 7E LD A,(HL) ; how many are waiting
55D8: A7 AND A ; any?
55D9: C8 RET Z ; none: nothing to send
55DA: 35 DEC (HL) ; drop the count by one
55DB: F5 PUSH AF ; remember whether that emptied the queue
55DC: 23 INC HL ; point at the head byte
55DD: 7E LD A,(HL) ; the oldest queued sound code
55DE: CD F8 55 CALL sendSoundCommand ; hand it to the audio processor
55E1: F1 POP AF ; recover the emptied-queue flag
55E2: C8 RET Z ; that was the last: nothing to slide, done
55E3: 3D DEC A ; one fewer byte still queued
55E4: 06 00 LD B,$00
55E6: 4F LD C,A ; that many bytes to slide
55E7: 5D LD E,L ; destination is the head slot
55E8: 54 LD D,H
55E9: 23 INC HL ; source is the byte after it
55EA: ED B0 LDIR ; slide every remaining byte down one, so the head holds the next code
55EC: C9 RET
; ---- $55ED-$55F7: data ----
55ED: 73 A6 14 7E 29 F8 96 5D 17 9B B9
; hand one byte to the audio processor: write it into the one-byte latch
; that processor reads, then drive its attention line high and back low,
; the edge that makes it look
sendSoundCommand:
55F8: 32 00 C0 LD (scanline),A ; drop the byte into the audio processor's command latch
55FB: 3E 01 LD A,$01 ; raise
55FD: 32 04 C3 LD ($C304),A ; the audio processor's attention line
5600: 00 NOP ; idle -- widen the attention pulse
5601: 00 NOP
5602: 00 NOP
5603: 00 NOP
5604: 00 NOP
5605: 00 NOP
5606: 3E 00 LD A,$00 ; then drive
5608: 32 04 C3 LD ($C304),A ; the attention line back low -- the high-to-low edge is what it notices
560B: C9 RET
; queue a sound code, but only while a game is being played; with the play
; flag clear the request is dropped and nothing is left behind for a later
; frame
enqueueSoundIfGameInProgress:
560C: E5 PUSH HL ; save the caller's pointer
560D: F5 PUSH AF ; hold the requested sound code
560E: 3A 30 AD LD A,(workRam+530) ; the play-in-progress flag
5611: A7 AND A ; is a game running?
5612: 20 16 JR NZ,loc_562a ; running: go queue the code
5614: F1 POP AF ; not running: drop the request silently and return
5615: E1 POP HL
5616: C9 RET
; queue a sound code when either the play flag or the cell at 0xA9C6 is
; set; only with both clear is the request dropped
enqueueSoundIfGameOrAttract:
5617: E5 PUSH HL ; save the caller's pointer
5618: F5 PUSH AF ; hold the requested sound code
5619: 3A 30 AD LD A,(workRam+530) ; the play-in-progress flag
561C: A7 AND A ; is a game running?
561D: 20 0B JR NZ,loc_562a ; running: go queue the code
561F: 3A C6 A9 LD A,(workRam+1C6) ; the attract-loop sound-enable flag
5622: A7 AND A ; set?
5623: 20 05 JR NZ,loc_562a ; set: queue it even with no game running
5625: F1 POP AF ; neither: drop the request and return
5626: E1 POP HL
5627: C9 RET
; queue a sound code with no permission test, so it is queued whether or
; not a game is being played
enqueueSoundUnconditional:
5628: E5 PUSH HL
5629: F5 PUSH AF ; hold the sound code, then fall into the shared queue tail
loc_562a:
562A: 21 43 AC LD HL,$AC43 ; the pending-sound queue's count
562D: 34 INC (HL) ; one more entry
562E: 7E LD A,(HL) ; the new count
562F: CF RST $08 ; index that many past the count -- the new tail slot
5630: F1 POP AF ; recover the code
5631: 77 LD (HL),A ; store it at the tail
5632: E1 POP HL
5633: C9 RET
; queue seven sound codes back to back with no play test: six fetched one
; each from its own cell of the program image, so an edit to the image
; changes what is asked for, and a seventh formed by adding the era index
; to a fixed base
enqueueTransitionSoundBurst:
5634: 3A 7C 16 LD A,(rom+167C) ; a sound code from the program image
5637: CD 28 56 CALL enqueueSoundUnconditional; queue it, no permission test
563A: 3A 9C 0A LD A,(rom+A9C) ; the next code
563D: CD 28 56 CALL enqueueSoundUnconditional; queue it
5640: 3A 84 14 LD A,(rom+1484) ; the next code
5643: CD 28 56 CALL enqueueSoundUnconditional; queue it
5646: 3A 78 0C LD A,(rom+C78) ; the next code
5649: CD 28 56 CALL enqueueSoundUnconditional; queue it
564C: 3A D3 07 LD A,(rom+7D3) ; the next code
564F: CD 28 56 CALL enqueueSoundUnconditional; queue it
5652: 3A B4 33 LD A,(rom+33B4) ; the next code
5655: CD 28 56 CALL enqueueSoundUnconditional; queue it
5658: 3A 04 AD LD A,(workRam+504) ; the current era index
565B: C6 8C ADD A,$8C ; offset it to an era-specific code
565D: 18 C9 JR enqueueSoundUnconditional; queue that one too
; read the byte at 0x07A2 and request it as a sound code, only while a
; game is being played
requestEnemyLaunchSound:
565F: 3A A2 07 LD A,(rom+7A2) ; a sound code from the program image
5662: 18 A8 JR enqueueSoundIfGameInProgress; request it, only while a game is in progress
; read the byte at 0x16DE and request it as a sound code, only while a
; game is being played
requestAttackerSpawnSoundEra0:
5664: 3A DE 16 LD A,(rom+16DE) ; a sound code from the program image
5667: 18 A3 JR enqueueSoundIfGameInProgress; request it, only while a game is in progress
; read the byte at 0x4C9F and request it as a sound code, only while a
; game is being played
requestEnemyLaunchSoundLateEra:
5669: 3A 9F 4C LD A,(rom+4C9F) ; a sound code from the program image
566C: 18 9E JR enqueueSoundIfGameInProgress; request it, only while a game is in progress
; ask for two sounds in a row, each code fetched from its own byte of the
; program image, both admitted only while a game is being played
requestTwoSoundsWhilePlaying:
566E: 3A D8 07 LD A,(rom+7D8) ; first sound code from the program image
5671: CD 0C 56 CALL enqueueSoundIfGameInProgress; request it while a game runs, then fall into the next request
; read the byte at 0x276B and request it as a sound code, only while a
; game is being played
requestAttackerSpawnSoundLateEra:
5674: 3A 6B 27 LD A,(rom+276B) ; a sound code from the program image
5677: 18 93 JR enqueueSoundIfGameInProgress; request it, only while a game is in progress
; read the byte at 0x07FE and request it as a sound code, only while a
; game is being played
requestLateEraProgressSound:
5679: 3A FE 07 LD A,(rom+7FE) ; a sound code from the program image
567C: 18 8E JR enqueueSoundIfGameInProgress; request it, only while a game is in progress
; read the byte at 0x3270 and request it as a sound code, admitted while a
; game is being played or the cell at 0xA9C6 is set
requestPlayerShotSound:
567E: 3A 70 32 LD A,(rom+3270) ; a sound code from the program image
5681: 18 94 JR enqueueSoundIfGameOrAttract; request it while a game runs or attract sound is enabled
; request two sounds in a row, each code fetched from its own byte of the
; program image, both admitted by the shared play-or-demo permission
requestTwoSounds:
5683: 3A A6 07 LD A,(rom+7A6) ; first sound code from the program image
5686: CD 17 56 CALL enqueueSoundIfGameOrAttract; request it -- game or attract sound
5689: 3A DA 4C LD A,(rom+4CDA) ; second sound code from the program image
568C: 18 89 JR enqueueSoundIfGameOrAttract; request it -- game or attract sound
loc_568e:
568E: 3A 87 2D LD A,(rom+2D87) ; a sound code from the program image
5691: C3 0C 56 JP enqueueSoundIfGameInProgress; request it, only while a game is in progress
loc_5694:
5694: 0E 00 LD C,$00
5696: 21 31 08 LD HL,$0831 ; a block of the program image
5699: 3A AB A9 LD A,(workRam+1AB) ; start from the current sequence phase
loc_569c:
569C: 96 SUB (HL) ; subtract each image byte in turn
569D: 23 INC HL
569E: 0D DEC C
569F: 20 FB JR NZ,loc_569c ;
56A1: EE C2 XOR $C2 ; fold in a trailing constant -- on an intact image this nets to the phase the sequence wants
56A3: 32 AB A9 LD (workRam+1AB),A ; store it back as the sequence phase -- a patched image lands in a different phase instead of failing
56A6: CD 97 0F CALL multiplexSpriteSlotsSkipping; multiplex the object list onto the eight hardware sprite slots
56A9: CD DF 1E CALL dispatchPlayerFrameByState; advance the player by its state
56AC: CD 97 0F CALL multiplexSpriteSlotsSkipping; multiplex the sprite slots again
56AF: CD BC 2C CALL runSceneryForEra ; run the era's scrolling scenery
56B2: CD E3 23 CALL fireAndSweepPlayerShots; advance the player's shots
56B5: CD 98 10 CALL multiplexSpriteSlots; multiplex the sprite slots once more
56B8: 21 EB A9 LD HL,$A9EB ; the sequence delay counter
56BB: 35 DEC (HL) ; count down one frame
56BC: C0 RET NZ ; not expired yet: done
56BD: 0E 00 LD C,$00
56BF: 21 A7 12 LD HL,$12A7 ; a second block of the program image
56C2: 3A AB A9 LD A,(workRam+1AB) ; the sequence phase again
loc_56c5:
56C5: 96 SUB (HL) ; subtract each byte
56C6: 23 INC HL
56C7: 0D DEC C
56C8: 20 FB JR NZ,loc_56c5 ;
56CA: EE 59 XOR $59 ; fold in its trailing constant
56CC: 32 AB A9 LD (workRam+1AB),A ; store back to the sequence phase
56CF: C3 1A 0F JP advanceSequenceSubStep; step the sequence to its next sub-step
; ask for three sounds whose codes come from bytes of the program image,
; all three refused unless a game is being played, then leave through the
; two-request tail whose permission is looser -- so a state that drops the
; three can still admit the pair
requestRoundIntroSoundBurst:
56D2: 3A 5B 0C LD A,(rom+C5B) ; first sound code from the program image
56D5: CD 0C 56 CALL enqueueSoundIfGameInProgress; request it, only while a game is in progress
56D8: 3A 55 08 LD A,(rom+855) ; second sound code from the program image
56DB: CD 0C 56 CALL enqueueSoundIfGameInProgress; request it, only while a game is in progress
56DE: 3A 75 16 LD A,(rom+1675) ; third sound code from the program image
56E1: CD 0C 56 CALL enqueueSoundIfGameInProgress; request it while a game runs, then fall into the inter-round pair
; read the byte at 0x27CB and request it as a sound code, then do the same
; with the byte at 0x33A0; each request goes through the door at 0x5617,
; which admits it while a game is being played or while the cell at 0xA9C6
; is set. It is reached two ways -- as a call from
; advanceScriptedCharPlaneBandTo4, in the arm that steps that routine's
; script pointer on, and by falling out of the bottom of
; requestRoundIntroSoundBurst, which has just asked for three other codes
; through the play-only door at 0x560C -- and it is the same two-load,
; two-request shape as requestTwoSounds at 0x5683 with a different pair of
; program bytes
requestInterRoundSoundPair:
56E4: 3A CB 27 LD A,(rom+27CB) ; first sound code from the program image
56E7: CD 17 56 CALL enqueueSoundIfGameOrAttract; request it -- game or attract sound
56EA: 3A A0 33 LD A,(rom+33A0) ; second sound code from the program image
56ED: C3 17 56 JP enqueueSoundIfGameOrAttract; request it -- game or attract sound
; ---- $56F0-$57F0: data ----
56F0: FF 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00
5700: 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
5710: 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
5720: 00 01 00 00 00 00 00 00 00 00 00 00 00 00 01 00
5730: 00 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00
5740: 00 00 01 01 00 00 00 00 00 00 00 00 01 00 00 00
5750: 00 00 00 01 01 01 00 00 00 00 00 00 00 00 00 00
5760: 00 00 00 00 00 01 01 01 01 00 01 00 00 00 00 00
5770: 00 00 00 00 00 00 00 00 00 01 01 00 00 00 00 00
5780: 00 00 00 00 00 00 00 00 01 00 01 00 00 00 00 00
5790: 00 00 00 00 00 00 00 01 00 00 01 01 00 00 00 00
57A0: 00 00 00 00 00 00 01 01 00 00 00 01 01 01 00 00
57B0: 00 00 00 00 01 01 00 01 01 00 00 00 00 01 01 00
57C0: 00 00 00 01 01 01 00 00 01 01 01 00 00 00 00 00
57D0: 00 00 01 01 00 01 01 00 00 00 00 00 00 00 00 00
57E0: 00 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00
57F0: FF
; read the byte at 0x322E and request it as a sound code, with no
; permission test
requestCoinSound:
57F1: 3A 2E 32 LD A,(rom+322E) ; fetch the coin sound code from the program image
57F4: C3 28 56 JP enqueueSoundUnconditional; request it with no permission test -- it sounds whether or not a game is running
; request the sound code that the era index selects out of a run beginning
; twelve codes up, only while a game is being played; the sum is not
; clamped
requestCurrentEraSound:
57F7: 3A 04 AD LD A,(workRam+504) ; read the current era index
57FA: C6 0C ADD A,$0C ; offset it twelve codes up -- each era selects its own sound
57FC: C3 0C 56 JP enqueueSoundIfGameInProgress; request that sound, only while a game is in progress
; read the byte at 0x079B and request it as a sound code, only while a
; game is being played
requestParachutistAwardSound:
57FF: 3A 9B 07 LD A,(rom+79B) ; fetch the parachutist-award sound code from the program image
5802: C3 0C 56 JP enqueueSoundIfGameInProgress; request it, only while a game is in progress
; read the byte at 0x2D4E and request it as a sound code, only while a
; game is being played
requestBonusLifeSound:
5805: 3A 4E 2D LD A,(rom+2D4E) ; fetch the bonus-life sound code from the program image
5808: C3 0C 56 JP enqueueSoundIfGameInProgress; request it, only while a game is in progress
; read the byte at 0x49EE and request it as a sound code, only while a
; game is being played
requestMotherShipWarpSound:
580B: 3A EE 49 LD A,(rom+49EE) ; fetch the mother-ship warp sound code from the program image
580E: C3 0C 56 JP enqueueSoundIfGameInProgress; request it, only while a game is in progress
; read the byte at 0x07A9 and request it as a sound code, only while a
; game is being played
requestPlayerSpawnFlashSound:
5811: 3A A9 07 LD A,(rom+7A9) ; fetch the player-spawn flash sound code from the program image
5814: C3 0C 56 JP enqueueSoundIfGameInProgress; request it, only while a game is in progress
; read the byte at 0x273A and request it as a sound code, only while a
; game is being played
requestEnemyWaveSound:
5817: 3A 3A 27 LD A,(rom+273A) ; fetch the enemy-wave sound code from the program image
581A: C3 0C 56 JP enqueueSoundIfGameInProgress; request it, only while a game is in progress
; ---- $581D-$5833: data ----
581D: 0C A7 13 88 57 34 A5 ED 34 F1 87 34 88 68 ED FD
582D: DC F1 77 68 FD 3B B9
; read the byte at 0x1767 and request it as a sound code, only while a
; game is being played
requestRoundStartSound:
5834: 3A 67 17 LD A,(rom+1767) ; fetch the round-start sound code from the program image
5837: C3 0C 56 JP enqueueSoundIfGameInProgress; request it, only while a game is in progress
loc_583a:
583A: 3A FA 18 LD A,(rom+18FA) ; fetch another sound code from the program image
583D: C3 0C 56 JP enqueueSoundIfGameInProgress; request it, only while a game is in progress
; fly one object a single step at the slowest of the velocity-table
; speeds, choosing that table for the flier and deciding nothing else;
; reached as a call from two per-slot actor handlers and as a tail jump
; from a third
flyAtSlowestSpeed:
5840: 21 D7 59 LD HL,$59D7 ; point at the slowest velocity table
5843: C3 BC 58 JP flyAlongHeading ; fly the object one step along its heading using that table
; ---- $5846-$5853: data ----
5846: 21 00 5C C3 BC 58 60 A7 14 96 10 0D 88 B9
loc_5854:
5854: 21 00 5E LD HL,$5E00 ; point at a velocity table
5857: C3 BC 58 JP flyAlongHeading ; fly the object one step along its heading using that table
; ---- $585A-$585F: data ----
585A: 21 30 25 C3 BC 58
loc_5860:
5860: 21 3E 2E LD HL,$2E3E ; point at a velocity table
5863: C3 BC 58 JP flyAlongHeading ; fly the object one step along its heading using that table
; cold-start clear then ROM tamper check: fill colour RAM 0xA000-0xA3FF
; with 0x10 and video RAM 0xA400-0xA7FF with 0xf1 (bases from ROM pointers
; at 0x2581/0x4A37), sum the whole program ROM 0x0000-0x5FFF and test the
; total against 0xAF, kicking the watchdog after the first fill and once
; per summed byte; a genuine image tail-calls cold-start init, a tampered
; one derails into data at 0x59D7
clearScreenRamAndVerifyImageThenColdInit:
5866: 2A 81 25 LD HL,(rom+2581) ; take the colour-RAM base from the program image
5869: 01 00 04 LD BC,$0400 ; 0x400 bytes to fill
586C: 16 10 LD D,$10 ; the colour fill value
loc_586e:
586E: 72 LD (HL),D ; store the fill byte
586F: 23 INC HL
5870: 0B DEC BC ; count one cell down
5871: 79 LD A,C
5872: B0 OR B ; test the 16-bit count for zero
5873: 20 F9 JR NZ,loc_586e ; loop until colour RAM is filled
5875: 32 00 C2 LD (dsw1),A ; kick the watchdog -- colour fill done
5878: 2A 37 4A LD HL,(rom+4A37) ; take the video-RAM base from the program image
587B: 01 00 04 LD BC,$0400 ; 0x400 bytes to fill
587E: 16 F1 LD D,$F1 ; the video fill value -- a blank tile
loc_5880:
5880: 72 LD (HL),D ; store the fill byte
5881: 23 INC HL
5882: 0B DEC BC ; count one cell down
5883: 79 LD A,C
5884: B0 OR B ; test the 16-bit count for zero
5885: 20 F9 JR NZ,loc_5880 ; loop until video RAM is filled
5887: 21 00 00 LD HL,$0000 ; point at the start of the program image
588A: 3A 00 00 LD A,(rom) ; seed the running total with the first program byte
loc_588d:
588D: 86 ADD A,(HL) ; add this program byte into the total
588E: 23 INC HL
588F: 08 EX AF,AF' ; keep the total safe while the pointer is tested
5890: 7C LD A,H ; take the pointer's high byte
5891: FE 60 CP $60 ; past the last program page (0x60)?
5893: 30 06 JR NC,loc_589b ; image exhausted: go check the total
5895: 08 EX AF,AF' ; bring the total back
5896: 32 00 C2 LD (dsw1),A ; kick the watchdog -- once per summed byte
5899: 18 F2 JR loc_588d ; keep summing
loc_589b:
589B: 08 EX AF,AF' ; bring the total back
589C: D6 AF SUB $AF ; subtract the value a genuine image sums to
589E: C2 D7 59 JP NZ,loc_59d7 ; a tampered image derails into the velocity-table data
58A1: C3 11 25 JP initColdStartRamThenSeedConfig; a genuine image hands on to cold-start init
loc_58a4:
58A4: 21 FA 08 LD HL,$08FA ; point at a velocity table
58A7: C3 BC 58 JP flyAlongHeading ; fly the object one step along its heading using that table
loc_58aa:
58AA: 21 D7 59 LD HL,$59D7 ; point at the slowest velocity table
58AD: C3 FE 58 JP flyAlongHeadingAtDoubleVelocity; fly the object one step at double velocity using that table
; ---- $58B0-$58B5: data ----
58B0: 21 00 5C C3 FE 58
loc_58b6:
58B6: 21 00 5E LD HL,$5E00 ; point at a velocity table
58B9: C3 FE 58 JP flyAlongHeadingAtDoubleVelocity; fly the object one step at double velocity using that table
; fly one object a single step along the heading it holds, and in the same
; add carry it with the world: each coordinate gains its own velocity
; component PLUS the shared per-frame scroll pair, so nothing else may
; drift this object
flyAlongHeading:
58BC: DD 7E 02 LD A,(IX+$02) ; read this object's heading
58BF: 4F LD C,A ; hold the heading for the perpendicular sample
58C0: 87 ADD A,A ; double the heading -- two bytes per table entry
58C1: 30 01 JR NC,loc_58c4 ;
58C3: 24 INC H
loc_58c4:
58C4: 85 ADD A,L
58C5: 6F LD L,A
58C6: 30 01 JR NC,loc_58c9 ;
58C8: 24 INC H
loc_58c9:
58C9: 5E LD E,(HL) ; read the low byte of the velocity component at the heading
58CA: 23 INC HL
58CB: 56 LD D,(HL) ; read its high byte
58CC: 79 LD A,C ; recall the heading
58CD: C6 C0 ADD A,$C0 ; step a quarter turn back -- the perpendicular partner
58CF: 01 80 01 LD BC,$0180 ; offset to that perpendicular sample
58D2: 30 03 JR NC,loc_58d7 ;
58D4: 01 80 FF LD BC,$FF80 ; or offset back when the quarter-turn index wrapped
loc_58d7:
58D7: 09 ADD HL,BC ; reach the perpendicular sample
58D8: 46 LD B,(HL) ; read its high byte
58D9: 2B DEC HL
58DA: 4E LD C,(HL) ; read its low byte -- the second component
58DB: 2A 08 A8 LD HL,(workRam+8) ; take the shared per-frame vertical world-scroll
58DE: 19 ADD HL,DE ; add this object's first-axis component
58DF: DD 5E 03 LD E,(IX+$03) ; read the fraction of the first coordinate
58E2: FD 56 31 LD D,(IY+$31) ; read its whole part from the sprite entry
58E5: 19 ADD HL,DE ; add the displacement onto the coordinate
58E6: DD 75 03 LD (IX+$03),L ; store the new fraction
58E9: FD 74 31 LD (IY+$31),H ; store the new whole part into the sprite entry
58EC: 2A 0A A8 LD HL,(workRam+A) ; take the shared per-frame horizontal world-scroll
58EF: 09 ADD HL,BC ; add this object's second-axis component
58F0: DD 5E 05 LD E,(IX+$05) ; read the fraction of the second coordinate
58F3: FD 56 00 LD D,(IY+$00) ; read its whole part from the sprite entry
58F6: 19 ADD HL,DE ; add the displacement onto the coordinate
58F7: DD 75 05 LD (IX+$05),L ; store the new fraction
58FA: FD 74 00 LD (IY+$00),H ; store the new whole part into the sprite entry
58FD: C9 RET
; fly one object a single step along the heading it holds, with TWICE its
; own velocity component and the shared world scroll added once, so
; nothing else may drift this object
flyAlongHeadingAtDoubleVelocity:
58FE: DD 7E 02 LD A,(IX+$02) ; read this object's heading
5901: 4F LD C,A ; hold the heading for the perpendicular sample
5902: 87 ADD A,A ; double the heading -- two bytes per table entry
5903: 30 01 JR NC,loc_5906 ;
5905: 24 INC H
loc_5906:
5906: 85 ADD A,L
5907: 6F LD L,A
5908: 30 01 JR NC,loc_590b ;
590A: 24 INC H
loc_590b:
590B: 5E LD E,(HL) ; read the low byte of the velocity component at the heading
590C: 23 INC HL
590D: 56 LD D,(HL) ; read its high byte
590E: 79 LD A,C ; recall the heading
590F: C6 C0 ADD A,$C0 ; step a quarter turn back -- the perpendicular partner
5911: 01 80 01 LD BC,$0180 ; offset to that perpendicular sample
5914: 30 03 JR NC,loc_5919 ;
5916: 01 80 FF LD BC,$FF80 ; or offset back when the quarter-turn index wrapped
loc_5919:
5919: 09 ADD HL,BC ; reach the perpendicular sample
591A: 46 LD B,(HL) ; read its high byte
591B: 2B DEC HL
591C: 4E LD C,(HL) ; read its low byte -- the second component
591D: 2A 08 A8 LD HL,(workRam+8) ; take the shared per-frame vertical world-scroll
5920: 19 ADD HL,DE ; add this object's first-axis component
5921: 19 ADD HL,DE ; add it again -- twice this object's own speed
5922: DD 5E 03 LD E,(IX+$03) ; read the fraction of the first coordinate
5925: FD 56 31 LD D,(IY+$31) ; read its whole part from the sprite entry
5928: 19 ADD HL,DE ; add the displacement onto the coordinate
5929: DD 75 03 LD (IX+$03),L ; store the new fraction
592C: FD 74 31 LD (IY+$31),H ; store the new whole part into the sprite entry
592F: 2A 0A A8 LD HL,(workRam+A) ; take the shared per-frame horizontal world-scroll
5932: 09 ADD HL,BC ; add this object's second-axis component
5933: 09 ADD HL,BC ; add it again -- twice this object's own speed
5934: DD 5E 05 LD E,(IX+$05) ; read the fraction of the second coordinate
5937: FD 56 00 LD D,(IY+$00) ; read its whole part from the sprite entry
593A: 19 ADD HL,DE ; add the displacement onto the coordinate
593B: DD 75 05 LD (IX+$05),L ; store the new fraction
593E: FD 74 00 LD (IY+$00),H ; store the new whole part into the sprite entry
5941: C9 RET
loc_5942:
5942: 21 D7 59 LD HL,$59D7 ; point at the slowest velocity table
5945: C3 6E 59 JP velocityForHeading ; look up the velocity vector for the heading using that table
; ---- $5948-$594D: data ----
5948: 21 00 5C C3 6E 59
loc_594e:
594E: 21 00 5E LD HL,$5E00 ; point at a velocity table
5951: C3 6E 59 JP velocityForHeading ; look up the velocity vector for the heading using that table
; ---- $5954-$5964: data ----
5954: 73 A6 14 7E 29 F8 96 5D 02 13 B9 21 30 25 C3 6E
5964: 59
loc_5965:
5965: 21 3E 2E LD HL,$2E3E ; point at a velocity table
5968: C3 6E 59 JP velocityForHeading ; look up the velocity vector for the heading using that table
loc_596b:
596B: 21 FA 08 LD HL,$08FA ; point at a velocity table, then look up the velocity vector for the heading
; look up the velocity vector for a heading: two perpendicular components
; a quarter turn apart, read from the table the caller supplies
velocityForHeading:
596E: DD 7E 02 LD A,(IX+$02) ; read this object's heading
5971: 4F LD C,A ; hold the heading for the perpendicular sample
5972: 87 ADD A,A ; double the heading -- two bytes per table entry
5973: 30 01 JR NC,loc_5976 ;
5975: 24 INC H
loc_5976:
5976: 85 ADD A,L
5977: 6F LD L,A
5978: 30 01 JR NC,loc_597b ;
597A: 24 INC H
loc_597b:
597B: 5E LD E,(HL) ; read the low byte of the velocity component at the heading
597C: 23 INC HL
597D: 56 LD D,(HL) ; read its high byte -- the component at the heading
597E: 79 LD A,C ; recall the heading
597F: C6 C0 ADD A,$C0 ; step a quarter turn back -- the perpendicular partner
5981: 01 80 01 LD BC,$0180 ; offset to that perpendicular sample
5984: 30 03 JR NC,loc_5989 ;
5986: 01 80 FF LD BC,$FF80 ; or offset back when the quarter-turn index wrapped
loc_5989:
5989: 09 ADD HL,BC ; reach the perpendicular sample
598A: 46 LD B,(HL) ; read its high byte
598B: 2B DEC HL
598C: 4E LD C,(HL) ; read its low byte -- the perpendicular component
598D: C9 RET
loc_598e:
598E: 21 D7 59 LD HL,$59D7 ; point at the slowest velocity table
5991: C3 9D 59 JP loc_599d ; read that table's doubled velocity for this object's heading
loc_5994:
5994: 21 00 5C LD HL,$5C00 ; point at a velocity table
5997: C3 9D 59 JP loc_599d ; read that table's doubled velocity for this object's heading
; ---- $599A-$599C: data ----
599A: 21 00 5E
loc_599d:
599D: DD 7E 02 LD A,(IX+$02) ; read this object's heading, then take the doubled velocity for it
; turn a heading handed straight in into the velocity pair the caller's
; table gives for it, doubled; the doubling wraps at sixteen bits and
; nothing is written
doubledVelocityForHeading:
59A0: 4F LD C,A ; hold the heading for the perpendicular sample
59A1: 87 ADD A,A ; double the heading -- two bytes per table entry
59A2: 30 01 JR NC,loc_59a5 ;
59A4: 24 INC H
loc_59a5:
59A5: 85 ADD A,L
59A6: 6F LD L,A
59A7: 30 01 JR NC,loc_59aa ;
59A9: 24 INC H
loc_59aa:
59AA: 5E LD E,(HL) ; read the low byte of the velocity component at the heading
59AB: 23 INC HL
59AC: 56 LD D,(HL) ; read its high byte -- the component at the heading
59AD: CB 23 SLA E ; double the component low byte
59AF: CB 12 RL D ; carry into the high byte -- twice the table's length
59B1: 79 LD A,C ; recall the heading
59B2: C6 C0 ADD A,$C0 ; step a quarter turn back -- the perpendicular partner
59B4: 01 80 01 LD BC,$0180 ; offset to that perpendicular sample
59B7: 30 03 JR NC,loc_59bc ;
59B9: 01 80 FF LD BC,$FF80 ; or offset back when the quarter-turn index wrapped
loc_59bc:
59BC: 09 ADD HL,BC ; reach the perpendicular sample
59BD: 46 LD B,(HL) ; read its high byte
59BE: 2B DEC HL
59BF: 4E LD C,(HL) ; read its low byte -- the perpendicular component
59C0: CB 21 SLA C ; double the component low byte
59C2: CB 10 RL B ; carry into the high byte -- twice the table's length
59C4: C9 RET
loc_59c5:
59C5: 21 D7 59 LD HL,$59D7 ; point at the slowest velocity table
59C8: C3 A0 59 JP doubledVelocityForHeading; take that table's doubled velocity for the heading in A
loc_59cb:
59CB: 21 00 5C LD HL,$5C00 ; point at a velocity table
59CE: C3 A0 59 JP doubledVelocityForHeading; take that table's doubled velocity for the heading in A
loc_59d1:
59D1: 21 00 5E LD HL,$5E00 ; point at a velocity table
59D4: C3 A0 59 JP doubledVelocityForHeading; take that table's doubled velocity for the heading in A
loc_59d7:
59D7: CE 00 ADC A,$00
59D9: CD 00 CC CALL $CC00
59DC: 00 NOP
59DD: CB 00 RLC B
59DF: CA 00 C9 JP Z,$C900
59E2: 00 NOP
59E3: C8 RET Z
59E4: 00 NOP
59E5: C8 RET Z
59E6: 00 NOP
59E7: C6 00 ADD A,$00
59E9: C4 00 C2 CALL NZ,dsw1 ;
59EC: 00 NOP
59ED: C0 RET NZ
59EE: 00 NOP
59EF: BF CP A
59F0: 00 NOP
59F1: BC CP H
59F2: 00 NOP
59F3: BA CP D
59F4: 00 NOP
59F5: B9 CP C
59F6: 00 NOP
59F7: B6 OR (HL)
59F8: 00 NOP
59F9: B3 OR E
59FA: 00 NOP
59FB: B0 OR B
59FC: 00 NOP
59FD: AF XOR A
59FE: 00 NOP
59FF: AC XOR H
5A00: 00 NOP
5A01: A9 XOR C
5A02: 00 NOP
5A03: A8 XOR B
5A04: 00 NOP
5A05: A5 AND L
5A06: 00 NOP
5A07: A2 AND D
5A08: 00 NOP
5A09: A1 AND C
5A0A: 00 NOP
5A0B: 9E SBC A,(HL)
5A0C: 00 NOP
5A0D: 9B SBC A,E
5A0E: 00 NOP
5A0F: 98 SBC A,B
5A10: 00 NOP
5A11: 97 SUB A
5A12: 00 NOP
5A13: 94 SUB H
5A14: 00 NOP
5A15: 91 SUB C
5A16: 00 NOP
5A17: 90 SUB B
5A18: 00 NOP
5A19: 8D ADC A,L
5A1A: 00 NOP
5A1B: 89 ADC A,C
5A1C: 00 NOP
5A1D: 88 ADC A,B
5A1E: 00 NOP
5A1F: 85 ADD A,L
5A20: 00 NOP
5A21: 81 ADD A,C
5A22: 00 NOP
5A23: 7F LD A,A
5A24: 00 NOP
5A25: 7B LD A,E
5A26: 00 NOP
5A27: 78 LD A,B
5A28: 00 NOP
5A29: 76 HALT
5A2A: 00 NOP
5A2B: 70 LD (HL),B
5A2C: 00 NOP
5A2D: 6D LD L,L
5A2E: 00 NOP
5A2F: 68 LD L,B
5A30: 00 NOP
5A31: 63 LD H,E
5A32: 00 NOP
5A33: 60 LD H,B
5A34: 00 NOP
5A35: 5C LD E,H
5A36: 00 NOP
5A37: 58 LD E,B
5A38: 00 NOP
5A39: 52 LD D,D
5A3A: 00 NOP
5A3B: 4E LD C,(HL)
5A3C: 00 NOP
5A3D: 49 LD C,C
5A3E: 00 NOP
5A3F: 43 LD B,E
5A40: 00 NOP
5A41: 3E 00 LD A,$00
5A43: 39 ADD HL,SP
5A44: 00 NOP
5A45: 32 00 2C LD (rom+2C00),A ;
5A48: 00 NOP
5A49: 27 DAA
5A4A: 00 NOP
5A4B: 20 00 JR NZ,loc_5a4d ;
loc_5a4d:
5A4D: 1A LD A,(DE)
5A4E: 00 NOP
5A4F: 14 INC D
5A50: 00 NOP
5A51: 0E 00 LD C,$00
5A53: 08 EX AF,AF'
5A54: 00 NOP
5A55: 00 NOP
5A56: 00 NOP
5A57: 00 NOP
5A58: 00 NOP
5A59: F8 RET M
5A5A: FF RST $38
5A5B: F2 FF 00 JP P,rom+FF ;
5A5E: 00 NOP
5A5F: E6 FF AND $FF
5A61: E0 RET PO
5A62: FF RST $38
5A63: D9 EXX
5A64: FF RST $38
5A65: D4 FF CE CALL NC,$CEFF
5A68: FF RST $38
5A69: C7 RST $00
5A6A: FF RST $38
5A6B: C2 FF BD JP NZ,$BDFF
5A6E: FF RST $38
5A6F: B7 OR A
5A70: FF RST $38
5A71: B2 OR D
5A72: FF RST $38
5A73: AE XOR (HL)
5A74: FF RST $38
5A75: A8 XOR B
5A76: FF RST $38
5A77: A4 AND H
5A78: FF RST $38
5A79: A0 AND B
5A7A: FF RST $38
5A7B: 9D SBC A,L
5A7C: FF RST $38
5A7D: A0 AND B
5A7E: FF RST $38
5A7F: 93 SUB E
5A80: FF RST $38
5A81: 90 SUB B
5A82: FF RST $38
5A83: 8A ADC A,D
5A84: FF RST $38
5A85: 88 ADC A,B
5A86: FF RST $38
5A87: 85 ADD A,L
5A88: FF RST $38
5A89: 81 ADD A,C
5A8A: FF RST $38
5A8B: 7F LD A,A
5A8C: FF RST $38
5A8D: 7B LD A,E
5A8E: FF RST $38
5A8F: 78 LD A,B
5A90: FF RST $38
5A91: 77 LD (HL),A
5A92: FF RST $38
5A93: 73 LD (HL),E
5A94: FF RST $38
5A95: 70 LD (HL),B
5A96: FF RST $38
5A97: 6F LD L,A
5A98: FF RST $38
5A99: 6C LD L,H
5A9A: FF RST $38
5A9B: 69 LD L,C
5A9C: FF RST $38
5A9D: 69 LD L,C
5A9E: FF RST $38
5A9F: 65 LD H,L
5AA0: FF RST $38
5AA1: 62 LD H,D
5AA2: FF RST $38
5AA3: 5F LD E,A
5AA4: FF RST $38
5AA5: 5E LD E,(HL)
5AA6: FF RST $38
5AA7: 5B LD E,E
5AA8: FF RST $38
5AA9: 58 LD E,B
5AAA: FF RST $38
5AAB: 57 LD D,A
5AAC: FF RST $38
5AAD: 54 LD D,H
5AAE: FF RST $38
5AAF: 51 LD D,C
5AB0: FF RST $38
5AB1: 50 LD D,B
5AB2: FF RST $38
5AB3: 4D LD C,L
5AB4: FF RST $38
5AB5: 4A LD C,D
5AB6: FF RST $38
5AB7: 47 LD B,A
5AB8: FF RST $38
5AB9: 46 LD B,(HL)
5ABA: FF RST $38
5ABB: 44 LD B,H
5ABC: FF RST $38
5ABD: 41 LD B,C
5ABE: FF RST $38
5ABF: 40 LD B,B
5AC0: FF RST $38
5AC1: 3E FF LD A,$FF
5AC3: 3C INC A
5AC4: FF RST $38
5AC5: 3A FF 38 LD A,(rom+38FF) ;
5AC8: FF RST $38
5AC9: 38 FF JR C,rom+5ACA ;
5ACB: 37 SCF
5ACC: FF RST $38
5ACD: 36 FF LD (HL),$FF
5ACF: 35 DEC (HL)
5AD0: FF RST $38
5AD1: 34 INC (HL)
5AD2: FF RST $38
5AD3: 33 INC SP
5AD4: FF RST $38
5AD5: 32 FF 32 LD (rom+32FF),A ;
5AD8: FF RST $38
5AD9: 33 INC SP
5ADA: FF RST $38
5ADB: 34 INC (HL)
5ADC: FF RST $38
5ADD: 35 DEC (HL)
5ADE: FF RST $38
5ADF: 36 FF LD (HL),$FF
5AE1: 37 SCF
5AE2: FF RST $38
5AE3: 38 FF JR C,rom+5AE4 ;
5AE5: 38 FF JR C,rom+5AE6 ;
5AE7: 3A FF 3C LD A,(rom+3CFF) ;
5AEA: FF RST $38
5AEB: 3E FF LD A,$FF
5AED: 40 LD B,B
5AEE: FF RST $38
5AEF: 41 LD B,C
5AF0: FF RST $38
5AF1: 44 LD B,H
5AF2: FF RST $38
5AF3: 46 LD B,(HL)
5AF4: FF RST $38
5AF5: 47 LD B,A
5AF6: FF RST $38
5AF7: 4A LD C,D
5AF8: FF RST $38
5AF9: 4D LD C,L
5AFA: FF RST $38
5AFB: 50 LD D,B
5AFC: FF RST $38
5AFD: 51 LD D,C
5AFE: FF RST $38
5AFF: 54 LD D,H
5B00: FF RST $38
5B01: 57 LD D,A
5B02: FF RST $38
5B03: 58 LD E,B
5B04: FF RST $38
5B05: 5B LD E,E
5B06: FF RST $38
5B07: 5E LD E,(HL)
5B08: FF RST $38
5B09: 5F LD E,A
5B0A: FF RST $38
5B0B: 62 LD H,D
5B0C: FF RST $38
5B0D: 65 LD H,L
5B0E: FF RST $38
5B0F: 68 LD L,B
5B10: FF RST $38
5B11: 69 LD L,C
5B12: FF RST $38
5B13: 6C LD L,H
5B14: FF RST $38
5B15: 6F LD L,A
5B16: FF RST $38
5B17: 70 LD (HL),B
5B18: FF RST $38
5B19: 73 LD (HL),E
5B1A: FF RST $38
5B1B: 77 LD (HL),A
5B1C: FF RST $38
5B1D: 78 LD A,B
5B1E: FF RST $38
5B1F: 7B LD A,E
5B20: FF RST $38
5B21: 7F LD A,A
5B22: FF RST $38
5B23: 81 ADD A,C
5B24: FF RST $38
5B25: 85 ADD A,L
5B26: FF RST $38
5B27: 88 ADC A,B
5B28: FF RST $38
5B29: 8A ADC A,D
5B2A: FF RST $38
5B2B: 90 SUB B
5B2C: FF RST $38
5B2D: 93 SUB E
5B2E: FF RST $38
5B2F: 98 SBC A,B
5B30: FF RST $38
5B31: 9D SBC A,L
5B32: FF RST $38
5B33: A0 AND B
5B34: FF RST $38
5B35: A4 AND H
5B36: FF RST $38
5B37: A8 XOR B
5B38: FF RST $38
5B39: AE XOR (HL)
5B3A: FF RST $38
5B3B: B2 OR D
5B3C: FF RST $38
5B3D: B7 OR A
5B3E: FF RST $38
5B3F: BD CP L
5B40: FF RST $38
5B41: C2 FF C7 JP NZ,$C7FF
5B44: FF RST $38
5B45: CE FF ADC A,$FF
5B47: D4 FF D9 CALL NC,$D9FF
5B4A: FF RST $38
5B4B: E0 RET PO
5B4C: FF RST $38
5B4D: E6 FF AND $FF
5B4F: EC FF F2 CALL PE,$F2FF
5B52: FF RST $38
5B53: F8 RET M
5B54: FF RST $38
5B55: 00 NOP
5B56: 00 NOP
5B57: 00 NOP
5B58: 00 NOP
5B59: 08 EX AF,AF'
5B5A: 00 NOP
5B5B: 0E 00 LD C,$00
5B5D: 14 INC D
5B5E: 00 NOP
5B5F: 1A LD A,(DE)
5B60: 00 NOP
5B61: 20 00 JR NZ,loc_5b63 ;
loc_5b63:
5B63: 27 DAA
5B64: 00 NOP
5B65: 2C INC L
5B66: 00 NOP
5B67: 32 00 39 LD (rom+3900),A ;
5B6A: 00 NOP
5B6B: 3E 00 LD A,$00
5B6D: 43 LD B,E
5B6E: 00 NOP
5B6F: 49 LD C,C
5B70: 00 NOP
5B71: 4E LD C,(HL)
5B72: 00 NOP
5B73: 52 LD D,D
5B74: 00 NOP
5B75: 58 LD E,B
5B76: 00 NOP
5B77: 5C LD E,H
5B78: 00 NOP
5B79: 60 LD H,B
5B7A: 00 NOP
5B7B: 63 LD H,E
5B7C: 00 NOP
5B7D: 63 LD H,E
5B7E: 00 NOP
5B7F: 6D LD L,L
5B80: 00 NOP
5B81: 70 LD (HL),B
5B82: 00 NOP
5B83: 76 HALT
5B84: 00 NOP
5B85: 78 LD A,B
5B86: 00 NOP
5B87: 7B LD A,E
5B88: 00 NOP
5B89: 7F LD A,A
5B8A: 00 NOP
5B8B: 81 ADD A,C
5B8C: 00 NOP
5B8D: 85 ADD A,L
5B8E: 00 NOP
5B8F: 88 ADC A,B
5B90: 00 NOP
5B91: 89 ADC A,C
5B92: 00 NOP
5B93: 8D ADC A,L
5B94: 00 NOP
5B95: 90 SUB B
5B96: 00 NOP
5B97: 91 SUB C
5B98: 00 NOP
5B99: 94 SUB H
5B9A: 00 NOP
5B9B: 97 SUB A
5B9C: 00 NOP
5B9D: 94 SUB H
5B9E: 00 NOP
5B9F: 9B SBC A,E
5BA0: 00 NOP
5BA1: 9E SBC A,(HL)
5BA2: 00 NOP
5BA3: A1 AND C
5BA4: 00 NOP
5BA5: A2 AND D
5BA6: 00 NOP
5BA7: A5 AND L
5BA8: 00 NOP
5BA9: A8 XOR B
5BAA: 00 NOP
5BAB: A9 XOR C
5BAC: 00 NOP
5BAD: AC XOR H
5BAE: 00 NOP
5BAF: AF XOR A
5BB0: 00 NOP
5BB1: B0 OR B
5BB2: 00 NOP
5BB3: B3 OR E
5BB4: 00 NOP
5BB5: B6 OR (HL)
5BB6: 00 NOP
5BB7: B9 CP C
5BB8: 00 NOP
5BB9: BA CP D
5BBA: 00 NOP
5BBB: BC CP H
5BBC: 00 NOP
5BBD: B9 CP C
5BBE: 00 NOP
5BBF: C0 RET NZ
5BC0: 00 NOP
5BC1: C2 00 C4 JP NZ,$C400
5BC4: 00 NOP
5BC5: C6 00 ADD A,$00
5BC7: C8 RET Z
5BC8: 00 NOP
5BC9: C8 RET Z
5BCA: 00 NOP
5BCB: C9 RET
; ---- $5BCC-$5BD6: data ----
5BCC: 00 CA 00 CB 00 CC 00 CD 00 CE 00
; inner sequence-dispatch arm (table 0x0f29 index 2): blank a fixed
; character run, advance the interpolated pen run, and bail unless it
; reseated to a zero row integer; on the full path fold two guarded code
; blocks (an anti-tamper XOR check that raises the sequence phase on
; mismatch, and a self-cancelling add-checksum over a work cell) then step
; the sequence sub-index
blankCaptionThenAdvancePenRunStep:
5BD7: CD D2 07 CALL blankFourteenCharCells; blank a fixed run of fourteen character cells
5BDA: CD 01 02 CALL drawInterpolatedPenRun; advance the interpolated pen run one step
5BDD: C0 RET NZ ; return unless the pen run reseated to a whole row
5BDE: 21 DD 0B LD HL,$0BDD ; point at a 256-byte program block to fold
5BE1: 97 SUB A ; clear the running fold
5BE2: 47 LD B,A ; 256 bytes to fold -- a zero count runs the full round
loc_5be3:
5BE3: AE XOR (HL) ; fold this byte into the running exclusive-or
5BE4: 23 INC HL
5BE5: 10 FC DJNZ loc_5be3 ; loop over all 256 bytes
5BE7: C6 E4 ADD A,$E4 ; test the fold against its expected value
5BE9: C4 11 0F CALL NZ,advanceSequencePhase; on a mismatch, raise the sequence phase -- anti-tamper
5BEC: 3A AB A9 LD A,(workRam+1AB) ; read the guard work cell
5BEF: 21 34 17 LD HL,$1734 ; point at a 20-byte program block to sum
5BF2: 06 14 LD B,$14 ; 20 bytes to sum
loc_5bf4:
5BF4: 86 ADD A,(HL) ; add this byte into the running total
5BF5: 23 INC HL
5BF6: 10 FC DJNZ loc_5bf4 ; loop over all 20 bytes
5BF8: C6 77 ADD A,$77 ; add the bias -- on a clean image this returns the cell to its old value
5BFA: 32 AB A9 LD (workRam+1AB),A ; write the guard work cell back
5BFD: C3 1A 0F JP advanceSequenceSubStep; step the sequence sub-index
; ---- $5C00-$5FFF: data ----
5C00: E7 00 E6 00 E5 00 E4 00 E3 00 E2 00 E1 00 E0 00
5C10: DE 00 DC 00 DA 00 D8 00 D6 00 D3 00 D1 00 CF 00
5C20: CC 00 C9 00 C6 00 C4 00 C1 00 BE 00 BC 00 B9 00
5C30: B6 00 B4 00 B1 00 AE 00 AB 00 A9 00 A6 00 A3 00
5C40: A1 00 9E 00 9A 00 98 00 95 00 91 00 8E 00 8A 00
5C50: 87 00 84 00 7E 00 7A 00 75 00 6F 00 6C 00 67 00
5C60: 62 00 5C 00 57 00 51 00 4B 00 45 00 3F 00 38 00
5C70: 31 00 2B 00 24 00 1D 00 16 00 0F 00 08 00 00 00
5C80: 00 00 F8 FF F1 FF 00 00 E3 FF DC FF D5 FF CF FF
5C90: C8 FF C1 FF BB FF B5 FF AF FF A9 FF A4 FF 9E FF
5CA0: 99 FF 94 FF 91 FF 94 FF 86 FF 82 FF 7C FF 79 FF
5CB0: 76 FF 72 FF 6F FF 6B FF 68 FF 66 FF 62 FF 5F FF
5CC0: 5D FF 5A FF 57 FF 57 FF 52 FF 4F FF 4C FF 4A FF
5CD0: 47 FF 44 FF 42 FF 3F FF 3C FF 3A FF 37 FF 34 FF
5CE0: 31 FF 2F FF 2D FF 2A FF 28 FF 26 FF 24 FF 22 FF
5CF0: 20 FF 1F FF 1E FF 1D FF 1C FF 1B FF 1A FF 19 FF
5D00: 19 FF 1A FF 1B FF 1C FF 1D FF 1E FF 1F FF 20 FF
5D10: 22 FF 24 FF 26 FF 28 FF 2A FF 2D FF 2F FF 31 FF
5D20: 34 FF 37 FF 3A FF 3C FF 3F FF 42 FF 44 FF 47 FF
5D30: 4A FF 4C FF 4F FF 52 FF 55 FF 57 FF 5A FF 5D FF
5D40: 5F FF 62 FF 66 FF 68 FF 6B FF 6F FF 72 FF 76 FF
5D50: 79 FF 7C FF 82 FF 86 FF 8B FF 91 FF 94 FF 99 FF
5D60: 9E FF A4 FF A9 FF AF FF B5 FF BB FF C1 FF C8 FF
5D70: CF FF D5 FF DC FF E3 FF EA FF F1 FF F8 FF 00 00
5D80: 00 00 08 00 0F 00 16 00 1D 00 24 00 2B 00 31 00
5D90: 38 00 3F 00 45 00 4B 00 51 00 57 00 5C 00 62 00
5DA0: 67 00 6C 00 6F 00 6F 00 7A 00 7E 00 84 00 87 00
5DB0: 8A 00 8E 00 91 00 95 00 98 00 9A 00 9E 00 A1 00
5DC0: A3 00 A6 00 A9 00 A6 00 AE 00 B1 00 B4 00 B6 00
5DD0: B9 00 BC 00 BE 00 C1 00 C4 00 C6 00 C9 00 CC 00
5DE0: CF 00 D1 00 D3 00 CF 00 D8 00 DA 00 DC 00 DE 00
5DF0: E0 00 E1 00 E2 00 E3 00 E4 00 E5 00 E6 00 E7 00
5E00: 00 01 FF 00 FE 00 FD 00 FC 00 FB 00 FA 00 F8 00
5E10: F6 00 F4 00 F2 00 F0 00 ED 00 EA 00 E8 00 E5 00
5E20: E2 00 DF 00 DC 00 D9 00 D6 00 D3 00 D0 00 CD 00
5E30: CA 00 C7 00 C4 00 C1 00 BE 00 BB 00 B8 00 B5 00
5E40: B2 00 AF 00 AB 00 A8 00 A5 00 A1 00 9D 00 99 00
5E50: 96 00 92 00 8C 00 87 00 82 00 7B 00 78 00 72 00
5E60: 6C 00 66 00 60 00 59 00 53 00 4C 00 45 00 3E 00
5E70: 36 00 2F 00 28 00 20 00 18 00 10 00 08 00 00 00
5E80: 00 00 F8 FF F0 FF 00 00 E0 FF D8 FF D1 FF CA FF
5E90: C2 FF BB FF B4 FF AD FF A7 FF A0 FF 9A FF 94 FF
5EA0: 8E FF 88 FF 85 FF 88 FF 79 FF 74 FF 6E FF 6A FF
5EB0: 67 FF 63 FF 5F FF 5B FF 58 FF 55 FF 51 FF 4E FF
5EC0: 4B FF 48 FF 45 FF 45 FF 3F FF 3C FF 39 FF 36 FF
5ED0: 33 FF 30 FF 2D FF 2A FF 27 FF 24 FF 21 FF 1E FF
5EE0: 1B FF 18 FF 16 FF 13 FF 10 FF 0E FF 0C FF 0A FF
5EF0: 08 FF 06 FF 05 FF 04 FF 03 FF 02 FF 01 FF 00 FF
5F00: 00 FF 01 FF 02 FF 03 FF 04 FF 05 FF 06 FF 08 FF
5F10: 0A FF 0C FF 0E FF 10 FF 13 FF 16 FF 18 FF 1B FF
5F20: 1E FF 21 FF 24 FF 27 FF 2A FF 2D FF 30 FF 33 FF
5F30: 36 FF 39 FF 3C FF 3F FF 42 FF 45 FF 48 FF 4B FF
5F40: 4E FF 51 FF 55 FF 58 FF 5B FF 5F FF 63 FF 67 FF
5F50: 6A FF 6E FF 74 FF 79 FF 7E FF 85 FF 88 FF 8E FF
5F60: 94 FF 9A FF A0 FF A7 FF AD FF B4 FF BB FF C2 FF
5F70: CA FF D1 FF D8 FF E0 FF E8 FF F0 FF F8 FF 00 00
5F80: 00 00 08 00 10 00 18 00 20 00 28 00 2F 00 36 00
5F90: 3E 00 45 00 4C 00 53 00 59 00 60 00 66 00 6C 00
5FA0: 72 00 78 00 7B 00 7B 00 87 00 8C 00 92 00 96 00
5FB0: 99 00 9D 00 A1 00 A5 00 A8 00 AB 00 AF 00 B2 00
5FC0: B5 00 B8 00 BB 00 B8 00 C1 00 C4 00 C7 00 CA 00
5FD0: CD 00 D0 00 D3 00 D6 00 D9 00 DC 00 DF 00 E2 00
5FE0: E5 00 E8 00 EA 00 E5 00 F0 00 F2 00 F4 00 F6 00
5FF0: F8 00 FA 00 FB 00 FC 00 FD 00 FE 00 FF 00 00 01