• Contact
  • Journal
  • Home
  • Arcade
  • Tempest
  • Code
  • Site
  • Page
  • Amiga
    • Rainbow Islands
      • 1loader-dec
      • 1loader
      • boot
      • orig-boot
      • Journal
  • Arcade
    • Asteroids 80%
      • Hardware
      • RAMUse
      • Code

      • DVG
      • VectorROM

      • Journal
    • Centipede
      • Hardware
      • RAMUse
      • Code
    • Crazy Climber 1%
      • BigSpriteGraphics.pdf
      • Buildings.pdf
      • cclimber.asm
      • CharEnc.inc
      • CrazyClimberMemoryMapInfo.pdf
      • GraphicsAndCharacterAssetInfo.pdf
      • MemoryMap.inc
    • Defender 75%
      • RAMUse
      • Hardware
      • Bank1
      • Bank2
      • Bank3
      • Bank7
      • BankFixed
      • Mapping.txt
      • SoundHardware
      • SoundRAMUse
      • SoundCode
      • Defender-Theory-Early.pdf
      • Defender-Theory-Later.pdf
      • Defender.CPU.jpg
      • Defender.ROM.B&W.jpg
      • Defender.Vid.B&W.jpg
      • SoundROM.txt
    • Donkey Kong (AI)
      • Hardware
      • RAMUse
      • Code
    • Frogger (Sound)
      • SoundHardware
      • SoundRAMUse
      • SoundCode

      • Hardware
      • RAMUse
      • Code

      • GFX

      • Journal
    • Galaga 5%
      • CPU1 (Main)
      • CPU2 (Secondary)
      • CPU3 (Sound)
      • GFX1 (Characters)
      • GFX2 (Sprites)
      • PROMcolors
      • PROMpaletteChar
      • PROMpaletteSprite

      • CPU1Fix

      • Journal
    • Moon Patrol 75%
      • Hardware
      • RAMUse
      • Code

      • GFX1 (Text)
      • GFX2 (Sprites)
      • GFX3 (Mountains)
      • GFX4 (Hills)
      • GFX5 (City)
      • ImageBackgroundColors
      • SpriteColors
      • SpriteColorSets
      • TextColors

      • MoonPatrolSound
      • SoundHardware
      • SoundRAMUse
      • SoundCode

      • Journal
    • Omega Race 10%
      • Hardware
      • RAMUse
      • MainBoard

      • SoundHardware
      • SoundRAMUse
      • SoundBoard

      • DVGPROM
      • VectorROM

      • Journal
    • Pooyan (AI)
      • Hardware
      • RAMUse
      • Code
    • Phoenix 90%
      • Hardware
      • RAMUse
      • Code

      • Background
      • Foreground

      • Journal

      • Scramble HHi
        • phoenixj-func-main2.pdf
        • phoenixj-func-main2.scap
        • phoenixj.asm-may2025.txt
        • phoenixj.asm-may2025.txt.pdf
    • Space Invaders
      • invaders.asm
      • Hardware
      • RAMUse
      • Code

      • Journal
    • Tempest
      • Hardware
      • RAMUse
      • Code
    • The Pit (AI)
      • Hardware
      • RAMUse
      • Code
    • Time Pilot (Sound)
      • SoundHardware
      • SoundRAMUse
      • SoundCode

      • Hardware
      • RAMUse
      • Code

      • Journal
    • Sea Wolf 1%
      • Hardware
      • RAMUse
      • Code

      • Journal
    • Scramble 1%
      • SoundHardware
      • SoundRAMUse
      • SoundCode

      • Journal
  • Atari2600
    • Stella (Hardware Info)
    • Asteroids 5%
      • RAMUse
      • Code

      • Journal
    • Battle Zone 1%
      • RAMUse
      • Code

      • Journal
    • Chess 1%
      • RAMUse
      • Code

      • Journal
    • Combat 10%
      • RAMUse
      • Code
      • CodePAL

      • Journal
    • Double Gap
      • Code
      • DoubleGap.asm

      • Journal
    • Entombed 1%
      • RAMUse
      • Code

      • Journal
    • ET 1%
      • RAMUse
      • Code

      • Journal
    • Burger Time 1%
      • RAMUse
      • CodeBank0
      • CodeBank1
      • CodeBank2
      • CodeBank3
      • CodeBank4
      • CodeBank5
      • CodeBank6
      • CodeBank7

      • Journal
    • Missile Command 1%
      • RAMUse
      • Code

      • Journal
    • Space Invaders 1%
      • RAMUse
      • Code

      • Journal
  • ColossalCave
    • CodeOrg
    • DataOrg

    • Code350
    • Data350
  • CoCo
    • Hardware
    • Early Work
    • Pyramid
      • RAMUse
      • Code

      • Journal
    • Raaka Tu
      • RAMUse
      • Code

      • Journal
    • Bedlam
      • RAMUse
      • Code

      • Journal
    • Madness & Minotaur
      • Walk Through
        • after_start.txt
        • after_start.cas
        • after_1.txt
        • after_1.cas
        • after_2.txt
        • after_2.cas
        • after_3.txt
        • after_3.cas
        • after_4.txt
        • after_4.cas
        • after_5.txt
        • after_5.cas
        • after_6.txt
        • after_6.cas
        • after_7.txt
        • after_7.cas
        • after_8.txt
        • after_8.cas
        • after_9.txt
        • after_9.cas
        • after_10.txt
        • after_10.cas
        • after_11.txt
        • after_11.cas
        • after_12.txt
        • after_12.cas
        • after_13.txt
        • after_13.cas
        • after_14.txt
        • after_14.cas
        • after_15.txt
        • after_15.cas
        • after_16.txt
        • after_16.cas
        • after_17.txt
        • after_17.cas
        • after_18.txt
        • after_18.cas
        • after_19.txt
        • after_19.cas
        • after_20.txt
        • after_20.cas
        • after_21.txt
        • after_21.cas
        • after_22.txt
        • after_22.cas
        • after_23.txt
        • after_23.cas
        • after_24.txt
        • after_24.cas
        • after_25.txt
        • after_25.cas
      • RAMUse
      • Code

      • SaveGameViewer

      • Journal
    • Mega-Bug
      • RAMUse
      • Code

      • Journal
    • Popcorn
      • RAMUse
      • Code

      • Journal
    • Daggorath
      • RAMUse
      • Code

      • Level Maps

      • Journal
    • Downland 5%
      • RAMUse
      • Code

      • Journal
    • Audio Analyzer 5%
      • RAMUse
      • Code

      • Journal
    • Doubleback
      • RAMUse
      • Code

      • Journal
  • NES
    • Zelda 5%
      • Hardware
      • RAMUse
      • Bank0
      • Bank1
      • Bank2
      • Bank3
      • Bank4
      • Bank5
      • Bank6
      • Bank7

      • Journal
    • Kid Icarus 1%
      • Hardware
      • RAMUse
      • Bank0
      • Bank1
      • Bank2
      • Bank3
      • Bank4
      • Bank5
      • Bank6
      • Bank7

      • Journal
  • Gameboy
    • Hardware
    • Link's Awakening 1%
      • RAMUse
      • Bank00
      • Bank01
      • Bank02
      • Bank03
      • Bank04
      • Bank05
      • Bank06
      • Bank07
      • Bank08
      • Bank09
      • Bank0A
      • Bank0B
      • Bank0C
      • Bank0D
      • Bank0E
      • Bank0F
      • Bank10
      • Bank11
      • Bank12
      • Bank13
      • Bank14
      • Bank15
      • Bank16
      • Bank17
      • Bank18
      • Bank19
      • Bank1A
      • Bank1B
      • Bank1C
      • Bank1D
      • Bank1E
      • Bank1F

      • Journal
    • Tetris 1%
      • RAMUse
      • Code

      • Journal
  • TRS80
    • HardwareLevel2
    • HardwareLevel1
    • HardwareDisk
    • HauntedHouse
      • RAMUse1
      • Code1
      • RAMUse2
      • Code2

      • Journal
    • Pyramid
      • RAMUse
      • Code

      • RAMUse1
      • Code1

      • Journal
    • RaakaTu
      • RAMUse
      • Code

      • Journal
    • Bedlam
      • RAMUse
      • Code

      • Journal
    • Xenos
      • RAMUse
      • Code
      • Section1
      • Section2
      • Section3
      • Section4
      • Section5
      • Section6
      • Section7
      • Section8
      • Section9
      • SSVDOBJS
      • USVDOBJS

      • Journal
  • Virus
    • Morris Worm 1%
      • Journal
    • Stoned
      • Journal

  • Tools
    • Blend
      • blend.zip

      • Journal
  • Tempest

Tempest

Hardware Info

RAM Usage

; Tempest (Atari, 1981).
;
; 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).


; ---- $9000-$9008: data ----
9000: 02 BB 5A 30 50 EE 3D A8 4D

; initialize wave state: run the four setup passes $92C5, $9234,
; resetWorkingRamForStateEntry, $A831 in order, then seed 0x5b=0xfa and
; clear 0x106, 0x5f, 0x1.
runWaveInit:
9009: 20 C5 92        JSR     reseedStateTables   ; rebuild the per-wave enemy state tables
900C: 20 34 92        JSR     seedPerLaneSpikeArray; lay out this wave's per-lane spikes
900F: 20 2B 90        JSR     resetWorkingRamForStateEntry; clear working RAM for the state being entered
9012: 20 31 A8        JSR     clearReadyLatchPair ; drop the paired ready latches
9015: A9 FA           LDA     #$FA                
9017: 85 5B           STA     workRam+5B          ; prime the tube-depth counter high byte to 250
9019: A9 00           LDA     #$00                
901B: 8D 06 01        STA     workRam+106         ; clear the moving-spike active flag
901E: 85 5F           STA     workRam+5F          ; clear the tube-depth counter low byte
9020: A9 00           LDA     #$00                
9022: 85 01           STA     workRam+1           ; reset the mode-dispatch selector
9024: 60              RTS                         

; level-entry init: run $921B then $92C5, then tail-delegate to the main
; init resetWorkingRamForStateEntry. No own memory write.
runLevelInit:
9025: 20 1B 92        JSR     seedFrameControlTimers; arm the per-frame control timers and plant the blaster's start pose
9028: 20 C5 92        JSR     reseedStateTables   ; rebuild the enemy state tables from the record tables

; master state-entry sweep: run the six reset/seed leaves back to back
; ($928F, $926F, $9246, $929F, $92AD, $C16E), then arm loc_124 and loc_148
; to 0xff and clear loc_123.
resetWorkingRamForStateEntry:
902B: 20 8F 92        JSR     clearActiveShots    ; wipe the active-shot bank
902E: 20 6F 92        JSR     clearShotTableAndStateFlags; clear the per-slot depth table and the enemy population counts
9031: 20 46 92        JSR     seedSlotRandomTags  ; give each active enemy slot a fresh random tag
9034: 20 9F 92        JSR     clearEightByteTableAndFlag; clear the shape-active table and its object count
9037: 20 AD 92        JSR     clearByte50         ; zero the spinner accumulator
903A: 20 6E C1        JSR     buildLevelLayout    ; rebuild the level layout
903D: A9 FF           LDA     #$FF                
903F: 8D 24 01        STA     workRam+124         ; arm the rim-color animation cursor high
9042: 8D 48 01        STA     workRam+148         ; arm the enemy-animation accumulator high
9045: A9 00           LDA     #$00                
9047: 8D 23 01        STA     workRam+123         ; clear the spiked-segment count
904A: 60              RTS                         

; feed the rim-rotation update from a fixed-stride position accumulator:
; set floor loc_202=0x10, sign-extend delta loc_121 across loc_29/2a/2b
; (asr twice), fold into the 24-bit total loc_122/68/69, step the 16-bit
; position loc_5f/loc_5b by stride 0x18 (arm loc_115 at loc_5b>=0xfc), and
; on a collapsed high difference against loc_5d rebuild the seeds, set
; mode loc_0 (0x04/0x08 by loc_5 sign) and clear loc_102[loc_3d]; marks
; loc_114=0xff and continues into rotateBlasterAroundRim.
autoAdvanceRimRotation:
904B: A9 10           LDA     #$10                
904D: 8D 02 02        STA     workRam+202         ; set the player-shot depth to the near/rim end
9050: A9 00           LDA     #$00                
9052: 85 29           STA     workRam+29          ; 
9054: 85 2B           STA     workRam+2B          ; 
9056: AD 21 01        LDA     workRam+121         ; read the level's signed tube-geometry scale delta
9059: 85 2A           STA     workRam+2A          ; 
905B: 10 02           BPL     loc_905f            ; 
905D: C6 2B           DEC     workRam+2B          ; sign-extend the delta when negative

loc_905f:
905F: A2 01           LDX     #$01                

loc_9061:
9061: A5 2A           LDA     workRam+2A          ; 
9063: 0A              ASL     A                   
9064: 66 2A           ROR     workRam+2A          ; 
9066: 66 29           ROR     workRam+29          ; 
9068: CA              DEX                         
9069: 10 F6           BPL     loc_9061            ; 
906B: A5 29           LDA     workRam+29          ; 
906D: 18              CLC                         
906E: 6D 22 01        ADC     workRam+122         ; fold the scaled delta into the running zoom/position accumulator
9071: 8D 22 01        STA     workRam+122         ; 
9074: A5 2A           LDA     workRam+2A          ; 
9076: 65 68           ADC     workRam+68          ; carry it up through the projection-offset low byte
9078: 85 68           STA     workRam+68          ; 
907A: A5 2B           LDA     workRam+2B          ; 
907C: 65 69           ADC     workRam+69          ; and the projection-offset high byte
907E: 85 69           STA     workRam+69          ; 
9080: A5 5F           LDA     workRam+5F          ; 
9082: 18              CLC                         
9083: 69 18           ADC     #$18                ; advance the tube-depth position by a fixed stride
9085: 85 5F           STA     workRam+5F          ; 
9087: A5 5B           LDA     workRam+5B          ; 
9089: 69 00           ADC     #$00                
908B: 85 5B           STA     workRam+5B          ; carry into the depth high byte
908D: C9 FC           CMP     #$FC                
908F: 90 05           BCC     loc_9096            ; 
9091: A9 01           LDA     #$01                
9093: 8D 15 01        STA     workRam+115         ; arm the descending-spike table guard once the depth reaches the far limit

loc_9096:
9096: A5 5F           LDA     workRam+5F          ; 
9098: 38              SEC                         
9099: E5 5D           SBC     workRam+5D          ; measure the remaining distance to the target depth
909B: A5 5B           LDA     workRam+5B          ; 
909D: F0 02           BEQ     loc_90a1            ; 
909F: E9 FF           SBC     #$FF                

loc_90a1:
90A1: D0 19           BNE     loc_90bc            ; skip the snap until the depth window collapses
90A3: A5 5D           LDA     workRam+5D          ; 
90A5: 85 5F           STA     workRam+5F          ; snap the position onto the target depth
90A7: A9 FF           LDA     #$FF                
90A9: 85 5B           STA     workRam+5B          ; 
90AB: A9 04           LDA     #$04                
90AD: 24 05           BIT     workRam+5           ; pick the next game mode by the play/active state bit
90AF: 30 02           BMI     loc_90b3            ; 
90B1: A9 08           LDA     #$08                

loc_90b3:
90B3: 85 00           STA     workRam             ; commit the chosen game mode
90B5: A6 3D           LDX     workRam+3D          ; 
90B7: A9 00           LDA     #$00                
90B9: 9D 02 01        STA     workRam+102,X       ; clear the active seat's cell

loc_90bc:
90BC: A9 FF           LDA     #$FF                
90BE: 8D 14 01        STA     workRam+114         ; flag the frame dirty for redraw
90C1: 4C 49 97        JMP     rotateBlasterAroundRim; place the blaster on the now-advanced rim

; choose the wave's start slot: scan threshold table 0x91fe downward for
; the highest slot at or below seed 0x126, clamp up against a floor
; derived from wave state (0x16a/0x71d/0x9), publish the floor in 0x29 and
; the start index in 0x127, then fall into reseedWaveWorkingSet.
selectWaveStartSlot:
90C4: AD 26 01        LDA     workRam+126         ; read the wave start-slot seed
90C7: A2 1C           LDX     #$1C                

loc_90c9:
90C9: CA              DEX                         
90CA: DD FE 91        CMP     rom+1FE,X           ; scan the slot-threshold table for the deepest slot at or below the seed
90CD: 90 FA           BCC     loc_90c9            ; 
90CF: A0 04           LDY     #$04                ; start the depth floor at 4
90D1: AD 6A 01        LDA     workRam+16A         ; 
90D4: 29 04           AND     #$04                ; test the difficulty switch that ratchets the start deeper
90D6: F0 12           BEQ     loc_90ea            ; 
90D8: AD 1D 07        LDA     workRam+71D         ; read the current wave number
90DB: C9 30           CMP     #$30                
90DD: 90 01           BCC     loc_90e0            ; 
90DF: C8              INY                         ; push the floor one deeper past wave 48

loc_90e0:
90E0: C9 50           CMP     #$50                
90E2: 90 01           BCC     loc_90e5            ; 
90E4: C8              INY                         ; again past wave 80

loc_90e5:
90E5: C9 70           CMP     #$70                
90E7: 90 01           BCC     loc_90ea            ; 
90E9: C8              INY                         ; again past wave 112

loc_90ea:
90EA: A5 09           LDA     workRam+9           ; 
90EC: 29 43           AND     #$43                ; test the cabinet configuration for the deep-start override
90EE: C9 40           CMP     #$40                
90F0: D0 02           BNE     loc_90f4            ; 
90F2: A0 1B           LDY     #$1B                ; force the start floor to 27

loc_90f4:
90F4: 84 29           STY     workRam+29          ; publish the depth floor
90F6: E4 29           CPX     workRam+29          ; 
90F8: B0 02           BCS     loc_90fc            ; 
90FA: A6 29           LDX     workRam+29          ; clamp the start slot up to the floor

loc_90fc:
90FC: 8E 27 01        STX     workRam+127         ; store the wave start-depth ceiling
90FF: A5 05           LDA     workRam+5           ; 
9101: 10 05           BPL     reseedWaveWorkingSet; 
9103: A9 00           LDA     #$00                
9105: 8D 26 01        STA     workRam+126         ; retire the seed so it does not carry into the next wave

; reseed the wave working set: latch 0x3d from 0x3f (running
; swapParallelTables when nonzero), seed 0x7c/0x5b/0x200/0x51/0x7b/0x605,
; and on the 0x5 sign flag prime the intro cells
; (0x605/0x111/0x00/0x1/0x9f via unpackLevelNibbleTables), write 0x4, then
; fall into tickWaveSpawnCadence.
reseedWaveWorkingSet:
9108: A6 3F           LDX     workRam+3F          ; latch the active level seat from the current level id
910A: 86 3D           STX     workRam+3D          ; 
910C: F0 03           BEQ     loc_9111            ; 
910E: 20 B2 92        JSR     swapParallelTables  ; swap in the level's parallel lane tables

loc_9111:
9111: A9 04           LDA     #$04                
9113: 85 7C           STA     workRam+7C          ; seed the spread coordinate
9115: A9 FF           LDA     #$FF                
9117: 85 5B           STA     workRam+5B          ; set the tube-depth high byte to the far end
9119: A9 00           LDA     #$00                
911B: 8D 00 02        STA     workRam+200         ; reset the player's rim segment
911E: 85 51           STA     workRam+51          ; clear the rim rotation offset
9120: 85 7B           STA     workRam+7B          ; clear the spread coordinate low byte
9122: 8D 05 06        STA     workRam+605         ; clear the pass counter
9125: A6 05           LDX     workRam+5           ; 
9127: 10 1B           BPL     loc_9144            ; branch unless this is a level intro
9129: A9 14           LDA     #$14                
912B: 8D 05 06        STA     workRam+605         ; arm the intro pass counter to 20
912E: A9 FF           LDA     #$FF                
9130: 8D 11 01        STA     workRam+111         ; raise the tube-geometry flag
9133: A9 16           LDA     #$16                
9135: 85 00           STA     workRam             ; enter the level-intro mode
9137: A9 08           LDA     #$08                
9139: 85 01           STA     workRam+1           ; set the mode-dispatch selector
913B: A9 00           LDA     #$00                
913D: 85 9F           STA     workRam+9F          ; clear the wave-progress index
913F: 20 96 C1        JSR     unpackLevelNibbleTables; unpack the level nibble tables
9142: A9 10           LDA     #$10                

loc_9144:
9144: 85 04           STA     workRam+4           ; store the mode-delay timer
9146: 20 AD 92        JSR     clearByte50         ; zero the spinner accumulator

; tick the wave spawn cadence: decrement frame counter 0x605, on underflow
; BCD-count-down the phase 0x4 and reload; when the 0x4e phase gate
; passes, release one entry (index 0x91fe, seed 0x102/0x46/0x9f, run the
; spawn chain unpackLevelNibbleTables/reseedStateTables/seedPerLaneSpikeAr
; ray/clearReadyLatchPair/clearByte50), then trim 0x4e to its low 3 bits.
tickWaveSpawnCadence:
9149: CE 05 06        DEC     workRam+605         ; tick down the frame counter and act only on underflow
914C: 10 1B           BPL     loc_9169            ; 
914E: F8              SED                         
914F: A5 04           LDA     workRam+4           ; 
9151: 38              SEC                         
9152: E9 01           SBC     #$01                ; decimal-decrement the intro/spawn phase
9154: 85 04           STA     workRam+4           ; 
9156: D8              CLD                         
9157: 10 04           BPL     loc_915d            ; 
9159: A9 10           LDA     #$10                
915B: 85 4E           STA     workRam+4E          ; on phase underflow set the spawn gate bit

loc_915d:
915D: C9 03           CMP     #$03                
915F: D0 03           BNE     loc_9164            ; 
9161: 20 FE CC        JSR     requestLevelIntroSound; at phase 3 cue the level-intro sound

loc_9164:
9164: A9 14           LDA     #$14                
9166: 8D 05 06        STA     workRam+605         ; reload the frame counter to 20

loc_9169:
9169: 20 AB B0        JSR     nudgeBlasterRimPosition; nudge the blaster's rim position
916C: A9 18           LDA     #$18                ; pick the spawn edge mask by phase
916E: A4 04           LDY     workRam+4           ; 
9170: C0 08           CPY     #$08                
9172: B0 02           BCS     loc_9176            ; 
9174: A9 78           LDA     #$78                

loc_9176:
9176: 25 4E           AND     workRam+4E          ; 
9178: F0 34           BEQ     loc_91ae            ; spawn only if a masked edge flag is set
917A: A9 00           LDA     #$00                
917C: 85 4E           STA     workRam+4E          ; clear the edge flags
917E: AD 00 02        LDA     workRam+200         ; read the player's rim segment
9181: A8              TAY                         
9182: A6 3D           LDX     workRam+3D          ; 
9184: 9D 02 01        STA     workRam+102,X       ; record the player segment against the active seat
9187: B9 FE 91        LDA     rom+1FE,Y           ; take the start slot from the threshold table by segment
918A: 24 05           BIT     workRam+5           ; 
918C: 30 09           BMI     loc_9197            ; off the play-state sign take a random slot instead
918E: A0 01           LDY     #$01                
9190: 84 48           STY     workRam+48          ; mark the slot countdown
9192: AD CA 60        LDA     pokey1+A            ; 
9195: 29 07           AND     #$07                ; pull a random 0..7 from the POKEY RNG

loc_9197:
9197: 95 46           STA     workRam+46,X        ; store the chosen slot into the per-slot level table
9199: 85 9F           STA     workRam+9F          ; and into the wave-progress index
919B: 20 96 C1        JSR     unpackLevelNibbleTables; unpack the level nibble tables
919E: 20 C5 92        JSR     reseedStateTables   ; rebuild the enemy state tables
91A1: 20 34 92        JSR     seedPerLaneSpikeArray; lay out the per-lane spikes
91A4: 20 31 A8        JSR     clearReadyLatchPair ; drop the paired ready latches
91A7: A9 02           LDA     #$02                
91A9: 85 00           STA     workRam             ; set the active-play game mode
91AB: 20 AD 92        JSR     clearByte50         ; zero the spinner accumulator

loc_91ae:
91AE: A5 4E           LDA     workRam+4E          ; 
91B0: 29 07           AND     #$07                ; keep only the low 3 edge bits for the next frame
91B2: 85 4E           STA     workRam+4E          ; 
91B4: 60              RTS                         

; seat the in-page working pointer 0x2a/0x2b: double the selector into a
; word index, clear paired flag byte 0x29, and copy the little-endian
; pointer from ROM table 0x91c6/0x91c7 at that index.
seatInPagePointer:
91B5: 0A              ASL     A                   ; double the selector into a two-byte table offset
91B6: AA              TAX                         
91B7: A9 00           LDA     #$00                
91B9: 85 29           STA     workRam+29          ; clear the flag byte paired with this pointer
91BB: BD C6 91        LDA     rom+1C6,X           ; copy the selected structure's pointer low byte from the in-page table
91BE: 85 2A           STA     workRam+2A          ; 
91C0: BD C7 91        LDA     rom+1C7,X           ; copy its pointer high byte
91C3: 85 2B           STA     workRam+2B          ; 
91C5: 60              RTS                         

; ---- $91C6-$921A: data ----
91C6: 00 00 60 00 60 01 20 03 40 05 40 07 40 09 40 11
91D6: 40 13 20 15 00 17 80 18 80 20 60 22 80 24 60 26
91E6: 00 30 00 34 20 38 50 41 90 43 20 47 10 53 10 58
91F6: 40 62 60 65 60 76 80 89 00 02 04 06 08 0A 0C 0E
9206: 10 13 15 17 19 1B 1E 20 23 27 2B 2E 30 33 37 3B
9216: 3E 40 48 50 FF

; init seeder: write fixed startup constants loc_200=0x0e, loc_51=0xf0,
; loc_106=0x00, loc_201=0x0f and loc_202=0x10 (the frame-control byte
; loc_201 and counter loc_202 later drive ageShotsAndAdvanceFrameClock).
seedFrameControlTimers:
921B: A9 0E           LDA     #$0E                
921D: 8D 00 02        STA     workRam+200         ; seat the blaster at the starting rim segment
9220: A9 F0           LDA     #$F0                
9222: 85 51           STA     workRam+51          ; set the rim fine-rotation offset
9224: A9 00           LDA     #$00                
9226: 8D 06 01        STA     workRam+106         ; disarm the moving spike
9229: A9 0F           LDA     #$0F                
922B: 8D 01 02        STA     workRam+201         ; set the player's fine rotation angle
922E: A9 10           LDA     #$10                
9230: 8D 02 02        STA     workRam+202         ; set the player-shot depth to the rim end
9233: 60              RTS                         

; init seeder: copy header byte loc_15b into loc_3ab, then fill the
; sixteen per-lane cells loc_3ac..loc_3ac+0x0f with the byte read from
; loc_15a.
seedPerLaneSpikeArray:
9234: AD 5B 01        LDA     workRam+15B         ; read the wave's initial active-slot count
9237: 8D AB 03        STA     workRam+3AB         ; seat it as the per-lane header count
923A: AD 5A 01        LDA     workRam+15A         ; read the per-lane fill constant
923D: A2 0F           LDX     #$0F                

loc_923f:
923F: 9D AC 03        STA     workRam+3AC,X       ; fill all sixteen lane cells with the constant
9242: CA              DEX                         
9243: 10 FA           BPL     loc_923f            ; 
9245: 60              RTS                         

; assign a fresh random tag to each active climber slot: zero the 64-byte
; tag table loc_243+0..0x3f, then for each slot from loc_3ab-1 down write
; a 4-bit POKEY-random $60CA&0x0f into loc_203,x and pack the slot index
; with it into loc_243,x=(x<<4)|nibble, substituting 0x0f when the packed
; tag would be zero.
seedSlotRandomTags:
9246: A9 00           LDA     #$00                
9248: A2 3F           LDX     #$3F                

loc_924a:
924A: 9D 43 02        STA     workRam+243,X       ; clear the 64-byte slot tag/record table
924D: CA              DEX                         
924E: 10 FA           BPL     loc_924a            ; 
9250: AE AB 03        LDX     workRam+3AB         ; start at the top active slot
9253: CA              DEX                         

loc_9254:
9254: AD CA 60        LDA     pokey1+A            ; take a 4-bit POKEY random for this slot
9257: 29 0F           AND     #$0F                
9259: 9D 03 02        STA     workRam+203,X       ; stash the random nibble in the slot's index cell
925C: 8A              TXA                         
925D: 0A              ASL     A                   ; pack the slot index with its random nibble
925E: 0A              ASL     A                   
925F: 0A              ASL     A                   
9260: 0A              ASL     A                   
9261: 1D 03 02        ORA     workRam+203,X       ; 
9264: D0 02           BNE     loc_9268            ; 
9266: A9 0F           LDA     #$0F                ; substitute 0x0f so a live slot never carries an all-zero tag

loc_9268:
9268: 9D 43 02        STA     workRam+243,X       ; store the packed slot tag
926B: CA              DEX                         
926C: 10 E6           BPL     loc_9254            ; 
926E: 60              RTS                         

; reset leaf: blank the seven shot-depth cells loc_2df..loc_2df+6 top-
; down, then clear seven scattered state flags loc_108, loc_109, loc_145,
; loc_142, loc_144, loc_143 and loc_146.
clearShotTableAndStateFlags:
926F: A2 06           LDX     #$06                
9271: A9 00           LDA     #$00                

loc_9273:
9273: 9D DF 02        STA     workRam+2DF,X       ; clear the seven-cell per-slot depth table
9276: CA              DEX                         
9277: 10 FA           BPL     loc_9273            ; 
9279: 8D 08 01        STA     workRam+108         ; clear the total enemy count
927C: 8D 09 01        STA     workRam+109         ; clear the enemy-type count
927F: 8D 45 01        STA     workRam+145         ; clear the five per-lane enemy counts
9282: 8D 42 01        STA     workRam+142         ; 
9285: 8D 44 01        STA     workRam+144         ; 
9288: 8D 43 01        STA     workRam+143         ; 
928B: 8D 46 01        STA     workRam+146         ; 
928E: 60              RTS                         

; reset the shot bank: zero the 12-byte depth array loc_2d3 (0x0b..0) and
; both count cells loc_135 and loc_a6 to baseline.
clearActiveShots:
928F: A9 00           LDA     #$00                
9291: A2 0B           LDX     #$0B                

loc_9293:
9293: 9D D3 02        STA     workRam+2D3,X       ; clear the 12-entry per-slot shot-state array
9296: CA              DEX                         
9297: 10 FA           BPL     loc_9293            ; 
9299: 8D 35 01        STA     workRam+135         ; clear the active object count
929C: 85 A6           STA     workRam+A6          ; clear the active enemy count
929E: 60              RTS                         

; reset leaf: blank the eight-byte table loc_30a..loc_30a+7 top-down, then
; clear the trailing flag byte loc_116.
clearEightByteTableAndFlag:
929F: A2 07           LDX     #$07                
92A1: A9 00           LDA     #$00                

loc_92a3:
92A3: 9D 0A 03        STA     workRam+30A,X       ; clear the eight-cell shape-active table
92A6: CA              DEX                         
92A7: 10 FA           BPL     loc_92a3            ; 
92A9: 8D 16 01        STA     workRam+116         ; clear the timed-object count
92AC: 60              RTS                         

; minimal reset leaf: clear the single state cell loc_50 (a cell that
; ranges 0x00..0xde in play) to zero and return.
clearByte50:
92AD: A9 00           LDA     #$00                
92AF: 85 50           STA     workRam+50          ; zero the spinner accumulator
92B1: 60              RTS                         

; swap the two parallel 18-entry tables loc_3aa and loc_3bc slot-for-slot
; (index 0x11 down to 0), so each table ends holding what its sibling
; held.
swapParallelTables:
92B2: A2 11           LDX     #$11                

loc_92b4:
92B4: BD AA 03        LDA     workRam+3AA,X       ; swap the two parallel 18-entry lane tables slot for slot
92B7: BC BC 03        LDY     workRam+3BC,X       ; 
92BA: 9D BC 03        STA     workRam+3BC,X       ; 
92BD: 98              TYA                         
92BE: 9D AA 03        STA     workRam+3AA,X       ; 
92C1: CA              DEX                         
92C2: 10 F0           BPL     loc_92b4            ; 
92C4: 60              RTS                         

; re-seed game state: build search key loc_2b from loc_9f (or a POKEY-
; random masked value via $60DA when >=98, then +1), walk the 4-byte
; record table at $9604 from record 111 down to 3 loading source ptr
; loc_2c/loc_2d and dest ptr loc_3b/loc_3c per record, scan each source
; list (via dispatchRangeValueBySelector/dispatchCursorAdvanceBySelector)
; for the range bracketing the key and store the resolved byte through the
; dest ptr, rescale loc_160/loc_15b per loc_16a&3, fold
; loc_163/loc_120/loc_160 through partitionByteToFineCoarseSeed, then seed
; many loc_01xx cells plus loc_155/loc_161/loc_166/loc_149/loc_14a.
reseedStateTables:
92C5: A5 9F           LDA     workRam+9F          ; read the wave-progress index as the difficulty key
92C7: C9 62           CMP     #$62                
92C9: 90 07           BCC     loc_92d2            ; branch unless the key runs off the top of the tables
92CB: AD DA 60        LDA     pokey2+A            ; substitute a POKEY random key when off the top
92CE: 29 1F           AND     #$1F                
92D0: 09 40           ORA     #$40                

loc_92d2:
92D2: 85 2B           STA     workRam+2B          ; store the difficulty search key
92D4: E6 2B           INC     workRam+2B          ; bump it by one
92D6: A2 6F           LDX     #$6F                
92D8: 86 37           STX     workRam+37          ; start the record walk at index 111

loc_92da:
92DA: A6 37           LDX     workRam+37          ; 
92DC: BD 07 96        LDA     rom+607,X           ; read the record's destination pointer high byte
92DF: 85 3C           STA     workRam+3C          ; 
92E1: BD 06 96        LDA     rom+606,X           ; and its low byte
92E4: 85 3B           STA     workRam+3B          ; 
92E6: BD 05 96        LDA     rom+605,X           ; read the source-list pointer high byte
92E9: 85 2D           STA     workRam+2D          ; 
92EB: BD 04 96        LDA     rom+604,X           ; and its low byte
92EE: 85 2C           STA     workRam+2C          ; 
92F0: A9 01           LDA     #$01                
92F2: 85 38           STA     workRam+38          ; reset the per-record scan cursor
92F4: A0 00           LDY     #$00                

loc_92f6:
92F6: B1 2C           LDA     (workRam+2C),Y      ; read the next source-list entry
92F8: 8D 5E 01        STA     workRam+15E         ; 
92FB: F0 1C           BEQ     loc_9319            ; end the list on a zero entry
92FD: A5 2B           LDA     workRam+2B          ; 
92FF: C8              INY                         
9300: D1 2C           CMP     (workRam+2C),Y      ; compare the key against the range low bound
9302: C8              INY                         
9303: 90 0E           BCC     loc_9313            ; 
9305: D1 2C           CMP     (workRam+2C),Y      ; compare against the range high bound
9307: D0 01           BNE     loc_930a            ; 
9309: 18              CLC                         

loc_930a:
930A: B0 07           BCS     loc_9313            ; skip this range if the key is outside it
930C: C8              INY                         
930D: 20 77 96        JSR     dispatchRangeValueBySelector; resolve the value for this destination cell
9310: 4C 19 93        JMP     loc_9319            ; 

loc_9313:
9313: 20 83 96        JSR     dispatchCursorAdvanceBySelector; advance the cursor to the next range
9316: 18              CLC                         
9317: 90 DD           BCC     loc_92f6            ; 

loc_9319:
9319: A0 00           LDY     #$00                
931B: 91 3B           STA     (workRam+3B),Y      ; store the resolved byte through the destination pointer
931D: A5 37           LDA     workRam+37          ; 
931F: 38              SEC                         
9320: E9 04           SBC     #$04                ; step to the next record, four bytes back
9322: 85 37           STA     workRam+37          ; 
9324: C9 FF           CMP     #$FF                
9326: D0 B2           BNE     loc_92da            ; loop until the record table is exhausted
9328: AD 6A 01        LDA     workRam+16A         ; 
932B: 29 03           AND     #$03                ; select the enemy-speed rescale by difficulty mode
932D: C9 01           CMP     #$01                
932F: D0 1C           BNE     loc_934d            ; 
9331: CE 1A 01        DEC     workRam+11A         ; easy mode: lower the free-flight slot ceiling
9334: AD 60 01        LDA     workRam+160         ; 
9337: 49 FF           EOR     #$FF                
9339: 4A              LSR     A                   
933A: 4A              LSR     A                   
933B: 4A              LSR     A                   
933C: 6D 60 01        ADC     workRam+160         ; 
933F: 8D 60 01        STA     workRam+160         ; ramp the segment-0 climb speed down
9342: A5 9F           LDA     workRam+9F          ; 
9344: C9 11           CMP     #$11                
9346: B0 02           BCS     loc_934a            ; 
9348: C6 B3           DEC     workRam+B3          ; ease the early waves further

loc_934a:
934A: B8              CLV                         
934B: 50 35           BVC     loc_9382            ; 

loc_934d:
934D: C9 02           CMP     #$02                ; hard mode?
934F: D0 31           BNE     loc_9382            ; 
9351: EE 1A 01        INC     workRam+11A         ; raise the free-flight slot ceiling
9354: AD 1A 01        LDA     workRam+11A         ; 
9357: C9 03           CMP     #$03                
9359: 90 05           BCC     loc_9360            ; 
935B: A9 03           LDA     #$03                
935D: 8D 1A 01        STA     workRam+11A         ; clamp the ceiling to 3

loc_9360:
9360: AD 60 01        LDA     workRam+160         ; 
9363: 4A              LSR     A                   
9364: 4A              LSR     A                   
9365: 4A              LSR     A                   
9366: 09 E0           ORA     #$E0                
9368: 6D 60 01        ADC     workRam+160         ; 
936B: 8D 60 01        STA     workRam+160         ; ramp the segment-0 climb speed up
936E: AD 5B 01        LDA     workRam+15B         ; 
9371: 4A              LSR     A                   
9372: 4A              LSR     A                   
9373: 4A              LSR     A                   
9374: 6D 5B 01        ADC     workRam+15B         ; 
9377: 8D 5B 01        STA     workRam+15B         ; raise the wave's initial active count
937A: AD 6D 01        LDA     workRam+16D         ; 
937D: 09 40           ORA     #$40                
937F: 8D 6D 01        STA     workRam+16D         ; select the hard-mode alternate list set

loc_9382:
9382: AD 63 01        LDA     workRam+163         ; 
9385: 20 E0 93        JSR     partitionByteToFineCoarseSeed; fold the segment-3 climb delta into fine and coarse seeds
9388: 8D 63 01        STA     workRam+163         ; store the segment-3 climb-delta low byte
938B: 8C 68 01        STY     workRam+168         ; store the segment-3 climb-delta high byte
938E: 8E 54 01        STX     workRam+154         ; store the segment-3 band threshold
9391: AD 20 01        LDA     workRam+120         ; 
9394: 20 E0 93        JSR     partitionByteToFineCoarseSeed; fold the object velocity into fine and coarse seeds
9397: 8D 20 01        STA     workRam+120         ; store the object-velocity low byte
939A: 8C 18 01        STY     workRam+118         ; store the object-velocity high byte
939D: 86 A7           STX     workRam+A7          ; store the hit-distance threshold
939F: AD 60 01        LDA     workRam+160         ; 
93A2: 20 E0 93        JSR     partitionByteToFineCoarseSeed; fold the segment-0 climb delta into fine and coarse seeds
93A5: 8D 60 01        STA     workRam+160         ; store the segment-0 climb-delta low byte
93A8: 8D 62 01        STA     workRam+162         ; mirror it into the segment-2 climb delta
93AB: 8C 67 01        STY     workRam+167         ; store the segment-2 climb-delta high byte
93AE: 8C 65 01        STY     workRam+165         ; store the segment-0 climb-delta high byte
93B1: 8E 51 01        STX     workRam+151         ; store band threshold 0
93B4: 8E 53 01        STX     workRam+153         ; store band threshold 2
93B7: 8E 52 01        STX     workRam+152         ; store band threshold 1
93BA: AD 60 01        LDA     workRam+160         ; 
93BD: 0A              ASL     A                   
93BE: 8D 64 01        STA     workRam+164         ; derive the segment-4 climb delta by doubling segment-0
93C1: AD 65 01        LDA     workRam+165         ; 
93C4: 2A              ROL     A                   
93C5: 8D 69 01        STA     workRam+169         ; and its high byte with the carry
93C8: A9 06           LDA     #$06                
93CA: 8D 55 01        STA     workRam+155         ; hard-seed band threshold 4
93CD: A9 A0           LDA     #$A0                
93CF: 8D 61 01        STA     workRam+161         ; hard-seed the segment-1 climb-delta low byte
93D2: A9 FE           LDA     #$FE                
93D4: 8D 66 01        STA     workRam+166         ; and its high byte
93D7: A9 01           LDA     #$01                
93D9: 8D 4A 01        STA     workRam+14A         ; seed the first candidate lane
93DC: 8D 49 01        STA     workRam+149         ; seed the second candidate lane
93DF: 60              RTS                         

; split one input byte into three derived; used by reseedStateTables to
; fan loc_163/loc_120/loc_160 out into their satellite cells.
partitionByteToFineCoarseSeed:
93E0: A0 FF           LDY     #$FF                ; seed the bit-fold accumulator with 0xff
93E2: 84 29           STY     workRam+29          ; hold the running seed in scratch
93E4: 0A              ASL     A                   ; shift the source byte's top bit out
93E5: 26 29           ROL     workRam+29          ; fold that top bit into the seed's low end
93E7: 0A              ASL     A                   ; shift the next source bit out
93E8: 26 29           ROL     workRam+29          ; fold it into the seed
93EA: 0A              ASL     A                   ; shift the third source bit out
93EB: 26 29           ROL     workRam+29          ; fold it into the seed
93ED: A4 29           LDY     workRam+29          ; take the finished fine seed into Y
93EF: 48              PHA                         
93F0: 98              TYA                         
93F1: 49 FF           EOR     #$FF                ; complement the seed
93F3: 18              CLC                         
93F4: 69 0D           ADC     #$0D                ; bias the complement by 0x0d
93F6: 4A              LSR     A                   ; halve it to form the coarse index
93F7: AA              TAX                         
93F8: 68              PLA                         
93F9: 60              RTS                         

; ---- $93FA-$9676: data ----
93FA: 08 01 14 50 FD 02 15 40 14 02 41 63 0A 04 01 09
940A: 01 01 01 02 03 02 02 03 03 02 0A 40 02 02 41 63
941A: 03 08 01 08 D4 FB 04 09 10 AF AC AC AC A8 A4 A0
942A: A0 08 11 19 AF FD 08 1A 20 9D FD 08 21 27 94 FD
943A: 08 28 30 92 FF 08 31 40 88 FF 0C 41 63 60 41 0A
944A: 01 63 C0 0A 01 14 00 0A 15 20 D0 0A 21 30 D8 0A
945A: 31 63 D0 02 01 20 A0 02 21 40 A0 02 41 63 C0 02
946A: 01 30 04 02 31 40 06 02 41 63 08 02 01 20 01 02
947A: 21 28 03 02 29 63 02 02 01 30 01 02 31 63 03 04
948A: 01 04 00 00 00 01 02 05 10 02 02 11 13 00 02 14
949A: 20 01 02 23 27 01 02 2C 63 01 00 04 01 06 00 00
94AA: 00 02 03 04 02 07 0A 04 02 0B 10 03 02 14 19 02
94BA: 04 1A 20 01 02 02 02 01 01 02 02 35 27 01 02 2B
94CA: 63 01 00 02 01 04 01 02 05 63 00 00 02 01 04 04
94DA: 02 05 10 05 02 11 13 03 02 14 19 04 02 1A 63 05
94EA: 00 04 01 04 00 00 01 00 02 05 10 01 02 11 20 01
94FA: 02 21 27 01 02 28 63 01 00 04 01 05 00 00 01 00
950A: 01 02 06 10 02 02 11 1A 01 02 1B 20 01 02 21 2C
951A: 02 02 2D 63 03 00 02 11 20 02 02 21 63 01 00 04
952A: 11 20 05 03 02 02 02 02 02 02 02 02 02 02 02 03
953A: 04 02 02 21 63 03 00 02 0B 10 01 02 16 19 01 02
954A: 1B 63 01 00 02 0B 10 01 02 16 19 01 02 1B 20 01
955A: 02 21 27 04 02 28 63 03 00 04 11 12 28 14 0C 13
956A: 20 14 28 08 21 27 14 FF 0C 28 63 14 0A 00 0C 11
957A: 20 00 40 0C 21 30 40 C0 02 31 63 C0 00 02 01 10
958A: DC 02 11 27 C0 08 28 40 C0 01 02 41 63 E6 02 01
959A: 63 06 06 01 63 00 00 00 E0 D8 D4 D0 C8 C0 B8 B0
95AA: A8 A0 A0 A0 A8 A0 9C 9A 98 04 01 10 0A 0C 0F 11
95BA: 14 16 14 18 1B 1D 1B 18 1A 1C 1E 1B 08 11 1A 14
95CA: 01 02 1B 27 1B 08 28 30 1D 01 08 31 40 1F 01 08
95DA: 41 50 23 01 08 51 63 2B 01 02 01 14 02 02 15 20
95EA: 02 02 21 63 03 02 3C 63 40 00 06 01 63 07 0B 19
95FA: 24 53 0B 24 19 53 87 24 19 53 07 87 24 EF 95 6D
960A: 01 E3 95 B3 00 FA 93 19 01 07 94 1A 01 CD 94 29
961A: 01 D6 94 2E 01 20 95 2A 01 29 95 2F 01 EB 94 2B
962A: 01 03 95 30 01 89 94 2C 01 A5 94 31 01 41 95 2D
963A: 01 4E 95 32 01 5D 94 57 01 69 94 47 01 75 94 4B
964A: 01 81 94 4C 01 98 95 1C 01 B3 95 5B 01 9C 95 5A
965A: 01 63 95 B2 00 F4 95 5D 01 4D 94 63 01 49 94 20
966A: 01 1B 94 60 01 78 95 59 01 87 95 5F 01

; resolve the range-bracketed list value: read the even selector byte
; 0x15e, halve it, and tail-call the matching 0x96xx coordinate helper
; ($96C4/96b7/96ab/96e2/96db/9700), returning its byte to $92C5's table
; walk.
dispatchRangeValueBySelector:
9677: AE 5E 01        LDX     workRam+15E         ; load the coordinate-value helper selector
967A: BD 90 96        LDA     rom+690,X           ; read the chosen helper's return-address high byte
967D: 48              PHA                         
967E: BD 8F 96        LDA     rom+68F,X           ; read its low byte
9681: 48              PHA                         
9682: 60              RTS                         ; computed jump into the selected coordinate-value helper

; advance the source-list cursor: use selector 0x15e to pick a cursor-
; advance helper (advanceListCursorByTwo/96cb/96c7) and tail-call it, pre-
; seating A to the selected pointer's low byte for the Y-only handlers.
dispatchCursorAdvanceBySelector:
9683: AE 5E 01        LDX     workRam+15E         ; load the cursor-advance helper selector
9686: BD 9E 96        LDA     rom+69E,X           ; read the chosen advance helper's high byte
9689: 48              PHA                         
968A: BD 9D 96        LDA     rom+69D,X           ; read its low byte
968D: 48              PHA                         
968E: 60              RTS                         ; computed jump into the selected cursor-advance helper

; ---- $968F-$96AA: data ----
968F: 00 00 C3 96 B6 96 AA 96 E1 96 DA 96 FF 96 00 00
969F: C7 96 CA 96 CA 96 C6 96 C7 96 C6 96

; fetch a coordinate-list entry by re-indexing: stash Y at 0x29, form the
; index value ((0x2b-1)&0x0f)+1, subtract the list byte two entries back
; (pointer+(Y-2)), add back the saved Y, re-index the pointer by the
; result, and load and return that entry.
fetchCoordListEntryByCounter:
96AB: A5 2B           LDA     workRam+2B          ; read the list counter
96AD: 38              SEC                         
96AE: E9 01           SBC     #$01                ; counter minus one
96B0: 29 0F           AND     #$0F                ; wrap into the low nibble
96B2: 18              CLC                         
96B3: 69 01           ADC     #$01                ; plus one -- the counter-derived seed
96B5: 10 02           BPL     loc_96b9            ; join the shared re-index

; sibling of 0x96ab that uses the raw index 0x2b (not the counter-wrapped
; value): stash Y at 0x29, subtract the list byte two entries back, add
; back saved Y, re-index the pointer, and load and return that entry.
fetchCoordListEntryByIndex:
96B7: A5 2B           LDA     workRam+2B          ; index variant -- seed from the raw counter instead

loc_96b9:
96B9: 84 29           STY     workRam+29          ; stash the incoming cursor
96BB: 88              DEY                         ; step back one entry
96BC: 88              DEY                         ; step back a second entry
96BD: 38              SEC                         
96BE: F1 2C           SBC     (workRam+2C),Y      ; subtract the stride byte two entries back
96C0: 18              CLC                         
96C1: 65 29           ADC     workRam+29          ; add the saved cursor back in
96C3: A8              TAY                         

; the bare coordinate-list read: return the byte at pointer 0x2c offset by
; Y, with no index arithmetic.
readCoordListEntry:
96C4: B1 2C           LDA     (workRam+2C),Y      ; load the coordinate-list entry the offset points at
96C6: 60              RTS                         

; cursor-skip helper: advance the list cursor Y by a fixed run to step
; over a packed record without reading it -- the first entry moves Y
; forward by three (bumps by one then falls into the second entry), the
; second entry by two; register only, writes no memory
advanceCursorPastPackedRecord:
96C7: C8              INY                         ; first entry -- advance the cursor by one

; step the list cursor forward past a two-byte record without reading it:
; return Y+2 (register only).
advanceListCursorByTwo:
96C8: C8              INY                         ; advance the cursor two past a packed record
96C9: C8              INY                         
96CA: 60              RTS                         

; walk the packed coordinate list through 0x2c/0x2d: read the entry at Y
; and its predecessor at Y-1, store their difference (cur-prev) at 0x29 as
; the step delta, and advance Y by that delta plus two.
advanceCoordListByEntryStride:
96CB: B1 2C           LDA     (workRam+2C),Y      ; read the coordinate entry under the cursor
96CD: 88              DEY                         ; back up to the predecessor entry
96CE: 38              SEC                         
96CF: F1 2C           SBC     (workRam+2C),Y      ; subtract it -- the inter-entry stride
96D1: 85 29           STA     workRam+29          ; publish the stride delta in scratch
96D3: 98              TYA                         
96D4: 38              SEC                         
96D5: 65 29           ADC     workRam+29          ; advance the cursor forward by the stride
96D7: A8              TAY                         
96D8: C8              INY                         ; leave the cursor one past
96D9: C8              INY                         ; leave it two past, ready for the next entry pair
96DA: 60              RTS                         

; resolve one list entry to an absolute coordinate: read the byte at
; pointer 0x2c offset by Y and add the base value in 0x160, returning the
; 8-bit sum in A.
resolveCoordListEntryToAbsolute:
96DB: B1 2C           LDA     (workRam+2C),Y      ; read the relative list entry
96DD: 18              CLC                         
96DE: 6D 60 01        ADC     workRam+160         ; fold it onto the anchor base for the absolute coordinate
96E1: 60              RTS                         

; fold a run of coordinate-list entries: use 0x96f4 as a repeat count,
; load the first entry at pointer 0x2c offset Y, then add that many
; further consecutive entries into a single wrapped one-byte total.
sumCoordListEntryRun:
96E2: 20 F4 96        JSR     computeCoordListBackDelta; get the repeat count for this run

; ---- $96E5-$96E5: data ----
96E5: AA
96E6: B1 2C           LDA     (workRam+2C),Y      ; seed the total with the first entry
96E8: C8              INY                         ; step to the next entry
96E9: E0 00           CPX     #$00                ; test the count
96EB: F0 06           BEQ     loc_96f3            ; nothing to add when the count is zero

loc_96ed:
96ED: 18              CLC                         ; add the next entry into the running total
96EE: 71 2C           ADC     (workRam+2C),Y      ; 
96F0: CA              DEX                         ; drop the count
96F1: D0 FA           BNE     loc_96ed            ; repeat until the run is summed

loc_96f3:
96F3: 60              RTS                         

; compute the coordinate-list index delta: record Y at 0x29 and return
; (0x2b minus the list byte two slots back at pointer+(Y-2)) & 0xff; a
; leaf whose result is consumed downstream.
computeCoordListBackDelta:
96F4: A5 2B           LDA     workRam+2B          ; read the scratch base value
96F6: 84 29           STY     workRam+29          ; record the cursor so the caller can recover it
96F8: 88              DEY                         ; step back one slot
96F9: 88              DEY                         ; step back a second slot
96FA: 38              SEC                         
96FB: F1 2C           SBC     (workRam+2C),Y      ; base minus the list byte two slots back -- the delta
96FD: C8              INY                         
96FE: C8              INY                         
96FF: 60              RTS                         

; ---- $9700-$9704: data ----
9700: 20 F4 96 29 01
9705: F0 01           BEQ     loc_9708            ; even delta -- fetch the entry as-is
9707: C8              INY                         ; odd delta -- step one slot forward

loc_9708:
9708: B1 2C           LDA     (workRam+2C),Y      ; load the entry the cursor now points at
970A: 60              RTS                         

; run the nine per-frame update passes in fixed order ($9749, $A23F,
; $A83A, $98A2, $9B1E, $A18F, $A2A6, $A454, $A416) then tail-delegate to
; $A504; makes no own role-defining write.
runPerFrameUpdates:
970B: 20 49 97        JSR     rotateBlasterAroundRim; rotate the player's blaster around the rim
970E: 20 3F A2        JSR     spawnEntityIntoFreeSlot; seat a newly spawned enemy into a free slot
9711: 20 3A A8        JSR     stepAttractEnemySweepTimer; advance the attract-mode enemy sweep timer
9714: 20 A2 98        JSR     tickSpawnSlotTimers ; count down the per-slot spawn timers
9717: 20 1E 9B        JSR     runObjectMotionScripts; run every occupied slot's motion script
971A: 20 8F A1        JSR     stepActiveShots     ; advance the active shots
971D: 20 A6 A2        JSR     spawnClimbersFromSourceSlots; spawn climbers up the lanes from their source slots
9720: 20 54 A4        JSR     scanAllSlotsForProximity; proximity and collision scan across all slots
9723: 20 16 A4        JSR     ageTimedObjects     ; age the transient timed objects
9726: 4C 04 A5        JMP     ageShotsAndAdvanceFrameClock; tail into aging shots and ticking the frame clock

; run the per-frame state updaters: clear bit7 of 0x123, run $9749, $97F8,
; $A416, $A23F, $A18F in order, then run the extra updater $A504 only when
; 0x201 is negative.
runFrameStateUpdaters:
9729: AD 23 01        LDA     workRam+123         ; read the spiked-segment tally cell
972C: 29 7F           AND     #$7F                ; clear its per-frame high bit, keep the tally
972E: 8D 23 01        STA     workRam+123         ; write back the clean count
9731: 20 49 97        JSR     rotateBlasterAroundRim; turn the spinner/aim into the player's new rim angle
9734: 20 F8 97        JSR     advanceMovingSpike  ; step the moving spike
9737: 20 16 A4        JSR     ageTimedObjects     ; age the timed objects
973A: 20 3F A2        JSR     spawnEntityIntoFreeSlot; seat a new enemy into a free slot
973D: 20 8F A1        JSR     stepActiveShots     ; advance the live shots
9740: AD 01 02        LDA     workRam+201         ; read the fine-angle rotation-pending flag
9743: 10 03           BPL     loc_9748            ; skip the extra pass unless its high bit is set
9745: 20 04 A5        JSR     ageShotsAndAdvanceFrameClock; extra pass -- age shots and tick the frame clock

loc_9748:
9748: 60              RTS                         

; advance the rim rotation/spinner state: skip while fine-angle flag
; loc_201 bit7 set; take the delta from manual reading loc_50 (clamped to
; band [0xe1,0x1f], then consumed) when loc_5 bit7 is set else the auto-
; aim from aimSpinnerAtNearestEnemy; fold it into work cells
; loc_2b/loc_2c, and on a live level (loc_111!=0) cap loc_2c to 0xef and
; saturate toward the stored sign on a sign flip; the high nibble becomes
; coarse angle loc_2a, ring sound cueRimRotationSound on a changed coarse
; angle, then commit loc_200/loc_201/loc_51.
rotateBlasterAroundRim:
9749: AD 01 02        LDA     workRam+201         ; read the fine-angle rotation-pending flag
974C: 10 01           BPL     loc_974f            ; proceed only when no rotation is pending
974E: 60              RTS                         ; pending or locked -- do nothing this frame

loc_974f:
974F: A2 00           LDX     #$00                
9751: A5 05           LDA     workRam+5           ; read the status flags
9753: 30 06           BMI     loc_975b            ; manual-play bit set -- take the raw spinner delta
9755: 20 C5 97        JSR     aimSpinnerAtNearestEnemy; aim mode -- auto-aim delta toward the nearest enemy
9758: B8              CLV                         
9759: 50 15           BVC     loc_9770            ; join the offset computation

loc_975b:
975B: A5 50           LDA     workRam+50          ; manual play -- read the raw spinner delta
975D: 10 09           BPL     loc_9768            ; positive delta -- go cap it
975F: C9 E1           CMP     #$E1                ; negative delta below the floor
9761: B0 02           BCS     loc_9765            ; 
9763: A9 E1           LDA     #$E1                ; floor the delta at 0xe1

loc_9765:
9765: B8              CLV                         
9766: 50 06           BVC     loc_976e            ; 

loc_9768:
9768: C9 1F           CMP     #$1F                ; positive delta at or below the cap
976A: 90 02           BCC     loc_976e            ; 
976C: A9 1F           LDA     #$1F                ; cap the delta at 0x1f

loc_976e:
976E: 86 50           STX     workRam+50          ; consume the spinner reading -- zero it

loc_9770:
9770: 85 2B           STA     workRam+2B          ; stash the chosen turn delta
9772: 49 FF           EOR     #$FF                
9774: 38              SEC                         
9775: 65 51           ADC     workRam+51          ; form rim-offset minus the delta
9777: 85 2C           STA     workRam+2C          ; provisional new rim offset
9779: AE 11 01        LDX     workRam+111         ; read the live-board tube-geometry flag
977C: F0 1F           BEQ     loc_979d            ; skip the end-of-rim clamps when no board is live
977E: C9 F0           CMP     #$F0                ; offset short of the top limit
9780: 90 04           BCC     loc_9786            ; 
9782: A9 EF           LDA     #$EF                ; cap the offset to the top lane
9784: 85 2C           STA     workRam+2C          ; 

loc_9786:
9786: 45 2B           EOR     workRam+2B          ; compare the offset's sign against the delta
9788: 10 13           BPL     loc_979d            ; same sign -- no over-rotation, continue
978A: A5 2C           LDA     workRam+2C          ; compare the offset's sign against the old offset
978C: 45 51           EOR     workRam+51          ; 
978E: 10 0D           BPL     loc_979d            ; same sign -- continue
9790: A5 51           LDA     workRam+51          ; pick the saturation end by the old offset's sign
9792: 30 05           BMI     loc_9799            ; 
9794: A9 00           LDA     #$00                ; saturate to the bottom end of the rim
9796: B8              CLV                         
9797: 50 02           BVC     loc_979b            ; 

loc_9799:
9799: A9 EF           LDA     #$EF                ; saturate to the top end of the rim

loc_979b:
979B: 85 2C           STA     workRam+2C          ; 

loc_979d:
979D: A5 2C           LDA     workRam+2C          ; take the offset's high nibble
979F: 4A              LSR     A                   
97A0: 4A              LSR     A                   
97A1: 4A              LSR     A                   
97A2: 4A              LSR     A                   
97A3: 85 2A           STA     workRam+2A          ; store it as the coarse lane
97A5: 18              CLC                         
97A6: 69 01           ADC     #$01                ; pair the fine angle as high-nibble plus one
97A8: 29 0F           AND     #$0F                
97AA: 85 2B           STA     workRam+2B          ; store the fine angle
97AC: A5 2A           LDA     workRam+2A          ; compare the new coarse lane against the current segment
97AE: CD 00 02        CMP     workRam+200         ; 
97B1: F0 03           BEQ     loc_97b6            ; unchanged -- skip the movement sound
97B3: 20 B5 CC        JSR     cueRimRotationSound ; crossed into a new lane -- ring the rim-movement sound

loc_97b6:
97B6: A5 2A           LDA     workRam+2A          ; 
97B8: 8D 00 02        STA     workRam+200         ; commit the coarse lane as the player's segment
97BB: A5 2B           LDA     workRam+2B          ; 
97BD: 8D 01 02        STA     workRam+201         ; commit the fine angle
97C0: A5 2C           LDA     workRam+2C          ; 
97C2: 85 51           STA     workRam+51          ; carry the rim offset to the next frame
97C4: 60              RTS                         

; scan the depth table loc_2df over count loc_11c for the smallest nonzero
; entry (value in loc_29, index in loc_2a); with a candidate take the
; signed segment delta of loc_2b9[idx] against player segment loc_200
; (signedSegmentDelta) and return an auto-aim spinner code -- 0x00
; aligned, 0x09 one side, 0xf7 the other -- consumed by
; rotateBlasterAroundRim.
aimSpinnerAtNearestEnemy:
97C5: A9 FF           LDA     #$FF                ; seed the shallowest-depth-seen with 0xff
97C7: 85 29           STA     workRam+29          ; 
97C9: 85 2A           STA     workRam+2A          ; seed its slot index with none
97CB: AE 1C 01        LDX     workRam+11C         ; start at the top enemy slot

loc_97ce:
97CE: BD DF 02        LDA     workRam+2DF,X       ; read the slot's depth
97D1: F0 08           BEQ     loc_97db            ; empty slot -- skip it
97D3: C5 29           CMP     workRam+29          ; 
97D5: B0 04           BCS     loc_97db            ; not shallower than the best -- skip it
97D7: 85 29           STA     workRam+29          ; record the new shallowest depth
97D9: 86 2A           STX     workRam+2A          ; record its slot index

loc_97db:
97DB: CA              DEX                         ; step down to the next slot
97DC: 10 F0           BPL     loc_97ce            ; keep walking while the index stays non-negative
97DE: A6 2A           LDX     workRam+2A          ; no candidate found -- return the last depth read
97E0: 30 15           BMI     loc_97f7            ; 
97E2: BD B9 02        LDA     workRam+2B9,X       ; read the nearest enemy's segment
97E5: AC 00 02        LDY     workRam+200         ; read the player's segment
97E8: 20 A6 A7        JSR     signedSegmentDelta  ; signed segment delta between them
97EB: A8              TAY                         
97EC: F0 09           BEQ     loc_97f7            ; already aligned -- turn code zero
97EE: 30 05           BMI     loc_97f5            ; enemy on one side versus the other
97F0: A9 F7           LDA     #$F7                ; turn code for one direction
97F2: B8              CLV                         
97F3: 50 02           BVC     loc_97f7            ; 

loc_97f5:
97F5: A9 09           LDA     #$09                ; turn code for the other direction

loc_97f7:
97F7: 60              RTS                         

; step the moving spike each frame while loc_201 bit7 is clear and arm
; flag loc_106 bit7 is set: cue a start sound at trigger height
; loc_202==0x10, advance 16-bit height loc_107/loc_202 by loc_104/loc_105
; (park loc_202=0xff, request mode loc_0=0x0e, cue end sound on ceiling
; overflow), rebuild the spike table (rebuildSpikeTable) past 0x50,
; rederive the per-frame delta from loc_9f, then scan loc_3ac for the
; player-segment lane loc_200 and register a collision
; (cueSpikeCollisionSound/insertObjectHeadTag7/clearActiveShots, clear
; loc_115).
advanceMovingSpike:
97F8: AD 01 02        LDA     workRam+201         ; read the field-transition gate -- spike stepping is frozen while the player field is mid-transition
97FB: 10 01           BPL     loc_97fe            ; run the spike only while the field is settled
97FD: 60              RTS                         

loc_97fe:
97FE: AD 06 01        LDA     workRam+106         ; read the spike arm flag
9801: 30 01           BMI     loc_9804            ; step the spike only while it is armed
9803: 60              RTS                         

loc_9804:
9804: AD 02 02        LDA     workRam+202         ; at the trigger height 0x10...
9807: C9 10           CMP     #$10                
9809: D0 03           BNE     loc_980e            ; 
980B: 20 EE CC        JSR     cueMovingSpikeStartSound; cue the spike's rising start note, exactly once at trigger height

loc_980e:
980E: AD 07 01        LDA     workRam+107         ; advance the spike height low byte by the per-frame growth step
9811: 18              CLC                         
9812: 6D 04 01        ADC     workRam+104         ; add the step low byte
9815: 8D 07 01        STA     workRam+107         ; store the spike height low byte
9818: AD 02 02        LDA     workRam+202         ; carry into the spike height high byte
981B: 6D 05 01        ADC     workRam+105         ; add the step high byte
981E: 8D 02 02        STA     workRam+202         ; store the spike height high byte
9821: B0 02           BCS     loc_9825            ; spike overflowed past the top -> retire it
9823: C9 F0           CMP     #$F0                ; still below the 0xf0 ceiling -> keep growing

loc_9825:
9825: 90 0C           BCC     loc_9833            ; 
9827: A9 0E           LDA     #$0E                ; request game mode 0x0e -- the spike has reached the top
9829: 85 00           STA     workRam             ; 
982B: 20 F2 CC        JSR     cueMovingSpikeEndSound; cue the spike's end sound
982E: A9 FF           LDA     #$FF                ; pin the height at max so it stops climbing
9830: 8D 02 02        STA     workRam+202         ; 

loc_9833:
9833: AD 02 02        LDA     workRam+202         ; once the spike passes the 0x50 reset height...
9836: C9 50           CMP     #$50                ; compare against 0x50
9838: 90 08           BCC     loc_9842            ; below it -> skip the table rebuild
983A: AD 15 01        LDA     workRam+115         ; ...and the spike-table guard is clear...
983D: D0 03           BNE     loc_9842            ; guard set -> skip
983F: 20 BD A7        JSR     rebuildSpikeTable   ; rebuild the on-screen spike table

loc_9842:
9842: A5 5C           LDA     workRam+5C          ; step the depth-shading accumulator low byte by the same growth step
9844: 18              CLC                         
9845: 6D 04 01        ADC     workRam+104         ; add the step low byte
9848: 85 5C           STA     workRam+5C          ; store the accumulator low byte
984A: A5 5F           LDA     workRam+5F          ; carry into the accumulator high byte
984C: 6D 05 01        ADC     workRam+105         ; add the step high byte
984F: 90 02           BCC     loc_9853            ; no overflow -> skip the page byte
9851: E6 5B           INC     workRam+5B          ; page the depth accumulator's extra byte on overflow

loc_9853:
9853: C5 5F           CMP     workRam+5F          ; did the depth high byte move this frame?
9855: F0 03           BEQ     loc_985a            ; unchanged -> skip the redraw bump
9857: EE 14 01        INC     workRam+114         ; bump the redraw counter so the depth-shaded view refreshes

loc_985a:
985A: 85 5F           STA     workRam+5F          ; store the depth accumulator high byte
985C: A5 9F           LDA     workRam+9F          ; take the step source cell
985E: 0A              ASL     A                   ; scale it by 4
985F: 0A              ASL     A                   
9860: C9 30           CMP     #$30                ; clamp the scaled step source to 0x30
9862: 90 02           BCC     loc_9866            ; 
9864: A9 30           LDA     #$30                ; clamp to 0x30

loc_9866:
9866: 18              CLC                         
9867: 69 20           ADC     #$20                ; bias by a minimum climb rate
9869: 18              CLC                         
986A: 6D 04 01        ADC     workRam+104         ; fold the new step into the growth step low byte
986D: 8D 04 01        STA     workRam+104         ; store the step low byte
9870: AD 05 01        LDA     workRam+105         ; carry into the growth step high byte
9873: 69 00           ADC     #$00                
9875: 8D 05 01        STA     workRam+105         ; store the step high byte
9878: AD 02 02        LDA     workRam+202         ; no collision test once the spike has parked at the ceiling
987B: C9 F0           CMP     #$F0                
987D: B0 22           BCS     loc_98a1            ; parked -> done
987F: A2 0F           LDX     #$0F                ; scan the 16 lane-height cells from the top down

loc_9881:
9881: BD AC 03        LDA     workRam+3AC,X       ; read this lane's stored spike height
9884: F0 18           BEQ     loc_989e            ; empty lane -> skip
9886: EC 00 02        CPX     workRam+200         ; only the player's own lane can be hit
9889: D0 13           BNE     loc_989e            ; other lane -> skip
988B: CD 02 02        CMP     workRam+202         ; spike hasn't grown past the player yet -> skip
988E: B0 0E           BCS     loc_989e            ; not yet -> skip
9890: 20 06 CD        JSR     cueSpikeCollisionSound; cue the spike collision sound
9893: 20 47 A3        JSR     insertObjectHeadTag7; drop an object-head marker at the hit
9896: A9 00           LDA     #$00                ; clear the spike-table guard
9898: 8D 15 01        STA     workRam+115         ; 
989B: 20 8F 92        JSR     clearActiveShots    ; clear the player's live shots

loc_989e:
989E: CA              DEX                         ; next lane
989F: 10 E0           BPL     loc_9881            ; loop the lane scan

loc_98a1:
98A1: 60              RTS                         

; age the 64-entry per-slot spawn-timer table loc_243 (slot 63..0): freeze
; ageing when the gate loc_2f has bit7 set (raised when loc_108+loc_109
; overshoots loc_11c or loc_125 is set), decrement each active timer, fire
; the expiry handler spawnEnemyOnTimerExpiry when it reaches 0, accumulate
; a per-slot bit mask (via loc_203/$CA38) into loc_14f and copy it out to
; loc_150.
tickSpawnSlotTimers:
98A2: A0 00           LDY     #$00                ; clear the spike-lane mask accumulator
98A4: 8C 4F 01        STY     workRam+14F         ; 
98A7: AD 08 01        LDA     workRam+108         ; sum the active-enemy and enemy-type counts
98AA: 18              CLC                         
98AB: 6D 09 01        ADC     workRam+109         ; add the enemy-type count
98AE: CD 1C 01        CMP     workRam+11C         ; compare the pair against the slot ceiling
98B1: 90 04           BCC     loc_98b7            ; within budget -> no freeze from crowding
98B3: F0 02           BEQ     loc_98b7            ; exactly at the ceiling -> no freeze
98B5: A0 FF           LDY     #$FF                ; overcrowded -> raise the ageing-freeze gate

loc_98b7:
98B7: AD 25 01        LDA     workRam+125         ; a latched wave transition...
98BA: F0 02           BEQ     loc_98be            ; none -> leave the gate as is
98BC: A0 FF           LDY     #$FF                ; ...also freezes timer ageing

loc_98be:
98BE: 84 2F           STY     workRam+2F          ; store the freeze gate
98C0: A2 3F           LDX     #$3F                ; walk the 64-entry spawn-timer table from the top

loc_98c2:
98C2: BD 43 02        LDA     workRam+243,X       ; read this slot's timer
98C5: F0 52           BEQ     loc_9919            ; empty slot -> skip
98C7: 24 2F           BIT     workRam+2F          ; freeze gate raised -> don't age this slot
98C9: 30 23           BMI     loc_98ee            ; 
98CB: 38              SEC                         ; age the timer down by one
98CC: E9 01           SBC     #$01                
98CE: 9D 43 02        STA     workRam+243,X       ; store the aged timer
98D1: D0 06           BNE     loc_98d9            ; reached zero?
98D3: 20 23 99        JSR     spawnEnemyOnTimerExpiry; timer expired -> fire the spawn handler for this slot
98D6: B8              CLV                         
98D7: 50 15           BVC     loc_98ee            ; 

loc_98d9:
98D9: C9 3F           CMP     #$3F                ; just crossed into the mask band at 0x3f?
98DB: D0 11           BNE     loc_98ee            ; no -> skip the hold check
98DD: BC 03 02        LDY     workRam+203,X       ; read this slot's lane index
98E0: AD 4F 01        LDA     workRam+14F         ; fold the mask
98E3: 0D 4F 01        ORA     workRam+14F         ; 
98E6: 39 38 CA        AND     rom+3A38,Y          ; take this lane's bit from the lane-bit table
98E9: F0 03           BEQ     loc_98ee            ; lane not already carrying a spike -> leave it
98EB: FE 43 02        INC     workRam+243,X       ; lane already busy -> hold the timer back at 0x40

loc_98ee:
98EE: BD 43 02        LDA     workRam+243,X       ; re-read the timer and classify it
98F1: C9 40           CMP     #$40                ; compare against 0x40
98F3: 90 14           BCC     loc_9909            ; below 0x40 -> mask band
98F5: A5 03           LDA     workRam+3           ; on the high band, act only on even frames
98F7: 29 01           AND     #$01                
98F9: D0 0B           BNE     loc_9906            ; odd frame -> hold
98FB: BD 03 02        LDA     workRam+203,X       ; rotate this slot's lane index by one, mod 16
98FE: 18              CLC                         
98FF: 69 01           ADC     #$01                
9901: 29 0F           AND     #$0F                
9903: 9D 03 02        STA     workRam+203,X       ; store the rotated lane index

loc_9906:
9906: B8              CLV                         
9907: 50 10           BVC     loc_9919            ; 

loc_9909:
9909: C9 20           CMP     #$20                ; in the 0x20..0x3f band?
990B: 90 0C           BCC     loc_9919            ; below 0x20 -> skip
990D: BC 03 02        LDY     workRam+203,X       ; take this lane's bit
9910: B9 38 CA        LDA     rom+3A38,Y          ; from the lane-bit table
9913: 0D 4F 01        ORA     workRam+14F         ; set this lane's bit in the spike-lane mask
9916: 8D 4F 01        STA     workRam+14F         ; store the mask accumulator

loc_9919:
9919: CA              DEX                         ; next slot
991A: 10 A6           BPL     loc_98c2            ; loop the slot scan
991C: AD 4F 01        LDA     workRam+14F         ; publish the rebuilt spike-lane mask for the rest of the frame
991F: 8D 50 01        STA     workRam+150         ; store the published mask
9922: 60              RTS                         

; spawn on slot-timer expiry for slot X: raise spawn request 0x29=0xf0,
; latch 0x203,x into 0x2a, save X in 0x35, run the placement pass
; placeSpawnListForColumnDeficit; if the request survives and
; spawnClimberInFreeSlot allocates a free slot, drop 0x3ab and clear the
; slot timer 0x243,x, else flag 0x2f=0xff and re-arm 0x243,x.
spawnEnemyOnTimerExpiry:
9923: A9 F0           LDA     #$F0                ; raise the spawn request with its depth seed 0xf0
9925: 85 29           STA     workRam+29          ; store the request depth
9927: BD 03 02        LDA     workRam+203,X       ; stage this slot's lane/segment for the spawn
992A: 85 2A           STA     workRam+2A          ; store the staged segment
992C: 86 35           STX     workRam+35          ; save the slot index across the placement pass
992E: 20 A5 99        JSR     placeSpawnListForColumnDeficit; run the per-column spawn placement
9931: A6 35           LDX     workRam+35          ; reload the saved slot index
9933: A5 29           LDA     workRam+29          ; spawn request still live?
9935: F0 0E           BEQ     loc_9945            ; cancelled by placement -> fail path
9937: 20 4D 99        JSR     spawnClimberInFreeSlot; try to seat a climber in a free enemy slot
993A: F0 09           BEQ     loc_9945            ; no free slot -> fail path
993C: CE AB 03        DEC     workRam+3AB         ; spend a fire-gate credit
993F: A9 00           LDA     #$00                ; clear this slot's timer -- the spawn is done
9941: 9D 43 02        STA     workRam+243,X       ; clear the slot timer
9944: 60              RTS                         

loc_9945:
9945: A9 FF           LDA     #$FF                ; flag the placement failed, freezing the rest of the scan
9947: 85 2F           STA     workRam+2F          ; store the freeze flag
9949: FE 43 02        INC     workRam+243,X       ; re-arm the slot timer to retry later
994C: 60              RTS                         

; spawn a new climber into a free slot: scan the free-slot index down from
; loc_11c skipping slots whose depth loc_2df,y is nonzero, and on a free
; slot seed depth loc_2df,y from loc_29, target segment loc_2b9,y from
; loc_2a (POKEY-random even $60CA&0x0e when loc_2a==0x0f and loc_111 bit7
; set), successor loc_2cc,y, timer loc_2a6,y=0, flags loc_28a,y from
; loc_2c, coord-high loc_291,y from loc_2d, lane byte loc_283,y from
; loc_2b; bump active count loc_108 and per-lane counter loc_142,lane,
; report 0x10 (0x00 when no slot free).
spawnClimberInFreeSlot:
994D: 84 36           STY     workRam+36          ; park the caller's scan index
994F: AC 1C 01        LDY     workRam+11C         ; scan enemy slots from the top down

loc_9952:
9952: B9 DF 02        LDA     workRam+2DF,Y       ; read this slot's depth cell
9955: D0 46           BNE     loc_999d            ; slot occupied -> keep scanning
9957: A5 29           LDA     workRam+29          ; seat the new enemy's depth from the staged request
9959: 99 DF 02        STA     workRam+2DF,Y       ; store the enemy depth
995C: A5 2A           LDA     workRam+2A          ; staged segment 0x0f...
995E: C9 0F           CMP     #$0F                ; compare against 0x0f
9960: D0 0A           BNE     loc_996c            ; not 0x0f -> take the segment as staged
9962: 2C 11 01        BIT     workRam+111         ; ...and a closed tube...
9965: 10 05           BPL     loc_996c            ; open tube -> keep the segment
9967: AD CA 60        LDA     pokey1+A            ; ...replaced by a random even lane so spawns spread across the rim
996A: 29 0E           AND     #$0E                ; random even lane

loc_996c:
996C: 99 B9 02        STA     workRam+2B9,Y       ; seat the enemy's tube segment
996F: 18              CLC                         ; animation phase...
9970: 69 01           ADC     #$01                ; segment + 1
9972: 29 0F           AND     #$0F                ; mod 16
9974: 99 CC 02        STA     workRam+2CC,Y       ; seat the enemy's animation phase
9977: A9 00           LDA     #$00                
9979: 99 A6 02        STA     workRam+2A6,Y       ; clear the enemy's timer
997C: A5 2C           LDA     workRam+2C          ; seat the coordinate-list pointer low byte for this enemy
997E: 99 8A 02        STA     workRam+28A,Y       ; store the pointer low byte
9981: A5 2D           LDA     workRam+2D          ; seat the coordinate-list pointer high byte
9983: 99 91 02        STA     workRam+291,Y       ; store the pointer high byte
9986: EE 08 01        INC     workRam+108         ; one more active enemy
9989: A5 2B           LDA     workRam+2B          ; take the staged flag byte
998B: 99 83 02        STA     workRam+283,Y       ; seat the enemy's flag byte
998E: A4 36           LDY     workRam+36          ; 
9990: 29 07           AND     #$07                ; lane = low 3 bits of the flag byte
9992: 86 36           STX     workRam+36          ; 
9994: AA              TAX                         
9995: FE 42 01        INC     workRam+142,X       ; bump this lane's enemy count
9998: A6 36           LDX     workRam+36          ; 
999A: A9 10           LDA     #$10                ; report a successful spawn
999C: 60              RTS                         

loc_999d:
999D: 88              DEY                         ; step down and continue the free-slot scan
999E: 10 B2           BPL     loc_9952            ; loop the scan
99A0: A4 36           LDY     workRam+36          ; 
99A2: A9 00           LDA     #$00                ; report no free slot found
99A4: 60              RTS                         

; top up per-column enemy quotas: build the five-column deficit table
; 0x13d[0..4] from 0x12e minus 0x142 (clamped >=0), deduct 2 per active
; lane (0x2df set, 0x28a&3 nonzero, lane3->col5), cap each at
; (0x11c+1)-sum(0x142), seed 0x61, then per nonzero-column count invoke
; the list-setup dispatcher (dispatchListSetupByColumn) to place a spawn
; list; every exhausted path clears the request flag 0x29.
placeSpawnListForColumnDeficit:
99A5: A9 00           LDA     #$00                
99A7: A2 04           LDX     #$04                

loc_99a9:
99A9: 9D 3D 01        STA     workRam+13D,X       ; clear the five-column deficit table
99AC: CA              DEX                         
99AD: 10 FA           BPL     loc_99a9            ; 
99AF: A2 04           LDX     #$04                

loc_99b1:
99B1: BD 2E 01        LDA     workRam+12E,X       ; this column's enemy target
99B4: 38              SEC                         ; minus the enemies already on the lane
99B5: FD 42 01        SBC     workRam+142,X       ; 
99B8: 90 03           BCC     loc_99bd            ; negative deficit -> leave this column at zero
99BA: 9D 3D 01        STA     workRam+13D,X       ; store the column's deficit

loc_99bd:
99BD: CA              DEX                         
99BE: 10 F1           BPL     loc_99b1            ; 
99C0: AC 1C 01        LDY     workRam+11C         ; walk every enemy slot

loc_99c3:
99C3: B9 DF 02        LDA     workRam+2DF,Y       ; skip empty slots
99C6: F0 14           BEQ     loc_99dc            ; 
99C8: B9 8A 02        LDA     workRam+28A,Y       ; take this enemy's lane, low two bits
99CB: 29 03           AND     #$03                
99CD: F0 0D           BEQ     loc_99dc            ; lane 0 -> skip
99CF: AA              TAX                         
99D0: E0 03           CPX     #$03                ; lane 3 remaps to column 5
99D2: D0 02           BNE     loc_99d6            ; 
99D4: A2 05           LDX     #$05                

loc_99d6:
99D6: DE 3C 01        DEC     workRam+13C,X       ; deduct 2 from that column's budget for this in-flight enemy
99D9: DE 3C 01        DEC     workRam+13C,X       ; deduct the second unit

loc_99dc:
99DC: 88              DEY                         ; next slot
99DD: 10 E4           BPL     loc_99c3            ; 
99DF: A2 04           LDX     #$04                
99E1: AD 1C 01        LDA     workRam+11C         ; global cap = slot ceiling plus one...
99E4: 18              CLC                         
99E5: 69 01           ADC     #$01                ; plus one

loc_99e7:
99E7: 38              SEC                         
99E8: FD 42 01        SBC     workRam+142,X       ; ...minus the total lane occupancy
99EB: CA              DEX                         
99EC: 10 F9           BPL     loc_99e7            ; 
99EE: A2 04           LDX     #$04                

loc_99f0:
99F0: DD 3D 01        CMP     workRam+13D,X       ; clamp each column's deficit down to the cap
99F3: B0 03           BCS     loc_99f8            ; 
99F5: 9D 3D 01        STA     workRam+13D,X       ; store the clamped deficit

loc_99f8:
99F8: CA              DEX                         
99F9: 10 F5           BPL     loc_99f0            ; 
99FB: A2 04           LDX     #$04                
99FD: A0 00           LDY     #$00                

loc_99ff:
99FF: BD 3D 01        LDA     workRam+13D,X       ; count the nonzero columns
9A02: F0 01           BEQ     loc_9a05            ; 
9A04: C8              INY                         ; one more nonzero column

loc_9a05:
9A05: CA              DEX                         
9A06: 10 F7           BPL     loc_99ff            ; 
9A08: 98              TYA                         ; no column needs enemies -> clear the request
9A09: F0 77           BEQ     loc_9a82            ; 
9A0B: 88              DEY                         ; two or more columns -> multi-column path
9A0C: D0 18           BNE     loc_9a26            ; 
9A0E: A2 04           LDX     #$04                

loc_9a10:
9A10: BD 3D 01        LDA     workRam+13D,X       ; single column: find the column with a deficit...
9A13: F0 0B           BEQ     loc_9a20            ; 
9A15: BD 29 01        LDA     workRam+129,X       ; ...that also has a spawn-cap entry
9A18: F0 06           BEQ     loc_9a20            ; none -> keep looking
9A1A: 20 87 9A        JSR     dispatchListSetupByColumn; seat a spawn list on that column
9A1D: F0 01           BEQ     loc_9a20            ; no placement -> keep looking
9A1F: 60              RTS                         ; placed -> done

loc_9a20:
9A20: CA              DEX                         
9A21: 10 ED           BPL     loc_9a10            ; 
9A23: B8              CLV                         
9A24: 50 5C           BVC     loc_9a82            ; exhausted -> clear the request

loc_9a26:
9A26: 84 61           STY     workRam+61          ; record the column count minus one
9A28: A2 04           LDX     #$04                

loc_9a2a:
9A2A: BD 3D 01        LDA     workRam+13D,X       ; multi-column: skip empty columns
9A2D: F0 0E           BEQ     loc_9a3d            ; 
9A2F: BD 42 01        LDA     workRam+142,X       ; skip columns already at their cap
9A32: DD 29 01        CMP     workRam+129,X       ; compare occupancy to the cap
9A35: B0 06           BCS     loc_9a3d            ; 
9A37: 20 87 9A        JSR     dispatchListSetupByColumn; seat a spawn list on the first below-cap column
9A3A: F0 01           BEQ     loc_9a3d            ; no placement -> keep looking
9A3C: 60              RTS                         ; placed -> done

loc_9a3d:
9A3D: CA              DEX                         
9A3E: 10 EA           BPL     loc_9a2a            ; 
9A40: AD 40 01        LDA     workRam+140         ; if columns 3 and 2 both still owe enemies...
9A43: F0 1C           BEQ     loc_9a61            ; 
9A45: AD 3F 01        LDA     workRam+13F         ; both live?
9A48: F0 17           BEQ     loc_9a61            ; 
9A4A: A4 2A           LDY     workRam+2A          ; read the staged lane's stored height
9A4C: B9 AC 03        LDA     workRam+3AC,Y       ; 
9A4F: D0 02           BNE     loc_9a53            ; nonempty -> use it
9A51: A9 FF           LDA     #$FF                ; default to 0xff when the lane is empty

loc_9a53:
9A53: A2 03           LDX     #$03                
9A55: C9 CC           CMP     #$CC                ; pick column 3 or 2 by the 0xcc height threshold
9A57: B0 02           BCS     loc_9a5b            ; 
9A59: A2 02           LDX     #$02                

loc_9a5b:
9A5B: 20 87 9A        JSR     dispatchListSetupByColumn; seat a spawn list on the chosen column
9A5E: F0 01           BEQ     loc_9a61            ; no placement -> fall through
9A60: 60              RTS                         ; placed -> done

loc_9a61:
9A61: AD DA 60        LDA     pokey2+A            ; POKEY-random start column for the round-robin sweep
9A64: 29 03           AND     #$03                
9A66: AA              TAX                         
9A67: E8              INX                         ; step to the start column
9A68: A0 04           LDY     #$04                ; sweep all five columns

loc_9a6a:
9A6A: BD 29 01        LDA     workRam+129,X       ; column has a spawn-cap entry...
9A6D: F0 0B           BEQ     loc_9a7a            ; none -> next
9A6F: BD 3D 01        LDA     workRam+13D,X       ; ...and a deficit...
9A72: F0 06           BEQ     loc_9a7a            ; none -> next
9A74: 20 87 9A        JSR     dispatchListSetupByColumn; ...seat a spawn list on it
9A77: F0 01           BEQ     loc_9a7a            ; no placement -> next
9A79: 60              RTS                         ; placed -> done

loc_9a7a:
9A7A: CA              DEX                         
9A7B: 10 02           BPL     loc_9a7f            ; wrap the column index
9A7D: A2 04           LDX     #$04                

loc_9a7f:
9A7F: 88              DEY                         
9A80: 10 E8           BPL     loc_9a6a            ; next column

loc_9a82:
9A82: A9 00           LDA     #$00                ; no placement made: clear the spawn request flag
9A84: 85 29           STA     workRam+29          ; store the cleared flag
9A86: 60              RTS                         

; route to the list-setup entry for column X: copy X into the dispatch
; index and enter the computed jump dispatchCoordListSetup, tail-returning
; the selected entry's result to placeSpawnListForColumnDeficit.
dispatchListSetupByColumn:
9A87: 8A              TXA                         ; take the column number as the dispatch selector

; route by the incoming value to one of five coordinate-list setup entries
; (0x9a9d, 0x9aa9, 0x9abb, 0x9ab7, 0x9ab3), passing the slot index X
; through, each of which front-loads a specific index/low-byte then falls
; into the shared 0x9aee/0x9af1 seating.
dispatchCoordListSetup:
9A88: 0A              ASL     A                   ; double the selector to a two-byte table offset
9A89: A8              TAY                         
9A8A: B9 94 9A        LDA     rom+A94,Y           ; push the selected setup entry's address high byte...
9A8D: 48              PHA                         
9A8E: B9 93 9A        LDA     rom+A93,Y           ; ...and its low byte
9A91: 48              PHA                         
9A92: 60              RTS                         ; return jumps into the selected coordinate-list setup entry

; ---- $9A93-$9A9C: data ----
9A93: 9C 9A A8 9A BA 9A B6 9A B2 9A

; seat the index-0 coordinate list: take the low pointer byte from fixed
; table byte 0x9b02, mark index 0 at 0x2b, take the high pointer from held
; source cell 0x15d into 0x2d, reload A from 0x29.
seatDemoCoordListPointer:
9A9D: AD 02 9B        LDA     rom+B02             ; coordinate-list pointer low from the fixed table head
9AA0: 85 2C           STA     workRam+2C          ; store the pointer low byte
9AA2: AD 5D 01        LDA     workRam+15D         ; high byte from the runtime-steered source cell
9AA5: A0 00           LDY     #$00                ; selecting index 0
9AA7: F0 4D           BEQ     seatCoordListPointerWithHighByte; finish via the shared seater's high-byte tail
9AA9: AD 03 9B        LDA     rom+B03             ; compose the pointer low byte from a table byte OR'd with the list-select flags
9AAC: 0D 6D 01        ORA     workRam+16D         ; OR in the list-select flags
9AAF: A0 01           LDY     #$01                ; selecting index 1
9AB1: D0 3E           BNE     seatCoordListPointerWithLowByte; finish via the shared seater with the computed low byte
9AB3: A0 04           LDY     #$04                ; selecting index 4
9AB5: D0 37           BNE     seatCoordListPointer; into the shared seater

; preset the selecting index to 3 and run the shared 0x9aee seating,
; parking the coordinate-list pointer 0x2c/0x2d from ROM tables
; 0x9b02[3]/0x9afd[3].
seatCoordListPointerAtIndex3:
9AB7: A0 03           LDY     #$03                ; selecting index 3
9AB9: D0 33           BNE     seatCoordListPointer; into the shared seater

; pick the lane a new climber will use: from a POKEY-random start
; (0x60ca&3) walk the four-entry lane table 0x149 with a four-step
; countdown in 0x2b, skipping lanes whose occupancy cell 0x13c is empty;
; on a hit set 0x2c to the chosen lane|0x40, seat the list-high byte
; 0x9afd[2] into 0x2d, index 0x02 into 0x2b, and report 0x29 (report 0x00
; on underflow).
selectClimberSpawnLane:
9ABB: AD CA 60        LDA     pokey1+A            ; POKEY-random start lane
9ABE: 29 03           AND     #$03                ; low two bits -> candidate index 0..3
9AC0: A8              TAY                         
9AC1: A9 04           LDA     #$04                ; set a four-attempt countdown
9AC3: 85 2B           STA     workRam+2B          ; 
9AC5: 86 39           STX     workRam+39          ; stash the caller's index

loc_9ac7:
9AC7: C6 2B           DEC     workRam+2B          ; count down an attempt
9AC9: 10 05           BPL     loc_9ad0            ; attempts remain?
9ACB: A6 39           LDX     workRam+39          ; restore the caller's index
9ACD: A9 00           LDA     #$00                ; report no eligible lane
9ACF: 60              RTS                         

loc_9ad0:
9AD0: 88              DEY                         ; step the candidate index down...
9AD1: 10 02           BPL     loc_9ad5            ; 
9AD3: A0 03           LDY     #$03                ; ...wrapping to the top

loc_9ad5:
9AD5: BE 49 01        LDX     workRam+149,Y       ; read the candidate lane id from the table
9AD8: E0 03           CPX     #$03                ; remap candidate lane 3 to 5
9ADA: D0 02           BNE     loc_9ade            ; 
9ADC: A2 05           LDX     #$05                ; lane 5

loc_9ade:
9ADE: BD 3C 01        LDA     workRam+13C,X       ; reject an empty lane and try again
9AE1: F0 E4           BEQ     loc_9ac7            ; 
9AE3: A6 39           LDX     workRam+39          ; restore the caller's index
9AE5: B9 49 01        LDA     workRam+149,Y       ; form the pointer low byte: chosen lane OR 0x40
9AE8: 09 40           ORA     #$40                ; OR in 0x40
9AEA: A0 02           LDY     #$02                ; selecting index 2
9AEC: D0 03           BNE     seatCoordListPointerWithLowByte; finish via the shared seater with this low byte

; aim the coordinate-list pointer at a packed vector list: read the low
; byte from ROM table 0x9b02+Y into 0x2c and the high byte from 0x9afd+Y
; into 0x2d, remember the selecting index at 0x2b, and reload A from
; holding cell 0x29.
seatCoordListPointer:
9AEE: B9 02 9B        LDA     rom+B02,Y           ; coordinate-list pointer low from the ROM table

; seat the coordinate-list pointer entered one step in: store the caller-
; supplied low byte straight into 0x2c, pull the high byte from ROM table
; 0x9afd+Y into 0x2d, stash index at 0x2b, reload A from 0x29.
seatCoordListPointerWithLowByte:
9AF1: 85 2C           STA     workRam+2C          ; install the pointer low byte
9AF3: B9 FD 9A        LDA     rom+AFD,Y           ; coordinate-list pointer high from the ROM table

; seat the coordinate-list pointer at its deepest entry: caller already
; parked the low byte at 0x2c, so take the high byte straight from A into
; 0x2d, stash index at 0x2b, reload A from 0x29.
seatCoordListPointerWithHighByte:
9AF6: 84 2B           STY     workRam+2B          ; remember the selecting index
9AF8: 85 2D           STA     workRam+2D          ; install the pointer high byte
9AFA: A5 29           LDA     workRam+29          ; reload the accumulator from its holding cell
9AFC: 60              RTS                         

; ---- $9AFD-$9B06: data ----
9AFD: 07 72 07 00 61 40 00 41 40 00

; set up the coordinate/shape list for the packed index in loc_2b
; (saving/restoring the caller index in loc_36): when the held count
; loc_29 >= 0x20 select a list-setup entry through dispatcher
; dispatchCoordListSetup, otherwise seat the pointer pair directly at that
; index via seatCoordListPointer.
setupEnemyCoordList:
9B07: 84 36           STY     workRam+36          ; save the caller's index across the setup
9B09: A5 29           LDA     workRam+29          ; held count at or above 0x20?
9B0B: C9 20           CMP     #$20                ; compare against 0x20
9B0D: A5 2B           LDA     workRam+2B          ; the packed list index
9B0F: B0 07           BCS     loc_9b18            ; high count -> route through the setup dispatcher
9B11: A8              TAY                         
9B12: 20 EE 9A        JSR     seatCoordListPointer; else seat the pointer directly at that index
9B15: B8              CLV                         
9B16: 50 03           BVC     loc_9b1b            ; 

loc_9b18:
9B18: 20 88 9A        JSR     dispatchCoordListSetup; route the index through the setup dispatcher

loc_9b1b:
9B1B: A4 36           LDY     workRam+36          ; restore the caller's index
9B1D: 60              RTS                         

; the per-frame motion-script walker: when loc_201>=0, walk slots
; loc_37=loc_11c down to 0 and for each nonzero loc_2df,x run its script
; from cursor loc_291,x, dispatching each script byte at $A0F7[loc_10b] to
; a motion opcode handler until the continuation flag loc_10a clears, then
; store the cursor back to loc_291,x; finally signed-accumulate loc_147
; into loc_148, fire the cd06/cd02 sound cues on a sign flip, and negate
; loc_147 to reverse sweep when loc_148 leaves the [0x0f,0xc0] band
runObjectMotionScripts:
9B1E: AD 01 02        LDA     workRam+201         ; skip the slot walk while the player field is mid-transition
9B21: 30 33           BMI     loc_9b56            ; 
9B23: AE 1C 01        LDX     workRam+11C         ; seed the slot loop from the top slot
9B26: 86 37           STX     workRam+37          ; 

loc_9b28:
9B28: A6 37           LDX     workRam+37          ; 
9B2A: BD DF 02        LDA     workRam+2DF,X       ; skip empty slots
9B2D: F0 23           BEQ     loc_9b52            ; 
9B2F: A9 01           LDA     #$01                ; raise the script-walk continue flag
9B31: 8D 0A 01        STA     workRam+10A         ; store the continue flag
9B34: BD 91 02        LDA     workRam+291,X       ; load this slot's saved script cursor
9B37: 8D 0B 01        STA     workRam+10B         ; store the working cursor

loc_9b3a:
9B3A: AD 0B 01        LDA     workRam+10B         ; the current cursor
9B3D: A8              TAY                         
9B3E: B9 F7 A0        LDA     rom+10F7,Y          ; read the motion opcode at the cursor
9B41: 20 98 9B        JSR     dispatchSlotMotionHandler; dispatch the motion opcode for this slot
9B44: EE 0B 01        INC     workRam+10B         ; advance the script cursor
9B47: AD 0A 01        LDA     workRam+10A         ; keep walking until a handler ends the script
9B4A: D0 EE           BNE     loc_9b3a            ; 
9B4C: AD 0B 01        LDA     workRam+10B         ; store the advanced cursor back to the slot
9B4F: 9D 91 02        STA     workRam+291,X       ; store the cursor

loc_9b52:
9B52: C6 37           DEC     workRam+37          ; next slot
9B54: 10 D2           BPL     loc_9b28            ; loop the slot scan

loc_9b56:
9B56: AD 48 01        LDA     workRam+148         ; signed-accumulate the sweep step into the sweep accumulator
9B59: 18              CLC                         
9B5A: 6D 47 01        ADC     workRam+147         ; add the step
9B5D: A8              TAY                         
9B5E: 4D 48 01        EOR     workRam+148         ; did the accumulate cross a sign boundary?
9B61: 8C 48 01        STY     workRam+148         ; store the new sweep accumulator
9B64: 10 16           BPL     loc_9b7c            ; no sign change -> skip the sweep cues
9B66: 98              TYA                         ; accumulator turned negative...
9B67: 10 06           BPL     loc_9b6f            ; 
9B69: 20 06 CD        JSR     cueSpikeCollisionSound; cue the segment/collision sound as the sweep turns negative
9B6C: B8              CLV                         
9B6D: 50 0D           BVC     loc_9b7c            ; 

loc_9b6f:
9B6F: AD 43 01        LDA     workRam+143         ; lanes populated...
9B72: F0 08           BEQ     loc_9b7c            ; empty -> skip
9B74: AD 01 02        LDA     workRam+201         ; ...and the field settled...
9B77: 30 03           BMI     loc_9b7c            ; mid-transition -> skip
9B79: 20 02 CD        JSR     requestMotionFlipSound; ...cue the motion-flip sound

loc_9b7c:
9B7C: AD 48 01        LDA     workRam+148         ; check the sweep accumulator against its band
9B7F: 30 07           BMI     loc_9b88            ; negative side -> test the low bound
9B81: C9 0F           CMP     #$0F                ; inside [0x0f,0xc0] -> reverse the sweep
9B83: B0 07           BCS     loc_9b8c            ; at or above 0x0f -> reverse
9B85: B8              CLV                         
9B86: 50 0F           BVC     loc_9b97            ; 

loc_9b88:
9B88: C9 C1           CMP     #$C1                ; outside the band -> leave the direction
9B8A: B0 0B           BCS     loc_9b97            ; at or above 0xc1 -> leave

loc_9b8c:
9B8C: AD 47 01        LDA     workRam+147         ; negate the sweep step to reverse the field's breathing
9B8F: 49 FF           EOR     #$FF                ; one's-complement
9B91: 18              CLC                         
9B92: 69 01           ADC     #$01                ; plus one
9B94: 8D 47 01        STA     workRam+147         ; store the reversed sweep step

loc_9b97:
9B97: 60              RTS                         

; route to one of twenty per-slot motion/steering/coordinate handlers
; ($9BCA..$9C3B, incl. the steering step $9CB6) by the pre-doubled table
; offset in the incoming value, passing slot x through and threading the
; offset as the object-insert seed for the collision handlers.
dispatchSlotMotionHandler:
9B98: A8              TAY                         ; the pre-doubled opcode as a table offset
9B99: B9 A3 9B        LDA     rom+BA3,Y           ; push the selected motion handler's address high byte...
9B9C: 48              PHA                         
9B9D: B9 A2 9B        LDA     rom+BA2,Y           ; ...and its low byte
9BA0: 48              PHA                         
9BA1: 60              RTS                         ; return jumps into the selected motion handler

; ---- $9BA2-$9BC9: data ----
9BA2: C9 9B CF 9B ED 9B 16 9C 0B 9C CE 9B 57 9C C3 9F
9BB2: DC 9B 5B 9E 81 9D 4E 9C 2E 9E F9 9B 20 9C F0 9E
9BC2: 47 9E B5 9C 66 9D 3A 9C

; housekeeping leaf: clear the walk-continuation flag by writing
; loc_10a=0, ending the walker's inner loop over this object's script
; entries
endObjectMotionScript:
9BCA: A9 00           LDA     #$00                
9BCC: 8D 0A 01        STA     workRam+10A         ; clear the walk-continue flag to end this object's script

; no-op leaf that returns immediately, occupying a slot in a computed-
; dispatch set so selecting it falls straight back to the caller.
noopDispatchStub:
9BCF: 60              RTS                         

; immediate store opcode: advance the script cursor loc_10b by one
; (wrapping to a byte) and copy the script byte it now points at,
; $A0F7[loc_10b], verbatim into the acting object's cell loc_298,x
writeScriptConstantToSlot:
9BD0: EE 0B 01        INC     workRam+10B         ; advance the motion-script cursor one byte
9BD3: AC 0B 01        LDY     workRam+10B         ; load the cursor as the table index
9BD6: B9 F7 A0        LDA     rom+10F7,Y          ; fetch the literal byte the cursor now points at from the motion-script table
9BD9: 9D 98 02        STA     workRam+298,X       ; store that constant into the acting slot's cell
9BDC: 60              RTS                         

; indirect store opcode: advance the script cursor loc_10b by one, treat
; the fetched script byte $A0F7[loc_10b] as a zero-page address, and copy
; the live variable at loc_00+ptr into the acting object's cell loc_298,x
writeScriptVariableToSlot:
9BDD: EE 0B 01        INC     workRam+10B         ; advance the motion-script cursor one byte
9BE0: AC 0B 01        LDY     workRam+10B         ; load the cursor as the table index
9BE3: B9 F7 A0        LDA     rom+10F7,Y          ; fetch the script byte -- here a zero-page pointer, not a value
9BE6: A8              TAY                         
9BE7: B9 00 00        LDA     workRam,Y           ; read the live zero-page variable the script byte points at
9BEA: 9D 98 02        STA     workRam+298,X       ; copy that variable's current value into the acting slot's cell
9BED: 60              RTS                         

; conditional-skip opcode: if the branch flag loc_10c is nonzero do
; nothing, otherwise advance the script cursor loc_10b by two to step past
; a two-byte operand
skipScriptOperandWhenFlagClear:
9BEE: AD 0C 01        LDA     workRam+10C         ; read the script branch-test flag
9BF1: D0 06           BNE     loc_9bf9            ; flag set: leave the cursor put -- the operand is consumed on the taken path
9BF3: EE 0B 01        INC     workRam+10B         ; flag clear: step the cursor past the...
9BF6: EE 0B 01        INC     workRam+10B         ; ...two-byte operand

loc_9bf9:
9BF9: 60              RTS                         

; conditional-jump opcode: advance the script cursor loc_10b by one, then
; only while the branch flag loc_10c is zero replace the cursor entirely
; with the operand target $A0F7[loc_10b], reloading the script position
jumpScriptCursorWhenFlagClear:
9BFA: EE 0B 01        INC     workRam+10B         ; advance the script cursor one
9BFD: AD 0C 01        LDA     workRam+10C         ; read the branch-suppress flag
9C00: D0 09           BNE     loc_9c0b            ; flag set: suppress the jump and fall through to the next entry
9C02: AC 0B 01        LDY     workRam+10B         ; 
9C05: B9 F7 A0        LDA     rom+10F7,Y          ; read the jump target from the script table at the new cursor
9C08: 8D 0B 01        STA     workRam+10B         ; reload the cursor with the target -- the scripted jump

loc_9c0b:
9C0B: 60              RTS                         

; dwell opcode: decrement the acting slot's timer loc_298,x; while it
; stays nonzero delegate to the $A0F8-driven goto (followScriptGoto) so
; the object keeps cycling its current state, and only when the timer hits
; zero bump the shared cursor loc_10b to release the script to the next
; instruction
holdSlotPoseUntilTimerExpires:
9C0C: DE 98 02        DEC     workRam+298,X       ; count this slot's dwell timer down one frame
9C0F: D0 06           BNE     followScriptGoto    ; still dwelling: re-run the current script state
9C11: EE 0B 01        INC     workRam+10B         ; timer expired: release the script to the next instruction
9C14: B8              CLV                         
9C15: 50 09           BVC     loc_9c20            ; 

; unconditional-goto opcode: use the current cursor loc_10b to index the
; parallel table $A0F8 and write $A0F8[loc_10b] back as the new cursor,
; following the chained operand
followScriptGoto:
9C17: AC 0B 01        LDY     workRam+10B         ; current script position
9C1A: B9 F8 A0        LDA     rom+10F8,Y          ; read the goto target from the parallel goto table
9C1D: 8D 0B 01        STA     workRam+10B         ; write it back as the new cursor -- the unconditional goto

loc_9c20:
9C20: 60              RTS                         

; boundary-test opcode: read the slot's segment loc_2b9,x, look up its
; boundary in loc_3ac (a zero entry reads as the maximum 0xff), and set
; the branch flag loc_10c to 1 when the boundary is at or beyond the
; slot's depth loc_2df,x, else 0
setFlagIfSlotPastSegmentBound:
9C21: BC B9 02        LDY     workRam+2B9,X       ; read slot x's tube segment
9C24: B9 AC 03        LDA     workRam+3AC,Y       ; index that segment's boundary depth in the per-lane limit table
9C27: D0 02           BNE     loc_9c2b            ; nonzero limit: use it
9C29: A9 FF           LDA     #$FF                ; a zero entry means no limit -- read as the maximum depth

loc_9c2b:
9C2B: DD DF 02        CMP     workRam+2DF,X       ; compare the boundary against the slot's depth
9C2E: B0 05           BCS     loc_9c35            ; boundary at or beyond the depth: flag 1
9C30: A9 00           LDA     #$00                ; else flag 0
9C32: B8              CLV                         
9C33: 50 02           BVC     loc_9c37            ; 

loc_9c35:
9C35: A9 01           LDA     #$01                ; boundary reached: flag 1

loc_9c37:
9C37: 8D 0C 01        STA     workRam+10C         ; leave the 1/0 verdict in the script branch flag
9C3A: 60              RTS                         

; phase-probe opcode: form ((loc_147<<2)+loc_148) & loc_148 & 0x80, XOR
; against 0x80, and store to the branch flag loc_10c so it becomes 0x00
; when that high bit is set and 0x80 when clear
setFlagFromPhaseAccumulatorSign:
9C3B: AD 47 01        LDA     workRam+147         ; read the per-frame enemy animation delta
9C3E: 0A              ASL     A                   ; scale the delta by four
9C3F: 0A              ASL     A                   
9C40: 18              CLC                         
9C41: 6D 48 01        ADC     workRam+148         ; add the animation-phase accumulator
9C44: 2D 48 01        AND     workRam+148         ; AND with the accumulator to isolate the shared sign
9C47: 29 80           AND     #$80                ; keep just the high sign bit
9C49: 49 80           EOR     #$80                ; invert it
9C4B: 8D 0C 01        STA     workRam+10C         ; leave the phase-sign verdict in the script branch flag
9C4E: 60              RTS                         

; toggle bit6 (the turn/rotation side flag) of the slot's flag cell
; loc_283,x in place and return the new value.
toggleEnemyTurnSide:
9C4F: BD 83 02        LDA     workRam+283,X       ; read slot x's flag byte
9C52: 49 40           EOR     #$40                ; invert bit6 -- the rim turn side
9C54: 9D 83 02        STA     workRam+283,X       ; store the flipped turn side back
9C57: 60              RTS                         

; step slot x's 16-bit tube depth (low loc_29f,x / high loc_2df,x) by the
; per-segment delta from the loc_160/loc_165 table indexed by the segment
; (loc_283,x & 7), routing to the add path advanceEnemyLaneDepth when
; loc_28a,x bit7 is clear or the subtract path reverseEnemyLaneDepth when
; set.
stepEnemyDepthInLaneDirection:
9C58: BD 83 02        LDA     workRam+283,X       ; read slot x's flag byte
9C5B: 29 07           AND     #$07                ; low 3 bits are the lane segment -- the per-segment speed-table index
9C5D: A8              TAY                         
9C5E: BD 8A 02        LDA     workRam+28A,X       ; read the slot's direction byte
9C61: 30 36           BMI     reverseEnemyLaneDepth; bit7 set: take the reverse (retreat) path

; add-direction depth step: loc_29f,x += loc_160,seg (16-bit) into
; loc_2df,x, then on the new high byte settle the slot via
; settleEnemyAtTargetDepth (hi <= floor loc_202), finish with the new high
; as exit value (hi >= 0x20), or retire/replace the slot via
; retireEnemyAndSpawnSplit when hi < 0x20 with an armed gate (loc_28a,x &
; 3).
advanceEnemyLaneDepth:
9C63: BD 9F 02        LDA     workRam+29F,X       ; low byte of the slot's 16-bit tube depth
9C66: 18              CLC                         
9C67: 79 60 01        ADC     workRam+160,Y       ; add the per-segment climb-speed low byte
9C6A: 9D 9F 02        STA     workRam+29F,X       ; store the depth low byte
9C6D: BD DF 02        LDA     workRam+2DF,X       ; depth high byte
9C70: 79 65 01        ADC     workRam+165,Y       ; add the per-segment climb-speed high byte with carry
9C73: 9D DF 02        STA     workRam+2DF,X       ; store the depth high byte -- enemy advanced toward the rim
9C76: CD 02 02        CMP     workRam+202         ; compare against the shared shot/floor depth
9C79: F0 02           BEQ     loc_9c7d            ; at the floor
9C7B: B0 06           BCS     loc_9c83            ; past the floor: carry on

loc_9c7d:
9C7D: 20 06 9D        JSR     settleEnemyAtTargetDepth; reached the floor: settle the enemy at target depth
9C80: B8              CLV                         
9C81: 50 13           BVC     loc_9c96            ; 

loc_9c83:
9C83: C9 20           CMP     #$20                ; high byte still above 0x20?
9C85: B0 0F           BCS     loc_9c96            ; yes, still deep: done
9C87: BD 8A 02        LDA     workRam+28A,X       ; read the direction byte
9C8A: 29 03           AND     #$03                ; the replacement/split gate bits
9C8C: F0 08           BEQ     loc_9c96            ; gate clear: done
9C8E: 8A              TXA                         
9C8F: 48              PHA                         
9C90: A8              TAY                         
9C91: 20 6F A0        JSR     retireEnemyAndSpawnSplit; retire the enemy and spawn its split/replacement
9C94: 68              PLA                         
9C95: AA              TAX                         

loc_9c96:
9C96: B8              CLV                         
9C97: 50 1C           BVC     loc_9cb5            ; 

; subtract-direction depth step: loc_29f,x -= loc_160,seg (16-bit, with
; borrow) into loc_2df,x, flooring the high byte to 0xf2 when it
; underflows past 0xf0; returns the new (or floored) high byte.
reverseEnemyLaneDepth:
9C99: BD 9F 02        LDA     workRam+29F,X       ; depth low byte
9C9C: 38              SEC                         
9C9D: F9 60 01        SBC     workRam+160,Y       ; subtract the per-segment climb-speed low byte
9CA0: 9D 9F 02        STA     workRam+29F,X       ; store the depth low byte
9CA3: BD DF 02        LDA     workRam+2DF,X       ; depth high byte
9CA6: F9 65 01        SBC     workRam+165,Y       ; subtract the climb-speed high byte with borrow
9CA9: 9D DF 02        STA     workRam+2DF,X       ; store the depth high byte -- enemy retreats up the tube
9CAC: C9 F0           CMP     #$F0                ; underflowed past the far rim?
9CAE: 90 05           BCC     loc_9cb5            ; no underflow: done
9CB0: A9 F2           LDA     #$F2                ; clamp value
9CB2: 9D DF 02        STA     workRam+2DF,X       ; floor the depth at the far rim (0xf2)

loc_9cb5:
9CB5: 60              RTS                         

; step slot x's coordinate keyed on loc_28a,x bit7 (sub-step via
; reverseEnemyLaneDepth flipping direction at the loc_157 threshold when
; loc_3ab is set, else add-step via advanceEnemyLaneDepth), and on the
; common tail -- loc_148 bit7 clear AND loc_2df,x < loc_157 AND
; loc_200==loc_2b9,x AND loc_201==loc_2cc,x -- seed a fresh object for
; that slot via insertObjectHeadTag7 with the stepped Y.
steerSlotCoordinate:
9CB6: A0 01           LDY     #$01                ; default steering index
9CB8: BD 8A 02        LDA     workRam+28A,X       ; read the slot's direction byte
9CBB: 30 10           BMI     loc_9ccd            ; bit7 set: retreating slot, take the sub-step path
9CBD: BD DF 02        LDA     workRam+2DF,X       ; depth high byte
9CC0: CD 57 01        CMP     workRam+157         ; compare against the near-rim threshold
9CC3: 90 02           BCC     loc_9cc7            ; below threshold: keep steering index 1
9CC5: A0 00           LDY     #$00                ; at or over threshold: steering index 0

loc_9cc7:
9CC7: 20 63 9C        JSR     advanceEnemyLaneDepth; add-step the depth forward one tick
9CCA: B8              CLV                         
9CCB: 50 17           BVC     loc_9ce4            ; 

loc_9ccd:
9CCD: 20 99 9C        JSR     reverseEnemyLaneDepth; sub-step the depth backward one tick -- retreat
9CD0: AC AB 03        LDY     workRam+3AB         ; read the enemy-fire gate
9CD3: D0 02           BNE     loc_9cd7            ; gate armed: probe with the stepped depth
9CD5: A9 FF           LDA     #$FF                ; gate clear: probe as the far value

loc_9cd7:
9CD7: CD 57 01        CMP     workRam+157         ; compare the probe against the near threshold
9CDA: 90 08           BCC     loc_9ce4            ; not yet at the far threshold: on to the tail
9CDC: BD 8A 02        LDA     workRam+28A,X       ; reached the threshold: read the direction byte
9CDF: 49 80           EOR     #$80                ; flip the climb-direction bit
9CE1: 9D 8A 02        STA     workRam+28A,X       ; reverse the slot's travel direction

loc_9ce4:
9CE4: AD 48 01        LDA     workRam+148         ; read the animation accumulator
9CE7: 30 1B           BMI     loc_9d04            ; busy (bit7 set): bail
9CE9: BD DF 02        LDA     workRam+2DF,X       ; depth high byte
9CEC: CD 57 01        CMP     workRam+157         ; near-rim threshold
9CEF: B0 13           BCS     loc_9d04            ; not close enough: bail
9CF1: AD 00 02        LDA     workRam+200         ; player's segment
9CF4: DD B9 02        CMP     workRam+2B9,X       ; same lane as this enemy?
9CF7: D0 0B           BNE     loc_9d04            ; wrong lane: bail
9CF9: AD 01 02        LDA     workRam+201         ; player's fine angle
9CFC: DD CC 02        CMP     workRam+2CC,X       ; matches the enemy's phase/angle?
9CFF: D0 03           BNE     loc_9d04            ; wrong angle: bail
9D01: 20 47 A3        JSR     insertObjectHeadTag7; enemy is on the player: seed a fresh object for the slot

loc_9d04:
9D04: 60              RTS                         

; ---- $9D05-$9D05: data ----
9D05: 16

; settle slot x at the target depth: stash floor loc_202 into loc_2df,x,
; then by kind -- a kind-1 slot (loc_283,x&7==1) with loc_3ab!=0 flips
; bit7 of loc_28a,x; a negative slot bumps its stashed depth; else drop
; count loc_108, and when per-type count loc_109==1 scan slots 6..0 for a
; matching-depth neighbour (index into loc_38) and copy its inverted bit6
; into loc_283,x, otherwise re-aim via faceEnemyTowardPlayerSegment,
; finally marking loc_10b=0x41 and bumping loc_109.
settleEnemyAtTargetDepth:
9D06: AD 02 02        LDA     workRam+202         ; read the shared floor/shot depth
9D09: 9D DF 02        STA     workRam+2DF,X       ; stash it as this slot's depth
9D0C: BD 83 02        LDA     workRam+283,X       ; read the flag byte
9D0F: 29 07           AND     #$07                ; low 3 bits: the lane kind
9D11: C9 01           CMP     #$01                ; kind 1?
9D13: D0 0E           BNE     loc_9d23            ; not kind 1: skip the fire flip
9D15: AD AB 03        LDA     workRam+3AB         ; read the enemy-fire gate
9D18: F0 09           BEQ     loc_9d23            ; gate clear: skip
9D1A: BD 8A 02        LDA     workRam+28A,X       ; read the direction byte
9D1D: 49 80           EOR     #$80                ; flip the climb direction -- turn to fire
9D1F: 9D 8A 02        STA     workRam+28A,X       ; store it
9D22: 60              RTS                         

loc_9d23:
9D23: BD 83 02        LDA     workRam+283,X       ; read the flag byte
9D26: 10 04           BPL     loc_9d2c            ; positive slot: go to the split logic
9D28: FE DF 02        INC     workRam+2DF,X       ; negative slot: nudge its stashed depth and stop
9D2B: 60              RTS                         

loc_9d2c:
9D2C: CE 08 01        DEC     workRam+108         ; drop the total live-enemy count
9D2F: AD 09 01        LDA     workRam+109         ; read the per-type enemy count
9D32: C9 01           CMP     #$01                ; exactly one of this type?
9D34: F0 06           BEQ     loc_9d3c            ; yes: scan for a matching neighbour
9D36: 20 67 9D        JSR     faceEnemyTowardPlayerSegment; otherwise re-aim the slot toward the player
9D39: B8              CLV                         
9D3A: 50 22           BVC     loc_9d5e            ; 

loc_9d3c:
9D3C: A0 06           LDY     #$06                ; scan from slot 6 downward

loc_9d3e:
9D3E: B9 DF 02        LDA     workRam+2DF,Y       ; read the candidate slot's depth
9D41: F0 0E           BEQ     loc_9d51            ; empty slot: skip it
9D43: 84 38           STY     workRam+38          ; record the current scan index
9D45: E4 38           CPX     workRam+38          ; is it this same slot?
9D47: F0 08           BEQ     loc_9d51            ; self: skip
9D49: B9 DF 02        LDA     workRam+2DF,Y       ; candidate depth
9D4C: CD 02 02        CMP     workRam+202         ; matches the shared floor depth?
9D4F: F0 03           BEQ     loc_9d54            ; match: take this neighbour

loc_9d51:
9D51: 88              DEY                         ; next slot down
9D52: 10 EA           BPL     loc_9d3e            ; keep scanning while slots remain

loc_9d54:
9D54: B9 83 02        LDA     workRam+283,Y       ; read the matched neighbour's flag byte
9D57: 29 40           AND     #$40                ; its turn-side bit
9D59: 49 40           EOR     #$40                ; inverted
9D5B: 9D 83 02        STA     workRam+283,X       ; set this slot's turn side opposite the neighbour's

loc_9d5e:
9D5E: A9 41           LDA     #$41                ; script-cursor seed
9D60: 8D 0B 01        STA     workRam+10B         ; point the motion-script cursor at 0x41
9D63: EE 09 01        INC     workRam+109         ; bump the per-type enemy count
9D66: 60              RTS                         

; aim slot x's turn side toward the player: take the signed segment delta
; of loc_2b9,x against player segment loc_200 via signedSegmentDelta, then
; clear bit6 of loc_283,x when the delta is negative (bit7 set) and set it
; otherwise.
faceEnemyTowardPlayerSegment:
9D67: BD B9 02        LDA     workRam+2B9,X       ; read slot x's own segment
9D6A: A8              TAY                         
9D6B: AD 00 02        LDA     workRam+200         ; player's segment
9D6E: 20 A6 A7        JSR     signedSegmentDelta  ; signed ring distance from the player to this enemy
9D71: 0A              ASL     A                   ; shift the sign into carry
9D72: BD 83 02        LDA     workRam+283,X       ; read the flag byte
9D75: B0 05           BCS     loc_9d7c            ; distance negative: clear the turn-side bit
9D77: 09 40           ORA     #$40                ; else set bit6 -- turn toward the player
9D79: B8              CLV                         
9D7A: 50 02           BVC     loc_9d7e            ; 

loc_9d7c:
9D7C: 29 BF           AND     #$BF                ; clear bit6 -- turn the other way

loc_9d7e:
9D7E: 9D 83 02        STA     workRam+283,X       ; store the turn-side decision
9D81: 60              RTS                         

; step the flipper's turn animation for slot x: advance phase counter
; loc_2cc,x by loc_283,x bit6 (keep nibble, force bit7); when state
; loc_283,x&7 != 4 re-aim (lookupRingHeading of loc_283,x^0x40 vs
; loc_2b9,x) and on a phase match drop bit7 and reseed the phase/lane
; loc_2b9,x; when state==4 at a step boundary rotate loc_2b9,x, reseed
; phase to 0x20, flip loc_28a,x bit7 and (loc_3ab==0) kick
; flipEnemyLaneTowardTarget when depth loc_2df,x==floor loc_202; every
; exit copies loc_283,x bit7 into shared flag loc_10c.
animateFlipperTurn:
9D82: BC CC 02        LDY     workRam+2CC,X       ; read the slot's phase counter
9D85: BD 83 02        LDA     workRam+283,X       ; read the flag byte
9D88: 29 40           AND     #$40                ; bit6: which way the phase steps
9D8A: D0 04           BNE     loc_9d90            ; bit6 set: step the phase down
9D8C: C8              INY                         ; bit6 clear: step the phase up
9D8D: B8              CLV                         
9D8E: 50 01           BVC     loc_9d91            ; 

loc_9d90:
9D90: 88              DEY                         ; step the phase down

loc_9d91:
9D91: 98              TYA                         
9D92: 29 0F           AND     #$0F                ; keep the low nibble
9D94: 09 80           ORA     #$80                ; force bit7 active
9D96: 9D CC 02        STA     workRam+2CC,X       ; store the advanced phase
9D99: BD 83 02        LDA     workRam+283,X       ; read the flag byte
9D9C: 29 07           AND     #$07                ; low 3 bits: the animation state
9D9E: C9 04           CMP     #$04                ; settling state (4)?
9DA0: D0 4C           BNE     loc_9dee            ; not settling: re-aim/walk branch
9DA2: BD CC 02        LDA     workRam+2CC,X       ; settling: read the phase
9DA5: 29 07           AND     #$07                ; at a step boundary?
9DA7: D0 42           BNE     loc_9deb            ; not on a boundary: done
9DA9: BD CC 02        LDA     workRam+2CC,X       ; read the phase
9DAC: 29 08           AND     #$08                ; phase bit3?
9DAE: F0 0B           BEQ     loc_9dbb            ; clear: skip the lane rotate
9DB0: BD B9 02        LDA     workRam+2B9,X       ; read the segment
9DB3: 18              CLC                         
9DB4: 69 01           ADC     #$01                ; plus one lane
9DB6: 29 0F           AND     #$0F                ; wrap to the 16-lane ring
9DB8: 9D B9 02        STA     workRam+2B9,X       ; rotate the enemy up one lane

loc_9dbb:
9DBB: BD 83 02        LDA     workRam+283,X       ; read the flag byte
9DBE: 29 7F           AND     #$7F                ; drop bit7
9DC0: 9D 83 02        STA     workRam+283,X       ; mark the flip settled
9DC3: A9 20           LDA     #$20                ; reseed value
9DC5: 9D CC 02        STA     workRam+2CC,X       ; reset the phase to 0x20
9DC8: BD 8A 02        LDA     workRam+28A,X       ; read the direction byte
9DCB: 49 80           EOR     #$80                ; flip the turn sign
9DCD: 9D 8A 02        STA     workRam+28A,X       ; store it
9DD0: AD AB 03        LDA     workRam+3AB         ; read the enemy-fire gate
9DD3: D0 16           BNE     loc_9deb            ; gate armed: done
9DD5: BD DF 02        LDA     workRam+2DF,X       ; read the flipper's depth
9DD8: CD 02 02        CMP     workRam+202         ; at the player's floor depth?
9DDB: D0 06           BNE     loc_9de3            ; no: just isolate the direction sign
9DDD: 20 81 9F        JSR     flipEnemyLaneTowardTarget; at the player: lunge across toward the target lane
9DE0: B8              CLV                         
9DE1: 50 08           BVC     loc_9deb            ; 

loc_9de3:
9DE3: BD 8A 02        LDA     workRam+28A,X       ; read the direction byte
9DE6: 29 80           AND     #$80                ; keep just the sign bit
9DE8: 9D 8A 02        STA     workRam+28A,X       ; mask the direction down to its sign

loc_9deb:
9DEB: B8              CLV                         
9DEC: 50 38           BVC     loc_9e26            ; 

loc_9dee:
9DEE: BC B9 02        LDY     workRam+2B9,X       ; not settling: read the segment
9DF1: BD 83 02        LDA     workRam+283,X       ; read the flag byte
9DF4: 49 40           EOR     #$40                ; flip bit6 for the heading lookup
9DF6: 20 D7 9E        JSR     lookupRingHeading   ; look up the target ring heading
9DF9: DD CC 02        CMP     workRam+2CC,X       ; does it match the current phase?
9DFC: D0 28           BNE     loc_9e26            ; no: done for this frame
9DFE: BD 83 02        LDA     workRam+283,X       ; read the flag byte
9E01: 29 7F           AND     #$7F                ; drop bit7
9E03: 9D 83 02        STA     workRam+283,X       ; clear the active bit
9E06: 29 40           AND     #$40                ; test the turn-side bit
9E08: D0 11           BNE     loc_9e1b            ; set: take the +1-lane branch
9E0A: BD B9 02        LDA     workRam+2B9,X       ; read the segment
9E0D: 9D CC 02        STA     workRam+2CC,X       ; copy the segment into the phase
9E10: 38              SEC                         
9E11: E9 01           SBC     #$01                ; minus one lane
9E13: 29 0F           AND     #$0F                ; wrap the ring
9E15: 9D B9 02        STA     workRam+2B9,X       ; step the segment down one lane
9E18: B8              CLV                         
9E19: 50 0B           BVC     loc_9e26            ; 

loc_9e1b:
9E1B: BD B9 02        LDA     workRam+2B9,X       ; read the segment
9E1E: 18              CLC                         
9E1F: 69 01           ADC     #$01                ; plus one lane
9E21: 29 0F           AND     #$0F                ; wrap the ring
9E23: 9D CC 02        STA     workRam+2CC,X       ; set the phase to the next lane up

loc_9e26:
9E26: BD 83 02        LDA     workRam+283,X       ; read the flag byte
9E29: 29 80           AND     #$80                ; isolate bit7 active
9E2B: 8D 0C 01        STA     workRam+10C         ; copy it into the shared script branch flag
9E2E: 60              RTS                         

; spawn a type-5 object when a live slot (loc_283,x bit7 clear) has its
; coordinate pair loc_2b9,x/loc_2cc,x matching the current target pair
; loc_200/loc_201.
spawnType5OnCoordMatch:
9E2F: BD 83 02        LDA     workRam+283,X       ; read this slot's enemy state byte
9E32: 30 13           BMI     loc_9e47            ; bail if the slot is already live -- only act on a not-yet-live slot
9E34: BD B9 02        LDA     workRam+2B9,X       ; read the slot's segment
9E37: CD 00 02        CMP     workRam+200         ; compare against the player's current rim segment
9E3A: D0 0B           BNE     loc_9e47            ; bail unless the slot's segment matches the player lane
9E3C: BD CC 02        LDA     workRam+2CC,X       ; read the slot's phase/successor heading
9E3F: CD 01 02        CMP     workRam+201         ; compare against the player fine-angle cell
9E42: D0 03           BNE     loc_9e47            ; bail unless it matches too
9E44: 20 3A A3        JSR     insertType5AndDrainPending; on a full coordinate match, spawn the type-5 object and drain the pending queue

loc_9e47:
9E47: 60              RTS                         

; fire the player-collision hit when a slot's depth loc_2df,x matches
; loc_202 and its segment loc_2b9,x matches loc_200.
fireHitOnPlayerCollision:
9E48: BD DF 02        LDA     workRam+2DF,X       ; read the slot's tube-depth high byte
9E4B: CD 02 02        CMP     workRam+202         ; compare with the player shot depth
9E4E: D0 0B           BNE     loc_9e5b            ; bail unless the depth matches
9E50: BD B9 02        LDA     workRam+2B9,X       ; read the slot's segment
9E53: CD 00 02        CMP     workRam+200         ; compare with the player rim segment
9E56: D0 03           BNE     loc_9e5b            ; bail unless the lane matches
9E58: 20 43 A3        JSR     insertObjectHeadTag9; register the hit on the player and insert the object head tag

loc_9e5b:
9E5B: 60              RTS                         

; step a climber's segment with the flip guard: run the depth-gated bit6
; keeper first, then fall into the shared segment-step body for slot x.
stepClimberSegmentGuarded:
9E5C: 20 AB 9E        JSR     keepClimberFlipBitByDepth; run the depth-gated flip-bit keeper first, then fall into the segment step

; step a climber one segment and set its next heading: force bit7 on
; loc_283,x (mark live) and branch on its low-3-bit segment -- for the
; seam segment 4 step depth loc_2b9,x down one mod16 and store 0x87 (bit6
; set) or store 0x81 (bit6 clear); for any other segment step depth up one
; mod16 (bit6 set) then store the ring-lookup heading into loc_2cc,x.
stepClimberSegmentAndHeading:
9E5F: BD 83 02        LDA     workRam+283,X       ; read the enemy state byte
9E62: 09 80           ORA     #$80                ; mark the slot live -- set bit7
9E64: 9D 83 02        STA     workRam+283,X       ; store it back
9E67: 29 07           AND     #$07                ; isolate the low-3-bit segment kind
9E69: C9 04           CMP     #$04                ; is this segment kind 4
9E6B: D0 1F           BNE     loc_9e8c            ; branch to the ordinary-segment case if not
9E6D: BD 83 02        LDA     workRam+283,X       ; reload state for the kind-4 case
9E70: 29 40           AND     #$40                ; test the turn-side bit
9E72: D0 05           BNE     loc_9e79            ; branch by side
9E74: A9 81           LDA     #$81                ; heading 0x81 for one side
9E76: B8              CLV                         
9E77: 50 0D           BVC     loc_9e86            ; 

loc_9e79:
9E79: BD B9 02        LDA     workRam+2B9,X       ; read the segment for the other side
9E7C: 38              SEC                         
9E7D: E9 01           SBC     #$01                ; step the segment down one
9E7F: 29 0F           AND     #$0F                ; wrap mod 16
9E81: 9D B9 02        STA     workRam+2B9,X       ; store the stepped segment
9E84: A9 87           LDA     #$87                ; heading 0x87 -- the bit6-set variant

loc_9e86:
9E86: 9D CC 02        STA     workRam+2CC,X       ; store the phase/heading
9E89: B8              CLV                         
9E8A: 50 1E           BVC     loc_9eaa            ; 

loc_9e8c:
9E8C: BD 83 02        LDA     workRam+283,X       ; reload state for the ordinary-segment case
9E8F: 29 40           AND     #$40                ; test the turn-side bit
9E91: F0 0B           BEQ     loc_9e9e            ; skip the step-up if the side bit is clear
9E93: BD B9 02        LDA     workRam+2B9,X       ; read the segment
9E96: 18              CLC                         
9E97: 69 01           ADC     #$01                ; step the segment up one
9E99: 29 0F           AND     #$0F                ; wrap mod 16
9E9B: 9D B9 02        STA     workRam+2B9,X       ; store the stepped segment

loc_9e9e:
9E9E: BD 83 02        LDA     workRam+283,X       ; reload state for the heading lookup
9EA1: BC B9 02        LDY     workRam+2B9,X       ; index by the current segment
9EA4: 20 D7 9E        JSR     lookupRingHeading   ; look up the ring heading
9EA7: 9D CC 02        STA     workRam+2CC,X       ; store the heading into the phase cell

loc_9eaa:
9EAA: 60              RTS                         

; keep the climber's flip bit (bit6 of loc_283,x) in step with its segment
; depth loc_2b9,x while gate loc_111 is on: when bit6 is set clear it once
; depth reaches 0x0e; when bit6 is clear set it only while depth is 0.
keepClimberFlipBitByDepth:
9EAB: AD 11 01        LDA     workRam+111         ; read the tube-geometry / live-board flag
9EAE: F0 26           BEQ     loc_9ed6            ; do nothing unless the board is live
9EB0: BD 83 02        LDA     workRam+283,X       ; read the enemy state byte
9EB3: 29 40           AND     #$40                ; test the flip/turn-side bit
9EB5: F0 12           BEQ     loc_9ec9            ; branch if the bit is clear
9EB7: BD B9 02        LDA     workRam+2B9,X       ; bit set: read the segment depth
9EBA: C9 0E           CMP     #$0E                ; has it reached 0x0e
9EBC: 90 08           BCC     loc_9ec6            ; keep the bit while depth is below 0x0e
9EBE: BD 83 02        LDA     workRam+283,X       ; else reload state to clear the flip bit
9EC1: 29 BF           AND     #$BF                ; clear bit6
9EC3: 9D 83 02        STA     workRam+283,X       ; store it back

loc_9ec6:
9EC6: B8              CLV                         
9EC7: 50 0D           BVC     loc_9ed6            ; 

loc_9ec9:
9EC9: BD B9 02        LDA     workRam+2B9,X       ; bit clear: read the segment depth
9ECC: D0 08           BNE     loc_9ed6            ; leave it clear unless depth is 0
9ECE: BD 83 02        LDA     workRam+283,X       ; at depth 0, reload state to set the flip bit
9ED1: 09 40           ORA     #$40                ; set bit6
9ED3: 9D 83 02        STA     workRam+283,X       ; store it back

loc_9ed6:
9ED6: 60              RTS                         

; look up a climber heading from the ring-direction table loc_3ee,y with
; bit7 forced on; when the caller's bit6 is set take the half-turn first
; -- index y=(y-1)&0x0f, value (loc_3ee,y+8)&0x0f.
lookupRingHeading:
9ED7: 29 40           AND     #$40                ; test the caller's turn-side bit
9ED9: F0 10           BEQ     loc_9eeb            ; straight lookup when clear
9EDB: 88              DEY                         ; half-turn: step the index back one
9EDC: 98              TYA                         
9EDD: 29 0F           AND     #$0F                ; wrap the index mod 16
9EDF: A8              TAY                         
9EE0: B9 EE 03        LDA     workRam+3EE,Y       ; read the ring-direction table
9EE3: 18              CLC                         
9EE4: 69 08           ADC     #$08                ; add a half turn (8)
9EE6: 29 0F           AND     #$0F                ; wrap mod 16
9EE8: B8              CLV                         
9EE9: 50 03           BVC     loc_9eee            ; 

loc_9eeb:
9EEB: B9 EE 03        LDA     workRam+3EE,Y       ; straight case: read the ring-direction table

loc_9eee:
9EEE: 09 80           ORA     #$80                ; force bit7 on the heading
9EF0: 60              RTS                         

; per-slot pursuit mover: when loc_28a,x bit7 is set re-seek the subtract
; way (reverseEnemyLaneDepth with delta index 0x04) and dispatch on the
; returned high (maybeFireEnemyStep fire step for hi<0x80, else
; flipEnemyLaneRandomSide/flipEnemyLaneTowardTarget by loc_159 bit6); when
; clear advance the 16-bit depth (loc_29f,x += loc_164; loc_2df,x +=
; loc_169) clamped to floor loc_202, producing a fire carry only when
; loc_3ab!=0 and (loc_9f>=0x11 or hi>=0x20), then run maybeFireEnemyStep
; or pick flipEnemyLaneTowardTarget/flipEnemyLaneRandomSide by loc_159
; sign.
advanceEnemyPursuit:
9EF1: A0 04           LDY     #$04                ; select climb-delta table entry 4
9EF3: BD 8A 02        LDA     workRam+28A,X       ; read the enemy direction/state byte
9EF6: 30 4B           BMI     loc_9f43            ; take the re-seek path when the climb-direction bit is set
9EF8: BD 9F 02        LDA     workRam+29F,X       ; read the depth low byte
9EFB: 18              CLC                         
9EFC: 6D 64 01        ADC     workRam+164         ; add the per-segment climb-speed delta (low)
9EFF: 9D 9F 02        STA     workRam+29F,X       ; store the depth low
9F02: BD DF 02        LDA     workRam+2DF,X       ; read the depth high byte
9F05: 6D 69 01        ADC     workRam+169         ; add the climb-speed delta (high) with carry
9F08: 9D DF 02        STA     workRam+2DF,X       ; store the depth high
9F0B: CD 02 02        CMP     workRam+202         ; compare against the player-shot floor depth
9F0E: B0 09           BCS     loc_9f19            ; branch if at or above the floor
9F10: AD 02 02        LDA     workRam+202         ; else clamp depth to the floor
9F13: 9D DF 02        STA     workRam+2DF,X       ; store the clamped depth
9F16: B8              CLV                         
9F17: 50 11           BVC     loc_9f2a            ; 

loc_9f19:
9F19: AC AB 03        LDY     workRam+3AB         ; read the enemy-fire gate
9F1C: F0 0B           BEQ     loc_9f29            ; no fire step when the gate is closed
9F1E: A4 9F           LDY     workRam+9F          ; read zero-page scratch $9f
9F20: C0 11           CPY     #$11                ; compare it against 0x11
9F22: B0 02           BCS     loc_9f26            ; arm the fire step when it is high enough
9F24: C9 20           CMP     #$20                ; else arm the fire step once depth reaches 0x20

loc_9f26:
9F26: B8              CLV                         
9F27: 50 01           BVC     loc_9f2a            ; 

loc_9f29:
9F29: 60              RTS                         

loc_9f2a:
9F2A: B0 11           BCS     loc_9f3d            ; with the fire carry, take the fire step
9F2C: AD 59 01        LDA     workRam+159         ; read the fire/aim selector
9F2F: 10 06           BPL     loc_9f37            ; pick the turn side by its sign
9F31: 20 81 9F        JSR     flipEnemyLaneTowardTarget; flip the enemy toward the target lane
9F34: B8              CLV                         
9F35: 50 03           BVC     loc_9f3a            ; 

loc_9f37:
9F37: 20 8A 9F        JSR     flipEnemyLaneRandomSide; else flip to a random side

loc_9f3a:
9F3A: B8              CLV                         
9F3B: 50 03           BVC     loc_9f40            ; 

loc_9f3d:
9F3D: 20 5F 9F        JSR     maybeFireEnemyStep  ; run the fire-gate step

loc_9f40:
9F40: B8              CLV                         
9F41: 50 1B           BVC     loc_9f5e            ; 

loc_9f43:
9F43: 20 99 9C        JSR     reverseEnemyLaneDepth; re-seek path: step depth in the lane direction (subtract way)
9F46: C9 80           CMP     #$80                ; test the returned depth high byte
9F48: 90 11           BCC     loc_9f5b            ; low half -> fire step
9F4A: 2C 59 01        BIT     workRam+159         ; test the fire/aim selector bit6
9F4D: 50 06           BVC     loc_9f55            ; 
9F4F: 20 81 9F        JSR     flipEnemyLaneTowardTarget; flip toward the target lane
9F52: B8              CLV                         
9F53: 50 03           BVC     loc_9f58            ; 

loc_9f55:
9F55: 20 8A 9F        JSR     flipEnemyLaneRandomSide; else flip to a random side

loc_9f58:
9F58: B8              CLV                         
9F59: 50 03           BVC     loc_9f5e            ; 

loc_9f5b:
9F5B: 20 5F 9F        JSR     maybeFireEnemyStep  ; fire-gate step

loc_9f5e:
9F5E: 60              RTS                         

; per-slot fire gate: run a step only when the slot's fire bit loc_2df,x &
; 0x20 is set and a fresh POKEY draw $60DA >= threshold loc_15f; then
; route to flipEnemyLaneRandomSide when loc_159 bit6 is clear or the slot
; index is even, otherwise flipEnemyLaneTowardTarget.
maybeFireEnemyStep:
9F5F: BD DF 02        LDA     workRam+2DF,X       ; read the slot's depth high byte
9F62: 29 20           AND     #$20                ; test the fire-enable bit
9F64: F0 1A           BEQ     loc_9f80            ; no step unless the bit is set
9F66: AD DA 60        LDA     pokey2+A            ; draw a fresh secondary-RNG byte
9F69: CD 5F 01        CMP     workRam+15F         ; compare against the fire threshold
9F6C: 90 12           BCC     loc_9f80            ; no fire when the draw is under threshold
9F6E: 2C 59 01        BIT     workRam+159         ; test the fire/aim selector bit6
9F71: 50 0A           BVC     loc_9f7d            ; 
9F73: 8A              TXA                         
9F74: 4A              LSR     A                   ; test the slot index parity
9F75: 90 13           BCC     flipEnemyLaneRandomSide; even slot -> flip to a random side
9F77: 20 81 9F        JSR     flipEnemyLaneTowardTarget; else flip toward the target lane
9F7A: B8              CLV                         
9F7B: 50 03           BVC     loc_9f80            ; 

loc_9f7d:
9F7D: 20 8A 9F        JSR     flipEnemyLaneRandomSide; flip to a random side

loc_9f80:
9F80: 60              RTS                         

; flip-step entry that re-derives slot x's turn side toward its target
; (faceEnemyTowardPlayerSegment) then toggles bit6 of loc_283,x
; (toggleEnemyTurnSide), then runs the shared tail: on a live board
; (loc_111!=0) flip bit6 by ring depth via loc_2b9,x, mark loc_10b=0x66,
; and continue into stepClimberSegmentAndHeading -- the flipper's lane-to-
; adjacent-lane hop.
flipEnemyLaneTowardTarget:
9F81: 20 67 9D        JSR     faceEnemyTowardPlayerSegment; re-derive this slot's turn side to face the player segment
9F84: 20 4F 9C        JSR     toggleEnemyTurnSide ; toggle the slot's turn-side bit
9F87: 4C 99 9F        JMP     loc_9f99            ; into the shared flip tail

; flip-step entry that reseeds slot x's turn side from a random bit ($60CA
; & 0x40) into bit6 of loc_283,x, then runs the shared tail: on a live
; board (loc_111!=0) flip bit6 again by ring depth (loc_2b9,x==0 when bit6
; set, >=0x0f when clear), mark loc_10b=0x66, and continue into
; stepClimberSegmentAndHeading.
flipEnemyLaneRandomSide:
9F8A: BD 83 02        LDA     workRam+283,X       ; read the enemy state byte
9F8D: 29 BF           AND     #$BF                ; clear the turn-side bit
9F8F: 2C CA 60        BIT     pokey1+A            ; test a primary-RNG bit (bit6)
9F92: 50 02           BVC     loc_9f96            ; leave the side clear on a 0 draw
9F94: 09 40           ORA     #$40                ; else set the turn-side bit from the random draw

loc_9f96:
9F96: 9D 83 02        STA     workRam+283,X       ; store the reseeded state

loc_9f99:
9F99: AD 11 01        LDA     workRam+111         ; shared tail: read the live-board flag
9F9C: F0 1E           BEQ     loc_9fbc            ; skip the ring-depth flip on a dead board
9F9E: BD 83 02        LDA     workRam+283,X       ; read the state byte
9FA1: 29 40           AND     #$40                ; test the turn-side bit
9FA3: D0 0A           BNE     loc_9faf            ; branch by side
9FA5: BD B9 02        LDA     workRam+2B9,X       ; side clear: read the segment
9FA8: C9 0F           CMP     #$0F                ; compare with 0x0f
9FAA: B0 08           BCS     loc_9fb4            ; flip the bit only at the top edge (>=0x0f)
9FAC: B8              CLV                         
9FAD: 50 0D           BVC     loc_9fbc            ; 

loc_9faf:
9FAF: BD B9 02        LDA     workRam+2B9,X       ; side set: read the segment
9FB2: D0 08           BNE     loc_9fbc            ; flip only when the segment is 0

loc_9fb4:
9FB4: BD 83 02        LDA     workRam+283,X       ; reload state to flip the turn-side bit
9FB7: 49 40           EOR     #$40                ; toggle bit6
9FB9: 9D 83 02        STA     workRam+283,X       ; store it back

loc_9fbc:
9FBC: A9 66           LDA     #$66                ; set the object-script cursor to 0x66
9FBE: 8D 0B 01        STA     workRam+10B         ; store the script cursor
9FC1: 4C 5F 9E        JMP     stepClimberSegmentAndHeading; continue into the segment step

; advance climber slot x toward the rim while tracking each column's
; shallowest occupant: raise loc_10c, seed empty column loc_3ac,col to
; 0xf1, record a fresh minimum for the column (tagging loc_39a,col=0x80),
; clamp a too-shallow depth loc_2df,x (set bit7 of loc_28a,x,
; depth->0x20), or past the far limit call the deepest-column aim, repark
; depth at 0xf0 and (only when loc_3ab==0) rewrite flag loc_28a,x and lane
; loc_283,x.
advanceClimberTrackingColumnMin:
9FC4: A9 01           LDA     #$01                ; raise the script-branch flag
9FC6: 8D 0C 01        STA     workRam+10C         ; store 1 into the script-branch flag
9FC9: BC B9 02        LDY     workRam+2B9,X       ; index by this enemy's segment/column
9FCC: B9 AC 03        LDA     workRam+3AC,Y       ; read the column's shallowest-depth record
9FCF: D0 05           BNE     loc_9fd6            ; skip seeding if it already holds a value
9FD1: A9 F1           LDA     #$F1                ; seed an empty column with 0xf1
9FD3: 99 AC 03        STA     workRam+3AC,Y       ; store it

loc_9fd6:
9FD6: BD DF 02        LDA     workRam+2DF,X       ; read this enemy's depth
9FD9: D9 AC 03        CMP     workRam+3AC,Y       ; compare with the column's recorded minimum
9FDC: B0 08           BCS     loc_9fe6            ; skip if not a new shallowest
9FDE: 99 AC 03        STA     workRam+3AC,Y       ; record the new column minimum
9FE1: A9 80           LDA     #$80                ; tag the column as flagged
9FE3: 99 9A 03        STA     workRam+39A,Y       ; store the lane-target flag

loc_9fe6:
9FE6: BD DF 02        LDA     workRam+2DF,X       ; read the depth again
9FE9: C9 20           CMP     #$20                ; is it too shallow (below 0x20)
9FEB: B0 10           BCS     loc_9ffd            ; branch if deep enough
9FED: BD 8A 02        LDA     workRam+28A,X       ; too shallow: read the direction byte
9FF0: 09 80           ORA     #$80                ; set the climb-direction bit -- turn it around
9FF2: 9D 8A 02        STA     workRam+28A,X       ; store it back
9FF5: A9 20           LDA     #$20                ; clamp depth to 0x20
9FF7: 9D DF 02        STA     workRam+2DF,X       ; store the clamped depth
9FFA: B8              CLV                         
9FFB: 50 2A           BVC     loc_a027            ; 

loc_9ffd:
9FFD: C9 F2           CMP     #$F2                ; past the far limit (>=0xf2)
9FFF: 90 26           BCC     loc_a027            ; branch out if still within the tube
A001: 20 28 A0        JSR     aimClimberAtDeepestColumn; aim the enemy at the deepest column
A004: A9 F0           LDA     #$F0                ; repark depth at the far wall 0xf0
A006: 9D DF 02        STA     workRam+2DF,X       ; store it
A009: AD AB 03        LDA     workRam+3AB         ; read the fire gate
A00C: D0 19           BNE     loc_a027            ; skip the rewrite while the gate is open
A00E: BD 8A 02        LDA     workRam+28A,X       ; rewrite the direction byte
A011: 29 FC           AND     #$FC                ; clear its low 2 bits
A013: 09 01           ORA     #$01                ; set kind to 1
A015: 9D 8A 02        STA     workRam+28A,X       ; store it back
A018: BD 83 02        LDA     workRam+283,X       ; rewrite the enemy state byte
A01B: 29 F8           AND     #$F8                ; clear the low 3 bits
A01D: 09 02           ORA     #$02                ; set segment kind to 2
A01F: 9D 83 02        STA     workRam+283,X       ; store it back
A022: A9 00           LDA     #$00                
A024: 8D 0C 01        STA     workRam+10C         ; clear the script-branch flag

loc_a027:
A027: 60              RTS                         

; aim a climber slot at the deepest tube column: scan the 16-column depth
; table loc_3ac from a POKEY2-random start $60DA&0x0f (empty column counts
; as maximal 0xff, last column skipped while loc_111 nonzero) keeping the
; max in loc_2d and its column in loc_29, then set the slot's target
; segment loc_2b9,x to the winner, successor loc_2cc,x=(winner+1)&0x0f,
; and clear bit7 of loc_28a,x.
aimClimberAtDeepestColumn:
A028: A9 00           LDA     #$00                
A02A: 85 2D           STA     workRam+2D          ; clear the running-max holder
A02C: A9 0F           LDA     #$0F                
A02E: 8D 40 01        STA     workRam+140         ; set the 16-column scan countdown
A031: AD DA 60        LDA     pokey2+A            ; draw a secondary-RNG start
A034: 29 0F           AND     #$0F                ; reduce to a column index 0..15
A036: A8              TAY                         

loc_a037:
A037: C0 0F           CPY     #$0F                ; is this the last column
A039: D0 05           BNE     loc_a040            ; skip the last-column special case
A03B: AD 11 01        LDA     workRam+111         ; read the live-board flag
A03E: D0 0F           BNE     loc_a04f            ; skip the last column while the board is live

loc_a040:
A040: B9 AC 03        LDA     workRam+3AC,Y       ; read the column's recorded depth
A043: D0 02           BNE     loc_a047            ; an empty column...
A045: A9 FF           LDA     #$FF                ; ...counts as maximal depth 0xff

loc_a047:
A047: C5 2D           CMP     workRam+2D          ; compare against the running max
A049: 90 04           BCC     loc_a04f            ; keep the previous max if not deeper
A04B: 85 2D           STA     workRam+2D          ; record the new max depth
A04D: 84 29           STY     workRam+29          ; record its column

loc_a04f:
A04F: 88              DEY                         ; step to the previous column
A050: 10 02           BPL     loc_a054            ; wrap...
A052: A0 0F           LDY     #$0F                ; ...back to column 15

loc_a054:
A054: CE 40 01        DEC     workRam+140         ; decrement the scan countdown
A057: 10 DE           BPL     loc_a037            ; loop over all 16 columns
A059: A5 29           LDA     workRam+29          ; take the winning column
A05B: 9D B9 02        STA     workRam+2B9,X       ; set the enemy's target segment
A05E: 18              CLC                         
A05F: 69 01           ADC     #$01                ; successor column = winner + 1
A061: 29 0F           AND     #$0F                ; wrap mod 16
A063: 9D CC 02        STA     workRam+2CC,X       ; store the successor heading
A066: BD 8A 02        LDA     workRam+28A,X       ; read the direction byte
A069: 29 7F           AND     #$7F                ; clear the climb-direction bit -- aim inward
A06B: 9D 8A 02        STA     workRam+28A,X       ; store it back
A06E: 60              RTS                         

; retire the enemy in slot y and optionally split it: clear loc_2df,y,
; drop per-type loc_109 (when depth==loc_202 and lane loc_283,y&7 != 4) or
; total loc_108, drop per-lane loc_142; then when the replacement gate
; loc_28a,y&3 is armed, seat draw cells loc_2b/loc_2a, build the
; coordinate list via setupEnemyCoordList, seed loc_10b/loc_10a, spawn a
; replacement via spawnClimberInFreeSlot, and if it took, spawn a mirrored
; second one.
retireEnemyAndSpawnSplit:
A06F: B9 DF 02        LDA     workRam+2DF,Y       ; read the retiring enemy's depth
A072: 85 29           STA     workRam+29          ; stash it
A074: CD 02 02        CMP     workRam+202         ; compare with the player-shot depth
A077: D0 0F           BNE     loc_a088            ; branch to the total-count path unless at that depth
A079: B9 83 02        LDA     workRam+283,Y       ; read the enemy state byte
A07C: 29 07           AND     #$07                ; isolate the segment kind
A07E: C9 04           CMP     #$04                ; is it segment kind 4
A080: F0 06           BEQ     loc_a088            ; if so, drop the total count instead
A082: CE 09 01        DEC     workRam+109         ; drop the per-type live-enemy count
A085: B8              CLV                         
A086: 50 03           BVC     loc_a08b            ; 

loc_a088:
A088: CE 08 01        DEC     workRam+108         ; drop the total live-enemy count

loc_a08b:
A08B: A9 00           LDA     #$00                
A08D: 99 DF 02        STA     workRam+2DF,Y       ; clear the slot depth -- retire it
A090: B9 83 02        LDA     workRam+283,Y       ; read the state byte
A093: 29 07           AND     #$07                ; isolate the segment kind
A095: 86 35           STX     workRam+35          ; save the caller's slot index
A097: AA              TAX                         
A098: DE 42 01        DEC     workRam+142,X       ; drop that column's active-enemy counter
A09B: A6 35           LDX     workRam+35          ; restore the slot index
A09D: B9 8A 02        LDA     workRam+28A,Y       ; read the direction byte
A0A0: 29 03           AND     #$03                ; isolate the replacement-gate bits
A0A2: F0 52           BEQ     loc_a0f6            ; no split when the gate is 0
A0A4: 38              SEC                         
A0A5: E9 01           SBC     #$01                ; decode the split count
A0A7: C9 02           CMP     #$02                ; is it 2
A0A9: D0 02           BNE     loc_a0ad            ; 
A0AB: A9 04           LDA     #$04                ; remap 2 to 4

loc_a0ad:
A0AD: 85 2B           STA     workRam+2B          ; seat the split draw cell
A0AF: B9 B9 02        LDA     workRam+2B9,Y       ; read the enemy's segment
A0B2: 38              SEC                         
A0B3: E9 01           SBC     #$01                ; step it back one
A0B5: 29 0F           AND     #$0F                ; wrap mod 16
A0B7: C9 0F           CMP     #$0F                ; did it wrap to 0x0f
A0B9: 90 07           BCC     loc_a0c2            ; skip the wrap fix-up if not
A0BB: 2C 11 01        BIT     workRam+111         ; test the live-board flag bit7
A0BE: 10 02           BPL     loc_a0c2            ; skip unless the wrap bit governs
A0C0: A9 00           LDA     #$00                ; clamp the segment to 0

loc_a0c2:
A0C2: 85 2A           STA     workRam+2A          ; seat the split lane cell
A0C4: 20 07 9B        JSR     setupEnemyCoordList ; build the coordinate list for the replacement
A0C7: A5 2D           LDA     workRam+2D          ; take the list-high byte
A0C9: 8D 0B 01        STA     workRam+10B         ; seed the object-script cursor
A0CC: CE 0B 01        DEC     workRam+10B         ; step it back one
A0CF: A9 00           LDA     #$00                
A0D1: 8D 0A 01        STA     workRam+10A         ; clear the script sub-cursor
A0D4: 20 4D 99        JSR     spawnClimberInFreeSlot; spawn the replacement climber in a free slot
A0D7: F0 1D           BEQ     loc_a0f6            ; done if no free slot took it
A0D9: A5 2A           LDA     workRam+2A          ; took: read the split lane
A0DB: 18              CLC                         
A0DC: 69 02           ADC     #$02                ; offset the mirror lane by 2
A0DE: 29 0F           AND     #$0F                ; wrap mod 16
A0E0: C9 0F           CMP     #$0F                ; did it land on 0x0f
A0E2: D0 07           BNE     loc_a0eb            ; skip the fix-up if not
A0E4: 2C 11 01        BIT     workRam+111         ; test the live-board bit7
A0E7: 10 02           BPL     loc_a0eb            ; skip unless it governs
A0E9: A9 0E           LDA     #$0E                ; clamp the mirror lane to 0x0e

loc_a0eb:
A0EB: 85 2A           STA     workRam+2A          ; seat the mirror lane
A0ED: A5 2B           LDA     workRam+2B          ; 
A0EF: 09 40           ORA     #$40                ; tag the mirror as the second of the pair
A0F1: 85 2B           STA     workRam+2B          ; store it
A0F3: 20 4D 99        JSR     spawnClimberInFreeSlot; spawn the mirrored second climber

loc_a0f6:
A0F6: 60              RTS                         

; ---- $A0F7-$A18E: data ----
A0F7: 0C 0E 1A 06 00 06 FF 0C 00 06 06 02 08 0C 00 08
A107: 0C 12 00 14 04 06 11 06 0A 0C 00 12 00 14 0C 04
A117: 06 1B 06 18 0C 00 02 02 12 00 14 0C 04 06 28 00
A127: 08 27 16 02 03 12 00 14 0C 04 06 35 00 08 34 16
A137: 06 23 02 04 18 00 08 43 12 00 10 B3 14 1A 41 08
A147: 4B 06 48 00 0C 1C 1A 52 12 00 0C 14 1A 52 00 06
A157: 5A 1E 20 00 06 60 00 02 03 20 00 08 68 14 1A 60
A167: 06 65 10 B2 22 00 08 73 26 1A 7E 22 00 06 77 24
A177: 12 00 14 1A 71 06 80 24 16 12 00 0C 14 04 06 89
A187: 02 04 00 0C 08 91 06 86

; advance every active shot slot (0x0b..0): slots >=8 integrate a 16-bit
; velocity loc_2e6,x/loc_2d3,x by loc_120/loc_118 and retire (drop loc_a6,
; finalize via primeTopObjectOnTargetMatch, clear loc_2d3,x) when the new
; high falls below the floor loc_202; slots <8 step counter loc_2d3,x by
; 0x09 (less 4 when loc_2f2,x is flagged), resolve via
; advanceShotAndScoreLaneHit, and clear (drop loc_135) at the far limit
; >=0xf0.
stepActiveShots:
A18F: A2 0B           LDX     #$0B                ; start at shot slot 0x0b
A191: 86 37           STX     workRam+37          ; seat the slot loop index

loc_a193:
A193: A6 37           LDX     workRam+37          ; reload the slot index (loop head)
A195: BD D3 02        LDA     workRam+2D3,X       ; read the shot slot's counter/state
A198: F0 45           BEQ     loc_a1df            ; skip an empty slot
A19A: E0 08           CPX     #$08                ; slots 8 and above...
A19C: B0 22           BCS     loc_a1c0            ; ...take the velocity-integration path
A19E: 69 09           ADC     #$09                ; near slot: step the counter by 0x09
A1A0: BC F2 02        LDY     workRam+2F2,X       ; read the slot's hit tally/flag
A1A3: F0 03           BEQ     loc_a1a8            ; if it is flagged...
A1A5: 38              SEC                         
A1A6: E9 04           SBC     #$04                ; ...take 4 back off the step

loc_a1a8:
A1A8: 9D D3 02        STA     workRam+2D3,X       ; store the stepped counter
A1AB: 20 FA A1        JSR     advanceShotAndScoreLaneHit; advance the shot and score a lane hit
A1AE: BD D3 02        LDA     workRam+2D3,X       ; reread the counter
A1B1: C9 F0           CMP     #$F0                ; past the far limit (>=0xf0)
A1B3: 90 08           BCC     loc_a1bd            ; branch if still in the tube
A1B5: CE 35 01        DEC     workRam+135         ; drop the active-object count
A1B8: A9 00           LDA     #$00                
A1BA: 9D D3 02        STA     workRam+2D3,X       ; clear the slot

loc_a1bd:
A1BD: B8              CLV                         
A1BE: 50 1F           BVC     loc_a1df            ; 

loc_a1c0:
A1C0: BD E6 02        LDA     workRam+2E6,X       ; far slot: read the velocity accumulator low
A1C3: 18              CLC                         
A1C4: 6D 20 01        ADC     workRam+120         ; add the per-frame velocity low
A1C7: 9D E6 02        STA     workRam+2E6,X       ; store it
A1CA: BD D3 02        LDA     workRam+2D3,X       ; read the position high byte
A1CD: 6D 18 01        ADC     workRam+118         ; add the velocity high with carry
A1D0: CD 02 02        CMP     workRam+202         ; compare against the shot-depth floor
A1D3: B0 07           BCS     loc_a1dc            ; keep it if still at or above the floor
A1D5: C6 A6           DEC     workRam+A6          ; else drop the live-climber count
A1D7: 20 E4 A1        JSR     primeTopObjectOnTargetMatch; finalize the top object on a target match
A1DA: A9 00           LDA     #$00                

loc_a1dc:
A1DC: 9D D3 02        STA     workRam+2D3,X       ; clear the slot

loc_a1df:
A1DF: C6 37           DEC     workRam+37          ; step to the previous slot
A1E1: 10 B0           BPL     loc_a193            ; loop over all shot slots
A1E3: 60              RTS                         

; prime the top-priority object only when the live byte loc_200 matches
; slot x's target loc_2ad,x and ready flag loc_201 bit7 is clear -- run
; the prime and latch loc_201=0x81.
primeTopObjectOnTargetMatch:
A1E4: AD 00 02        LDA     workRam+200         ; read the player rim segment
A1E7: DD AD 02        CMP     workRam+2AD,X       ; compare with the slot's target segment
A1EA: D0 0D           BNE     loc_a1f9            ; bail unless they match
A1EC: AD 01 02        LDA     workRam+201         ; read the player fine-angle/ready cell
A1EF: 30 08           BMI     loc_a1f9            ; bail if the ready bit (bit7) is set
A1F1: 20 4B A3        JSR     primeTopPriorityObject; prime the top-priority object
A1F4: A9 81           LDA     #$81                
A1F6: 8D 01 02        STA     workRam+201         ; latch the ready flag 0x81

loc_a1f9:
A1F9: 60              RTS                         

; advance shot slot x's counter loc_2d3,x toward the per-lane target-depth
; loc_3ac,y (y=loc_2ad,x); on reaching it shrink/clear loc_3ac,y, bump the
; hit tally loc_2f2,x, flag the target loc_39a,y=0xc0, chime
; (requestSegmentHitSound) and award (addBcdScoreAndAwardAtThreshold),
; returning the live slot; a second hit spends the shot (clear loc_2d3,x,
; drop the shot count loc_135).
advanceShotAndScoreLaneHit:
A1FA: BC AD 02        LDY     workRam+2AD,X       ; index by the shot's target segment
A1FD: B9 AC 03        LDA     workRam+3AC,Y       ; read that lane's target depth
A200: F0 3C           BEQ     loc_a23e            ; nothing to hit if the lane depth is 0
A202: BD D3 02        LDA     workRam+2D3,X       ; read the shot's counter
A205: D9 AC 03        CMP     workRam+3AC,Y       ; compare with the lane target depth
A208: 90 25           BCC     loc_a22f            ; not yet reached -> check the tally
A20A: C9 F0           CMP     #$F0                ; reached: past the far limit
A20C: 90 02           BCC     loc_a210            ; branch if within the tube
A20E: A9 00           LDA     #$00                ; else zero the value to store

loc_a210:
A210: 99 AC 03        STA     workRam+3AC,Y       ; shrink or clear the lane target depth
A213: FE F2 02        INC     workRam+2F2,X       ; bump the shot's hit tally
A216: A9 C0           LDA     #$C0                
A218: 99 9A 03        STA     workRam+39A,Y       ; flag the hit lane (0xc0)
A21B: 20 F6 CC        JSR     requestSegmentHitSound; request the segment-hit sound
A21E: A2 FF           LDX     #$FF                ; seat the award parameters
A220: A9 00           LDA     #$00                
A222: 85 2A           STA     workRam+2A          ; 
A224: 85 2B           STA     workRam+2B          ; 
A226: A9 01           LDA     #$01                
A228: 85 29           STA     workRam+29          ; 
A22A: 20 6C CA        JSR     addBcdScoreAndAwardAtThreshold; add the BCD score and award at the threshold
A22D: A6 37           LDX     workRam+37          ; restore the shot slot index

loc_a22f:
A22F: BD F2 02        LDA     workRam+2F2,X       ; read the hit tally
A232: C9 02           CMP     #$02                ; a second hit
A234: 90 08           BCC     loc_a23e            ; not yet -> keep the shot live
A236: A9 00           LDA     #$00                
A238: 9D D3 02        STA     workRam+2D3,X       ; spend the shot -- clear it
A23B: CE 35 01        DEC     workRam+135         ; drop the active-object count

loc_a23e:
A23E: 60              RTS                         

; spawn a new entity into a free slot: bail if 0x201 negative; form a gate
; (0x4d&0x10 when 0x5 negative, else 0x29 from 0x106 plus one per live
; 0x2db slot whose 0x2b5 sits within 1 of 0x200); on a nonzero gate scan
; 0x2d3 (x=7..0) for the first zero slot, seed it across
; 0x2d3/0x2ad/0x2c0/0x2f2 from 0x202/0x200/0x201/0, bump live count 0x135,
; and fire requestEnemySpawnSound + resolveSlotProximityInteractions.
spawnEntityIntoFreeSlot:
A23F: AD 01 02        LDA     workRam+201         ; read the player fine-angle/ready cell
A242: 30 61           BMI     loc_a2a5            ; bail while its bit7 is set
A244: A5 05           LDA     workRam+5           ; read the game status byte
A246: 30 28           BMI     loc_a270            ; take the fire-button path during active play
A248: AD 06 01        LDA     workRam+106         ; read the moving-spike active flag
A24B: 85 29           STA     workRam+29          ; seed the gate accumulator with it
A24D: A2 0A           LDX     #$0A                ; scan slots 0x0a..0

loc_a24f:
A24F: BD DB 02        LDA     workRam+2DB,X       ; read the slot's live byte (loop head)
A252: F0 14           BEQ     loc_a268            ; skip an empty slot
A254: BD B5 02        LDA     workRam+2B5,X       ; read the slot's segment
A257: 38              SEC                         
A258: ED 00 02        SBC     workRam+200         ; distance from the player segment
A25B: 10 05           BPL     loc_a262            ; take the absolute value...
A25D: 49 FF           EOR     #$FF                
A25F: 18              CLC                         
A260: 69 01           ADC     #$01                ; ...negate if below

loc_a262:
A262: C9 02           CMP     #$02                ; within 1 segment of the player
A264: B0 02           BCS     loc_a268            ; skip if farther
A266: E6 29           INC     workRam+29          ; count this near slot into the gate

loc_a268:
A268: CA              DEX                         
A269: 10 E4           BPL     loc_a24f            ; loop over the slots
A26B: A5 29           LDA     workRam+29          ; take the gate value
A26D: B8              CLV                         
A26E: 50 04           BVC     loc_a274            ; 

loc_a270:
A270: A5 4D           LDA     workRam+4D          ; fire-button path: read the debounced input
A272: 29 10           AND     #$10                ; isolate the fire button bit

loc_a274:
A274: F0 2F           BEQ     loc_a2a5            ; bail if the gate is 0 / button not held
A276: A2 07           LDX     #$07                ; scan slots 7..0 for a free one

loc_a278:
A278: BD D3 02        LDA     workRam+2D3,X       ; read the slot state (loop head)
A27B: D0 25           BNE     loc_a2a2            ; skip an occupied slot
A27D: EE 35 01        INC     workRam+135         ; take it: bump the active-object count
A280: AD 02 02        LDA     workRam+202         ; seed the slot depth from the player-shot depth
A283: 9D D3 02        STA     workRam+2D3,X       ; 
A286: AD 00 02        LDA     workRam+200         ; seed the target segment from the player segment
A289: 9D AD 02        STA     workRam+2AD,X       ; 
A28C: AD 01 02        LDA     workRam+201         ; seed the fine angle from the player fine-angle
A28F: 9D C0 02        STA     workRam+2C0,X       ; 
A292: A9 00           LDA     #$00                
A294: 9D F2 02        STA     workRam+2F2,X       ; clear the slot's hit tally
A297: 20 EA CC        JSR     requestEnemySpawnSound; request the enemy-spawn sound
A29A: AD 02 02        LDA     workRam+202         ; read the player-shot depth
A29D: 20 63 A4        JSR     resolveSlotProximityInteractions; resolve interactions with nearby slots
A2A0: A2 00           LDX     #$00                

loc_a2a2:
A2A2: CA              DEX                         
A2A3: 10 D3           BPL     loc_a278            ; loop -- and exit after a take

loc_a2a5:
A2A5: 60              RTS                         

; spawn climbers from the seven source slots each frame: skip while
; loc_201 bit7 set; for each armed slot (loc_28a,x bit6) with depth
; loc_2df,x>=0x30 whose timer loc_2a6,x underflows and whose POKEY roll
; $60CA beats the per-wave gate $A304[loc_a6], copy
; depth/segment/successor into the first empty destination loc_2db,y,
; reseed the timer from loc_119, cue the sound, and bump live count
; loc_a6.
spawnClimbersFromSourceSlots:
A2A6: AD 01 02        LDA     workRam+201         ; read the player rotation / object-pending flag
A2A9: 30 58           BMI     loc_a303            ; skip the whole hatch pass while the pending flag (bit7) is set
A2AB: A2 06           LDX     #$06                ; index the top source enemy slot (6..0)

loc_a2ad:
A2AD: BD DF 02        LDA     workRam+2DF,X       ; read the source slot's tube depth
A2B0: F0 4E           BEQ     loc_a300            ; skip an empty slot
A2B2: C9 30           CMP     #$30                
A2B4: 90 4A           BCC     loc_a300            ; skip unless the source sits deep enough down the tube to hatch
A2B6: BD 8A 02        LDA     workRam+28A,X       ; read the slot's direction/state byte
A2B9: 29 40           AND     #$40                
A2BB: F0 43           BEQ     loc_a300            ; skip unless the slot is an armed source (bit6)
A2BD: DE A6 02        DEC     workRam+2A6,X       ; tick the source's emit timer down
A2C0: 10 3E           BPL     loc_a300            ; fire only on the tick the timer underflows
A2C2: FE A6 02        INC     workRam+2A6,X       ; restore the underflow tick to the timer
A2C5: BD 83 02        LDA     workRam+283,X       ; read the slot flags
A2C8: 29 80           AND     #$80                
A2CA: D0 34           BNE     loc_a300            ; skip a slot flagged done (bit7)
A2CC: AD CA 60        LDA     pokey1+A            ; read the RNG
A2CF: A4 A6           LDY     workRam+A6          ; index the per-wave spawn-rate gate by the live-enemy count
A2D1: D9 04 A3        CMP     rom+1304,Y          ; gate the hatch against the per-wave spawn-rate table
A2D4: 90 2A           BCC     loc_a300            ; a low roll blocks the hatch -- denser waves emit less often
A2D6: AC 1A 01        LDY     workRam+11A         ; start the free flyer-slot scan at the difficulty-clamped top index

loc_a2d9:
A2D9: B9 DB 02        LDA     workRam+2DB,Y       ; read a candidate flyer destination slot
A2DC: D0 1F           BNE     loc_a2fd            ; skip an occupied destination slot
A2DE: BD DF 02        LDA     workRam+2DF,X       ; read the source depth
A2E1: 99 DB 02        STA     workRam+2DB,Y       ; seed the new flyer's depth from the source
A2E4: BD B9 02        LDA     workRam+2B9,X       ; read the source segment
A2E7: 99 B5 02        STA     workRam+2B5,Y       ; copy the source segment into the flyer
A2EA: BD CC 02        LDA     workRam+2CC,X       ; read the source phase
A2ED: 99 C8 02        STA     workRam+2C8,Y       ; copy the source phase into the flyer
A2F0: AD 19 01        LDA     workRam+119         ; load the source timer reload period
A2F3: 9D A6 02        STA     workRam+2A6,X       ; reload the source's emit timer
A2F6: 20 BD CC        JSR     gateSound8f         ; cue the hatch sound
A2F9: E6 A6           INC     workRam+A6          ; count the new enemy
A2FB: A0 00           LDY     #$00                ; end the free-slot scan once the flyer is seated

loc_a2fd:
A2FD: 88              DEY                         
A2FE: 10 D9           BPL     loc_a2d9            ; step to the next destination slot

loc_a300:
A300: CA              DEX                         ; next source slot
A301: 10 AA           BPL     loc_a2ad            ; loop back over the source slots

loc_a303:
A303: 60              RTS                         

; ---- $A304-$A308: data ----
A304: 00 E0 F0 FA FF

; spawn the lane enemy in slot X and award: mark slot active
; (0x2f2,x=0xff), seed 0x2d from the (Y-4)-indexed geometry byte 0x2b9,
; clamp POKEY random 0x60da low 3 bits to under 3 (else 0), run the
; insert/retire chain insertObjectFromSlotDepth+retireEnemyAndSpawnSplit
; with clamp+2, then award via addBcdScoreAndAwardAtThreshold indexed by
; clamp+5.
spawnLaneEnemyAndAward:
A309: 86 37           STX     workRam+37          ; stash the slot index for the insert/retire chain
A30B: A9 FF           LDA     #$FF                
A30D: 9D F2 02        STA     workRam+2F2,X       ; mark enemy slot X live
A310: 98              TYA                         
A311: 38              SEC                         
A312: E9 04           SBC     #$04                ; convert the 4-based slot handle to a 0-based lane index
A314: A8              TAY                         
A315: B9 B9 02        LDA     workRam+2B9,Y       ; read the lane's geometry byte
A318: 85 2D           STA     workRam+2D          ; latch the lane geometry for the object inserter
A31A: AD DA 60        LDA     pokey2+A            ; draw a random variant seed
A31D: 29 07           AND     #$07                
A31F: C9 03           CMP     #$03                ; keep the variant only in 0..2
A321: 90 02           BCC     loc_a325            ; values 0..2 pass through
A323: A9 00           LDA     #$00                ; fold a 3..7 variant to 0

loc_a325:
A325: 48              PHA                         
A326: 18              CLC                         
A327: 69 02           ADC     #$02                ; depth class = variant + 2
A329: 20 CA A3        JSR     insertObjectFromSlotDepth; place the object at the variant's depth class
A32C: 20 6F A0        JSR     retireEnemyAndSpawnSplit; fold the prior lane occupant and spawn any split
A32F: 68              PLA                         
A330: 18              CLC                         
A331: 69 05           ADC     #$05                ; score tier = variant + 5
A333: AA              TAX                         
A334: 20 6C CA        JSR     addBcdScoreAndAwardAtThreshold; award the variant's score tier and grant a bonus life at the threshold
A337: A6 37           LDX     workRam+37          ; restore the slot index
A339: 60              RTS                         

; insert a type-5 object through the shared tail, then step the pending
; counter loc_201 down one.
insertType5AndDrainPending:
A33A: A9 05           LDA     #$05                ; spawn a fixed type-5 object
A33C: 20 52 A3        JSR     insertObjectAndSignalReady; seat it through the shared insert-and-signal-ready tail
A33F: CE 01 02        DEC     workRam+201         ; draw the pending-spawn counter down by one
A342: 60              RTS                         

; insert a type-1 object after stamping head flag loc_13b with 0x09.
insertObjectHeadTag9:
A343: A9 09           LDA     #$09                ; preset the head-flag tag to 0x09
A345: D0 06           BNE     insertType1WithHeadFlag; 

; insert a type-1 object after stamping head flag loc_13b with 0x07.
insertObjectHeadTag7:
A347: A9 07           LDA     #$07                ; alternate entry: preset the head-flag tag to 0x07
A349: D0 02           BNE     insertType1WithHeadFlag; 

; prime a fresh top-priority object: stamp head flag loc_13b=0xff and run
; the type-1 insert.
primeTopPriorityObject:
A34B: A9 FF           LDA     #$FF                ; top-priority head flag 0xff

; insert a type-1 object after stamping the head flag loc_13b with the
; caller's value, then run the shared insert tail.
insertType1WithHeadFlag:
A34D: 8D 3B 01        STA     workRam+13B         ; stamp the object's head / animation-phase flag
A350: A9 01           LDA     #$01                ; object type 1

; insert an object and signal the spawn ready (shared insert tail): seat
; the type byte loc_2c, copy source loc_29 from loc_202 and target loc_2d
; from loc_200, fire the sound gate, insert into the 8-slot table, then
; raise ready flags loc_201=0x81 and loc_13c=0x01.
insertObjectAndSignalReady:
A352: 85 2C           STA     workRam+2C          ; seat the object type byte
A354: AD 02 02        LDA     workRam+202         ; read the current shot depth
A357: 85 29           STA     workRam+29          ; source = current shot depth
A359: AD 00 02        LDA     workRam+200         ; read the player segment
A35C: 85 2D           STA     workRam+2D          ; target = player segment
A35E: 20 B0 CC        JSR     gateSound5f         ; fire the spawn sound
A361: 20 D6 A3        JSR     insertTimedObject   ; seat the object in the eight-slot table
A364: A9 81           LDA     #$81                
A366: 8D 01 02        STA     workRam+201         ; raise the spawn-ready flag (bit7 pending)
A369: A9 01           LDA     #$01                
A36B: 8D 3C 01        STA     workRam+13C         ; arm the object's animation timer
A36E: 60              RTS                         

; retire the object in slot Y: cue the sound, stage source loc_29 from
; loc_2db,y and target loc_2d from loc_2b5,y, re-insert a zeroed object,
; clear the slot loc_2db,y, drop live count loc_a6, and flag lane X spent
; with loc_2f2,x=0xff.
retireSpawnedObject:
A36F: 20 C1 CC        JSR     gateSound1f         ; cue the destruction sound
A372: B9 DB 02        LDA     workRam+2DB,Y       ; read the object's depth
A375: 85 29           STA     workRam+29          ; stage the object's depth as the retract source
A377: B9 B5 02        LDA     workRam+2B5,Y       ; read the object's segment
A37A: 85 2D           STA     workRam+2D          ; stage the object's segment
A37C: A9 00           LDA     #$00                
A37E: 20 D4 A3        JSR     insertTimedObjectOfType; retract the object's draw record (type 0)
A381: A9 00           LDA     #$00                
A383: 99 DB 02        STA     workRam+2DB,Y       ; empty the slot
A386: C6 A6           DEC     workRam+A6          ; one fewer live enemy
A388: A9 FF           LDA     #$FF                
A38A: 9D F2 02        STA     workRam+2F2,X       ; flag lane X spent for the caller's teardown
A38D: 60              RTS                         

; activate enemy slot X (0x2f2,x=0xff), step the lane index back by four,
; then tail-delegate the retire/spawn/award for that stepped-back slot to
; respawnEnemyAndAward.
activateSlotAndRespawn:
A38E: A9 FF           LDA     #$FF                ; raise slot X's active flag
A390: 9D F2 02        STA     workRam+2F2,X       ; 
A393: 98              TYA                         
A394: 38              SEC                         
A395: E9 04           SBC     #$04                ; step the lane index back by four -- the slot recycled this pass
A397: A8              TAY                         

; retire enemy slot Y and spawn its replacement, then award: seed 0x2d
; from 0x2b9,y (decremented into the low nibble when descriptor 0x283,y
; has both top bits set), run
; insertObjectFromSlotDepth+retireEnemyAndSpawnSplit, then tail-delegate a
; score award (addBcdScoreAndAwardAtThreshold) selected by the re-read
; slot's lane through the $A3C5 table.
respawnEnemyAndAward:
A398: B9 83 02        LDA     workRam+283,Y       ; read the slot descriptor
A39B: 29 C0           AND     #$C0                
A39D: C9 C0           CMP     #$C0                
A39F: F0 06           BEQ     loc_a3a7            ; both top bits set marks the split/offset case
A3A1: B9 B9 02        LDA     workRam+2B9,Y       ; take the seated segment as-is
A3A4: B8              CLV                         
A3A5: 50 08           BVC     loc_a3af            ; 

loc_a3a7:
A3A7: B9 B9 02        LDA     workRam+2B9,Y       ; read the seated segment
A3AA: 38              SEC                         
A3AB: E9 01           SBC     #$01                ; split case: step the segment back one
A3AD: 29 0F           AND     #$0F                ; wrap it within the low nibble

loc_a3af:
A3AF: 85 2D           STA     workRam+2D          ; seed the list-anchor segment
A3B1: A9 00           LDA     #$00                
A3B3: 20 CA A3        JSR     insertObjectFromSlotDepth; insert the replacement object from the slot's depth
A3B6: 20 6F A0        JSR     retireEnemyAndSpawnSplit; retire the enemy and spawn any split
A3B9: B9 83 02        LDA     workRam+283,Y       ; re-read the descriptor -- the spawn may have reused the slot
A3BC: 29 07           AND     #$07                ; lane = descriptor low 3 bits
A3BE: A8              TAY                         
A3BF: BE C5 A3        LDX     rom+13C5,Y          ; look up the lane's score selector
A3C2: 4C 6C CA        JMP     addBcdScoreAndAwardAtThreshold; award the kill score for that lane

; ---- $A3C5-$A3C9: data ----
A3C5: 01 02 03 04 01

; insert an object seeded from a slot's depth: cue the sound, copy the
; y-indexed depth loc_2df,y into loc_29, then insert a fresh object into
; the table.
insertObjectFromSlotDepth:
A3CA: 48              PHA                         
A3CB: 20 C1 CC        JSR     gateSound1f         ; cue the spawn sound
A3CE: B9 DF 02        LDA     workRam+2DF,Y       ; read slot Y's depth
A3D1: 85 29           STA     workRam+29          ; seed the new object's depth from the slot
A3D3: 68              PLA                         

; insert a timed object of a given type: stash A into the type scratch
; loc_2c, then insert into the 8-slot table.
insertTimedObjectOfType:
A3D4: 85 2C           STA     workRam+2C          ; seat the caller's object type

; insert a timed object into the 8-slot table: reuse the first empty slot
; (loc_30a,i==0) or evict the slot holding the largest counter loc_312,i
; (dropping loc_116 by one), then fill counter loc_312=0, type loc_302
; from loc_2c, presence loc_30a from loc_29, lane loc_2fa from loc_2d, and
; bump loc_116.
insertTimedObject:
A3D6: 86 35           STX     workRam+35          ; 
A3D8: 84 36           STY     workRam+36          ; 
A3DA: A9 00           LDA     #$00                
A3DC: 85 2A           STA     workRam+2A          ; 
A3DE: 85 2B           STA     workRam+2B          ; 
A3E0: A2 07           LDX     #$07                ; scan the eight object slots for a home, high index first

loc_a3e2:
A3E2: BD 0A 03        LDA     workRam+30A,X       ; read the slot's presence byte
A3E5: F0 13           BEQ     loc_a3fa            ; an empty slot wins outright
A3E7: BD 12 03        LDA     workRam+312,X       ; read the slot's age counter
A3EA: C5 2A           CMP     workRam+2A          ; 
A3EC: 90 04           BCC     loc_a3f2            ; 
A3EE: 85 2A           STA     workRam+2A          ; remember the oldest slot so far as the eviction candidate
A3F0: 86 2B           STX     workRam+2B          ; 

loc_a3f2:
A3F2: CA              DEX                         
A3F3: 10 ED           BPL     loc_a3e2            ; loop over the slots
A3F5: CE 16 01        DEC     workRam+116         ; pool full: evict the oldest, balancing the bump below
A3F8: A6 2B           LDX     workRam+2B          ; reuse the oldest slot

loc_a3fa:
A3FA: A9 00           LDA     #$00                
A3FC: 9D 12 03        STA     workRam+312,X       ; reset the chosen slot's age -- a fresh object
A3FF: A5 2C           LDA     workRam+2C          ; 
A401: 9D 02 03        STA     workRam+302,X       ; write the object type
A404: A5 29           LDA     workRam+29          ; 
A406: 9D 0A 03        STA     workRam+30A,X       ; write the object's presence / depth
A409: A5 2D           LDA     workRam+2D          ; 
A40B: 9D FA 02        STA     workRam+2FA,X       ; write the object's lane coordinate
A40E: EE 16 01        INC     workRam+116         ; one more live object
A411: A6 35           LDX     workRam+35          ; 
A413: A4 36           LDY     workRam+36          ; 
A415: 60              RTS                         

; age the timed-object table: if pending flag loc_116 is zero do nothing,
; else clear it and advance each live slot's counter loc_312,i by the per-
; type step $A44E[type], freeing a slot that reaches its per-type limit
; $A448[type] (loc_30a,i=0) and re-raising loc_116 for any slot still
; short.
ageTimedObjects:
A416: AD 16 01        LDA     workRam+116         ; read the pending-animation flag
A419: F0 2C           BEQ     loc_a447            ; skip when nothing is animating
A41B: A9 00           LDA     #$00                
A41D: 8D 16 01        STA     workRam+116         ; clear the count -- rebuilt below as the live-slot tally
A420: A2 07           LDX     #$07                ; walk the eight object slots

loc_a422:
A422: BD 0A 03        LDA     workRam+30A,X       ; read the slot's presence byte
A425: F0 1D           BEQ     loc_a444            ; skip an empty slot
A427: BD 12 03        LDA     workRam+312,X       ; read the animation counter
A42A: BC 02 03        LDY     workRam+302,X       ; index the per-type tables by the object's type
A42D: 18              CLC                         
A42E: 79 4E A4        ADC     rom+144E,Y          ; advance the counter by the object's per-type step
A431: 9D 12 03        STA     workRam+312,X       ; 
A434: D9 48 A4        CMP     rom+1448,Y          ; compare against the object's per-type animation limit
A437: 90 08           BCC     loc_a441            ; still below the limit -- keep animating
A439: A9 00           LDA     #$00                
A43B: 9D 0A 03        STA     workRam+30A,X       ; reached the limit -- free the slot
A43E: B8              CLV                         
A43F: 50 03           BVC     loc_a444            ; 

loc_a441:
A441: EE 16 01        INC     workRam+116         ; count this slot as still animating

loc_a444:
A444: CA              DEX                         
A445: 10 DB           BPL     loc_a422            ; loop over the slots

loc_a447:
A447: 60              RTS                         

; ---- $A448-$A453: data ----
A448: 10 15 20 20 20 10 03 01 03 03 03 03

; drive the proximity pass across the live set: scan slots x=7..0 and, for
; each nonzero 0x2d3 entry, invoke resolveSlotProximityInteractions with
; that entry as the threshold and x as the slot index.
scanAllSlotsForProximity:
A454: A2 07           LDX     #$07                ; walk the eight active object slots, high index first

loc_a456:
A456: BD D3 02        LDA     workRam+2D3,X       ; read the slot's state byte
A459: F0 03           BEQ     loc_a45e            ; skip a dead slot
A45B: 20 63 A4        JSR     resolveSlotProximityInteractions; resolve this slot's proximity interactions -- state byte is the depth threshold

loc_a45e:
A45E: CA              DEX                         
A45F: 10 F5           BPL     loc_a456            ; loop over the slots
A461: 60              RTS                         

; ---- $A462-$A462: data ----
A462: AB

; resolve slot X's proximity interactions: store threshold A in 0x2e, scan
; 0x2db slots y=10..0 forming delta=|entry-threshold|; near slots (y<4)
; under 0x a7 with a matching 0x2b5/0x2ad pair retire via
; retireSpawnedObject; far slots (y>=4) fold 0x27f,y to a 3-bit band, test
; delta against 0x151,band, then dispatch spawnLaneEnemyAndAward (band 4)
; or activateSlotAndRespawn (other bands); after the scan, if
; 0x2f2,x==0xff tear down the slot (clear 0x2d3,x/0x2f2,x, drop live count
; 0x135).
resolveSlotProximityInteractions:
A463: 85 2E           STA     workRam+2E          ; park the query depth for the scan
A465: A0 0A           LDY     #$0A                ; scan all eleven enemy slots (10..0)

loc_a467:
A467: B9 DB 02        LDA     workRam+2DB,Y       ; read the enemy slot's depth
A46A: F0 7F           BEQ     loc_a4eb            ; skip an empty enemy slot
A46C: C5 2E           CMP     workRam+2E          ; 
A46E: 90 05           BCC     loc_a475            ; 
A470: E5 2E           SBC     workRam+2E          ; form the absolute depth distance between the object and the enemy
A472: B8              CLV                         
A473: 50 06           BVC     loc_a47b            ; 

loc_a475:
A475: A5 2E           LDA     workRam+2E          ; 
A477: 38              SEC                         
A478: F9 DB 02        SBC     workRam+2DB,Y       ; 

loc_a47b:
A47B: C0 04           CPY     #$04                
A47D: B0 12           BCS     loc_a491            ; slots 0..3 are near the rim; 4..10 are far
A47F: C5 A7           CMP     workRam+A7          ; near the rim: require the enemy within the kill distance
A481: B0 0B           BCS     loc_a48e            ; 
A483: B9 B5 02        LDA     workRam+2B5,Y       ; 
A486: 5D AD 02        EOR     workRam+2AD,X       ; and require a matching segment
A489: D0 03           BNE     loc_a48e            ; 
A48B: 20 6F A3        JSR     retireSpawnedObject ; retire the object -- a near, same-segment kill

loc_a48e:
A48E: B8              CLV                         
A48F: 50 5A           BVC     loc_a4eb            ; 

loc_a491:
A491: 48              PHA                         
A492: 84 38           STY     workRam+38          ; record the scan cursor
A494: B9 7F 02        LDA     workRam+27F,Y       ; read the object's band attribute
A497: 29 07           AND     #$07                ; fold it to a 3-bit distance band
A499: A8              TAY                         
A49A: 68              PLA                         
A49B: D9 51 01        CMP     workRam+151,Y       ; test the distance against the per-band threshold
A49E: B0 49           BCS     loc_a4e9            ; skip when the enemy is beyond the band's reach
A4A0: C0 04           CPY     #$04                
A4A2: D0 1D           BNE     loc_a4c1            ; band 4 has its own spawn/award handler
A4A4: A4 38           LDY     workRam+38          ; 
A4A6: B9 DB 02        LDA     workRam+2DB,Y       ; 
A4A9: CD 02 02        CMP     workRam+202         ; band 4: skip when the enemy is at the shot depth
A4AC: F0 10           BEQ     loc_a4be            ; 
A4AE: BD AD 02        LDA     workRam+2AD,X       ; 
A4B1: D9 B5 02        CMP     workRam+2B5,Y       ; require a matching segment
A4B4: D0 08           BNE     loc_a4be            ; 
A4B6: B9 C8 02        LDA     workRam+2C8,Y       ; 
A4B9: 10 03           BPL     loc_a4be            ; require the enemy's armed bit set
A4BB: 20 09 A3        JSR     spawnLaneEnemyAndAward; spawn a fresh lane enemy and award the points

loc_a4be:
A4BE: B8              CLV                         
A4BF: 50 28           BVC     loc_a4e9            ; 

loc_a4c1:
A4C1: A4 38           LDY     workRam+38          ; 
A4C3: B9 C8 02        LDA     workRam+2C8,Y       ; branch on the enemy's armed bit
A4C6: 10 0A           BPL     loc_a4d2            ; 
A4C8: B9 B5 02        LDA     workRam+2B5,Y       ; 
A4CB: DD C0 02        CMP     workRam+2C0,X       ; armed: match the enemy segment against the slot's alternate target
A4CE: F0 12           BEQ     loc_a4e2            ; on a match, re-activate the slot
A4D0: D0 08           BNE     loc_a4da            ; 

loc_a4d2:
A4D2: B9 DB 02        LDA     workRam+2DB,Y       ; 
A4D5: CD 02 02        CMP     workRam+202         ; unarmed and at the shot depth -> skip
A4D8: F0 0F           BEQ     loc_a4e9            ; 

loc_a4da:
A4DA: B9 B5 02        LDA     workRam+2B5,Y       ; 
A4DD: DD AD 02        CMP     workRam+2AD,X       ; otherwise require a matching target segment
A4E0: D0 07           BNE     loc_a4e9            ; 

loc_a4e2:
A4E2: 86 37           STX     workRam+37          ; stash the slot index
A4E4: 20 8E A3        JSR     activateSlotAndRespawn; re-activate the slot and respawn
A4E7: A6 37           LDX     workRam+37          ; 

loc_a4e9:
A4E9: A4 38           LDY     workRam+38          ; 

loc_a4eb:
A4EB: 88              DEY                         
A4EC: 30 03           BMI     loc_a4f1            ; 
A4EE: 4C 67 A4        JMP     loc_a467            ; continue the enemy-slot scan

loc_a4f1:
A4F1: BD F2 02        LDA     workRam+2F2,X       ; after the scan, check the slot's spent sentinel
A4F4: C9 FF           CMP     #$FF                
A4F6: D0 0B           BNE     loc_a503            ; 
A4F8: A9 00           LDA     #$00                
A4FA: 9D D3 02        STA     workRam+2D3,X       ; spent slot: clear its state
A4FD: CE 35 01        DEC     workRam+135         ; drop the live object count
A500: 9D F2 02        STA     workRam+2F2,X       ; clear the spent sentinel

loc_a503:
A503: 60              RTS                         

; per-frame ager keyed on the sign of loc_201: the positive arm bumps
; timer cell loc_00+loc_40 behind a gate and conditionally re-inits shot
; state via initWaveStateCountingSpikes/clearActiveShots; the negative arm
; ages every live shot in loc_2df from index loc_11c down by +0x0f
; (snapping >=0xf0 to 0), steps loc_202 or the loc_5f/loc_5b countdown
; clock, and on proceed writes loc_00=0x06, runs clearActiveShots, and
; folds loc_108+loc_109+loc_3ab into loc_3ab clamped to 0x3f.
ageShotsAndAdvanceFrameClock:
A504: AD 01 02        LDA     workRam+201         ; read the per-frame control byte -- its sign selects which bookkeeping job runs
A507: 10 78           BPL     loc_a581            ; positive: branch to the wave-setup arm
A509: AD 35 01        LDA     workRam+135         ; gather the live-object counts
A50C: 05 A6           ORA     workRam+A6          ; 
A50E: 0D 16 01        ORA     workRam+116         ; fold in the timed-object count
A511: D0 6B           BNE     loc_a57e            ; bail while any object is still live
A513: AE 1C 01        LDX     workRam+11C         ; start at the top shot slot

loc_a516:
A516: BD DF 02        LDA     workRam+2DF,X       ; read this shot's depth down the tube
A519: F0 0E           BEQ     loc_a529            ; skip an empty slot
A51B: 18              CLC                         
A51C: 69 0F           ADC     #$0F                ; age the shot by a fixed step
A51E: B0 02           BCS     loc_a522            ; 
A520: C9 F0           CMP     #$F0                ; clamp against the far rim

loc_a522:
A522: 90 02           BCC     loc_a526            ; 
A524: A9 00           LDA     #$00                ; snap to zero at the far rim

loc_a526:
A526: 9D DF 02        STA     workRam+2DF,X       ; store the aged depth

loc_a529:
A529: CA              DEX                         
A52A: 10 EA           BPL     loc_a516            ; next shot slot
A52C: A6 3D           LDX     workRam+3D          ; index the current slot
A52E: B5 48           LDA     workRam+48,X        ; read this slot's countdown
A530: C9 01           CMP     #$01                ; branch on whether the countdown is at its last tick
A532: D0 20           BNE     loc_a554            ; 
A534: A9 00           LDA     #$00                
A536: 8D 0F 01        STA     workRam+10F         ; clear a wave flag
A539: A9 01           LDA     #$01                
A53B: 8D 14 01        STA     workRam+114         ; arm the redraw
A53E: A5 5F           LDA     workRam+5F          ; step the between-wave clock low byte down by 0x20
A540: 38              SEC                         
A541: E9 20           SBC     #$20                
A543: 85 5F           STA     workRam+5F          ; store the clock low byte
A545: A5 5B           LDA     workRam+5B          ; borrow into the clock high byte
A547: E9 00           SBC     #$00                
A549: 85 5B           STA     workRam+5B          ; store the clock high byte
A54B: C9 FA           CMP     #$FA                ; proceed when the clock reaches its marker
A54D: 18              CLC                         
A54E: D0 01           BNE     loc_a551            ; 
A550: 38              SEC                         

loc_a551:
A551: B8              CLV                         
A552: 50 0D           BVC     loc_a561            ; 

loc_a554:
A554: AD 02 02        LDA     workRam+202         ; step the shot-depth counter by a fixed amount
A557: 18              CLC                         
A558: 69 0F           ADC     #$0F                
A55A: 8D 02 02        STA     workRam+202         ; store the stepped shot depth
A55D: B0 02           BCS     loc_a561            ; 
A55F: C9 F0           CMP     #$F0                ; proceed once it passes the far rim

loc_a561:
A561: 90 1B           BCC     loc_a57e            ; not yet -- exit
A563: A9 06           LDA     #$06                ; hand off: set the game mode to 6
A565: 85 00           STA     workRam             ; 
A567: 20 8F 92        JSR     clearActiveShots    ; clear all active shots
A56A: AD 08 01        LDA     workRam+108         ; sum the enemy-total and enemy-type budgets
A56D: 18              CLC                         
A56E: 6D 09 01        ADC     workRam+109         ; 
A571: 18              CLC                         
A572: 6D AB 03        ADC     workRam+3AB         ; add in the running fire budget
A575: C9 3F           CMP     #$3F                ; clamp the enemy budget to 0x3f
A577: 90 02           BCC     loc_a57b            ; 
A579: A9 3F           LDA     #$3F                

loc_a57b:
A57B: 8D AB 03        STA     workRam+3AB         ; store the clamped fire budget

loc_a57e:
A57E: B8              CLV                         
A57F: 50 49           BVC     loc_a5ca            ; 

loc_a581:
A581: AD 55 04        LDA     workRam+455         ; test the wave-setup gate cell pair
A584: 0D 1B 01        ORA     workRam+11B         ; 
A587: F0 0A           BEQ     loc_a593            ; skip the timer bump unless a gate is live
A589: A9 17           LDA     #$17                
A58B: C5 42           CMP     workRam+42          ; only bump past the threshold
A58D: B0 04           BCS     loc_a593            ; 
A58F: A6 40           LDX     workRam+40          ; index the per-slot setup timer
A591: F6 00           INC     workRam,X           ; bump the per-slot setup timer

loc_a593:
A593: AD 06 01        LDA     workRam+106         ; read the spike-active flag
A596: D0 32           BNE     loc_a5ca            ; bail while a spike is active
A598: AD AB 03        LDA     workRam+3AB         ; combine the fire budget and timed-object count
A59B: 0D 16 01        ORA     workRam+116         ; 
A59E: D0 15           BNE     loc_a5b5            ; only recycle the wave when both are clear
A5A0: AC 1C 01        LDY     workRam+11C         ; scan the shot table from the top slot

loc_a5a3:
A5A3: B9 DF 02        LDA     workRam+2DF,Y       ; read this shot's depth
A5A6: F0 04           BEQ     loc_a5ac            ; 
A5A8: C9 11           CMP     #$11                ; abort the reset if any shot has grown past 0x11
A5AA: B0 09           BCS     loc_a5b5            ; 

loc_a5ac:
A5AC: 88              DEY                         
A5AD: 10 F4           BPL     loc_a5a3            ; keep scanning
A5AF: 20 CB A5        JSR     initWaveStateCountingSpikes; re-seed the wave state
A5B2: 20 8F 92        JSR     clearActiveShots    ; clear active shots

loc_a5b5:
A5B5: A5 4D           LDA     workRam+4D          ; gate the second wave reset on a start input
A5B7: 29 60           AND     #$60                
A5B9: F0 0F           BEQ     loc_a5ca            ; 
A5BB: 24 05           BIT     workRam+5           ; gate on the attract/status flag
A5BD: 10 0B           BPL     loc_a5ca            ; 
A5BF: A5 09           LDA     workRam+9           ; gate on a dip-switch setting
A5C1: 29 43           AND     #$43                
A5C3: C9 40           CMP     #$40                
A5C5: D0 03           BNE     loc_a5ca            ; 
A5C7: 20 CB A5        JSR     initWaveStateCountingSpikes; re-seed the wave state

loc_a5ca:
A5CA: 60              RTS                         

; wave-state init: reset the batch loc_0=0x20, loc_104=0, loc_107=0,
; loc_5c=0, loc_123=0, loc_105=0x02 and OR bit7 into loc_106, tally into
; loc_123 how many of the sixteen loc_3ac lane cells are nonzero, and if
; that tally is nonzero with level loc_9f<0x07 overwrite the intro block
; (loc_4=0x1e, loc_0=0x0a, loc_2=0x20, loc_123=0x80), finally latching
; loc_125=0xff ready.
initWaveStateCountingSpikes:
A5CB: A9 20           LDA     #$20                ; set the game mode to the wave-start value
A5CD: 85 00           STA     workRam             ; 
A5CF: AD 06 01        LDA     workRam+106         ; arm the spike-active bit
A5D2: 09 80           ORA     #$80                
A5D4: 8D 06 01        STA     workRam+106         ; store the spike-active flag
A5D7: A9 00           LDA     #$00                
A5D9: 8D 04 01        STA     workRam+104         ; clear the spike step-low byte
A5DC: 8D 07 01        STA     workRam+107         ; clear the spike height-low byte
A5DF: 85 5C           STA     workRam+5C          ; clear the depth accumulator
A5E1: 8D 23 01        STA     workRam+123         ; clear the spiked-lane tally
A5E4: A9 02           LDA     #$02                
A5E6: 8D 05 01        STA     workRam+105         ; set the spike step-high byte
A5E9: A2 0F           LDX     #$0F                ; scan the sixteen lane-limit cells

loc_a5eb:
A5EB: BD AC 03        LDA     workRam+3AC,X       ; read this lane's limit
A5EE: F0 03           BEQ     loc_a5f3            ; 
A5F0: EE 23 01        INC     workRam+123         ; count each spiked lane

loc_a5f3:
A5F3: CA              DEX                         
A5F4: 10 F5           BPL     loc_a5eb            ; 
A5F6: AD 23 01        LDA     workRam+123         ; skip the intro if no lane is spiked
A5F9: F0 17           BEQ     loc_a612            ; 
A5FB: A5 9F           LDA     workRam+9F          ; only run the intro on early waves -- level below 7
A5FD: C9 07           CMP     #$07                
A5FF: B0 11           BCS     loc_a612            ; 
A601: A9 1E           LDA     #$1E                ; arm the intro delay timer
A603: 85 04           STA     workRam+4           ; 
A605: A9 0A           LDA     #$0A                ; set the game mode to the spike-intro value
A607: 85 00           STA     workRam             ; 
A609: A9 20           LDA     #$20                ; queue the pending mode
A60B: 85 02           STA     workRam+2           ; 
A60D: A9 80           LDA     #$80                
A60F: 8D 23 01        STA     workRam+123         ; stamp the spike-intro marker into the tally

loc_a612:
A612: A9 FF           LDA     #$FF                ; arm the block-ready latch
A614: 8D 25 01        STA     workRam+125         ; 
A617: 60              RTS                         

; drive one frame of the enemy bank: seed frame-active flag loc_10d from
; spawn budget loc_10e, walk slots 0x0f..0, integrate+decay each live slot
; (loc_283,x!=0) via the flight steps and refill each free slot from the
; budget, tick loc_10e on even frames (loc_3 bit0 clear), settle
; loc_37=0xff, and raise mode-request loc_0=0x12 when nothing was live or
; spawned.
stepEnemyFleetAndSpawn:
A618: AD 0E 01        LDA     workRam+10E         ; seed the frame-active flag from the spawn budget
A61B: 8D 0D 01        STA     workRam+10D         ; 
A61E: A2 0F           LDX     #$0F                ; walk the sixteen enemy slots top-down
A620: 86 37           STX     workRam+37          ; 

loc_a622:
A622: A6 37           LDX     workRam+37          ; 
A624: BD 83 02        LDA     workRam+283,X       ; read this slot's flags
A627: D0 0B           BNE     loc_a634            ; branch on whether the slot holds a live enemy
A629: AD 0E 01        LDA     workRam+10E         ; free slot: spawn only while the spawn budget remains
A62C: F0 03           BEQ     loc_a631            ; 
A62E: 20 5B A6        JSR     spawnEnemyInSlot    ; spawn a new enemy in this slot

loc_a631:
A631: B8              CLV                         
A632: 50 0B           BVC     loc_a63f            ; 

loc_a634:
A634: 20 A9 A6        JSR     advanceEnemyFreeFlight; advance the live enemy's free flight
A637: 20 21 A7        JSR     decayEnemyFreeFlightVelocity; decay its velocity
A63A: A9 FF           LDA     #$FF                
A63C: 8D 0D 01        STA     workRam+10D         ; mark the frame active

loc_a63f:
A63F: C6 37           DEC     workRam+37          ; next slot
A641: 10 DF           BPL     loc_a622            ; 
A643: A5 03           LDA     workRam+3           ; tick the spawn budget only on even frames
A645: 29 01           AND     #$01                
A647: D0 08           BNE     loc_a651            ; 
A649: AD 0E 01        LDA     workRam+10E         ; 
A64C: F0 03           BEQ     loc_a651            ; 
A64E: CE 0E 01        DEC     workRam+10E         ; count the spawn budget down

loc_a651:
A651: AD 0D 01        LDA     workRam+10D         ; test whether anything was live or spawned
A654: D0 04           BNE     loc_a65a            ; something happened -- exit
A656: A9 12           LDA     #$12                ; nothing left: raise the mode request to 0x12
A658: 85 00           STA     workRam             ; 

loc_a65a:
A65A: 60              RTS                         

; spawn an enemy into free slot x: mark loc_263,x/loc_283,x/loc_2a3,x =
; 0x80, seed three velocity/coordinate pairs from POKEY draws $60DA/$60CA
; (raw into loc_2c3/2e3/303,x, signed nudge from drawSignedVelocityNudge
; into loc_323/343/363,x, forcing the middle nudge non-positive), then cue
; the spawn sound gateSound1f.
spawnEnemyInSlot:
A65B: A5 03           LDA     workRam+3           ; 
A65D: 29 00           AND     #$00                
A65F: D0 39           BNE     loc_a69a            ; 
A661: A9 80           LDA     #$80                ; center axis-1 position and mark the slot live
A663: 9D 63 02        STA     workRam+263,X       ; 
A666: 9D 83 02        STA     workRam+283,X       ; mark the slot occupied
A669: 9D A3 02        STA     workRam+2A3,X       ; center the third axis position
A66C: AD DA 60        LDA     pokey2+A            ; seed axis-1 velocity low from a random source
A66F: 9D C3 02        STA     workRam+2C3,X       ; store axis-1 velocity low
A672: 20 9B A6        JSR     drawSignedVelocityNudge; draw a signed velocity nudge
A675: 9D 23 03        STA     workRam+323,X       ; store axis-1 velocity nudge
A678: AD CA 60        LDA     pokey1+A            ; seed axis-0 velocity low from a random source
A67B: 9D E3 02        STA     workRam+2E3,X       ; store axis-0 velocity low
A67E: 20 9B A6        JSR     drawSignedVelocityNudge; draw a nudge for the middle axis
A681: 30 05           BMI     loc_a688            ; force the middle nudge non-positive
A683: 49 FF           EOR     #$FF                
A685: 18              CLC                         
A686: 69 01           ADC     #$01                

loc_a688:
A688: 9D 43 03        STA     workRam+343,X       ; store axis-0 velocity nudge
A68B: AD CA 60        LDA     pokey1+A            ; seed axis-2 velocity low from a random source
A68E: 9D 03 03        STA     workRam+303,X       ; store axis-2 velocity low
A691: 20 9B A6        JSR     drawSignedVelocityNudge; draw a nudge for axis 2
A694: 9D 63 03        STA     workRam+363,X       ; store axis-2 velocity nudge
A697: 20 C1 CC        JSR     gateSound1f         ; cue the spawn sound for this slot

loc_a69a:
A69A: 60              RTS                         

; produce a signed random velocity nudge: take a 3-bit magnitude ($60DA &
; 0x07, 0..7) and negate it when the caller's incoming value has bit0 set,
; yielding a signed step in [-7,+7] consumed by spawnEnemyInSlot as a
; whole-velocity seed.
drawSignedVelocityNudge:
A69B: 4A              LSR     A                   ; shift the caller's low bit into carry to pick the sign
A69C: AD DA 60        LDA     pokey2+A            ; take a 3-bit random magnitude, 0..7
A69F: 29 07           AND     #$07                
A6A1: 90 05           BCC     loc_a6a8            ; keep it positive when the selector bit was clear
A6A3: 49 FF           EOR     #$FF                ; two's-complement negate for a negative step
A6A5: 18              CLC                         
A6A6: 69 01           ADC     #$01                

loc_a6a8:
A6A8: 60              RTS                         

; integrate slot x's three free-flight axes: fold each low velocity
; (loc_2e3/2c3/303,x) into its fraction (loc_223/203/243,x) and add
; carry+signed whole (loc_343/323/363,x) into a whole coordinate,
; committing loc_263,x and loc_2a3,x and loc_283,x, but forcing loc_283,x
; to 0 (retiring the slot) if any axis crosses the tube ring [0x10,0xf0).
advanceEnemyFreeFlight:
A6A9: BD E3 02        LDA     workRam+2E3,X       ; axis 0: fold velocity-low into the fraction
A6AC: 18              CLC                         
A6AD: 7D 23 02        ADC     workRam+223,X       ; 
A6B0: 9D 23 02        STA     workRam+223,X       ; store the axis-0 fraction
A6B3: BD 43 03        LDA     workRam+343,X       ; branch on the axis-0 velocity sign
A6B6: 30 0C           BMI     loc_a6c4            ; 
A6B8: 7D 83 02        ADC     workRam+283,X       ; rising axis: add the whole, overflow at the far rim
A6BB: C9 F0           CMP     #$F0                
A6BD: 90 02           BCC     loc_a6c1            ; 
A6BF: A9 00           LDA     #$00                ; off the ring -> zero

loc_a6c1:
A6C1: B8              CLV                         
A6C2: 50 09           BVC     loc_a6cd            ; 

loc_a6c4:
A6C4: 7D 83 02        ADC     workRam+283,X       ; falling axis: add the whole, overflow below the near rim
A6C7: C9 10           CMP     #$10                
A6C9: B0 02           BCS     loc_a6cd            ; 
A6CB: A9 00           LDA     #$00                ; off the ring -> zero

loc_a6cd:
A6CD: A8              TAY                         ; hold the axis-0 whole
A6CE: BD C3 02        LDA     workRam+2C3,X       ; axis 1: fold velocity-low into the fraction
A6D1: 18              CLC                         
A6D2: 7D 03 02        ADC     workRam+203,X       ; 
A6D5: 9D 03 02        STA     workRam+203,X       ; store the axis-1 fraction
A6D8: BD 23 03        LDA     workRam+323,X       ; branch on the axis-1 velocity sign
A6DB: 30 0C           BMI     loc_a6e9            ; 
A6DD: 7D 63 02        ADC     workRam+263,X       ; rising axis: add the whole, overflow at the far rim
A6E0: C9 F0           CMP     #$F0                
A6E2: 90 02           BCC     loc_a6e6            ; 
A6E4: A0 00           LDY     #$00                ; overflow retires the slot -- zero the shared whole

loc_a6e6:
A6E6: B8              CLV                         
A6E7: 50 09           BVC     loc_a6f2            ; 

loc_a6e9:
A6E9: 7D 63 02        ADC     workRam+263,X       ; falling axis: add the whole, overflow below the near rim
A6EC: C9 10           CMP     #$10                
A6EE: B0 02           BCS     loc_a6f2            ; 
A6F0: A0 00           LDY     #$00                ; overflow retires the slot

loc_a6f2:
A6F2: 9D 63 02        STA     workRam+263,X       ; commit the axis-1 whole position
A6F5: BD 03 03        LDA     workRam+303,X       ; axis 2: fold velocity-low into the fraction
A6F8: 18              CLC                         
A6F9: 7D 43 02        ADC     workRam+243,X       ; 
A6FC: 9D 43 02        STA     workRam+243,X       ; store the axis-2 fraction
A6FF: BD 63 03        LDA     workRam+363,X       ; branch on the axis-2 velocity sign
A702: 30 0C           BMI     loc_a710            ; 
A704: 7D A3 02        ADC     workRam+2A3,X       ; rising axis: add the whole, overflow at the far rim
A707: C9 F0           CMP     #$F0                
A709: 90 02           BCC     loc_a70d            ; 
A70B: A0 00           LDY     #$00                ; overflow retires the slot

loc_a70d:
A70D: B8              CLV                         
A70E: 50 09           BVC     loc_a719            ; 

loc_a710:
A710: 7D A3 02        ADC     workRam+2A3,X       ; falling axis: add the whole, overflow below the near rim
A713: C9 10           CMP     #$10                
A715: B0 02           BCS     loc_a719            ; 
A717: A0 00           LDY     #$00                ; overflow retires the slot

loc_a719:
A719: 9D A3 02        STA     workRam+2A3,X       ; commit the axis-2 whole position
A71C: 98              TYA                         
A71D: 9D 83 02        STA     workRam+283,X       ; stamp the axis-0 whole into the shared slot cell -- zero here retires the slot
A720: 60              RTS                         

; decay slot x's free-flight velocities: seed saturation counter
; loc_29=0xfd, step each velocity pair (loc_2c3/323, loc_2e3/343,
; loc_303/363,x) one increment toward zero via stepVelocityTowardZero, and
; clear the slot's whole coordinate loc_283,x only when all three axes
; saturate in the same frame.
decayEnemyFreeFlightVelocity:
A721: A9 FD           LDA     #$FD                ; seed the saturation counter
A723: 85 29           STA     workRam+29          ; 
A725: BD C3 02        LDA     workRam+2C3,X       ; axis 1: read the velocity pair
A728: BC 23 03        LDY     workRam+323,X       ; 
A72B: 20 5D A7        JSR     stepVelocityTowardZero; step it one increment toward zero
A72E: 9D C3 02        STA     workRam+2C3,X       ; store the stepped axis-1 low byte
A731: 98              TYA                         
A732: 9D 23 03        STA     workRam+323,X       ; store the stepped axis-1 whole byte
A735: BD E3 02        LDA     workRam+2E3,X       ; axis 0: read the velocity pair
A738: BC 43 03        LDY     workRam+343,X       ; 
A73B: 20 5D A7        JSR     stepVelocityTowardZero; step it toward zero
A73E: 9D E3 02        STA     workRam+2E3,X       ; store the stepped axis-0 low byte
A741: 98              TYA                         
A742: 9D 43 03        STA     workRam+343,X       ; store the stepped axis-0 whole byte
A745: BD 03 03        LDA     workRam+303,X       ; axis 2: read the velocity pair
A748: BC 63 03        LDY     workRam+363,X       ; 
A74B: 20 5D A7        JSR     stepVelocityTowardZero; step it toward zero
A74E: 9D 03 03        STA     workRam+303,X       ; store the stepped axis-2 low byte
A751: 98              TYA                         
A752: 9D 63 03        STA     workRam+363,X       ; store the stepped axis-2 whole byte
A755: A5 29           LDA     workRam+29          ; only when every axis reached rest
A757: D0 03           BNE     loc_a75c            ; 
A759: 9D 83 02        STA     workRam+283,X       ; retire the slot -- clear its coordinate

loc_a75c:
A75C: 60              RTS                         

; step one signed 16-bit velocity (whole:low) one fixed increment $A788
; (0x20) toward zero (add when whole negative, subtract otherwise),
; snapping low loc_2a=0 and bumping saturation counter loc_29 on crossing
; zero; returns the stepped [low,whole].
stepVelocityTowardZero:
A75D: 84 2B           STY     workRam+2B          ; snapshot the incoming whole byte
A75F: 24 2B           BIT     workRam+2B          ; branch on the velocity sign
A761: 30 0F           BMI     loc_a772            ; 
A763: 38              SEC                         ; non-negative: subtract the fixed decay step
A764: ED 88 A7        SBC     rom+1788            ; 
A767: 85 2A           STA     workRam+2A          ; store the stepped low byte
A769: A5 2B           LDA     workRam+2B          ; borrow from the whole byte
A76B: E9 00           SBC     #$00                
A76D: 90 0F           BCC     loc_a77e            ; below zero -> zero crossing
A76F: B8              CLV                         
A770: 50 12           BVC     loc_a784            ; 

loc_a772:
A772: 18              CLC                         ; negative: add the fixed decay step
A773: 6D 88 A7        ADC     rom+1788            ; 
A776: 85 2A           STA     workRam+2A          ; store the stepped low byte
A778: A5 2B           LDA     workRam+2B          ; carry into the whole byte
A77A: 69 00           ADC     #$00                
A77C: 90 06           BCC     loc_a784            ; past the ceiling -> zero crossing

loc_a77e:
A77E: E6 29           INC     workRam+29          ; count this axis as saturated
A780: A9 00           LDA     #$00                ; snap the velocity to zero
A782: 85 2A           STA     workRam+2A          ; 

loc_a784:
A784: A8              TAY                         
A785: A5 2A           LDA     workRam+2A          ; 
A787: 60              RTS                         

; ---- $A788-$A788: data ----
A788: 20

; slot-arm init leaf: zero the sixteen-byte per-slot state/flags table
; loc_283..loc_283+0x0f, then re-seed scalars loc_10e=0x20, loc_10d=0x20,
; loc_1=0x04, loc_68=0 and loc_69=0.
resetPerSlotStateTable:
A789: A2 0F           LDX     #$0F                ; start at the top of the per-slot flags table

loc_a78b:
A78B: A9 00           LDA     #$00                
A78D: 9D 83 02        STA     workRam+283,X       ; clear this slot's flags
A790: CA              DEX                         ; next slot
A791: 10 F8           BPL     loc_a78b            ; 
A793: A9 20           LDA     #$20                ; reset the spawn budget
A795: 8D 0E 01        STA     workRam+10E         ; 
A798: 8D 0D 01        STA     workRam+10D         ; reset the spawn-found flag
A79B: A9 04           LDA     #$04                
A79D: 85 01           STA     workRam+1           ; reset the mode selector
A79F: A9 00           LDA     #$00                ; clear the projection X offset low
A7A1: 85 68           STA     workRam+68          ; 
A7A3: 85 69           STA     workRam+69          ; clear the projection X offset high
A7A5: 60              RTS                         

; shared signed segment-distance helper: compute A minus Y, stash it in
; loc_2a, then keep the full byte when loc_111 bit7 is set, else mask to
; the low nibble and sign-extend bit3 into a signed byte.
signedSegmentDelta:
A7A6: 84 2A           STY     workRam+2A          ; stash the target segment
A7A8: 38              SEC                         
A7A9: E5 2A           SBC     workRam+2A          ; compute the signed segment distance
A7AB: 85 2A           STA     workRam+2A          ; store the raw difference
A7AD: 2C 11 01        BIT     workRam+111         ; test the tube-geometry flag
A7B0: 30 09           BMI     loc_a7bb            ; open tube -> keep the full signed byte
A7B2: 29 0F           AND     #$0F                ; closed tube -> take the low nibble
A7B4: 2C BC A7        BIT     rom+17BC            ; test the nibble sign bit
A7B7: F0 02           BEQ     loc_a7bb            ; 
A7B9: 09 F8           ORA     #$F8                ; sign-extend the nibble into a signed byte

loc_a7bb:
A7BB: 60              RTS                         

; ---- $A7BC-$A7BC: data ----
A7BC: 08

; rebuild the spike table: zero the 8-byte array loc_3fe (7..0), stamp its
; last slot loc_405=0xf0, and arm the remap reference loc_115=0xff.
rebuildSpikeTable:
A7BD: A2 07           LDX     #$07                ; walk the eight-lane spike table
A7BF: A9 00           LDA     #$00                

loc_a7c1:
A7C1: 9D FE 03        STA     workRam+3FE,X       ; clear this lane's spike height
A7C4: CA              DEX                         ; next lane
A7C5: 10 FA           BPL     loc_a7c1            ; 
A7C7: A9 F0           LDA     #$F0                ; stamp the final slot with the sentinel guard
A7C9: 8D 05 04        STA     workRam+405         ; 
A7CC: A9 FF           LDA     #$FF                ; arm the remap-reference guard
A7CE: 8D 15 01        STA     workRam+115         ; 
A7D1: 60              RTS                         

; remap the 8-entry spike table loc_3fe against reference loc_115: entries
; >=0x17 shrink by 7, smaller nonzero entries snap to a rail (0xf0/0) by
; loc_115's sign, a zero entry adopts a neighbour's rail; OR-fold results
; into loc_29, set loc_37=0xff, and clear loc_115 when the whole table has
; collapsed to zero. No-op while loc_115 is zero.
stepSpikeTableCollapse:
A7D2: AD 15 01        LDA     workRam+115         ; no-op while the collapse object is disarmed
A7D5: F0 59           BEQ     loc_a830            ; 
A7D7: A9 00           LDA     #$00                
A7D9: 85 29           STA     workRam+29          ; clear the remaining-height accumulator
A7DB: A2 07           LDX     #$07                ; walk the eight height slots top-down
A7DD: 86 37           STX     workRam+37          ; 

loc_a7df:
A7DF: A6 37           LDX     workRam+37          ; 
A7E1: BD FE 03        LDA     workRam+3FE,X       ; read this lane's spike height
A7E4: F0 18           BEQ     loc_a7fe            ; handle an empty slot separately
A7E6: 38              SEC                         
A7E7: E9 07           SBC     #$07                ; shrink a spike by a fixed step
A7E9: 90 02           BCC     loc_a7ed            ; 
A7EB: C9 10           CMP     #$10                

loc_a7ed:
A7ED: B0 0C           BCS     loc_a7fb            ; tall enough -> keep the shrunk height
A7EF: AC 15 01        LDY     workRam+115         ; short spike: pick a rail from the guard sign
A7F2: 10 05           BPL     loc_a7f9            ; 
A7F4: A9 F0           LDA     #$F0                ; negative guard -> high rail
A7F6: B8              CLV                         
A7F7: 50 02           BVC     loc_a7fb            ; 

loc_a7f9:
A7F9: A9 00           LDA     #$00                ; non-negative guard -> low rail

loc_a7fb:
A7FB: B8              CLV                         
A7FC: 50 20           BVC     loc_a81e            ; 

loc_a7fe:
A7FE: AC 15 01        LDY     workRam+115         ; empty slot stays empty unless the guard is negative
A801: 10 1B           BPL     loc_a81e            ; 
A803: 8A              TXA                         
A804: 18              CLC                         
A805: 69 01           ADC     #$01                ; index the wrap-around next neighbour
A807: C9 08           CMP     #$08                
A809: 90 02           BCC     loc_a80d            ; 
A80B: A9 00           LDA     #$00                ; wrap the top slot back to slot 0

loc_a80d:
A80D: A8              TAY                         
A80E: B9 FE 03        LDA     workRam+3FE,Y       ; read the neighbour's height
A811: F0 0B           BEQ     loc_a81e            ; no neighbour to borrow from
A813: C9 D5           CMP     #$D5                ; neighbour too tall to borrow
A815: B0 05           BCS     loc_a81c            ; 
A817: A9 F0           LDA     #$F0                ; adopt the high rail from the neighbour
A819: B8              CLV                         
A81A: 50 02           BVC     loc_a81e            ; 

loc_a81c:
A81C: A9 00           LDA     #$00                

loc_a81e:
A81E: 9D FE 03        STA     workRam+3FE,X       ; store the new spike height
A821: 05 29           ORA     workRam+29          ; fold into the remaining-height accumulator
A823: 85 29           STA     workRam+29          ; 
A825: C6 37           DEC     workRam+37          ; 
A827: 10 B6           BPL     loc_a7df            ; next slot
A829: A5 29           LDA     workRam+29          ; 
A82B: D0 03           BNE     loc_a830            ; leave the object armed while any height remains
A82D: 8D 15 01        STA     workRam+115         ; table fully collapsed -> disarm the object

loc_a830:
A830: 60              RTS                         

; reset leaf: clear the pair loc_3aa and loc_125 together, where loc_125
; is the block-ready latch set to 0xff elsewhere.
clearReadyLatchPair:
A831: A9 00           LDA     #$00                ; reset the sweep/stage cell
A833: 8D AA 03        STA     workRam+3AA         ; 
A836: 8D 25 01        STA     workRam+125         ; disarm the block-ready latch
A839: 60              RTS                         

; step the attract-mode phase timer (only while 0x5 bit7 set): with 0x125
; running advance it and at the 0x3aa-indexed limit (table $A883) restart
; it and run the sweep sweepLaneSlotsForRespawn; with 0x125 idle, arm the
; next stage (bump 0x3aa, seed 0x125=1) when 0x201 is clear and 0x4e bit3
; is set; every path clears bit7 of 0x4e.
stepAttractEnemySweepTimer:
A83A: A5 05           LDA     workRam+5           ; run only while attract mode is active
A83C: 10 3E           BPL     loc_a87c            ; 
A83E: AD 25 01        LDA     workRam+125         ; phase already running -> advance it
A841: D0 23           BNE     loc_a866            ; 
A843: AD 01 02        LDA     workRam+201         ; idle: arm the next stage only when the control byte is positive
A846: 30 1B           BMI     loc_a863            ; 
A848: A5 4E           LDA     workRam+4E          ; and a trigger edge is present
A84A: 29 08           AND     #$08                
A84C: F0 15           BEQ     loc_a863            ; 
A84E: AD AA 03        LDA     workRam+3AA         ; up to three stages
A851: C9 02           CMP     #$02                
A853: B0 08           BCS     loc_a85d            ; 
A855: EE AA 03        INC     workRam+3AA         ; bump the sweep stage
A858: A9 01           LDA     #$01                ; seed the phase latch
A85A: 8D 25 01        STA     workRam+125         ; 

loc_a85d:
A85D: A5 4E           LDA     workRam+4E          ; consume the trigger edge
A85F: 29 77           AND     #$77                
A861: 85 4E           STA     workRam+4E          ; 

loc_a863:
A863: B8              CLV                         
A864: 50 16           BVC     loc_a87c            ; 

loc_a866:
A866: EE 25 01        INC     workRam+125         ; tick the running phase
A869: AE AA 03        LDX     workRam+3AA         ; the stage indexes the timer-limit table
A86C: AD 25 01        LDA     workRam+125         ; 
A86F: DD 83 A8        CMP     rom+1883,X          ; phase reached its limit?
A872: 90 05           BCC     loc_a879            ; 
A874: A9 00           LDA     #$00                ; phase done -> reset the latch
A876: 8D 25 01        STA     workRam+125         ; 

loc_a879:
A879: 20 88 A8        JSR     sweepLaneSlotsForRespawn; run the lane-respawn sweep

loc_a87c:
A87C: A5 4E           LDA     workRam+4E          ; always clear the input-edge bit7 on the way out
A87E: 29 7F           AND     #$7F                
A880: 85 4E           STA     workRam+4E          ; 
A882: 60              RTS                         

; ---- $A883-$A887: data ----
A883: 00 13 05 00 00

; on the timed phase (phase cell 0x125 >= 3 and even) sweep 0x2df,y
; downward from y=0x11c for the first nonzero slot: on a hit clear the low
; two bits of 0x28a,y and tail-delegate to respawnEnemyAndAward, on no hit
; reset 0x125 to 0.
sweepLaneSlotsForRespawn:
A888: AD 25 01        LDA     workRam+125         ; only sweep once the phase reaches 3
A88B: C9 03           CMP     #$03                
A88D: 90 14           BCC     loc_a8a3            ; 
A88F: 29 01           AND     #$01                ; and only on even phases
A891: D0 10           BNE     loc_a8a3            ; 
A893: AC 1C 01        LDY     workRam+11C         ; scan the slots from the top

loc_a896:
A896: B9 DF 02        LDA     workRam+2DF,Y       ; first occupied lane is the hit
A899: D0 09           BNE     loc_a8a4            ; 
A89B: 88              DEY                         ; keep scanning
A89C: 10 F8           BPL     loc_a896            ; 
A89E: A9 00           LDA     #$00                ; nothing occupied -> mark the phase done
A8A0: 8D 25 01        STA     workRam+125         ; 

loc_a8a3:
A8A3: 60              RTS                         

loc_a8a4:
A8A4: B9 8A 02        LDA     workRam+28A,Y       ; clear the found slot's low two direction bits
A8A7: 29 FC           AND     #$FC                
A8A9: 99 8A 02        STA     workRam+28A,Y       ; store back the masked direction
A8AC: 4C 98 A3        JMP     respawnEnemyAndAward; respawn the enemy and award its points

; ---- $A8AF-$A8B3: data ----
A8AF: E1 24 26 28 2A

; compose the per-frame text/marker overlay into buffer 0x2f60: mirror
; control 0x72, refresh the header (emitColorStatIfChanged), and when 0x5
; bit7 is clear pick a marker slot, draw it
; (drawSlotShapeRecord/emitFixedVectorWord) and duplicate a glyph snapshot
; into 0x2fa6/0x2fa8; emit glyph strings via
; buildMarkerRowVectorList/buildTextBufferDigitString; off the safe mode
; (0x0!=0x04) rebuild checksum 0x16c and a 3-entry mirror; then emit the
; framing word (emitCoordinateVectorWord) and, in the active phase, the
; indexed slot pair (0x102) and two trailing markers.
buildTextOverlayList:
A8B4: A9 01           LDA     #$01                ; seed the last-stat cell
A8B6: 85 72           STA     workRam+72          ; 
A8B8: 20 6A DF        JSR     emitBlankVectorWordTag70; lead the list with the blank/tag-70 word
A8BB: A0 05           LDY     #$05                
A8BD: 20 D1 B0        JSR     emitColorStatIfChanged; refresh the colour-stat header
A8C0: A5 05           LDA     workRam+5           ; skip the live-glyph block in attract mode
A8C2: 30 26           BMI     loc_a8ea            ; 
A8C4: A2 00           LDX     #$00                
A8C6: A5 03           LDA     workRam+3           ; pick the marker shape from the frame phase
A8C8: 29 20           AND     #$20                
A8CA: D0 0C           BNE     loc_a8d8            ; frame bit set -> first marker shape
A8CC: A2 22           LDX     #$22                
A8CE: A5 06           LDA     workRam+6           ; 
A8D0: F0 06           BEQ     loc_a8d8            ; idle phase -> alternate marker shape
A8D2: 24 A2           BIT     workRam+A2          ; or the phase sign flag -> alternate marker shape
A8D4: 30 02           BMI     loc_a8d8            ; 
A8D6: A2 06           LDX     #$06                

loc_a8d8:
A8D8: 20 14 AB        JSR     drawSlotShapeRecord ; draw the chosen marker slot shape
A8DB: 20 0D AB        JSR     emitFixedVectorWord ; emit the fixed vector word
A8DE: AD E4 31        LDA     vectorRom+1E4       ; read the glyph snapshot
A8E1: 8D A6 2F        STA     vectorRam+FA6       ; mirror it into the first snapshot cell
A8E4: 8D A8 2F        STA     vectorRam+FA8       ; and the second snapshot cell

; compose one frame's full display list: draw the base overlay
; (drawOverlayFrame), emit the base list (buildMarkerRowVectorList) and —
; when the flag source is live (loc_3e while loc_5 bit7 set, else
; loc_43|loc_44|loc_45) — a second list; unless loc_00==0x04 rebuild the
; self-check byte loc_16c by XOR-folding 0xa7 over eleven bytes at $AACE
; and rebuild the strided mirror $2F60 from loc_61b through $31FA; emit
; framing word emitCoordinateVectorWord, draw slot 0x36 when loc_123 bit7
; set, and when loc_00==0x18 with loc_5 bit7 set draw slots 0x30 (+numeric
; run) / 0x3a / 0x38.
composeFrameDisplayList:
A8E7: 20 A8 AA        JSR     drawOverlayFrame    ; draw the recurring base overlay

loc_a8ea:
A8EA: A9 01           LDA     #$01                
A8EC: A0 00           LDY     #$00                
A8EE: 20 7F A9        JSR     buildMarkerRowVectorList; emit the first marker row
A8F1: 24 05           BIT     workRam+5           ; attract mode -> gate the second row on the active-slot count
A8F3: 30 09           BMI     loc_a8fe            ; 
A8F5: A5 43           LDA     workRam+43          ; else gate on the score/status flags
A8F7: 05 44           ORA     workRam+44          ; 
A8F9: 05 45           ORA     workRam+45          ; 
A8FB: B8              CLV                         
A8FC: 50 02           BVC     loc_a900            ; 

loc_a8fe:
A8FE: A5 3E           LDA     workRam+3E          ; attract: the active-slot count

loc_a900:
A900: F0 06           BEQ     loc_a908            ; no gate -> skip the second row
A902: A9 01           LDA     #$01                
A904: A8              TAY                         
A905: 20 7F A9        JSR     buildMarkerRowVectorList; emit the second marker row

loc_a908:
A908: A5 00           LDA     workRam             ; mode 4 skips the text and self-check rebuild
A90A: C9 04           CMP     #$04                
A90C: F0 35           BEQ     loc_a943            ; 
A90E: A9 1D           LDA     #$1D                ; aim the work pointer at the glyph source
A910: 85 3B           STA     workRam+3B          ; 
A912: A9 07           LDA     #$07                
A914: 85 3C           STA     workRam+3C          ; 
A916: AE E4 CD        LDX     rom+3DE4            ; load the glyph-buffer offset
A919: 20 D7 A9        JSR     buildTextBufferDigitString; build the digit/glyph text buffer
A91C: A0 0A           LDY     #$0A                
A91E: A9 A7           LDA     #$A7                ; seed the self-check fold

loc_a920:
A920: 59 CE AA        EOR     rom+1ACE,Y          ; XOR-fold the self-check byte table
A923: 88              DEY                         
A924: 10 FA           BPL     loc_a920            ; 
A926: 8D 6C 01        STA     workRam+16C         ; publish the self-check byte
A929: AE E5 CD        LDX     rom+3DE5            ; load the mirror destination offset
A92C: A9 02           LDA     #$02                ; three doubled glyph entries
A92E: 85 38           STA     workRam+38          ; 

loc_a930:
A930: A4 38           LDY     workRam+38          ; 
A932: B9 1B 06        LDA     workRam+61B,Y       ; index the glyph through the source table
A935: 0A              ASL     A                   ; double the index
A936: A8              TAY                         
A937: B9 FA 31        LDA     vectorRom+1FA,Y     ; read the glyph word
A93A: 9D 60 2F        STA     vectorRam+F60,X     ; copy it into the strided mirror
A93D: E8              INX                         
A93E: E8              INX                         
A93F: C6 38           DEC     workRam+38          ; next entry
A941: 10 ED           BPL     loc_a930            ; 

loc_a943:
A943: A9 2F           LDA     #$2F                
A945: A2 60           LDX     #$60                
A947: 20 39 DF        JSR     emitCoordinateVectorWord; post the framing coordinate word
A94A: AD 23 01        LDA     workRam+123         ; spiked-segment marker only when its high bit is set
A94D: 10 05           BPL     loc_a954            ; 
A94F: A2 36           LDX     #$36                
A951: 20 14 AB        JSR     drawSlotShapeRecord ; draw the spike marker slot

loc_a954:
A954: A5 00           LDA     workRam             ; 
A956: C9 18           CMP     #$18                ; tail decorations only in mode 0x18
A958: D0 22           BNE     loc_a97c            ; 
A95A: A5 05           LDA     workRam+5           ; 
A95C: 10 1E           BPL     loc_a97c            ; and only with the status flag set
A95E: A6 3D           LDX     workRam+3D          ; 
A960: BD 02 01        LDA     workRam+102,X       ; draw only when the current slot is live
A963: F0 0D           BEQ     loc_a972            ; 
A965: A2 30           LDX     #$30                
A967: 20 14 AB        JSR     drawSlotShapeRecord ; draw the labelled slot record
A96A: A4 3D           LDY     workRam+3D          ; 
A96C: BE 02 01        LDX     workRam+102,Y       ; 
A96F: 20 C6 B0        JSR     emitTableValueDigitRun; emit its numeric value run

loc_a972:
A972: A2 3A           LDX     #$3A                
A974: 20 14 AB        JSR     drawSlotShapeRecord ; draw a trailing marker slot
A977: A2 38           LDX     #$38                
A979: 20 14 AB        JSR     drawSlotShapeRecord ; draw a trailing marker slot

loc_a97c:
A97C: 60              RTS                         

; ---- $A97D-$A97E: data ----
A97D: 42 45

; Lays a header byte at $2F60 (zeroed only when row index y equals the
; marker loc_3d with loc_5 bit7 set), fills seven glyph entries from
; $3284/$3286, and unless state 4 away from the marker seeds the glyph
; pointer loc_3b/loc_3c and hands off to the digit-string builder.
buildMarkerRowVectorList:
A97F: A6 00           LDX     workRam             ; 
A981: E0 04           CPX     #$04                
A983: 84 2B           STY     workRam+2B          ; stash the row index in scratch
A985: C4 3D           CPY     workRam+3D          ; is this row the currently selected marker
A987: D0 06           BNE     loc_a98f            ; skip the head-blank unless this is the marker row
A989: 24 05           BIT     workRam+5           ; test the marker-active flag
A98B: 10 02           BPL     loc_a98f            ; skip unless the marker-active bit is set
A98D: A9 00           LDA     #$00                ; blank the row head so the highlight can overdraw it

loc_a98f:
A98F: 09 70           ORA     #$70                ; or in the 0x70 vector tag for the head word
A991: BE DE CD        LDX     rom+3DDE,Y          ; head-word offset for this row
A994: 9D 60 2F        STA     vectorRam+F60,X     ; write the head word into the glyph buffer
A997: BE E0 CD        LDX     rom+3DE0,Y          ; glyph-slot offset for this row
A99A: B9 48 00        LDA     workRam+48,Y        ; the row's filled-tick count
A99D: 85 38           STA     workRam+38          ; seed the count cursor
A99F: F0 06           BEQ     loc_a9a7            ; skip the dock when the count is zero
A9A1: C4 3D           CPY     workRam+3D          ; is this the selected marker row
A9A3: D0 02           BNE     loc_a9a7            ; skip unless the marker row
A9A5: C6 38           DEC     workRam+38          ; the selected row shows one fewer filled tick

loc_a9a7:
A9A7: A0 01           LDY     #$01                ; start at glyph slot 1

loc_a9a9:
A9A9: AD 84 32        LDA     vectorRom+284       ; filled-tick glyph
A9AC: C4 38           CPY     workRam+38          ; slot index vs the tick count
A9AE: 90 05           BCC     loc_a9b5            ; slots up to the count get the filled glyph
A9B0: F0 03           BEQ     loc_a9b5            ; the slot at the count gets the filled glyph too
A9B2: AD 86 32        LDA     vectorRom+286       ; past the count -- the empty-tick glyph

loc_a9b5:
A9B5: 9D 60 2F        STA     vectorRam+F60,X     ; write the glyph into the buffer
A9B8: E8              INX                         ; advance two bytes -- one glyph plus its tag
A9B9: E8              INX                         
A9BA: C8              INY                         ; next slot
A9BB: C0 07           CPY     #$07                ; seven glyph slots per row
A9BD: 90 EA           BCC     loc_a9a9            ; loop the seven slots
A9BF: A4 2B           LDY     workRam+2B          ; restore the row index
A9C1: A5 00           LDA     workRam             ; read the game mode
A9C3: C9 04           CMP     #$04                ; mode 4
A9C5: D0 04           BNE     loc_a9cb            ; otherwise go on to emit the number
A9C7: C4 3D           CPY     workRam+3D          ; is this the marker row
A9C9: D0 30           BNE     loc_a9fb            ; in mode 4, skip the number on non-marker rows

loc_a9cb:
A9CB: BE E2 CD        LDX     rom+3DE2,Y          ; buffer cursor for this row's digit string
A9CE: B9 7D A9        LDA     rom+197D,Y          ; low byte of the digit-source pointer
A9D1: 85 3B           STA     workRam+3B          ; seat the source pointer low
A9D3: A9 00           LDA     #$00                
A9D5: 85 3C           STA     workRam+3C          ; source high byte 0 -- the digits live in zero page

; Walks three source bytes backward (pointer loc_3b/loc_3c) emitting each
; byte's high then low nibble into the text buffer, threading carry so
; only the final low nibble on the last pass sees it cleared.
buildTextBufferDigitString:
A9D7: A0 02           LDY     #$02                ; three digit passes -- two down to zero
A9D9: 84 2A           STY     workRam+2A          ; seat the pass counter
A9DB: 38              SEC                         ; set the leading-zero suppress flag

loc_a9dc:
A9DC: 08              PHP                         ; carry the suppress flag across the high-nibble emit
A9DD: A0 00           LDY     #$00                
A9DF: B1 3B           LDA     (workRam+3B),Y      ; read the packed-BCD source byte
A9E1: 4A              LSR     A                   ; shift the high nibble down
A9E2: 4A              LSR     A                   
A9E3: 4A              LSR     A                   
A9E4: 4A              LSR     A                   
A9E5: 28              PLP                         ; restore the suppress carry
A9E6: 20 FC A9        JSR     writeNibbleGlyphToTextBuffer; emit the high-nibble digit glyph
A9E9: A5 2A           LDA     workRam+2A          ; the pass counter
A9EB: D0 01           BNE     loc_a9ee            ; on the final pass only
A9ED: 18              CLC                         ; clear suppress so the units digit always prints

loc_a9ee:
A9EE: A0 00           LDY     #$00                
A9F0: B1 3B           LDA     (workRam+3B),Y      ; reread the byte for its low nibble
A9F2: 20 FC A9        JSR     writeNibbleGlyphToTextBuffer; emit the low-nibble digit glyph
A9F5: C6 3B           DEC     workRam+3B          ; walk the source pointer back one byte
A9F7: C6 2A           DEC     workRam+2A          ; count down the passes
A9F9: 10 E1           BPL     loc_a9dc            ; loop until the pass counter underflows

loc_a9fb:
A9FB: 60              RTS                         

; Maps A's low nibble to a stroke byte from ROM table $31E4, stores it at
; the text-buffer cursor $2F60+x, and advances x by two.
writeNibbleGlyphToTextBuffer:
A9FC: 29 0F           AND     #$0F                ; take the low nibble -- the digit value
A9FE: A8              TAY                         
A9FF: F0 01           BEQ     loc_aa02            ; is the nibble zero
AA01: 18              CLC                         ; a nonzero digit clears the leading-zero suppress

loc_aa02:
AA02: B0 01           BCS     loc_aa05            ; a suppressed leading zero selects the blank glyph
AA04: C8              INY                         ; otherwise index the digit's own glyph

loc_aa05:
AA05: 08              PHP                         
AA06: 98              TYA                         
AA07: 0A              ASL     A                   ; double the index -- two bytes per glyph entry
AA08: A8              TAY                         
AA09: B9 E4 31        LDA     vectorRom+1E4,Y     ; fetch the digit's stroke byte from the glyph table
AA0C: 9D 60 2F        STA     vectorRam+F60,X     ; store it into the text buffer at the cursor
AA0F: E8              INX                         ; advance the cursor
AA10: E8              INX                         ; past this two-byte glyph
AA11: 28              PLP                         
AA12: 60              RTS                         

; stage a text line into the vector text buffer at 0x2f60: pick a string
; index in loc_3e (forced to 0x01 when loc_5 bit7 is clear and any of
; loc_43/loc_44/loc_45 is set), seat the write cursor loc_74/loc_75 at
; 0x2f60, copy $CE66[index]+1 stroke bytes from ROM $CDE6 into it, and on
; the loc_5 bit7 path also emit the BCD of (loc_9f+1) at 0x2fa6 before
; restoring the cursor low byte and terminating the record via
; emitRecordBodyC0.
stageTextLineWithCount:
AA13: A6 3E           LDX     workRam+3E          ; default template index -- the active slot count
AA15: 24 05           BIT     workRam+5           ; test the status flag bit7
AA17: 30 0A           BMI     loc_aa23            ; bit7 set -- keep the default template
AA19: A5 43           LDA     workRam+43          ; combine the loc_43/44/45 trio
AA1B: 05 44           ORA     workRam+44          ; fold in loc_44
AA1D: 05 45           ORA     workRam+45          ; fold in loc_45
AA1F: F0 02           BEQ     loc_aa23            ; all zero -- keep the default template
AA21: A2 01           LDX     #$01                ; any of the trio set -- force template index 1

loc_aa23:
AA23: A9 60           LDA     #$60                ; write cursor low byte
AA25: 85 74           STA     workRam+74          ; seat the cursor low
AA27: A9 2F           LDA     #$2F                ; write cursor high byte
AA29: 85 75           STA     workRam+75          ; seat the cursor high -- text buffer at 0x2f60
AA2B: BD 66 CE        LDA     rom+3E66,X          ; template byte length from the length table
AA2E: A8              TAY                         
AA2F: 38              SEC                         
AA30: 65 74           ADC     workRam+74          ; length plus cursor low
AA32: 48              PHA                         ; save the post-copy cursor low

loc_aa33:
AA33: B9 E6 CD        LDA     rom+3DE6,Y          ; copy a stroke byte from the template block
AA36: 91 74           STA     (workRam+74),Y      ; into the text buffer
AA38: 88              DEY                         ; step down the copy counter
AA39: D0 F8           BNE     loc_aa33            ; copy the template down toward index 0
AA3B: B9 E6 CD        LDA     rom+3DE6,Y          ; final stroke byte at index 0
AA3E: 91 74           STA     (workRam+74),Y      ; store it
AA40: A5 05           LDA     workRam+5           ; read the status flags
AA42: 10 10           BPL     loc_aa54            ; bit7 clear -- skip the live count
AA44: A9 2F           LDA     #$2F                
AA46: 85 75           STA     workRam+75          ; repoint the cursor high
AA48: A9 A6           LDA     #$A6                
AA4A: 85 74           STA     workRam+74          ; and low -- to the count field at 0x2fa6
AA4C: A5 9F           LDA     workRam+9F          ; the count source
AA4E: 18              CLC                         
AA4F: 69 01           ADC     #$01                ; plus one
AA51: 20 77 AF        JSR     emitByteAsBcdDigits ; render it as BCD digits

loc_aa54:
AA54: 68              PLA                         ; restore the saved cursor low
AA55: 85 74           STA     workRam+74          ; seat it back
AA57: 4C 09 DF        JMP     emitRecordBodyC0    ; tail into the record-close emitter

; compose a frame led by slot 0x08: draw slot 0x08 (drawSlotShapeRecord)
; then chain prepCountThenComposeFrame (the count prep followed by the
; per-frame composition $A8E7).
drawFrameWithSlot08:
AA5A: A2 08           LDX     #$08                ; lead this frame with draw slot 0x08
AA5C: 20 14 AB        JSR     drawSlotShapeRecord ; draw that slot's shape record
AA5F: 4C 69 AA        JMP     prepCountThenComposeFrame; chain into the shared count-prep and frame composer

; compose a frame led by slot 0x00: prime slot 0x00 with header 0x30
; (drawSlotShapeWithHeader), run the shared count prep
; (drawSlotThenDigitRun), then dispatch the per-frame composition $A8E7.
drawFrameWithSlot00:
AA62: A9 30           LDA     #$30                ; header/colour seed 0x30
AA64: A2 00           LDX     #$00                ; lead with slot 0x00 -- the player/overlay slot
AA66: 20 17 AB        JSR     drawSlotShapeWithHeader; draw that slot's record with the given header

; run the shared count prep (drawSlotThenDigitRun: slot 0x02 + digit run)
; then dispatch the per-frame composition $A8E7.
prepCountThenComposeFrame:
AA69: 20 92 AA        JSR     drawSlotThenDigitRun; count prep -- draw slot 0x02 and lay the digit run
AA6C: 4C E7 A8        JMP     composeFrameDisplayList; hand to the per-frame composition driver

; run the alternate per-frame composition $A8B4, then prime slot 0x06
; through the shared entry drawSlotShapeWithHeader (header 0x00).
composeFrameThenDrawSlot06:
AA6F: 20 B4 A8        JSR     buildTextOverlayList; build the alternate text-overlay display list
AA72: A9 00           LDA     #$00                ; header seed 0x00
AA74: A2 06           LDX     #$06                ; slot 0x06
AA76: 4C 17 AB        JMP     drawSlotShapeWithHeader; append slot 0x06's shape record

; compose a frame led by slot 0x32: draw slot 0x32
; (drawSlotShapeWithHeader, header 0x00), add a second draw of slot 0x22
; (header 0xe0) only while status nibble loc_3&0x1f < 0x10, then finish
; with the alternate per-frame composition $A8B4.
drawFrameWithSlot32:
AA79: A9 00           LDA     #$00                ; header seed 0x00
AA7B: A2 32           LDX     #$32                ; lead with slot 0x32
AA7D: 20 17 AB        JSR     drawSlotShapeWithHeader; draw that slot's shape record
AA80: A5 03           LDA     workRam+3           ; read the frame counter
AA82: 29 1F           AND     #$1F                ; low bits of the 32-frame cycle
AA84: C9 10           CMP     #$10                ; in the first sixteen frames
AA86: B0 07           BCS     loc_aa8f            ; skip the blink slot in the second half
AA88: A9 E0           LDA     #$E0                ; header seed 0xe0
AA8A: A2 22           LDX     #$22                ; slot 0x22
AA8C: 20 17 AB        JSR     drawSlotShapeWithHeader; draw it only in the first half -- a 50%-duty blink

loc_aa8f:
AA8F: 4C B4 A8        JMP     buildTextOverlayList; finish through the text-overlay builder

; draw fixed slot 0x02 (drawSlotShapeRecord) then hand off to
; emitCountDigitRun to lay down the numeric run.
drawSlotThenDigitRun:
AA92: A2 02           LDX     #$02                ; the fixed slot 0x02
AA94: 20 14 AB        JSR     drawSlotShapeRecord ; draw its shape record

; publish a zero scale header (emitScaleWordIfChanged with 0x00) then emit
; the one-byte digit run for the slot named by loc_3d via $AA9E, drawing
; the count as nibble digits.
emitCountDigitRun:
AA97: A9 00           LDA     #$00                ; baseline scale value 0
AA99: 20 DD B0        JSR     emitScaleWordIfChanged; set the vector scale to baseline, emit only if it changed
AA9C: A6 3D           LDX     workRam+3D          ; the slot index named by loc_3d

; Increments the slot index, publishes it into loc_61, and emits that
; single byte as a digit run.
emitSlotIndexDigit:
AA9E: E8              INX                         ; advance the slot index by one
AA9F: 86 61           STX     workRam+61          ; publish it into the byte the digit emitter reads
AAA1: A9 61           LDA     #$61                ; source pointer 0x61
AAA3: A0 01           LDY     #$01                ; one byte long
AAA5: 4C B1 DF        JMP     emitNibbleDigitRun  ; emit it as a single digit glyph

; the recurring per-frame overlay driver: draw the phase-selected slot
; from ROM table $A8B0[loc_9&0x03], tick timer loc_16e down, draw either
; alternate slot 0x32 (when loc_a bit0 set and loc_3 bit5 clear) or defer
; to computeDisplayListChecksum, always redraw marker slot 0x2c and slot
; 0x2e, clamp loc_6 to ceiling 0x28 and emit it via emitByteAsBcdDigits,
; and when loc_17 is nonzero post a final coordinate word
; (emitCoordinateVectorWord from $AAF4/$AAF3).
drawOverlayFrame:
AAA8: A5 09           LDA     workRam+9           ; the config-switch snapshot
AAAA: 29 03           AND     #$03                ; its low two bits
AAAC: AA              TAX                         
AAAD: BD B0 A8        LDA     rom+18B0,X          ; pick a shape id from the slot table
AAB0: AA              TAX                         
AAB1: 20 14 AB        JSR     drawSlotShapeRecord ; draw the config-selected slot
AAB4: CE 6E 01        DEC     workRam+16E         ; tick down the display countdown timer
AAB7: A5 0A           LDA     workRam+A           ; the second config snapshot
AAB9: 29 01           AND     #$01                ; bit 0
AABB: F0 0E           BEQ     loc_aacb            ; clear -- spend the slot on the checksum instead
AABD: A5 03           LDA     workRam+3           ; the frame counter
AABF: 29 20           AND     #$20                ; phase-gate bit
AAC1: D0 08           BNE     loc_aacb            ; gate high -- take the checksum path
AAC3: A2 32           LDX     #$32                ; alternate slot 0x32
AAC5: 20 14 AB        JSR     drawSlotShapeRecord ; draw it
AAC8: B8              CLV                         
AAC9: 50 03           BVC     loc_aace            ; skip the checksum this frame

loc_aacb:
AACB: 20 CA AE        JSR     computeDisplayListChecksum; recompute the display-list checksum

loc_aace:
AACE: A2 2C           LDX     #$2C                ; fixed marker slot 0x2c
AAD0: 20 14 AB        JSR     drawSlotShapeRecord ; draw it
AAD3: A2 2E           LDX     #$2E                ; fixed marker slot 0x2e
AAD5: 20 14 AB        JSR     drawSlotShapeRecord ; draw it
AAD8: A5 06           LDA     workRam+6           ; the level/phase index
AADA: C9 28           CMP     #$28                ; against its ceiling 0x28
AADC: 90 04           BCC     loc_aae2            ; below -- keep it
AADE: A9 28           LDA     #$28                ; clamp to 0x28
AAE0: 85 06           STA     workRam+6           ; store the clamp

loc_aae2:
AAE2: 20 77 AF        JSR     emitByteAsBcdDigits ; draw the phase index as BCD digits
AAE5: A5 17           LDA     workRam+17          ; the heartbeat accumulator high byte
AAE7: F0 09           BEQ     loc_aaf2            ; skip the trailing word while it is zero
AAE9: AD F4 AA        LDA     rom+1AF4            ; trailing coordinate word, low byte
AAEC: AE F3 AA        LDX     rom+1AF3            ; and high byte
AAEF: 20 39 DF        JSR     emitCoordinateVectorWord; post it into the display list

loc_aaf2:
AAF2: 60              RTS                         

; ---- $AAF3-$AAF4: data ----
AAF3: 5C 32

; Converts the binary byte in A to packed BCD by double-dabble and writes
; the packed digits into loc_29 and loc_2c.
packBinaryToBcd:
AAF5: F8              SED                         ; switch the CPU to decimal mode for the conversion
AAF6: 85 29           STA     workRam+29          ; the binary byte to convert
AAF8: A9 00           LDA     #$00                
AAFA: 85 2C           STA     workRam+2C          ; clear the BCD accumulator
AAFC: A0 07           LDY     #$07                ; eight double-dabble passes

loc_aafe:
AAFE: 06 29           ASL     workRam+29          ; shift the top bit out of the source byte
AB00: A5 2C           LDA     workRam+2C          ; load the accumulator
AB02: 65 2C           ADC     workRam+2C          ; double it in decimal, folding in that bit
AB04: 85 2C           STA     workRam+2C          ; back to the accumulator
AB06: 88              DEY                         
AB07: 10 F5           BPL     loc_aafe            ; repeat for all eight bits
AB09: D8              CLD                         ; back to binary mode
AB0A: 85 29           STA     workRam+29          ; publish the packed-BCD result
AB0C: 60              RTS                         

; emit one fixed vector word (bytes 0x20, 0x80) through the draw cursor
; 0x74 and step past it (via $DF57).
emitFixedVectorWord:
AB0D: A9 20           LDA     #$20                ; constant vector word, low byte
AB0F: A2 80           LDX     #$80                ; high byte
AB11: 4C 57 DF        JMP     emitVectorWord      ; emit it through the draw cursor

; draw slot x's object record into the display list at cursor (loc_74):
; latch slot into loc_35, take the colour/header seed from ROM $D122+x
; into loc_2b, load the slot's shape-list pointer from (loc_ac)+x into
; loc_3b/loc_3c (snapshotting cursor into loc_b6/loc_b7 for marker slot
; 0x2c), position via loc_2a, split scale key $D121+loc_35 into
; emitColorStatIfChanged/emitScaleWordIfChanged, then walk the shape list
; copying point pairs from $31E4/$31E5 into (loc_74) until a high-bit
; terminator and close with advanceDisplayCursor.
drawSlotShapeRecord:
AB14: BD 22 D1        LDA     rom+4122,X          ; fetch the slot's colour/header seed from the ROM table

; draw slot x's object record like drawSlotShapeRecord but take the loc_2b
; colour/header seed from the argument a instead of ROM $D122: latch
; loc_35=x, loc_2b=a, load shape-list pointer (loc_ac)+x, set scale from
; $D121, expand the shape list of point pairs ($31E4/$31E5) into (loc_74)
; and close with advanceDisplayCursor.
drawSlotShapeWithHeader:
AB17: 86 35           STX     workRam+35          ; remember the slot index
AB19: 85 2B           STA     workRam+2B          ; the caller's colour/header seed
AB1B: A4 35           LDY     workRam+35          ; 
AB1D: B1 AC           LDA     (workRam+AC),Y      ; shape-list pointer low from the per-slot table
AB1F: 85 3B           STA     workRam+3B          ; seat the pointer low
AB21: C8              INY                         
AB22: B1 AC           LDA     (workRam+AC),Y      ; and the high byte
AB24: 85 3C           STA     workRam+3C          ; seat the pointer high
AB26: E0 2C           CPX     #$2C                ; is this the marker slot 0x2c
AB28: D0 08           BNE     loc_ab32            ; if not, skip the snapshot
AB2A: A5 74           LDA     workRam+74          ; snapshot the draw cursor low so a later pass can find this record
AB2C: 85 B6           STA     workRam+B6          ; store it
AB2E: A5 75           LDA     workRam+75          ; and the cursor high
AB30: 85 B7           STA     workRam+B7          ; store it

loc_ab32:
AB32: A0 00           LDY     #$00                
AB34: B1 3B           LDA     (workRam+3B),Y      ; the record's first byte -- its position
AB36: 85 2A           STA     workRam+2A          ; seat the position
AB38: 20 0D AB        JSR     emitFixedVectorWord ; emit the fixed framing word

; the shared tail all three builders converge on: with
; loc_2a/loc_2b/loc_35 pre-seeded, clear loc_73, set loc_72=0x01, emit the
; intensity header (emitBlankVectorWordTag70) and beam-position record
; (emitScaledCoordinateRecord), reload the shape-list pointer from
; (loc_ac)+loc_35, split scale key $D121+loc_35 into
; emitColorStatIfChanged/emitScaleWordIfChanged, then walk the list
; copying point pairs from $31E4/$31E5 into (loc_74) until a high-bit
; terminator and close with advanceDisplayCursor.
expandShapeListToVectors:
AB3B: A9 00           LDA     #$00                
AB3D: 85 73           STA     workRam+73          ; clear the vector-record header cell
AB3F: A9 01           LDA     #$01                
AB41: 85 72           STA     workRam+72          ; seed the last-status latch = 1
AB43: 20 6A DF        JSR     emitBlankVectorWordTag70; emit the blank/intensity vector header word
AB46: A5 2A           LDA     workRam+2A          ; 
AB48: A6 2B           LDX     workRam+2B          ; 
AB4A: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the scaled beam move to the shape's position
AB4D: A4 35           LDY     workRam+35          ; load this shape's slot index
AB4F: B1 AC           LDA     (workRam+AC),Y      ; read the shape's coordinate-list pointer low from the slot table
AB51: 85 3B           STA     workRam+3B          ; 
AB53: C8              INY                         
AB54: B1 AC           LDA     (workRam+AC),Y      ; read the pointer high from the next table byte
AB56: 85 3C           STA     workRam+3C          ; 
AB58: A6 35           LDX     workRam+35          ; 
AB5A: BD 21 D1        LDA     rom+4121,X          ; read this shape's packed colour/scale key
AB5D: 48              PHA                         
AB5E: 4A              LSR     A                   
AB5F: 4A              LSR     A                   
AB60: 4A              LSR     A                   
AB61: 4A              LSR     A                   
AB62: A8              TAY                         
AB63: 20 D1 B0        JSR     emitColorStatIfChanged; set the beam colour from the key's high nibble
AB66: 68              PLA                         
AB67: 29 0F           AND     #$0F                
AB69: 20 DD B0        JSR     emitScaleWordIfChanged; set the draw scale from the key's low nibble
AB6C: A0 01           LDY     #$01                
AB6E: A9 00           LDA     #$00                
AB70: 85 2A           STA     workRam+2A          ; reset the output offset

loc_ab72:
AB72: B1 3B           LDA     (workRam+3B),Y      ; read the next point entry from the shape list
AB74: 85 2B           STA     workRam+2B          ; 
AB76: 29 7F           AND     #$7F                ; low 7 bits index the point-coordinate table
AB78: C8              INY                         
AB79: 84 2C           STY     workRam+2C          ; 
AB7B: AA              TAX                         
AB7C: BD E4 31        LDA     vectorRom+1E4,X     ; fetch the point's low byte from the coordinate table
AB7F: A4 2A           LDY     workRam+2A          ; 
AB81: 91 74           STA     (workRam+74),Y      ; write the point low byte into the display buffer
AB83: C8              INY                         
AB84: BD E5 31        LDA     vectorRom+1E5,X     ; fetch the point's high byte
AB87: 91 74           STA     (workRam+74),Y      ; write the point high byte into the display buffer
AB89: C8              INY                         
AB8A: 84 2A           STY     workRam+2A          ; 
AB8C: A4 2C           LDY     workRam+2C          ; 
AB8E: 24 2B           BIT     workRam+2B          ; test the entry's terminator bit
AB90: 10 E0           BPL     loc_ab72            ; loop until an entry's high bit ends the shape list
AB92: A4 2A           LDY     workRam+2A          ; 
AB94: 88              DEY                         
AB95: 4C 5F DF        JMP     advanceDisplayCursor; close the record, advancing the cursor past the points

; thin front over the shared builder: seat loc_35=x (slot), loc_2a=a
; (position seed), and a zero colour header loc_2b=0x00, then fall into
; expandShapeListToVectors to emit the slot's vector run.
drawShapeListAtPosition:
AB98: 86 35           STX     workRam+35          ; set which shape slot to draw
AB9A: 85 2A           STA     workRam+2A          ; set the draw position seed
AB9C: A9 00           LDA     #$00                
AB9E: 85 2B           STA     workRam+2B          ; clear the colour/flag header
ABA0: F0 99           BEQ     expandShapeListToVectors; fall into the shared shape-list emitter

; guarded front for the rebuilder: refresh via $AC20, and if loc_1c9's low
; two request bits are clear take the no-op tail $AC07, otherwise run the
; rebuild path $ABAC.
rebuildControlBlocksIfRequested:
ABA2: 20 20 AC        JSR     requestRebuildIfSwitchesChanged; refresh the option-switch snapshot -- may request a rebuild
ABA5: AD C9 01        LDA     workRam+1C9         ; read the pending-work flags
ABA8: 29 03           AND     #$03                ; test the two rebuild-request bits
ABAA: F0 5B           BEQ     noRebuildRequestReturn; no rebuild requested -- take the do-nothing tail

; control-block rebuilder: refresh via requestRebuildIfSwitchesChanged,
; write loc_100=0x08, raise the requests via raiseRebuildRequestBits when
; (loc_71b|loc_71c|loc_71d) are idle, then keyed on loc_1c9's low two bits
; copy template $AC08 into loc_606 (top 0x17 if bit0 else 0x0e) and fill
; loc_706 with 0x01 (top 0x17 if bit1 else 0x0e), latch
; loc_71e=(loc_a&0xf8)/loc_71f=(loc_16a&0x03) when any request bit was
; set, and clear the two request bits (loc_1c9 &= 0xfc).
rebuildControlBlocksFromTemplate:
ABAC: 20 20 AC        JSR     requestRebuildIfSwitchesChanged; refresh the option-switch snapshot -- may request a rebuild
ABAF: A9 08           LDA     #$08                
ABB1: 8D 00 01        STA     workRam+100         ; stamp the block-system state marker
ABB4: AD 1B 07        LDA     workRam+71B         ; gather the three activity sources
ABB7: 0D 1C 07        ORA     workRam+71C         ; 
ABBA: 0D 1D 07        ORA     workRam+71D         ; 
ABBD: D0 03           BNE     loc_abc2            ; skip the force when any source is active
ABBF: 20 36 AC        JSR     raiseRebuildRequestBits; fully idle machine -- force both rebuild requests on

loc_abc2:
ABC2: A2 17           LDX     #$17                ; wider copy run when the copy bit is armed
ABC4: AD C9 01        LDA     workRam+1C9         ; test the copy-block request bit
ABC7: 29 01           AND     #$01                
ABC9: D0 02           BNE     loc_abcd            ; 
ABCB: A2 0E           LDX     #$0E                ; narrower copy run otherwise

loc_abcd:
ABCD: BD 08 AC        LDA     rom+1C08,X          ; read a template control-block byte
ABD0: 9D 06 06        STA     workRam+606,X       ; write it into the live control block
ABD3: CA              DEX                         
ABD4: 10 F7           BPL     loc_abcd            ; loop over the block
ABD6: A2 17           LDX     #$17                
ABD8: AD C9 01        LDA     workRam+1C9         ; test the glyph-fill request bit
ABDB: 29 02           AND     #$02                
ABDD: D0 02           BNE     loc_abe1            ; 
ABDF: A2 0E           LDX     #$0E                

loc_abe1:
ABE1: A9 01           LDA     #$01                
ABE3: 9D 06 07        STA     workRam+706,X       ; fill the glyph-parameter block with ones
ABE6: CA              DEX                         
ABE7: 10 F8           BPL     loc_abe1            ; loop over the block
ABE9: AD C9 01        LDA     workRam+1C9         ; test whether any request bit is set
ABEC: 29 03           AND     #$03                
ABEE: F0 0F           BEQ     loc_abff            ; no rebuild happened -- skip the snapshot latch
ABF0: A5 0A           LDA     workRam+A           ; 
ABF2: 29 F8           AND     #$F8                
ABF4: 8D 1E 07        STA     workRam+71E         ; latch the option-switch snapshot (high bits)
ABF7: AD 6A 01        LDA     workRam+16A         ; 
ABFA: 29 03           AND     #$03                
ABFC: 8D 1F 07        STA     workRam+71F         ; latch the difficulty snapshot (low bits)

loc_abff:
ABFF: AD C9 01        LDA     workRam+1C9         ; 
AC02: 29 FC           AND     #$FC                
AC04: 8D C9 01        STA     workRam+1C9         ; clear the two rebuild-request bits, keep the rest

; no-op return tail taken by rebuildControlBlocksIfRequested when no
; rebuild request bit is set in loc_1c9; does nothing and returns.
noRebuildRequestReturn:
AC07: 60              RTS                         

; ---- $AC08-$AC1F: data ----
AC08: 07 04 01 0F 09 0C 0B 03 12 13 05 03 07 0F 0C 11
AC18: 11 11 12 04 03 03 09 04

; refresh the live option/switch snapshot via decodeOptionSwitches, then
; compare (loc_a & 0xf8) to cached loc_71e and (loc_16a & 0x03) to cached
; loc_71f: a match takes the no-op tail switchesUnchangedReturn, a
; mismatch calls raiseRebuildRequestBits to raise the pending-rebuild
; request bits.
requestRebuildIfSwitchesChanged:
AC20: 20 BB D6        JSR     decodeOptionSwitches; re-read and decode the operator option switches
AC23: A5 0A           LDA     workRam+A           ; 
AC25: 29 F8           AND     #$F8                ; mask the option-switch high bits
AC27: CD 1E 07        CMP     workRam+71E         ; compare against the cached snapshot
AC2A: D0 08           BNE     loc_ac34            ; differ -- request a rebuild
AC2C: AD 6A 01        LDA     workRam+16A         ; 
AC2F: 29 03           AND     #$03                
AC31: CD 1F 07        CMP     workRam+71F         ; compare difficulty against the cached snapshot

loc_ac34:
AC34: F0 08           BEQ     switchesUnchangedReturn; switches unchanged -- take the no-op tail

; set both low request bits by OR-ing 0x03 into the pending-rebuild flags
; cell loc_1c9 and return the merged value.
raiseRebuildRequestBits:
AC36: AD C9 01        LDA     workRam+1C9         ; 
AC39: 09 03           ORA     #$03                ; arm both rebuild-request bits
AC3B: 8D C9 01        STA     workRam+1C9         ; store the armed request flags

; shared no-op return tail taken by requestRebuildIfSwitchesChanged when
; the option/switch snapshot still matches the cached targets; does
; nothing and returns.
switchesUnchangedReturn:
AC3E: 60              RTS                         

; build a sound draw request: clear bit6 of loc_5, pre-clear the staging
; block via clearChannelStagingBlock when loc_9&0x43==0x40, zero total
; loc_601; for each of the two channels (0 or 3 by loc_3e) seat the key
; triple loc_2c/loc_2d/loc_2e from loc_42/loc_41/loc_40 and bubble-sort
; the row triples loc_620/loc_61f/loc_61e (with payload
; loc_51e/loc_51f/loc_520) into lexicographic order, counting settle
; passes in loc_605 stored to loc_600,channel; then nudge loc_601, derive
; the packed request byte loc_603 from loc_3d, and hand off to the request
; walker armRequestedSoundSlot.
buildSortedSoundRequest:
AC3F: A5 05           LDA     workRam+5           ; 
AC41: 29 BF           AND     #$BF                
AC43: 85 05           STA     workRam+5           ; clear the request-in-progress flag
AC45: A5 09           LDA     workRam+9           ; 
AC47: 29 43           AND     #$43                
AC49: C9 40           CMP     #$40                ; option-switch test gating the staging wipe
AC4B: D0 03           BNE     loc_ac50            ; 
AC4D: 20 62 CA        JSR     clearChannelStagingBlock; wipe the sound staging block

loc_ac50:
AC50: 20 FB DD        JSR     queueEaromRegionSave; fold in the periodic high-score save
AC53: A9 00           LDA     #$00                
AC55: 8D 01 06        STA     workRam+601         ; zero the running total
AC58: A6 3E           LDX     workRam+3E          ; pick the starting channel from the active-slot count
AC5A: F0 02           BEQ     loc_ac5e            ; 
AC5C: A2 03           LDX     #$03                

loc_ac5e:
AC5E: B5 42           LDA     workRam+42,X        ; seat this channel's sort-key triple from its base cells
AC60: 85 2C           STA     workRam+2C          ; 
AC62: B5 41           LDA     workRam+41,X        ; 
AC64: 85 2D           STA     workRam+2D          ; 
AC66: B5 40           LDA     workRam+40,X        ; 
AC68: 85 2E           STA     workRam+2E          ; 
AC6A: 8A              TXA                         
AC6B: 29 01           AND     #$01                
AC6D: 85 36           STA     workRam+36          ; remember the channel parity across the sort
AC6F: A9 00           LDA     #$00                
AC71: 85 2B           STA     workRam+2B          ; 
AC73: A9 1A           LDA     #$1A                ; seed the swap-temp triple
AC75: 85 2A           STA     workRam+2A          ; 
AC77: 85 29           STA     workRam+29          ; 
AC79: A9 00           LDA     #$00                
AC7B: 8D 05 06        STA     workRam+605         ; clear this channel's pass counter
AC7E: A0 FD           LDY     #$FD                ; start the row cursor at the top

loc_ac80:
AC80: B9 20 06        LDA     workRam+620,Y       ; 
AC83: C5 2C           CMP     workRam+2C          ; compare this row's key high against the running key
AC85: D0 14           BNE     loc_ac9b            ; 
AC87: B9 1F 06        LDA     workRam+61F,Y       ; 
AC8A: C5 2D           CMP     workRam+2D          ; compare the key middle byte
AC8C: D0 0D           BNE     loc_ac9b            ; 
AC8E: C0 52           CPY     #$52                
AC90: 90 08           BCC     loc_ac9a            ; 
AC92: B9 1E 06        LDA     workRam+61E,Y       ; 
AC95: C5 2E           CMP     workRam+2E          ; compare the key low byte
AC97: B8              CLV                         
AC98: 50 01           BVC     loc_ac9b            ; 

loc_ac9a:
AC9A: 38              SEC                         

loc_ac9b:
AC9B: B0 4F           BCS     loc_acec            ; rows already ordered -- skip the swap

loc_ac9d:
AC9D: C0 E8           CPY     #$E8                
AC9F: 90 1E           BCC     loc_acbf            ; below the high region -- skip the payload swap
ACA1: A5 29           LDA     workRam+29          ; 
ACA3: BE 1E 05        LDX     workRam+51E,Y       ; 
ACA6: 99 1E 05        STA     workRam+51E,Y       ; swap the parallel payload triple alongside the key
ACA9: 86 29           STX     workRam+29          ; 
ACAB: A5 2A           LDA     workRam+2A          ; 
ACAD: BE 1F 05        LDX     workRam+51F,Y       ; 
ACB0: 99 1F 05        STA     workRam+51F,Y       ; 
ACB3: 86 2A           STX     workRam+2A          ; 
ACB5: A5 2B           LDA     workRam+2B          ; 
ACB7: BE 20 05        LDX     workRam+520,Y       ; 
ACBA: 99 20 05        STA     workRam+520,Y       ; 
ACBD: 86 2B           STX     workRam+2B          ; 

loc_acbf:
ACBF: A5 2D           LDA     workRam+2D          ; 
ACC1: BE 1F 06        LDX     workRam+61F,Y       ; 
ACC4: 99 1F 06        STA     workRam+61F,Y       ; swap this row's key with the running key
ACC7: 86 2D           STX     workRam+2D          ; 
ACC9: A5 2C           LDA     workRam+2C          ; 
ACCB: BE 20 06        LDX     workRam+620,Y       ; 
ACCE: 99 20 06        STA     workRam+620,Y       ; 
ACD1: 86 2C           STX     workRam+2C          ; 
ACD3: C0 52           CPY     #$52                
ACD5: 90 0A           BCC     loc_ace1            ; 
ACD7: A5 2E           LDA     workRam+2E          ; 
ACD9: BE 1E 06        LDX     workRam+61E,Y       ; 
ACDC: 99 1E 06        STA     workRam+61E,Y       ; swap the key's low byte too
ACDF: 86 2E           STX     workRam+2E          ; 

loc_ace1:
ACE1: C0 55           CPY     #$55                
ACE3: 90 01           BCC     loc_ace6            ; 
ACE5: 88              DEY                         

loc_ace6:
ACE6: 88              DEY                         
ACE7: 88              DEY                         
ACE8: D0 B3           BNE     loc_ac9d            ; keep bubbling the swap down the table
ACEA: A0 02           LDY     #$02                

loc_acec:
ACEC: EE 05 06        INC     workRam+605         ; count a settle pass
ACEF: C0 55           CPY     #$55                
ACF1: 90 01           BCC     loc_acf4            ; 
ACF3: 88              DEY                         

loc_acf4:
ACF4: 88              DEY                         
ACF5: 88              DEY                         
ACF6: D0 88           BNE     loc_ac80            ; advance to the next row pair
ACF8: A6 36           LDX     workRam+36          ; 
ACFA: AD 05 06        LDA     workRam+605         ; 
ACFD: 9D 00 06        STA     workRam+600,X       ; record this channel's pass count
AD00: CA              DEX                         
AD01: 30 03           BMI     loc_ad06            ; both channels done -- exit the sort loop

; ---- $AD03-$AD05: data ----
AD03: 4C 5E AC

loc_ad06:
AD06: AD 01 06        LDA     workRam+601         ; 
AD09: CD 00 06        CMP     workRam+600         ; 
AD0C: 90 07           BCC     loc_ad15            ; 
AD0E: C9 63           CMP     #$63                
AD10: B0 03           BCS     loc_ad15            ; 
AD12: EE 01 06        INC     workRam+601         ; nudge the running total up by one

loc_ad15:
AD15: A5 3D           LDA     workRam+3D          ; 
AD17: 49 01           EOR     #$01                
AD19: 0A              ASL     A                   
AD1A: 0A              ASL     A                   
AD1B: 05 3D           ORA     workRam+3D          ; 
AD1D: 69 05           ADC     #$05                
AD1F: 8D 03 06        STA     workRam+603         ; pack the sound-request byte and store it

; walk the packed request word loc_603 two bits at a time: on an empty
; word write idle status loc_0=0x14 and return; else take the low two bits
; as slot index loc_3d, consume them, read loc_600+index, skip slots whose
; byte is 0 or >=9, and for a live slot form loc_602 = ((3*byte) ^ 0xff) -
; 0xe5, call selectProjectionScale, seed
; loc_605=0x60/loc_4e=0/loc_50=0/loc_604=2, run the per-slot reset
; resetPerSlotStateTable, write armed status loc_0=0x24 and return.
armRequestedSoundSlot:
AD22: A0 14           LDY     #$14                
AD24: AD 03 06        LDA     workRam+603         ; 
AD27: F0 42           BEQ     loc_ad6b            ; no pending sound request -- drop to idle
AD29: 29 03           AND     #$03                
AD2B: 85 3D           STA     workRam+3D          ; take the low two bits as the slot index
AD2D: C6 3D           DEC     workRam+3D          ; 
AD2F: 4E 03 06        LSR     workRam+603         ; consume those two bits from the request word
AD32: 4E 03 06        LSR     workRam+603         ; 
AD35: A6 3D           LDX     workRam+3D          ; 
AD37: BD 00 06        LDA     workRam+600,X       ; read this slot's metric
AD3A: F0 2C           BEQ     loc_ad68            ; skip empty or out-of-range slots
AD3C: C9 09           CMP     #$09                
AD3E: B0 28           BCS     loc_ad68            ; 
AD40: 0A              ASL     A                   
AD41: 18              CLC                         
AD42: 7D 00 06        ADC     workRam+600,X       ; 
AD45: 49 FF           EOR     #$FF                
AD47: 38              SEC                         
AD48: E9 E5           SBC     #$E5                
AD4A: 8D 02 06        STA     workRam+602         ; form the slot's control value (scale by three, invert, offset)
AD4D: 20 48 CA        JSR     selectProjectionScale; select the projection scale
AD50: A9 60           LDA     #$60                
AD52: 8D 05 06        STA     workRam+605         ; seed the slow pass counter
AD55: A9 00           LDA     #$00                
AD57: 85 4E           STA     workRam+4E          ; clear the input-edge flags
AD59: 85 50           STA     workRam+50          ; clear the spinner accumulator
AD5B: A9 02           LDA     #$02                
AD5D: 8D 04 06        STA     workRam+604         ; seed the re-arm counter
AD60: 20 89 A7        JSR     resetPerSlotStateTable; reset the per-slot state table
AD63: A0 24           LDY     #$24                
AD65: 84 00           STY     workRam             ; set the sound state to armed
AD67: 60              RTS                         

loc_ad68:
AD68: 4C 22 AD        JMP     armRequestedSoundSlot; walk on to the next requested slot

loc_ad6b:
AD6B: 84 00           STY     workRam             ; set the sound state to idle
AD6D: 60              RTS                         

; per-frame tick of the active slot: set loc_1=0x06; while the low 5 bits
; of loc_3 are clear run countdown loc_605 down and on zero write
; loc_00=0x14 and return; otherwise clamp the active slot value
; loc_606,slot via foldStepIntoFraction (negative->0x1a, >=0x1b->0), gate
; on bits 3-4 of loc_4e (clearing them plus bit7), and when gated step
; cursor loc_602 and counter loc_604 -- on underflow re-arm
; (requestWriteLowRegions when loc_600,loc_3d < 4, then
; armRequestedSoundSlot), else clear the retired slot loc_606,slot-1.
tickActiveSoundSlot:
AD6E: A9 06           LDA     #$06                
AD70: 85 01           STA     workRam+1           ; select this mode's dispatch code
AD72: A5 03           LDA     workRam+3           ; 
AD74: 29 1F           AND     #$1F                
AD76: D0 0A           BNE     loc_ad82            ; run the slow tick only every 32nd frame
AD78: CE 05 06        DEC     workRam+605         ; count down the slow pass counter
AD7B: D0 05           BNE     loc_ad82            ; 
AD7D: A0 14           LDY     #$14                
AD7F: 84 00           STY     workRam             ; pass counter expired -- leave this mode
AD81: 60              RTS                         

loc_ad82:
AD82: AE 02 06        LDX     workRam+602         ; 
AD85: BD 06 06        LDA     workRam+606,X       ; load the active slot's ramp value
AD88: 20 CE AD        JSR     foldStepIntoFraction; fold the spinner step into the value
AD8B: A8              TAY                         
AD8C: 10 05           BPL     loc_ad93            ; 
AD8E: A9 1A           LDA     #$1A                ; rail a negative value to 0x1a
AD90: B8              CLV                         
AD91: 50 06           BVC     loc_ad99            ; 

loc_ad93:
AD93: C9 1B           CMP     #$1B                
AD95: 90 02           BCC     loc_ad99            ; 
AD97: A9 00           LDA     #$00                ; rail an over-range value to 0x00

loc_ad99:
AD99: AE 02 06        LDX     workRam+602         ; 
AD9C: 9D 06 06        STA     workRam+606,X       ; store the clamped ramp value back
AD9F: A5 4E           LDA     workRam+4E          ; 
ADA1: 29 18           AND     #$18                ; read the input-edge gate (bits 3-4)
ADA3: A8              TAY                         
ADA4: A5 4E           LDA     workRam+4E          ; 
ADA6: 29 67           AND     #$67                
ADA8: 85 4E           STA     workRam+4E          ; consume the edge bits
ADAA: 98              TYA                         
ADAB: F0 20           BEQ     loc_adcd            ; no edge -- done
ADAD: CE 02 06        DEC     workRam+602         ; step the slot cursor down
ADB0: CE 04 06        DEC     workRam+604         ; count down the re-arm counter
ADB3: 10 12           BPL     loc_adc7            ; 
ADB5: A6 3D           LDX     workRam+3D          ; 
ADB7: BD 00 06        LDA     workRam+600,X       ; 
ADBA: C9 04           CMP     #$04                ; metric gate for the low-region write
ADBC: B0 03           BCS     loc_adc1            ; 
ADBE: 20 F7 DD        JSR     requestWriteLowRegions; request the low-region write for a low-metric slot

loc_adc1:
ADC1: 20 22 AD        JSR     armRequestedSoundSlot; arm the next requested slot
ADC4: B8              CLV                         
ADC5: 50 06           BVC     loc_adcd            ; 

loc_adc7:
ADC7: CA              DEX                         
ADC8: A9 00           LDA     #$00                
ADCA: 9D 06 06        STA     workRam+606,X       ; silence the slot just retired

loc_adcd:
ADCD: 60              RTS                         

; fold the signed sub-step loc_50 (times 8) into the fraction cell loc_51,
; add the fold carry plus loc_50's sign-extension into A, and clear
; loc_50; returns the updated whole byte in A.
foldStepIntoFraction:
ADCE: 48              PHA                         
ADCF: A5 50           LDA     workRam+50          ; read the signed spinner sub-step
ADD1: 0A              ASL     A                   ; scale the sub-step by eight
ADD2: 0A              ASL     A                   
ADD3: 0A              ASL     A                   
ADD4: 18              CLC                         
ADD5: 65 51           ADC     workRam+51          ; 
ADD7: 85 51           STA     workRam+51          ; fold it into the fine rotation fraction
ADD9: 68              PLA                         
ADDA: A4 50           LDY     workRam+50          ; check the sub-step's sign
ADDC: 30 05           BMI     loc_ade3            ; 
ADDE: 69 00           ADC     #$00                ; positive step -- add only the fraction carry
ADE0: B8              CLV                         
ADE1: 50 02           BVC     loc_ade5            ; 

loc_ade3:
ADE3: 69 FF           ADC     #$FF                ; negative step -- carry one down from the whole byte

loc_ade5:
ADE5: A0 00           LDY     #$00                
ADE7: 84 50           STY     workRam+50          ; consume the spinner sub-step
ADE9: 60              RTS                         

; draw a fixed frame ($AB17/$AB14 chain), tick the countdown loc_16e down
; by one, then hand the score delta loc_602-loc_604 to the glyph-row
; builder drawHighlightedGlyphRowList.
drawScoreDeltaPanel:
ADEA: 20 B4 A8        JSR     buildTextOverlayList; build the static text overlay
ADED: A9 C0           LDA     #$C0                
ADEF: A2 02           LDX     #$02                
ADF1: 20 17 AB        JSR     drawSlotShapeWithHeader; draw the framing shape
ADF4: CE 6E 01        DEC     workRam+16E         ; tick down the score-display countdown
ADF7: 20 97 AA        JSR     emitCountDigitRun   ; draw the countdown digits
ADFA: A2 0A           LDX     #$0A                
ADFC: 20 14 AB        JSR     drawSlotShapeRecord ; draw a marker shape
ADFF: A9 A6           LDA     #$A6                
AE01: A2 0C           LDX     #$0C                
AE03: 20 17 AB        JSR     drawSlotShapeWithHeader; draw a label shape
AE06: A9 9C           LDA     #$9C                
AE08: A2 0E           LDX     #$0E                
AE0A: 20 17 AB        JSR     drawSlotShapeWithHeader; draw a label shape
AE0D: A2 2C           LDX     #$2C                
AE0F: 20 14 AB        JSR     drawSlotShapeRecord ; draw a marker shape
AE12: AD 02 06        LDA     workRam+602         ; 
AE15: 38              SEC                         
AE16: ED 04 06        SBC     workRam+604         ; form the highlighted-row selector (control value minus re-arm counter)
AE19: 4C 4E AE        JMP     drawHighlightedGlyphRowList; draw the glyph rows, highlighting the selected one

; fold two POKEY random samples $60CA/$60DA into scratch loc_29 and stored
; nibble loc_11f, draw the counters via drawCounterPair, then draw the
; highlighted glyph-row list via drawHighlightedGlyphRowList(0xff).
seedRngAndDrawCounterPanel:
AE1C: 20 B4 A8        JSR     buildTextOverlayList; build the static text overlay
AE1F: 78              SEI                         ; block interrupts around the paired random reads
AE20: AD CA 60        LDA     pokey1+A            ; sample the first POKEY random register
AE23: AC CA 60        LDY     pokey1+A            ; 
AE26: 84 29           STY     workRam+29          ; seed the scratch random byte
AE28: 4A              LSR     A                   
AE29: 4A              LSR     A                   
AE2A: 4A              LSR     A                   
AE2B: 4A              LSR     A                   
AE2C: 45 29           EOR     workRam+29          ; fold its high nibble into the scratch byte
AE2E: 85 29           STA     workRam+29          ; 
AE30: AD DA 60        LDA     pokey2+A            ; sample the second POKEY random register
AE33: AC DA 60        LDY     pokey2+A            ; 
AE36: 58              CLI                         ; re-enable interrupts
AE37: 45 29           EOR     workRam+29          ; 
AE39: 29 F0           AND     #$F0                
AE3B: 45 29           EOR     workRam+29          ; 
AE3D: 85 29           STA     workRam+29          ; fold the second sample's high nibble in
AE3F: 98              TYA                         
AE40: 0A              ASL     A                   
AE41: 0A              ASL     A                   
AE42: 0A              ASL     A                   
AE43: 0A              ASL     A                   
AE44: 45 29           EOR     workRam+29          ; 
AE46: 8D 1F 01        STA     workRam+11F         ; store the stirred random nibble
AE49: 20 26 AF        JSR     drawCounterPair     ; draw the counter pair
AE4C: A9 FF           LDA     #$FF                ; no row highlighted -- fall into the row list

; draw a descending run of glyph rows: prime the pen ($B0DD(0x01)), set
; column loc_2c=0x28 and row index loc_37=0x15, and each pass step loc_2c
; back 0x0a, position ($DF75), select tag 0x00 when the row loc_37 equals
; the argument loc_63 else 0x07 ($B0D1), draw the glyph body
; ($DFB1/$B56A/$AEF8), re-seat loc_56/loc_57/loc_58 from
; loc_706/loc_707/loc_708, and drop loc_37 by 3.
drawHighlightedGlyphRowList:
AE4E: 85 63           STA     workRam+63          ; remember which row to highlight
AE50: A2 10           LDX     #$10                
AE52: 20 14 AB        JSR     drawSlotShapeRecord ; prime the pen with a fixed shape
AE55: A9 01           LDA     #$01                
AE57: 85 61           STA     workRam+61          ; set the row base position
AE59: 20 DD B0        JSR     emitScaleWordIfChanged; set the drawing scale
AE5C: A9 28           LDA     #$28                
AE5E: 85 2C           STA     workRam+2C          ; seed the column cursor
AE60: A2 15           LDX     #$15                
AE62: 86 37           STX     workRam+37          ; set the top row index (steps down by three)

loc_ae64:
AE64: 20 0D AB        JSR     emitFixedVectorWord ; emit a fixed vector word
AE67: A9 00           LDA     #$00                
AE69: 85 73           STA     workRam+73          ; clear the vector-record header
AE6B: A5 2C           LDA     workRam+2C          ; 
AE6D: AA              TAX                         
AE6E: 38              SEC                         
AE6F: E9 0A           SBC     #$0A                
AE71: 85 2C           STA     workRam+2C          ; step the column cursor back one row
AE73: A9 D0           LDA     #$D0                
AE75: 20 75 DF        JSR     emitScaledCoordinateRecord; position this row
AE78: A0 07           LDY     #$07                
AE7A: A5 63           LDA     workRam+63          ; 
AE7C: C5 37           CMP     workRam+37          ; pick the highlight tint when this is the selected row
AE7E: D0 02           BNE     loc_ae82            ; 
AE80: A0 00           LDY     #$00                

loc_ae82:
AE82: 20 D1 B0        JSR     emitColorStatIfChanged; set the row colour
AE85: A9 61           LDA     #$61                
AE87: A0 01           LDY     #$01                
AE89: 20 B1 DF        JSR     emitNibbleDigitRun  ; emit a one-digit numeric run
AE8C: A9 A0           LDA     #$A0                
AE8E: 20 6A B5        JSR     emitBlankValueRecord; emit a blank value record
AE91: A9 00           LDA     #$00                
AE93: 85 73           STA     workRam+73          ; 
AE95: AA              TAX                         
AE96: A9 08           LDA     #$08                
AE98: 20 75 DF        JSR     emitScaledCoordinateRecord; position the label
AE9B: E6 61           INC     workRam+61          ; advance down a row
AE9D: A5 37           LDA     workRam+37          ; 
AE9F: 20 F8 AE        JSR     drawThreeCharGlyphString; draw this row's three-character label
AEA2: A2 00           LDX     #$00                
AEA4: A9 08           LDA     #$08                
AEA6: 20 75 DF        JSR     emitScaledCoordinateRecord; position the numeric run
AEA9: A6 37           LDX     workRam+37          ; 
AEAB: BD 06 07        LDA     workRam+706,X       ; seat the row's numeric triple from the per-row tables
AEAE: 85 56           STA     workRam+56          ; 
AEB0: BD 07 07        LDA     workRam+707,X       ; 
AEB3: 85 57           STA     workRam+57          ; 
AEB5: BD 08 07        LDA     workRam+708,X       ; 
AEB8: 85 58           STA     workRam+58          ; 
AEBA: A9 56           LDA     #$56                
AEBC: A0 03           LDY     #$03                
AEBE: 20 B1 DF        JSR     emitNibbleDigitRun  ; emit the three-value numeric run
AEC1: C6 37           DEC     workRam+37          ; step to the next row up (index minus three)
AEC3: C6 37           DEC     workRam+37          ; 
AEC5: C6 37           DEC     workRam+37          ; 
AEC7: 10 9B           BPL     loc_ae64            ; loop until the row index wraps below zero

; ---- $AEC9-$AEC9: data ----
AEC9: 60

; publish the object-list gate checksum and optionally draw a flag record:
; when loc_156 is nonzero seat it into loc_58, open a record
; (drawSlotShapeRecord 0x34) and emit a cleared coordinate pair
; (loc_56=loc_57=0x00, emitNibbleDigitRun); always fold the seventeen
; bytes at $D575 (indices 0x10..0) into an accumulator seeded 0x85 with
; carry and store the result to loc_b5 (the end-of-list gate
; buildObjectDisplayList reads).
computeDisplayListChecksum:
AECA: AD 56 01        LDA     workRam+156         ; read the bonus-life interval -- skip the marker when zero
AECD: F0 14           BEQ     loc_aee3            ; 
AECF: 85 58           STA     workRam+58          ; seat the interval into the flag record
AED1: A2 34           LDX     #$34                
AED3: 20 14 AB        JSR     drawSlotShapeRecord ; open the bonus-life flag shape
AED6: A9 00           LDA     #$00                
AED8: 85 56           STA     workRam+56          ; clear the flag's coordinate pair
AEDA: 85 57           STA     workRam+57          ; 
AEDC: A9 56           LDA     #$56                
AEDE: A0 03           LDY     #$03                
AEE0: 20 B1 DF        JSR     emitNibbleDigitRun  ; emit the flag's numeric run

loc_aee3:
AEE3: 18              CLC                         
AEE4: A0 10           LDY     #$10                
AEE6: A9 85           LDA     #$85                ; seed the checksum accumulator

loc_aee8:
AEE8: 79 75 D5        ADC     rom+4575,Y          ; fold the next source byte into the checksum
AEEB: 88              DEY                         
AEEC: 10 FA           BPL     loc_aee8            ; fold all seventeen source bytes
AEEE: 85 B5           STA     workRam+B5          ; publish the display-list checksum to the end-of-list gate
AEF0: 60              RTS                         

; ---- $AEF1-$AEF7: data ----
AEF1: AD 02 06 38 ED 04 06

; render three characters into the display list: walk three codes from the
; text buffer 0x606 (position at 0x38, count at 0x39), clamp each code to
; 0x1a (0x1e+ folds to 0x1a), double it to index glyph word table 0x31fa,
; copy each 2-byte glyph word to the cursor 0x74, then advance the cursor
; past everything written.
drawThreeCharGlyphString:
AEF8: 18              CLC                         
AEF9: 69 02           ADC     #$02                
AEFB: 85 38           STA     workRam+38          ; point at the last of the three character codes
AEFD: A0 00           LDY     #$00                
AEFF: A9 02           LDA     #$02                
AF01: 85 39           STA     workRam+39          ; set the three-character pass count

loc_af03:
AF03: A6 38           LDX     workRam+38          ; 
AF05: BD 06 06        LDA     workRam+606,X       ; read this character's code
AF08: C9 1E           CMP     #$1E                
AF0A: 90 02           BCC     loc_af0e            ; 
AF0C: A9 1A           LDA     #$1A                ; fold an out-of-range code to a blank

loc_af0e:
AF0E: 0A              ASL     A                   ; double the code into a glyph-table index
AF0F: AA              TAX                         
AF10: BD FA 31        LDA     vectorRom+1FA,X     ; copy the glyph's low byte into the display list
AF13: 91 74           STA     (workRam+74),Y      ; 
AF15: C8              INY                         
AF16: BD FB 31        LDA     vectorRom+1FB,X     ; copy the glyph's high byte
AF19: 91 74           STA     (workRam+74),Y      ; 
AF1B: C8              INY                         
AF1C: C6 38           DEC     workRam+38          ; step back to the previous character
AF1E: C6 39           DEC     workRam+39          ; 
AF20: 10 E1           BPL     loc_af03            ; 
AF22: 88              DEY                         
AF23: 4C 5F DF        JMP     advanceDisplayCursor; advance the display cursor past the string

; when either counter byte loc_600/loc_601 is nonzero, draw a shared
; header ($AB14(0x12), emitCappedCount(0x63)) and both counter slots via
; drawCounterSlot(0x00) and drawCounterSlot(0x01); otherwise return
; through the bare tail $AF6E.
drawCounterPair:
AF26: AD 00 06        LDA     workRam+600         ; read the first counter byte of the paired readout panel
AF29: 0D 01 06        ORA     workRam+601         ; or in the second counter byte -- panel is empty only if both are zero
AF2C: F0 40           BEQ     sharedReturnTail    ; both counters zero: draw nothing, return through the shared tail
AF2E: A2 12           LDX     #$12                ; select the shared header shape record
AF30: 20 14 AB        JSR     drawSlotShapeRecord ; emit the panel header shape record
AF33: A9 63           LDA     #$63                ; header count value -- 99
AF35: 20 71 AF        JSR     emitCappedCount     ; emit the header count, clamped to 99
AF38: A2 00           LDX     #$00                ; slot 0
AF3A: 20 3F AF        JSR     drawCounterSlot     ; draw counter slot 0
AF3D: A2 01           LDX     #$01                ; slot 1: fall through into the per-slot worker

; draw one counter slot x: return when count loc_600+x is zero, else
; record the slot in loc_2e, position it at $AF6F+loc_2e ($DF75), and emit
; the capped count (emitCappedCount, $B56A, $AB98, $AA9E).
drawCounterSlot:
AF3F: BD 00 06        LDA     workRam+600,X       ; read this slot's counter byte from the counter array
AF42: F0 2A           BEQ     sharedReturnTail    ; empty slot: draw nothing
AF44: 48              PHA                         
AF45: 86 2E           STX     workRam+2E          ; stash the slot index in scratch
AF47: A0 03           LDY     #$03                ; colour/intensity attribute for the slot
AF49: 20 D1 B0        JSR     emitColorStatIfChanged; emit the colour stat only if it changed
AF4C: 20 0D AB        JSR     emitFixedVectorWord ; emit a fixed vector word
AF4F: A9 D0           LDA     #$D0                ; coordinate scale for the count glyph
AF51: A4 2E           LDY     workRam+2E          ; reload the slot index
AF53: BE 6F AF        LDX     rom+1F6F,Y          ; read this slot's Y position from the glyph-coordinate table
AF56: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the scaled coordinate record
AF59: 68              PLA                         
AF5A: 20 71 AF        JSR     emitCappedCount     ; emit the count itself, clamped to 99
AF5D: A9 A0           LDA     #$A0                ; blank spacer value
AF5F: 20 6A B5        JSR     emitBlankValueRecord; emit a blank value spacer record
AF62: A9 10           LDA     #$10                
AF64: A2 04           LDX     #$04                
AF66: 20 98 AB        JSR     drawShapeListAtPosition; draw the small count shape list at position
AF69: A6 2E           LDX     workRam+2E          ; reload the slot index
AF6B: 20 9E AA        JSR     emitSlotIndexDigit  ; emit the trailing slot-index digit

; no-op leaf: return immediately; the shared RTS tail that drawCounterPair
; falls through to when both counters loc_600/loc_601 are zero.
sharedReturnTail:
AF6E: 60              RTS                         

; ---- $AF6F-$AF70: data ----
AF6F: C0 B0

; clamp the incoming byte to a max of 0x63 (99), then pack-and-emit it via
; $AF77.
emitCappedCount:
AF71: C9 63           CMP     #$63                ; compare the count against the 99 ceiling
AF73: 90 02           BCC     emitByteAsBcdDigits ; already under the ceiling: emit as is
AF75: A9 63           LDA     #$63                ; clamp the count to 99

; Packs the incoming byte to BCD (into loc_29) then emits that single
; zeropage byte as its two decimal nibbles.
emitByteAsBcdDigits:
AF77: 20 F5 AA        JSR     packBinaryToBcd     ; pack the binary value to packed BCD in the scratch cell
AF7A: A9 29           LDA     #$29                ; point at the packed-BCD scratch byte
AF7C: A0 01           LDY     #$01                ; one byte to walk
AF7E: 4C B1 DF        JMP     emitNibbleDigitRun  ; emit that byte's two nibbles as decimal digit glyphs

; draw the whole playfield well: refresh gates (selectProjectionScale),
; tick loc_16e, draw the eight rim segments walking loc_37 7->0
; (drawSlotShapeRecord($B09B+loc_37)), nudge the depth window pair
; loc_7b/loc_7c one step toward target loc_200 (bounded by ceiling
; loc_127), draw five depth rows deepest-first from $91FE+loc_3a (skipping
; rows >=0x63; emitTableValueDigitRun/drawTubeShapeOutline), and close
; with a framing draw and a four-entry trailer walk over $B0A3.
drawTubeWell:
AF81: 20 48 CA        JSR     selectProjectionScale; refresh the projection/scale gates for this frame
AF84: CE 6E 01        DEC     workRam+16E         ; tick the score-display timer down
AF87: A0 03           LDY     #$03                ; colour attribute for the tube preamble
AF89: 20 D1 B0        JSR     emitColorStatIfChanged; open the draw stream with a colour stat if it changed
AF8C: A9 01           LDA     #$01                
AF8E: 85 72           STA     workRam+72          ; prime the last-scale latch
AF90: 20 6A DF        JSR     emitBlankVectorWordTag70; emit a blank vector word
AF93: A2 2C           LDX     #$2C                
AF95: A9 60           LDA     #$60                
AF97: 20 17 AB        JSR     drawSlotShapeWithHeader; draw the header slot shape record
AF9A: 20 92 AA        JSR     drawSlotThenDigitRun; draw the preamble slot then its digit run
AF9D: A2 07           LDX     #$07                ; rim ring: start at the top segment index 7
AF9F: 86 37           STX     workRam+37          ; seed the rim loop counter

loc_afa1:
AFA1: A4 37           LDY     workRam+37          ; current rim segment index
AFA3: BE 9B B0        LDX     rom+209B,Y          ; read this rim segment's shape arg from the table
AFA6: 20 14 AB        JSR     drawSlotShapeRecord ; draw this rim segment shape record
AFA9: C6 37           DEC     workRam+37          ; next rim segment
AFAB: 10 F4           BPL     loc_afa1            ; loop through rim segments 7 down to 0
AFAD: AD 00 02        LDA     workRam+200         ; read the blaster's rim target position
AFB0: 38              SEC                         
AFB1: E5 7B           SBC     workRam+7B          ; signed distance from the depth window to the target
AFB3: 10 07           BPL     loc_afbc            ; target at or above the window: handle the non-negative case
AFB5: C6 7B           DEC     workRam+7B          ; target below the window: step the depth-window pair down
AFB7: C6 7C           DEC     workRam+7C          ; step the window's second byte down as well
AFB9: B8              CLV                         
AFBA: 50 25           BVC     loc_afe1            ; 

loc_afbc:
AFBC: D0 0D           BNE     loc_afcb            ; nonzero distance: window still chasing the target
AFBE: C6 7C           DEC     workRam+7C          ; on target: pull the window in by one
AFC0: C6 7B           DEC     workRam+7B          ; pull the window's other byte in as well
AFC2: 10 04           BPL     loc_afc8            ; skip the undo unless the step underflowed
AFC4: E6 7B           INC     workRam+7B          ; undo the pull-in if it dropped below zero
AFC6: E6 7C           INC     workRam+7C          ; restore the window's second byte

loc_afc8:
AFC8: B8              CLV                         
AFC9: 50 16           BVC     loc_afe1            ; 

loc_afcb:
AFCB: A5 7C           LDA     workRam+7C          ; load the window far byte
AFCD: CD 27 01        CMP     workRam+127         ; compare it against the depth ceiling
AFD0: F0 02           BEQ     loc_afd4            ; 
AFD2: B0 0D           BCS     loc_afe1            ; past the ceiling: settle without stepping closer

loc_afd4:
AFD4: 38              SEC                         
AFD5: ED 00 02        SBC     workRam+200         ; distance past the target position
AFD8: D0 01           BNE     loc_afdb            ; 
AFDA: 18              CLC                         

loc_afdb:
AFDB: B0 04           BCS     loc_afe1            ; at or beyond the target: settle
AFDD: E6 7B           INC     workRam+7B          ; step the window one closer to the target
AFDF: E6 7C           INC     workRam+7C          ; step the window's second byte closer as well

loc_afe1:
AFE1: A5 7C           LDA     workRam+7C          ; seed the depth cursor from the window far byte
AFE3: 85 3A           STA     workRam+3A          ; store the depth-row cursor
AFE5: A2 04           LDX     #$04                ; five depth rings, deepest first: index 4
AFE7: 86 37           STX     workRam+37          ; 

loc_afe9:
AFE9: A0 05           LDY     #$05                ; colour stat for this depth ring
AFEB: 20 D1 B0        JSR     emitColorStatIfChanged; 
AFEE: A9 00           LDA     #$00                
AFF0: 85 73           STA     workRam+73          ; clear the record header byte
AFF2: 20 0D AB        JSR     emitFixedVectorWord ; emit a fixed vector word
AFF5: A2 D8           LDX     #$D8                ; coordinate scale for the ring
AFF7: A4 37           LDY     workRam+37          ; 
AFF9: B9 96 B0        LDA     rom+2096,Y          ; read this ring's tube-well segment coordinate from the table
AFFC: 18              CLC                         
AFFD: 69 F8           ADC     #$F8                ; offset the coordinate back for the ring position
AFFF: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the ring's scaled coordinate record
B002: A6 3A           LDX     workRam+3A          ; 
B004: BC FE 91        LDY     rom+1FE,X           ; read this ring's depth threshold from the table
B007: C0 63           CPY     #$63                ; compare the threshold against the far edge -- 99
B009: B0 37           BCS     loc_b042            ; ring already at the far edge: skip its label body
B00B: C8              INY                         ; depth label value: threshold plus one
B00C: 98              TYA                         
B00D: 20 77 AF        JSR     emitByteAsBcdDigits ; draw the depth label as two decimal digits
B010: A0 03           LDY     #$03                ; colour attribute for the label
B012: 20 D1 B0        JSR     emitColorStatIfChanged; emit the colour stat if changed
B015: 20 0D AB        JSR     emitFixedVectorWord ; emit a fixed vector word
B018: A2 BA           LDX     #$BA                ; label scale
B01A: A4 37           LDY     workRam+37          ; 
B01C: B9 96 B0        LDA     rom+2096,Y          ; this ring's segment coordinate
B01F: 18              CLC                         
B020: 69 EC           ADC     #$EC                ; offset the coordinate for the label position
B022: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the label's scaled coordinate record
B025: A6 3A           LDX     workRam+3A          ; emit the depth row's table-value digit run
B027: 20 C6 B0        JSR     emitTableValueDigitRun; 
B02A: 20 0D AB        JSR     emitFixedVectorWord ; emit a fixed vector word
B02D: A2 CC           LDX     #$CC                ; outline scale
B02F: A4 37           LDY     workRam+37          ; 
B031: B9 96 B0        LDA     rom+2096,Y          ; this ring's segment coordinate for the outline
B034: 18              CLC                         
B035: 69 00           ADC     #$00                
B037: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the outline's scaled coordinate record
B03A: A6 3A           LDX     workRam+3A          ; 
B03C: BD FE 91        LDA     rom+1FE,X           ; this ring's depth threshold
B03F: 20 E1 C4        JSR     drawTubeShapeOutline; draw the tube ring outline

loc_b042:
B042: C6 3A           DEC     workRam+3A          ; recede one ring deeper
B044: C6 37           DEC     workRam+37          ; next ring
B046: 10 A1           BPL     loc_afe9            ; loop through the five depth rings
B048: A9 00           LDA     #$00                
B04A: 85 73           STA     workRam+73          ; clear the record header for the trailer
B04C: 20 0D AB        JSR     emitFixedVectorWord ; emit a fixed vector word
B04F: A2 1C           LDX     #$1C                ; draw the framing slot shape record
B051: 20 14 AB        JSR     drawSlotShapeRecord ; 
B054: A9 04           LDA     #$04                
B056: A0 01           LDY     #$01                
B058: 20 B1 DF        JSR     emitNibbleDigitRun  ; emit a short nibble digit run
B05B: A0 00           LDY     #$00                ; colour attribute for the blaster
B05D: 20 D1 B0        JSR     emitColorStatIfChanged; emit the colour stat if changed
B060: 20 0D AB        JSR     emitFixedVectorWord ; emit a fixed vector word
B063: A2 B8           LDX     #$B8                
B065: 20 AB B0        JSR     nudgeBlasterRimPosition; advance the blaster rim position and clamp it into the tube
B068: 38              SEC                         
B069: E5 7B           SBC     workRam+7B          ; index the well coordinate relative to the depth window
B06B: A8              TAY                         
B06C: B9 96 B0        LDA     rom+2096,Y          ; read the well segment coordinate for the blaster position
B06F: 38              SEC                         
B070: E9 16           SBC     #$16                ; offset the blaster coordinate
B072: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the blaster-relative coordinate record
B075: A9 E0           LDA     #$E0                
B077: 85 73           STA     workRam+73          ; closing frame uses the $e0 record header
B079: A2 00           LDX     #$00                
B07B: 86 38           STX     workRam+38          ; vertex table cursor: start at 0
B07D: A0 03           LDY     #$03                
B07F: 84 37           STY     workRam+37          ; four closing frame vertices

loc_b081:
B081: A4 38           LDY     workRam+38          ; current vertex table index
B083: B9 A3 B0        LDA     rom+20A3,Y          ; read the vertex x byte from the table
B086: AA              TAX                         
B087: C8              INY                         
B088: B9 A3 B0        LDA     rom+20A3,Y          ; read the paired vertex a byte
B08B: C8              INY                         
B08C: 84 38           STY     workRam+38          ; step to the next vertex pair
B08E: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the closing frame vertex coordinate
B091: C6 37           DEC     workRam+37          ; next vertex
B093: 10 EC           BPL     loc_b081            ; loop through the four closing vertices
B095: 60              RTS                         

; ---- $B096-$B0AA: data ----
B096: BE E3 09 30 58 14 0C 0E 16 18 1E 20 1A 00 26 28
B0A6: 00 00 DA D8 00

; nudge the blaster's rim position loc_200 by its signed sub-step (via
; foldStepIntoFraction), clamp into [0, ceiling loc_127] (a negative
; result floors to 0, an over-ceiling result pins to loc_127), write it
; back to loc_200, and return it in both A and Y.
nudgeBlasterRimPosition:
B0AB: AD 00 02        LDA     workRam+200         ; read the current blaster rim position
B0AE: 20 CE AD        JSR     foldStepIntoFraction; fold the pending signed sub-step into the position
B0B1: A8              TAY                         
B0B2: 10 05           BPL     loc_b0b9            ; negative result: stepped below the first lane?
B0B4: A9 00           LDA     #$00                ; floor to lane 0
B0B6: B8              CLV                         
B0B7: 50 08           BVC     loc_b0c1            ; 

loc_b0b9:
B0B9: CD 27 01        CMP     workRam+127         ; compare against the depth ceiling
B0BC: 90 03           BCC     loc_b0c1            ; under the ceiling: keep the position
B0BE: AD 27 01        LDA     workRam+127         ; clamp to the ceiling

loc_b0c1:
B0C1: 8D 00 02        STA     workRam+200         ; write back the new rim position
B0C4: A8              TAY                         ; return it in Y as well
B0C5: 60              RTS                         

; Seats a table pointer by index (via $91B5) then emits the three-byte
; zeropage run at loc_29 as nibble digits.
emitTableValueDigitRun:
B0C6: 8A              TXA                         
B0C7: 20 B5 91        JSR     seatInPagePointer   ; seat the working pointer from the ROM pointer table at this index
B0CA: A9 29           LDA     #$29                ; point at the three-byte scratch run
B0CC: A0 03           LDY     #$03                ; three bytes to walk
B0CE: 4C B1 DF        JMP     emitNibbleDigitRun  ; emit those three bytes as decimal digit glyphs

; emit a colour/intensity stat word (emitTaggedVectorWord with 0x08 and y)
; into the display list only when it changes: return if loc_9e already
; equals y, otherwise latch loc_9e=y and emit — the coarse (high-nibble)
; scale/colour attribute for the current slot.
emitColorStatIfChanged:
B0D1: C4 9E           CPY     workRam+9E          ; compare the requested attribute against the latched colour value
B0D3: F0 07           BEQ     loc_b0dc            ; unchanged: emit nothing
B0D5: 84 9E           STY     workRam+9E          ; latch the new colour/intensity value
B0D7: A9 08           LDA     #$08                
B0D9: 4C 4C DF        JMP     emitTaggedVectorWord; emit the tagged colour/intensity attribute word

loc_b0dc:
B0DC: 60              RTS                         

; emit a scale word (emitBlankVectorWordTag70 with a) into the display
; list only when it changes: return if a already equals loc_72, otherwise
; latch loc_72=a and emit — the fine (low-nibble) scale attribute for the
; current slot.
emitScaleWordIfChanged:
B0DD: C5 72           CMP     workRam+72          ; compare the requested scale against the last-emitted scale
B0DF: F0 05           BEQ     loc_b0e6            ; unchanged: emit nothing
B0E1: 85 72           STA     workRam+72          ; latch the new scale value
B0E3: 4C 6A DF        JMP     emitBlankVectorWordTag70; emit it as a tagged scale word

loc_b0e6:
B0E6: 60              RTS                         

; alternate state-entry seeder: write loc_0=0x0a, loc_2=0x00, loc_4=0xdf,
; loc_1=0x12, loc_14e=0x19 and loc_14d=0x18 into the config block plus the
; two bound cells.
seedModeParamsWithBounds:
B0E7: A9 0A           LDA     #$0A                ; set the game mode to live
B0E9: 85 00           STA     workRam             ; store the game mode
B0EB: A9 00           LDA     #$00                ; clear the pending mode
B0ED: 85 02           STA     workRam+2           ; 
B0EF: A9 DF           LDA     #$DF                ; seed the mode-promotion countdown
B0F1: 85 04           STA     workRam+4           ; 
B0F3: A9 12           LDA     #$12                
B0F5: 85 01           STA     workRam+1           ; set the display-mode selector -- pre-doubled table offset
B0F7: A9 19           LDA     #$19                ; far cursor of the animated span
B0F9: 8D 4E 01        STA     workRam+14E         ; store the far cursor
B0FC: A9 18           LDA     #$18                ; near cursor of the animated span
B0FE: 8D 4D 01        STA     workRam+14D         ; store the near cursor
B101: 60              RTS                         

; redraw the paired-cursor span via
; emitSegmentedSpanBetweenCursors(0x34,0xaa) then spread the two cursors
; apart one frame: wrap the far cursor loc_14e up by 0x14 while below
; 0xa0, and once it clears 0x50 step the near cursor loc_14d up by 0x08,
; pinning it at 0xa0 and latching phase byte loc_1=0x14 when near reaches
; far.
advanceSpreadingSpanAnimation:
B102: A9 34           LDA     #$34                ; coordinate byte handed to the span emitter -- reused at each step
B104: A2 AA           LDX     #$AA                ; second coordinate byte handed to the span emitter
B106: 20 5A B1        JSR     emitSegmentedSpanBetweenCursors; redraw the segmented span between the current cursors
B109: AD 4E 01        LDA     workRam+14E         ; load the far cursor
B10C: C9 A0           CMP     #$A0                
B10E: B0 05           BCS     loc_b115            ; far cursor at the 0xa0 ceiling: stop growing it
B110: 69 14           ADC     #$14                ; step the far cursor up by 0x14
B112: 8D 4E 01        STA     workRam+14E         ; store the far cursor

loc_b115:
B115: C9 50           CMP     #$50                
B117: 90 17           BCC     loc_b130            ; hold the near cursor until the far cursor clears 0x50
B119: AD 4D 01        LDA     workRam+14D         ; 
B11C: 18              CLC                         
B11D: 69 08           ADC     #$08                ; step the near cursor up by 0x08
B11F: 8D 4D 01        STA     workRam+14D         ; store the near cursor
B122: CD 4E 01        CMP     workRam+14E         ; near cursor still trailing the far cursor?
B125: 90 09           BCC     loc_b130            ; still trailing: keep spreading this frame
B127: A9 A0           LDA     #$A0                ; spread complete: pin the near cursor at the ceiling
B129: 8D 4D 01        STA     workRam+14D         ; store the pinned near cursor
B12C: A9 14           LDA     #$14                
B12E: 85 01           STA     workRam+1           ; latch the display-mode selector to advance to the next state

loc_b130:
B130: 60              RTS                         

; redraw the paired-cursor span via
; emitSegmentedSpanBetweenCursors(0x3f,0x4e) then squeeze the two cursors
; together one frame: decrement the near cursor loc_14d while at/above
; 0x30 (bail if it wraps to >=0x80) and pull the far cursor loc_14e down
; by one but never below the near cursor.
advancePinchingSpanAnimation:
B131: A9 3F           LDA     #$3F                ; coordinate byte handed to the span emitter -- reused at each step
B133: A2 4E           LDX     #$4E                ; second coordinate byte handed to the span emitter
B135: 20 5A B1        JSR     emitSegmentedSpanBetweenCursors; redraw the segmented span between the current cursors
B138: AD 4D 01        LDA     workRam+14D         ; load the near cursor
B13B: C9 30           CMP     #$30                
B13D: 90 05           BCC     loc_b144            ; near cursor below the 0x30 floor: hold it in place
B13F: E9 01           SBC     #$01                ; step the near cursor down by one
B141: 8D 4D 01        STA     workRam+14D         ; store the near cursor

loc_b144:
B144: C9 80           CMP     #$80                
B146: B0 11           BCS     loc_b159            ; underflowed past zero: pinch done, leave the far cursor
B148: AD 4E 01        LDA     workRam+14E         ; load the far cursor
B14B: 38              SEC                         
B14C: E9 01           SBC     #$01                ; step the far cursor down by one
B14E: CD 4D 01        CMP     workRam+14D         ; compare the far cursor against the near cursor
B151: B0 03           BCS     loc_b156            ; far cursor still above the near cursor: keep it
B153: AD 4D 01        LDA     workRam+14D         ; clamp the far cursor at the near cursor so the pair meets

loc_b156:
B156: 8D 4E 01        STA     workRam+14E         ; store the far cursor

loc_b159:
B159: 60              RTS                         

; emit a segmented vector run spanning the near-to-far cursor pair: stash
; A/X into loc_57/loc_56, seed loc_37 from the near cursor loc_14d,
; decrement loc_16e, and for each step (cursor +=2 until it reaches the
; far cursor loc_14e) lay a header word (emitVectorWordTag70), a position-
; derived marker (emitTaggedVectorWord 0x68 with segment (cur>>3)&7, 7->3,
; 0 at the start) and the stashed coordinate pair
; (emitCoordinateVectorWord), then two fixed trailer words
; (drawSlotShapeWithHeader 0xd0,0x2c and emitCoordinateVectorWord
; 0x3f,0xf2).
emitSegmentedSpanBetweenCursors:
B15A: 85 57           STA     workRam+57          ; stash the coordinate pair's first byte
B15C: 86 56           STX     workRam+56          ; stash the coordinate pair's second byte
B15E: AD 4D 01        LDA     workRam+14D         ; seed the walking cursor at the near bound
B161: 85 37           STA     workRam+37          ; store the walking cursor
B163: CE 6E 01        DEC     workRam+16E         ; tick the score-display countdown down

loc_b166:
B166: A5 37           LDA     workRam+37          ; load the walking cursor
B168: 0A              ASL     A                   ; shift the cursor low bits into the header payload
B169: 0A              ASL     A                   
B16A: 29 7F           AND     #$7F                ; mask to seven bits
B16C: A8              TAY                         
B16D: A5 37           LDA     workRam+37          ; load the walking cursor again
B16F: 4A              LSR     A                   ; shift out the cursor high bits for the header Y byte
B170: 4A              LSR     A                   
B171: 4A              LSR     A                   
B172: 4A              LSR     A                   
B173: 4A              LSR     A                   
B174: 20 6C DF        JSR     emitVectorWordTag70 ; emit the segment header word
B177: A5 37           LDA     workRam+37          ; 
B179: CD 4D 01        CMP     workRam+14D         ; first cursor position?
B17C: D0 05           BNE     loc_b183            ; 
B17E: A9 00           LDA     #$00                ; first position gets a plain 0 marker
B180: B8              CLV                         
B181: 50 0C           BVC     loc_b18f            ; 

loc_b183:
B183: 4A              LSR     A                   ; derive the segment number from the cursor
B184: 4A              LSR     A                   
B185: 4A              LSR     A                   
B186: EA              NOP                         
B187: 29 07           AND     #$07                ; mask to the low three bits
B189: C9 07           CMP     #$07                ; top segment 7?
B18B: D0 02           BNE     loc_b18f            ; 
B18D: A9 03           LDA     #$03                ; fold the top segment down to 3

loc_b18f:
B18F: A8              TAY                         
B190: A9 68           LDA     #$68                
B192: 20 4C DF        JSR     emitTaggedVectorWord; emit the tick marker tagged 0x68
B195: A5 57           LDA     workRam+57          ; 
B197: A6 56           LDX     workRam+56          ; 
B199: 20 39 DF        JSR     emitCoordinateVectorWord; emit the stashed coordinate pair for this step
B19C: A5 37           LDA     workRam+37          ; 
B19E: 18              CLC                         
B19F: 69 02           ADC     #$02                ; step the cursor two forward
B1A1: 85 37           STA     workRam+37          ; store the walking cursor
B1A3: CD 4E 01        CMP     workRam+14E         ; loop while the cursor stays below the far bound
B1A6: 90 BE           BCC     loc_b166            ; 
B1A8: A2 2C           LDX     #$2C                
B1AA: A9 D0           LDA     #$D0                
B1AC: 20 17 AB        JSR     drawSlotShapeWithHeader; draw the span's closing slot shape trailer
B1AF: A9 3F           LDA     #$3F                
B1B1: A2 F2           LDX     #$F2                
B1B3: 4C 39 DF        JMP     emitCoordinateVectorWord; emit the final trailer coordinate word

; per-frame vector housekeeping: clear frame work cells
; (resetMathboxInputs), early-return when guard cells 0x2000==0xcec6 &&
; 0x133==0 say the frame is settled; when mode cell 0x1==0 hand the whole
; draw to the frame builder drawFrame; otherwise publish the active
; pointer, run the computed-jump trampoline dispatchDisplayModeHandler,
; fold a 40-byte block under 0xb6 into checksum 0x455 (unless
; emitFrameLink reports a change), then emit the trailing header and latch
; 0xcec4/0xcec5 into display words 0x2000/0x2001.
buildFrameVectors:
B1B6: 20 C3 C1        JSR     resetMathboxInputs  ; clear the math-box and frame work cells
B1B9: AD 00 20        LDA     vectorRam           ; read the display-list header guard word
B1BC: CD C6 CE        CMP     rom+3EC6            ; compare it against its level checkpoint
B1BF: D0 06           BNE     loc_b1c7            ; guard differs: rebuild the list
B1C1: AD 33 01        LDA     workRam+133         ; read the pending level-layout trigger
B1C4: D0 01           BNE     loc_b1c7            ; trigger set: rebuild the list
B1C6: 60              RTS                         ; frame already settled: return

loc_b1c7:
B1C7: A5 01           LDA     workRam+1           ; read the display-mode selector
B1C9: C9 00           CMP     #$00                
B1CB: F0 3C           BEQ     loc_b209            ; selector zero: route the whole draw through the frame builder
B1CD: A9 00           LDA     #$00                
B1CF: 20 BE B2        JSR     seatDrawCursor      ; seat the draw cursor for the list
B1D2: 20 32 B3        JSR     emitFrameLink       ; publish the frame link
B1D5: B0 1E           BCS     loc_b1f5            ; a change was published: skip the mode dispatch and checksum
B1D7: 20 0D B2        JSR     dispatchDisplayModeHandler; run the display-mode dispatch trampoline
B1DA: AD 6E 01        LDA     workRam+16E         ; score-display timer zero: skip the checksum fold
B1DD: F0 16           BEQ     loc_b1f5            ; 
B1DF: A0 27           LDY     #$27                ; walk 40 bytes of the record
B1E1: A9 0E           LDA     #$0E                ; checksum seed
B1E3: 38              SEC                         

loc_b1e4:
B1E4: F1 B6           SBC     (workRam+B6),Y      ; subtract each record byte under the draw pointer, carry-chained
B1E6: 88              DEY                         
B1E7: 10 FB           BPL     loc_b1e4            ; fold all 40 bytes
B1E9: A8              TAY                         
B1EA: F0 02           BEQ     loc_b1ee            ; 
B1EC: 49 E5           EOR     #$E5                ; whiten the checksum with 0xe5

loc_b1ee:
B1EE: F0 02           BEQ     loc_b1f2            ; 
B1F0: 49 29           EOR     #$29                ; whiten it again with 0x29

loc_b1f2:
B1F2: 8D 55 04        STA     workRam+455         ; store the record checksum

loc_b1f5:
B1F5: A9 00           LDA     #$00                
B1F7: 20 FE B2        JSR     closeLayerPointer   ; close the layer pointer
B1FA: AD C4 CE        LDA     rom+3EC4            ; latch the play-mode header low byte into the first display word
B1FD: 8D 00 20        STA     vectorRam           ; store it into the first display word
B200: AD C5 CE        LDA     rom+3EC5            ; latch the play-mode header high byte
B203: 8D 01 20        STA     vectorRam+1         ; store it into the second display word
B206: B8              CLV                         
B207: 50 03           BVC     loc_b20c            ; 

loc_b209:
B209: 4C 30 B2        JMP     drawFrame           ; route the whole frame through the frame builder

loc_b20c:
B20C: 60              RTS                         

; computed-jump trampoline turned on by the vector housekeeping: the pre-
; doubled selector in mode cell 0x1 picks one of twelve display-mode
; targets (drawFrame, $D804, $B8BA,...) from the word table and runs it,
; dissolved here into a direct TABLE[0x1>>1] select.
dispatchDisplayModeHandler:
B20D: A6 01           LDX     workRam+1           ; read the pre-doubled display-mode selector
B20F: BD 19 B2        LDA     rom+2219,X          ; push the selected handler address high byte from the mode table
B212: 48              PHA                         
B213: BD 18 B2        LDA     rom+2218,X          ; push the handler address low byte
B216: 48              PHA                         
B217: 60              RTS                         ; jump to the selected display-mode handler

; ---- $B218-$B22F: data ----
B218: 2F B2 03 D8 B9 B8 E9 AD 80 AF 1B AE 61 AA 59 AA
B228: 6E AA 01 B1 30 B1 78 AA

; draw one whole frame: route each drawing subsystem in a fixed layer
; order (ids 0x07,0x04,0x03,0x06,0x05,0x00,0x01,0x08), bracketing each
; with cursor-setup $B2BE and teardown $B2FE; inside the player layer
; (0x00), when 0x5 bit7 is clear, sum a 40-byte block reached via pointer
; 0xb6/0xb7 into status cell 0x11b, then zero change-counter 0x114 and
; latch ROM constants 0xcec2/0xcec3 into head words 0x2000/0x2001.
drawFrame:
B230: A9 07           LDA     #$07                ; seat the draw cursor for layer 07
B232: 20 BE B2        JSR     seatDrawCursor      ; 
B235: 20 86 B5        JSR     drawScoreStatusList ; draw the score / status text list
B238: A9 07           LDA     #$07                
B23A: 20 FE B2        JSR     closeLayerPointer   ; close layer 07
B23D: A9 04           LDA     #$04                
B23F: 20 BE B2        JSR     seatDrawCursor      ; seat the draw cursor for layer 04
B242: 20 5B B7        JSR     drawSlotShapeList   ; draw the styled slot shape list
B245: A9 04           LDA     #$04                
B247: 20 FE B2        JSR     closeLayerPointer   ; close layer 04
B24A: A9 03           LDA     #$03                
B24C: 20 BE B2        JSR     seatDrawCursor      ; seat the draw cursor for layer 03
B24F: 20 AD B5        JSR     drawStyledSlotList  ; draw the secondary styled slot list
B252: A9 03           LDA     #$03                
B254: 20 FE B2        JSR     closeLayerPointer   ; close layer 03
B257: A9 06           LDA     #$06                
B259: 20 BE B2        JSR     seatDrawCursor      ; seat the draw cursor for layer 06
B25C: 20 9A B7        JSR     drawEnemyShapeList  ; draw the enemy shape list
B25F: A9 06           LDA     #$06                
B261: 20 FE B2        JSR     closeLayerPointer   ; close layer 06
B264: A9 05           LDA     #$05                
B266: 20 BE B2        JSR     seatDrawCursor      ; seat the draw cursor for layer 05
B269: 20 98 B4        JSR     buildObjectDisplayList; build the general object display list
B26C: A9 05           LDA     #$05                
B26E: 20 FE B2        JSR     closeLayerPointer   ; close layer 05
B271: A9 00           LDA     #$00                
B273: 20 BE B2        JSR     seatDrawCursor      ; seat the draw cursor for the player layer 00
B276: 20 B4 A8        JSR     buildTextOverlayList; build the text overlay list
B279: A5 05           LDA     workRam+5           ; 
B27B: 30 0D           BMI     loc_b28a            ; status sign bit set: skip the player-shape checksum
B27D: A9 F2           LDA     #$F2                ; player-shape checksum seed
B27F: 18              CLC                         
B280: A0 27           LDY     #$27                

loc_b282:
B282: 71 B6           ADC     (workRam+B6),Y      ; add each player-shape byte under the draw pointer, carry-chained
B284: 88              DEY                         
B285: 10 FB           BPL     loc_b282            ; fold all 40 bytes
B287: 8D 1B 01        STA     workRam+11B         ; store the player-shape signature

loc_b28a:
B28A: A9 00           LDA     #$00                
B28C: 20 FE B2        JSR     closeLayerPointer   ; close the player layer 00
B28F: 20 67 B3        JSR     paintRimLanes       ; draw the tube rim lanes
B292: A9 01           LDA     #$01                
B294: 20 BE B2        JSR     seatDrawCursor      ; seat the draw cursor for layer 01
B297: 20 C2 C5        JSR     buildEnemyDisplayList; build the enemy display list
B29A: A9 01           LDA     #$01                
B29C: 20 FE B2        JSR     closeLayerPointer   ; close layer 01
B29F: A9 08           LDA     #$08                
B2A1: 20 BE B2        JSR     seatDrawCursor      ; seat the draw cursor for layer 08
B2A4: 20 4D C5        JSR     drawTimedObjectList ; draw the timed-object list
B2A7: A9 08           LDA     #$08                
B2A9: 20 FE B2        JSR     closeLayerPointer   ; close layer 08
B2AC: A9 00           LDA     #$00                
B2AE: 8D 14 01        STA     workRam+114         ; clear the redraw change-counter
B2B1: AD C2 CE        LDA     rom+3EC2            ; latch the per-frame header low byte
B2B4: 8D 00 20        STA     vectorRam           ; store it into the first display word
B2B7: AD C3 CE        LDA     rom+3EC3            ; latch the per-frame header high byte
B2BA: 8D 01 20        STA     vectorRam+1         ; store it into the second display word
B2BD: 60              RTS                         

; seat the indirect draw cursor 0x74/0x75 for a layer: at stride 2*index
; pick the 16-bit pointer from table 0xce68 (when per-index flag
; 0x415+index is nonzero) else 0xce7a, publish it into 0x74/0x75, then
; clear status cell 0xa9.
seatDrawCursor:
B2BE: AA              TAX                         
B2BF: 0A              ASL     A                   ; double the layer index into a two-byte pointer-table stride
B2C0: A8              TAY                         
B2C1: BD 15 04        LDA     workRam+415,X       ; read this layer's pointer-parity flag
B2C4: D0 09           BNE     loc_b2cf            ; nonzero flag selects pointer table A
B2C6: BE 7A CE        LDX     rom+3E7A,Y          ; zero flag: take the pointer low byte from table B
B2C9: B9 7B CE        LDA     rom+3E7B,Y          ; and its high byte
B2CC: B8              CLV                         
B2CD: 50 06           BVC     loc_b2d5            ; 

loc_b2cf:
B2CF: BE 68 CE        LDX     rom+3E68,Y          ; nonzero flag: take the pointer low byte from table A
B2D2: B9 69 CE        LDA     rom+3E69,Y          ; and its high byte

loc_b2d5:
B2D5: 86 74           STX     workRam+74          ; seat the indirect draw cursor low byte
B2D7: 85 75           STA     workRam+75          ; seat the draw cursor high byte
B2D9: A9 00           LDA     #$00                
B2DB: 85 A9           STA     workRam+A9          ; clear the list offset so the walk starts at the head
B2DD: 60              RTS                         

; seat the alternate draw pointer 0x3b/0x3c: at stride 2*index pick from
; table 0xce7a (when flag 0x415+index nonzero) else 0xce68 (table sense
; reversed vs seatDrawCursor), publish it into 0x3b/0x3c, then clear
; status cell 0xa9.
seatAltDrawPointer:
B2DE: AA              TAX                         
B2DF: 0A              ASL     A                   ; double the layer index into a two-byte pointer-table stride
B2E0: A8              TAY                         
B2E1: BD 15 04        LDA     workRam+415,X       ; read this layer's pointer-parity flag
B2E4: D0 09           BNE     loc_b2ef            ; nonzero flag selects pointer table B -- sense reversed from the primary seater
B2E6: BE 68 CE        LDX     rom+3E68,Y          ; zero flag: take the pointer low byte from table A
B2E9: B9 69 CE        LDA     rom+3E69,Y          ; and its high byte
B2EC: B8              CLV                         
B2ED: 50 06           BVC     loc_b2f5            ; 

loc_b2ef:
B2EF: BE 7A CE        LDX     rom+3E7A,Y          ; nonzero flag: take the pointer low byte from table B
B2F2: B9 7B CE        LDA     rom+3E7B,Y          ; and its high byte

loc_b2f5:
B2F5: 86 3B           STX     workRam+3B          ; seat the alternate draw pointer low byte
B2F7: 85 3C           STA     workRam+3C          ; and its high byte
B2F9: A9 00           LDA     #$00                
B2FB: 85 A9           STA     workRam+A9          ; clear the list offset so the consumer starts at the head
B2FD: 60              RTS                         

; close a layer: emit a header record (emitRecordBodyC0), seat base
; pointer from 0xce8c/0xce8d at stride 2*slot, toggle the layer's parity
; flag 0x415+slot, and write the ($3b) pointer target with the word chosen
; by parity — 0xceb0/0xceb1 when set, else 0xce9e/0xce9f.
closeLayerPointer:
B2FE: 48              PHA                         
B2FF: 20 09 DF        JSR     emitRecordBodyC0    ; finalize the layer's header record
B302: 68              PLA                         
B303: AA              TAX                         
B304: 0A              ASL     A                   ; double the slot into a two-byte table index
B305: A8              TAY                         
B306: B9 8C CE        LDA     rom+3E8C,Y          ; read the layer's base pointer low byte
B309: 85 3B           STA     workRam+3B          ; seat the working pointer low byte
B30B: B9 8D CE        LDA     rom+3E8D,Y          ; base pointer high byte
B30E: 85 3C           STA     workRam+3C          ; seat working pointer high byte
B310: BD 15 04        LDA     workRam+415,X       ; read this slot's double-buffer parity flag
B313: 49 01           EOR     #$01                ; toggle it
B315: 9D 15 04        STA     workRam+415,X       ; store the toggled parity back
B318: D0 09           BNE     loc_b323            ; parity set selects the odd draw buffer
B31A: B9 9E CE        LDA     rom+3E9E,Y          ; even buffer pointer low byte
B31D: BE 9F CE        LDX     rom+3E9F,Y          ; even buffer pointer high byte
B320: B8              CLV                         
B321: 50 06           BVC     loc_b329            ; 

loc_b323:
B323: B9 B0 CE        LDA     rom+3EB0,Y          ; odd buffer pointer low byte
B326: BE B1 CE        LDX     rom+3EB1,Y          ; odd buffer pointer high byte

loc_b329:
B329: A0 00           LDY     #$00                
B32B: 91 3B           STA     (workRam+3B),Y      ; write the selected buffer pointer low byte through the working pointer
B32D: 8A              TXA                         
B32E: C8              INY                         
B32F: 91 3B           STA     (workRam+3B),Y      ; and its high byte
B331: 60              RTS                         

; emit a frame-link record with a mid-frame-change guard: if source 0xcec4
; differs from checkpoint 0x2000, latch it into 0x2000 and return carry
; set (caller redoes the frame); else copy a word from 0xce9e (offset 8
; when 0x415 nonzero, else 2) through cursor 0x74, clear 0x16e, reload
; cursor 0x74/0x75 from 0xce68 at that offset, and return carry clear.
emitFrameLink:
B332: AD C4 CE        LDA     rom+3EC4            ; read the live vector-list source header
B335: CD 00 20        CMP     vectorRam           ; compare it against the checkpoint copy
B338: F0 05           BEQ     loc_b33f            ; unchanged: go emit the frame link
B33A: 8D 00 20        STA     vectorRam           ; source moved mid-frame: re-latch the checkpoint
B33D: 38              SEC                         ; signal the caller to rebuild the frame
B33E: 60              RTS                         

loc_b33f:
B33F: AD 15 04        LDA     workRam+415         ; read the pointer-parity mode flag
B342: D0 05           BNE     loc_b349            ; nonzero mode picks the 0x08 record slot
B344: A2 02           LDX     #$02                ; otherwise the 0x02 record slot
B346: B8              CLV                         
B347: 50 02           BVC     loc_b34b            ; 

loc_b349:
B349: A2 08           LDX     #$08                ; the 0x08 record slot

loc_b34b:
B34B: BD 9E CE        LDA     rom+3E9E,X          ; read the selected buffer word's low byte
B34E: A0 00           LDY     #$00                
B350: 8C 6E 01        STY     workRam+16E         ; clear the score-display timer
B353: 91 74           STA     (workRam+74),Y      ; splice the low byte into the list at the draw cursor
B355: C8              INY                         
B356: BD 9F CE        LDA     rom+3E9F,X          ; selected buffer word's high byte
B359: 91 74           STA     (workRam+74),Y      ; splice the high byte
B35B: BD 68 CE        LDA     rom+3E68,X          ; reload the draw cursor low byte from the second table so the next record chains on
B35E: 85 74           STA     workRam+74          ; 
B360: BD 69 CE        LDA     rom+3E69,X          ; and its high byte
B363: 85 75           STA     workRam+75          ; 
B365: 18              CLC                         
B366: 60              RTS                         

; rebuild the sixteen-entry lane-flag block 0x425 from the active enemy
; tables (depth 0x2df, lane 0x283, near/far segments 0x2cc/0x2b9) and
; paint the rim lanes: an optional pre-pass seats pointers
; (seatDrawCursor/initAndDrawRimDepthCounters/closeLayerPointer on 0x114),
; a first pass writes each column's colour value through the ($3b) list,
; and a second pass ORs colour bits (0x00 or 0xc0) into the ($b0) list.
paintRimLanes:
B367: AD 14 01        LDA     workRam+114         ; read the redraw counter
B36A: F0 0D           BEQ     loc_b379            ; clear: skip the pointer pre-pass
B36C: A9 02           LDA     #$02                
B36E: 20 BE B2        JSR     seatDrawCursor      ; seat the draw cursor for rim layer 2
B371: 20 0D C3        JSR     initAndDrawRimDepthCounters; init and draw the rim depth counters
B374: A9 02           LDA     #$02                
B376: 20 FE B2        JSR     closeLayerPointer   ; close rim layer 2 and flip its buffer

loc_b379:
B379: A9 02           LDA     #$02                
B37B: 20 DE B2        JSR     seatAltDrawPointer  ; refresh the alternate draw pointer for layer 2
B37E: A9 00           LDA     #$00                
B380: A2 0F           LDX     #$0F                

loc_b382:
B382: 9D 25 04        STA     workRam+425,X       ; clear the sixteen-entry lane-flag block
B385: CA              DEX                         
B386: 10 FA           BPL     loc_b382            ; 

; ---- $B388-$B38A: data ----
B388: AD 06 01
B38B: 30 49           BMI     loc_b3d6            ; spike/close-up guard negative: skip the enemy-state merge
B38D: AE 1C 01        LDX     workRam+11C         ; start the enemy-slot sweep at the top slot

loc_b390:
B390: BD DF 02        LDA     workRam+2DF,X       ; read this slot's depth
B393: F0 3E           BEQ     loc_b3d3            ; empty slot: skip
B395: A0 00           LDY     #$00                
B397: BD 83 02        LDA     workRam+283,X       ; read this slot's state flags
B39A: 29 07           AND     #$07                ; low three state bits
B39C: C9 01           CMP     #$01                ; on-rim state?
B39E: D0 33           BNE     loc_b3d3            ; not drawn on the rim: skip
B3A0: C8              INY                         
B3A1: 84 29           STY     workRam+29          ; seed the per-lane flag byte at 1
B3A3: BD 83 02        LDA     workRam+283,X       ; re-read the slot state flags
B3A6: 29 80           AND     #$80                ; test the sign bit
B3A8: D0 1C           BNE     loc_b3c6            ; sign set: skip the near-lane contribution
B3AA: AD 48 01        LDA     workRam+148         ; read the enemy animation accumulator
B3AD: 30 0C           BMI     loc_b3bb            ; not animating: don't bump the flag
B3AF: BD DF 02        LDA     workRam+2DF,X       ; read this slot's depth
B3B2: CD 57 01        CMP     workRam+157         ; compare against the near-rim depth threshold
B3B5: B0 04           BCS     loc_b3bb            ; farther than the threshold: don't bump
B3B7: E6 29           INC     workRam+29          ; nearer while animating: bump the lane flag so the lane blinks
B3B9: E6 29           INC     workRam+29          ; 

loc_b3bb:
B3BB: A5 29           LDA     workRam+29          ; load the per-lane flag byte
B3BD: BC CC 02        LDY     workRam+2CC,X       ; near lane index
B3C0: 19 25 04        ORA     workRam+425,Y       ; 
B3C3: 99 25 04        STA     workRam+425,Y       ; OR the flag into the near lane

loc_b3c6:
B3C6: BC B9 02        LDY     workRam+2B9,X       ; far lane index
B3C9: A5 29           LDA     workRam+29          ; 
B3CB: 09 80           ORA     #$80                ; tag the far lane with bit7
B3CD: 19 25 04        ORA     workRam+425,Y       ; 
B3D0: 99 25 04        STA     workRam+425,Y       ; OR the tagged flag into the far lane

loc_b3d3:
B3D3: CA              DEX                         
B3D4: 10 BA           BPL     loc_b390            ; 

loc_b3d6:
B3D6: A9 06           LDA     #$06                ; base rim colour
B3D8: AC 25 01        LDY     workRam+125         ; read the wave-phase latch
B3DB: F0 0C           BEQ     loc_b3e9            ; 
B3DD: 30 0A           BMI     loc_b3e9            ; 
B3DF: A5 03           LDA     workRam+3           ; 
B3E1: 29 07           AND     #$07                
B3E3: C9 07           CMP     #$07                ; every eighth frame while the wave is ready...
B3E5: D0 02           BNE     loc_b3e9            ; 
B3E7: A9 01           LDA     #$01                ; ...use the alternate base colour

loc_b3e9:
B3E9: 85 29           STA     workRam+29          ; latch the base colour
B3EB: A0 FF           LDY     #$FF                
B3ED: A2 FF           LDX     #$FF                
B3EF: 86 2C           STX     workRam+2C          ; default the colour-cycle ramp offset to none
B3F1: AD 02 02        LDA     workRam+202         ; read the player's shot depth
B3F4: F0 0B           BEQ     loc_b401            ; no live shot: no aim highlight
B3F6: AD 01 02        LDA     workRam+201         ; read the player's fine aim angle
B3F9: 30 06           BMI     loc_b401            ; 
B3FB: AE 00 02        LDX     workRam+200         ; aim column A = the player's segment
B3FE: AC 01 02        LDY     workRam+201         ; aim column B = the player's fine angle

loc_b401:
B401: 86 2A           STX     workRam+2A          ; cache aim column A
B403: 84 2B           STY     workRam+2B          ; cache aim column B
B405: AD 24 01        LDA     workRam+124         ; read the rim colour-cycle phase
B408: 30 08           BMI     loc_b412            ; phase exhausted: no rotating ramp
B40A: 29 0E           AND     #$0E                
B40C: 4A              LSR     A                   
B40D: 85 2C           STA     workRam+2C          ; set the ramp offset from the colour-cycle phase
B40F: CE 24 01        DEC     workRam+124         ; advance the rim colour cycle

loc_b412:
B412: A2 0F           LDX     #$0F                ; first pass: lanes 15..0

loc_b414:
B414: A0 06           LDY     #$06                
B416: BD 25 04        LDA     workRam+425,X       ; read this lane's flag
B419: F0 0C           BEQ     loc_b427            ; unflagged lane: pick an aim or ramp colour
B41B: 29 02           AND     #$02                ; test the blink bit
B41D: F0 05           BEQ     loc_b424            ; flagged but non-blink: solid colour
B41F: A5 03           LDA     workRam+3           ; blink with frame parity
B421: 29 01           AND     #$01                
B423: A8              TAY                         

loc_b424:
B424: B8              CLV                         
B425: 50 24           BVC     loc_b44b            ; 

loc_b427:
B427: E4 2A           CPX     workRam+2A          ; compare the lane to aim column A
B429: F0 02           BEQ     loc_b42d            ; 
B42B: E4 2B           CPX     workRam+2B          ; compare the lane to aim column B

loc_b42d:
B42D: D0 05           BNE     loc_b434            ; not an aim column
B42F: A0 01           LDY     #$01                ; aim column: highlight colour
B431: B8              CLV                         
B432: 50 17           BVC     loc_b44b            ; 

loc_b434:
B434: AD 24 01        LDA     workRam+124         ; read the colour-cycle phase
B437: 30 10           BMI     loc_b449            ; ramp exhausted: use the held base colour
B439: 8A              TXA                         
B43A: 18              CLC                         
B43B: 65 2C           ADC     workRam+2C          ; rotate the ramp by the lane index
B43D: 29 07           AND     #$07                
B43F: C9 07           CMP     #$07                
B441: D0 02           BNE     loc_b445            ; 
B443: A9 03           LDA     #$03                ; wrap the top ramp step to colour 3

loc_b445:
B445: A8              TAY                         
B446: B8              CLV                         
B447: 50 02           BVC     loc_b44b            ; 

loc_b449:
B449: A4 29           LDY     workRam+29          ; held base colour

loc_b44b:
B44B: 98              TYA                         
B44C: BC 76 B4        LDY     rom+2476,X          ; look up this lane's slot in the rim list
B44F: 91 3B           STA     (workRam+3B),Y      ; write the colour into the rim display list
B451: CA              DEX                         
B452: 10 C0           BPL     loc_b414            ; 

; ---- $B454-$B455: data ----
B454: A2 0F
B456: 2C 11 01        BIT     workRam+111         ; read the tube-geometry flag
B459: 10 01           BPL     loc_b45c            ; 
B45B: CA              DEX                         ; geometry set: start the second pass one lane lower

loc_b45c:
B45C: A0 C0           LDY     #$C0                
B45E: BD 25 04        LDA     workRam+425,X       ; read this lane's flag
B461: 10 02           BPL     loc_b465            ; bit7 clear keeps the colour bits
B463: A0 00           LDY     #$00                ; bit7 set clears the colour bits

loc_b465:
B465: 84 58           STY     workRam+58          ; 
B467: BC 87 B4        LDY     rom+2487,X          ; look up this lane's slot in the patch list
B46A: B1 B0           LDA     (workRam+B0),Y      ; read the current patch-list byte
B46C: 29 1F           AND     #$1F                ; keep its low five bits
B46E: 05 58           ORA     workRam+58          ; fold in the chosen colour bits
B470: 91 B0           STA     (workRam+B0),Y      ; write the recoloured byte back to the patch list
B472: CA              DEX                         
B473: 10 E7           BPL     loc_b45c            ; 

; ---- $B475-$B497: data ----
B475: 60 A8 9C 92 86 7C 70 66 5A 50 44 3A 2E 24 18 0E
B485: 02 B2 3B 37 33 2F 2B 27 23 1F 1B 17 13 0F 0B 07
B495: 03 3F 1D

; build a vector display list for up to 0x12 active objects (kind from tag
; table 0x243, coords via 0x203 into the four coord banks
; 0x35a/0x36a/0x37a/0x38a): emit a rotated header code, screen-relative
; coordinate words and their negated shadow words per object through
; cursor 0x74/0x75, flushing the cursor when the byte offset saturates,
; then close with a trailing header (emitBlankVectorWordTag70).
buildObjectDisplayList:
B498: A0 0C           LDY     #$0C                
B49A: 84 9E           STY     workRam+9E          ; run count for the record header
B49C: A9 08           LDA     #$08                
B49E: 20 4C DF        JSR     emitTaggedVectorWord; emit the leading tagged vector word
B4A1: A2 66           LDX     #$66                
B4A3: 20 65 C7        JSR     layHeaderAndBuildRecord; open the object record header
B4A6: A9 12           LDA     #$12                
B4A8: 85 56           STA     workRam+56          ; draw budget: at most 18 objects
B4AA: A2 3F           LDX     #$3F                
B4AC: 86 37           STX     workRam+37          ; scan object slots from the top down
B4AE: A0 00           LDY     #$00                

loc_b4b0:
B4B0: A6 37           LDX     workRam+37          ; 
B4B2: BD 43 02        LDA     workRam+243,X       ; read this slot's object kind
B4B5: D0 03           BNE     loc_b4ba            ; 
B4B7: 4C 49 B5        JMP     loc_b549            ; empty slot: move to the next

loc_b4ba:
B4BA: C9 50           CMP     #$50                ; high kinds...
B4BC: 90 02           BCC     loc_b4c0            ; 
B4BE: C6 37           DEC     workRam+37          ; ...consume an extra slot

loc_b4c0:
B4C0: 48              PHA                         
B4C1: 29 3F           AND     #$3F                ; low six bits are the shape selector
B4C3: 91 74           STA     (workRam+74),Y      ; write the shape selector into the display list
B4C5: 68              PLA                         
B4C6: 2A              ROL     A                   ; rotate the kind byte to lift its top bits
B4C7: 2A              ROL     A                   
B4C8: 2A              ROL     A                   
B4C9: 29 03           AND     #$03                
B4CB: 18              CLC                         
B4CC: 69 01           ADC     #$01                
B4CE: 09 70           ORA     #$70                ; form the 0x70-tagged header code
B4D0: C8              INY                         
B4D1: 91 74           STA     (workRam+74),Y      ; write the header word
B4D3: C8              INY                         
B4D4: BD 03 02        LDA     workRam+203,X       ; look up the object's record index
B4D7: AA              TAX                         
B4D8: BD 8A 03        LDA     workRam+38A,X       ; object X low byte
B4DB: 38              SEC                         
B4DC: E5 68           SBC     workRam+68          ; subtract the viewpoint X offset
B4DE: 85 63           STA     workRam+63          ; 
B4E0: 91 74           STA     (workRam+74),Y      ; emit the projected X low byte
B4E2: C8              INY                         
B4E3: BD 7A 03        LDA     workRam+37A,X       ; object X high byte
B4E6: E5 69           SBC     workRam+69          ; subtract the viewpoint X offset with borrow
B4E8: 85 64           STA     workRam+64          ; 
B4EA: 29 1F           AND     #$1F                ; vector word high byte is five bits
B4EC: 91 74           STA     (workRam+74),Y      ; emit the projected X high byte
B4EE: C8              INY                         
B4EF: BD 6A 03        LDA     workRam+36A,X       ; object Y low byte
B4F2: 85 61           STA     workRam+61          ; 
B4F4: 91 74           STA     (workRam+74),Y      ; emit the Y low byte
B4F6: C8              INY                         
B4F7: BD 5A 03        LDA     workRam+35A,X       ; object Y high byte
B4FA: 85 62           STA     workRam+62          ; 
B4FC: 29 1F           AND     #$1F                
B4FE: 91 74           STA     (workRam+74),Y      ; emit the Y high byte
B500: C8              INY                         
B501: A9 00           LDA     #$00                
B503: 91 74           STA     (workRam+74),Y      ; three zero separator bytes...
B505: C8              INY                         
B506: 91 74           STA     (workRam+74),Y      ; 
B508: C8              INY                         
B509: 91 74           STA     (workRam+74),Y      ; 
B50B: A9 A0           LDA     #$A0                ; ...ending in the 0xa0 tag
B50D: C8              INY                         
B50E: 91 74           STA     (workRam+74),Y      ; write the tag byte
B510: C8              INY                         
B511: A5 63           LDA     workRam+63          ; negated X low byte -- the beam return stroke to origin
B513: 49 FF           EOR     #$FF                
B515: 18              CLC                         
B516: 69 01           ADC     #$01                
B518: 91 74           STA     (workRam+74),Y      ; emit it
B51A: C8              INY                         
B51B: A5 64           LDA     workRam+64          ; negated X high byte with carry
B51D: 49 FF           EOR     #$FF                
B51F: 69 00           ADC     #$00                
B521: 29 1F           AND     #$1F                
B523: 91 74           STA     (workRam+74),Y      ; emit it
B525: C8              INY                         
B526: A5 61           LDA     workRam+61          ; negated Y low byte
B528: 49 FF           EOR     #$FF                
B52A: 18              CLC                         
B52B: 69 01           ADC     #$01                
B52D: 91 74           STA     (workRam+74),Y      ; emit it
B52F: C8              INY                         
B530: A5 62           LDA     workRam+62          ; negated Y high byte with carry
B532: 49 FF           EOR     #$FF                
B534: 69 00           ADC     #$00                
B536: 29 1F           AND     #$1F                
B538: 91 74           STA     (workRam+74),Y      ; emit it
B53A: C8              INY                         
B53B: C0 F0           CPY     #$F0                ; page nearly full?
B53D: 90 06           BCC     loc_b545            ; 
B53F: 88              DEY                         
B540: 20 5F DF        JSR     advanceDisplayCursor; flush the display page
B543: A0 00           LDY     #$00                

loc_b545:
B545: C6 56           DEC     workRam+56          ; spend one from the draw budget
B547: 30 07           BMI     loc_b550            ; budget exhausted: done

loc_b549:
B549: C6 37           DEC     workRam+37          ; step to the next slot down
B54B: 30 03           BMI     loc_b550            ; scanned past slot 0: done
B54D: 4C B0 B4        JMP     loc_b4b0            ; 

loc_b550:
B550: 98              TYA                         
B551: F0 04           BEQ     loc_b557            ; 
B553: 88              DEY                         
B554: 20 5F DF        JSR     advanceDisplayCursor; flush the final partial page

loc_b557:
B557: A5 B5           LDA     workRam+B5          ; read the rolling display checksum
B559: F0 0A           BEQ     loc_b565            ; 
B55B: A5 46           LDA     workRam+46          ; 
B55D: C9 0A           CMP     #$0A                ; on level 10 or higher...
B55F: 90 04           BCC     loc_b565            ; 
B561: A9 7A           LDA     #$7A                ; ...a nonzero checksum stamps the interrupt heartbeat -- an anti-tamper poke
B563: 85 53           STA     workRam+53          ; 

loc_b565:
B565: A9 01           LDA     #$01                
B567: 4C 6A DF        JMP     emitBlankVectorWordTag70; close the list with a trailing blank tagged word

; Stores 0, 0, 0, A into the four bytes at the working pointer
; loc_74/loc_75 and advances the cursor by four.
emitBlankValueRecord:
B56A: 48              PHA                         
B56B: A0 00           LDY     #$00                
B56D: 98              TYA                         
B56E: 91 74           STA     (workRam+74),Y      ; three zero fields...
B570: C8              INY                         
B571: 91 74           STA     (workRam+74),Y      ; 
B573: C8              INY                         
B574: 91 74           STA     (workRam+74),Y      ; 
B576: C8              INY                         
B577: 68              PLA                         
B578: 91 74           STA     (workRam+74),Y      ; ...then the caller's value byte
B57A: A9 04           LDA     #$04                
B57C: 18              CLC                         
B57D: 65 74           ADC     workRam+74          ; advance the draw cursor by four
B57F: 85 74           STA     workRam+74          ; 
B581: 90 02           BCC     loc_b585            ; 
B583: E6 75           INC     workRam+75          ; carry into the cursor high byte

loc_b585:
B585: 60              RTS                         

; build the score/status vector run: raise rebuild flag 0x9e=0x01, read
; gate 0x202 and bail if 0 or >=0xf0, else latch it into 0x57 and 0x2f and
; — unless marker 0x201 holds 0x81 — kick the run builder
; drawTubeRimSegmentFromCorner with corner index 0x200 and size
; ((0x51>>1)&7)+1.
drawScoreStatusList:
B586: A9 01           LDA     #$01                
B588: 85 9E           STA     workRam+9E          ; raise the rebuild flag for this frame
B58A: AD 02 02        LDA     workRam+202         ; read the marker's depth/gate
B58D: F0 1D           BEQ     loc_b5ac            ; zero: nothing to show
B58F: C9 F0           CMP     #$F0                
B591: B0 19           BCS     loc_b5ac            ; out of the valid depth band: skip
B593: 85 57           STA     workRam+57          ; latch the depth
B595: 85 2F           STA     workRam+2F          ; and its mirror
B597: AD 01 02        LDA     workRam+201         ; read the marker byte
B59A: C9 81           CMP     #$81                
B59C: F0 0E           BEQ     loc_b5ac            ; skip marker 0x81: draw nothing
B59E: AC 00 02        LDY     workRam+200         ; the player's segment is the corner index
B5A1: A5 51           LDA     workRam+51          ; read the rim rotation offset
B5A3: 4A              LSR     A                   
B5A4: 29 07           AND     #$07                
B5A6: 18              CLC                         
B5A7: 69 01           ADC     #$01                ; spread size 1..8 from the rotation offset
B5A9: 20 A0 BD        JSR     drawTubeRimSegmentFromCorner; emit the rim-segment spread from the corner

loc_b5ac:
B5AC: 60              RTS                         

; draw a per-slot vector record for seven slots (0x37 from 6 down) when
; guard 0x106 bit7 is clear: for each non-empty control 0x2df+x cache it
; in 0x57, split paired byte 0x283+x into style nibble
; 0x55=(paired&0x18)>>3 and a doubled selector (paired&7)<<1, and dispatch
; the style's draw handler via dispatchSlotDrawHandler carrying slot index
; x.
drawStyledSlotList:
B5AD: AD 06 01        LDA     workRam+106         ; read the spike guard
B5B0: 30 24           BMI     loc_b5d6            ; spike/close-up pass suppresses the slot draw
B5B2: A2 06           LDX     #$06                
B5B4: 86 37           STX     workRam+37          ; walk the seven tube slots 6..0

loc_b5b6:
B5B6: A6 37           LDX     workRam+37          ; 
B5B8: BD DF 02        LDA     workRam+2DF,X       ; read this slot's enemy control/depth
B5BB: F0 15           BEQ     loc_b5d2            ; empty slot: skip
B5BD: 85 57           STA     workRam+57          ; hand the depth to the draw handler
B5BF: BD 83 02        LDA     workRam+283,X       ; read the paired flags byte
B5C2: 29 18           AND     #$18                ; style nibble, bits 4..3
B5C4: 4A              LSR     A                   
B5C5: 4A              LSR     A                   
B5C6: 4A              LSR     A                   
B5C7: 85 55           STA     workRam+55          ; latch the draw style
B5C9: BD 83 02        LDA     workRam+283,X       ; low three bits, doubled into a shape selector
B5CC: 29 07           AND     #$07                
B5CE: 0A              ASL     A                   
B5CF: 20 D7 B5        JSR     dispatchSlotDrawHandler; dispatch this slot's draw handler

loc_b5d2:
B5D2: C6 37           DEC     workRam+37          ; 
B5D4: 10 E0           BPL     loc_b5b6            ; 

loc_b5d6:
B5D6: 60              RTS                         

; computed jump: select one of five slot-draw handlers
; [$B5EB,$B71B,$B60F,$B622,$B69B] by A>>1 and tail-return its result,
; carrying the slot index x.
dispatchSlotDrawHandler:
B5D7: A8              TAY                         
B5D8: B9 E2 B5        LDA     rom+25E2,Y          ; look up the draw handler's address for this style
B5DB: 48              PHA                         
B5DC: B9 E1 B5        LDA     rom+25E1,Y          ; 
B5DF: 48              PHA                         
B5E0: 60              RTS                         ; jump into the selected handler

; ---- $B5E1-$B5EA: data ----
B5E1: EA B5 1A B7 0E B6 21 B6 9A B6

; draw the rim segment for slot x: set run count loc_9e=0x03, and on a
; negative slot byte loc_283+x prep a coordinate (buildSlotScreenPoint)
; and build at corner 0 (emitTubeRimSegmentVectors(0)), else build at the
; slot's own corner loc_2b9+x with a header picked from $B60B by style
; loc_55.
drawSlotRimSegment:
B5EB: A9 03           LDA     #$03                
B5ED: 85 9E           STA     workRam+9E          ; three vectors make the rim segment
B5EF: BD 83 02        LDA     workRam+283,X       ; read this slot's flag byte
B5F2: 30 0E           BMI     loc_b602            ; negative slot: compute the point and draw at corner 0
B5F4: BC B9 02        LDY     workRam+2B9,X       ; the slot's own tube corner
B5F7: A6 55           LDX     workRam+55          ; 
B5F9: BD 0B B6        LDA     rom+260B,X          ; pick the shape header for this draw style
B5FC: 20 A0 BD        JSR     drawTubeRimSegmentFromCorner; draw the rim segment from the corner
B5FF: B8              CLV                         
B600: 50 08           BVC     loc_b60a            ; 

loc_b602:
B602: 20 34 B6        JSR     buildSlotScreenPoint; derive the slot's screen point
B605: A0 00           LDY     #$00                
B607: 20 CB BD        JSR     emitTubeRimSegmentVectors; emit the rim segment anchored at corner 0

loc_b60a:
B60A: 60              RTS                         

; ---- $B60B-$B60E: data ----
B60B: 00 00 00 00

; Pairs a jump-mode byte from table $B61E (indexed by loc_28a+x & 0x03)
; with slot target loc_2b9+x and emits via bcfd.
emitJumpModeSlot:
B60F: BD 8A 02        LDA     workRam+28A,X       ; read the enemy's direction byte
B612: 29 03           AND     #$03                ; low two bits pick one of four jump-frame shapes
B614: A8              TAY                         
B615: B9 1E B6        LDA     rom+261E,Y          ; look up the jump-frame shape
B618: BC B9 02        LDY     workRam+2B9,X       ; the enemy's target segment
B61B: 4C FD BC        JMP     seatShapeParamsAndEmit; seat the shape and emit its vector record

; ---- $B61E-$B621: data ----
B61E: 1A 1A 4A 4C

; Pairs slot target loc_2b9+x with a four-phase animation offset (((loc_3
; & 0x03)<<1)+0x12) and emits via bcfd.
emitAnimatedPhaseSlot:
B622: BC B9 02        LDY     workRam+2B9,X       ; the lane this enemy occupies
B625: A5 03           LDA     workRam+3           ; frame counter
B627: 29 03           AND     #$03                
B629: 0A              ASL     A                   
B62A: 18              CLC                         
B62B: 69 12           ADC     #$12                ; four-phase shape index 0x12..0x18 from the frame counter
B62D: 4C FD BC        JMP     seatShapeParamsAndEmit; seat the shape and emit its record

; ---- $B630-$B633: data ----
B630: 12 14 16 18

; build slot x's screen point: index base coords loc_3ce/loc_3de by the
; slot's segment loc_2b9,x into loc_56/loc_58, offset each by the signed
; animation delta $B68B/$B687 at phase loc_2cc,x&0x0f (0x80-biased
; saturating add) into loc_2e (X)/loc_30 (Y), copy loc_57 to loc_2f, and
; load the style pair $BCDC/$BCEC at loc_112 into loc_59/loc_5a.
buildSlotScreenPoint:
B634: A5 57           LDA     workRam+57          ; stage the projection depth
B636: 85 2F           STA     workRam+2F          ; 
B638: BC B9 02        LDY     workRam+2B9,X       ; the slot's segment number
B63B: B9 CE 03        LDA     workRam+3CE,Y       ; base X of the segment corner
B63E: 85 56           STA     workRam+56          ; 
B640: B9 DE 03        LDA     workRam+3DE,Y       ; base Y of the segment corner
B643: 85 58           STA     workRam+58          ; 
B645: BD CC 02        LDA     workRam+2CC,X       ; animation phase, low nibble
B648: 29 0F           AND     #$0F                
B64A: A8              TAY                         
B64B: A5 56           LDA     workRam+56          ; 
B64D: 49 80           EOR     #$80                
B64F: 18              CLC                         
B650: 79 8B B6        ADC     rom+268B,Y          ; add the per-phase X vertex offset -- signed, excess-128
B653: 50 09           BVC     loc_b65e            ; 
B655: 10 05           BPL     loc_b65c            ; 
B657: A9 7F           LDA     #$7F                ; clamp toward the sign that overflowed
B659: B8              CLV                         
B65A: 50 02           BVC     loc_b65e            ; 

loc_b65c:
B65C: A9 80           LDA     #$80                

loc_b65e:
B65E: 49 80           EOR     #$80                
B660: 85 2E           STA     workRam+2E          ; store the finished screen X
B662: A5 58           LDA     workRam+58          ; 
B664: 49 80           EOR     #$80                
B666: 18              CLC                         
B667: 79 87 B6        ADC     rom+2687,Y          ; add the per-phase Y vertex offset
B66A: 50 09           BVC     loc_b675            ; 
B66C: 10 05           BPL     loc_b673            ; 
B66E: A9 7F           LDA     #$7F                ; clamp on signed overflow
B670: B8              CLV                         
B671: 50 02           BVC     loc_b675            ; 

loc_b673:
B673: A9 80           LDA     #$80                

loc_b675:
B675: 49 80           EOR     #$80                
B677: 85 30           STA     workRam+30          ; store the finished screen Y
B679: AC 12 01        LDY     workRam+112         ; current tube shape
B67C: B9 DC BC        LDA     rom+2CDC,Y          ; first draw-style byte for this shape
B67F: 85 59           STA     workRam+59          ; 
B681: B9 EC BC        LDA     rom+2CEC,Y          ; second draw-style byte
B684: 85 5A           STA     workRam+5A          ; 
B686: 60              RTS                         

; ---- $B687-$B69A: data ----
B687: 00 10 1F 28 2C 28 1F 10 00 F0 E1 D8 D4 D8 E1 F0
B697: 00 10 1F 28

; Builds slot x's screen position from loc_2df+x and segment base
; loc_3ce/loc_3de (segment loc_2b9+x), interpolating toward the next
; segment via b6fa when loc_2cc+x is negative, then folds deltas (c098),
; lays the header (c765), appends the pair (bd3e), and emits a frame-
; phased template word from $CEC8/$CEC9.
emitInterpolatedSlotVector:
B69B: BD DF 02        LDA     workRam+2DF,X       ; stage the slot's depth
B69E: 85 57           STA     workRam+57          ; 
B6A0: BC B9 02        LDY     workRam+2B9,X       ; the slot's segment
B6A3: B9 CE 03        LDA     workRam+3CE,Y       ; base X of the segment corner
B6A6: 85 56           STA     workRam+56          ; 
B6A8: B9 DE 03        LDA     workRam+3DE,Y       ; base Y of the segment corner
B6AB: 85 58           STA     workRam+58          ; 
B6AD: BD CC 02        LDA     workRam+2CC,X       ; read the phase byte
B6B0: 10 23           BPL     loc_b6d5            ; not mid-flip: use the segment position directly
B6B2: 98              TYA                         
B6B3: 18              CLC                         
B6B4: 69 01           ADC     #$01                ; the next segment, wrapping 0..15
B6B6: 29 0F           AND     #$0F                
B6B8: A8              TAY                         
B6B9: B9 CE 03        LDA     workRam+3CE,Y       ; 
B6BC: 38              SEC                         
B6BD: E5 56           SBC     workRam+56          ; delta to the next segment's X
B6BF: 20 FA B6        JSR     scaleByPhaseFraction; scale it by the flip-phase fraction
B6C2: 18              CLC                         
B6C3: 65 56           ADC     workRam+56          ; interpolate the X toward the next segment
B6C5: 85 56           STA     workRam+56          ; 
B6C7: B9 DE 03        LDA     workRam+3DE,Y       ; delta to the next segment's Y
B6CA: 38              SEC                         
B6CB: E5 58           SBC     workRam+58          ; 
B6CD: 20 FA B6        JSR     scaleByPhaseFraction; scale it by the flip-phase fraction
B6D0: 18              CLC                         
B6D1: 65 58           ADC     workRam+58          ; interpolate the Y toward the next segment
B6D3: 85 58           STA     workRam+58          ; 

loc_b6d5:
B6D5: 20 98 C0        JSR     projectPointThroughMathbox; project the point through the math box
B6D8: A2 61           LDX     #$61                
B6DA: 20 65 C7        JSR     layHeaderAndBuildRecord; lay the 0x61 header and open the record
B6DD: A9 00           LDA     #$00                
B6DF: 85 A9           STA     workRam+A9          ; reset the list offset
B6E1: 20 3E BD        JSR     appendNormalizedMantissaExponent; append the normalized mantissa/exponent pair
B6E4: 84 A9           STY     workRam+A9          ; save the appender's exit cursor
B6E6: A5 03           LDA     workRam+3           ; 
B6E8: 29 03           AND     #$03                
B6EA: 0A              ASL     A                   
B6EB: 18              CLC                         
B6EC: 69 4E           ADC     #$4E                ; pick a frame-phased template word
B6EE: A8              TAY                         
B6EF: BE C9 CE        LDX     rom+3EC9,Y          ; template word high byte
B6F2: B9 C8 CE        LDA     rom+3EC8,Y          ; template word low byte
B6F5: A4 A9           LDY     workRam+A9          ; 
B6F7: 4C 59 DF        JMP     emitVectorWordAtOffset; emit the template vector word at the saved offset

; Scales a value by slot x's low-three-bit phase fraction (loc_2cc+x &
; 0x07 into loc_2c) via three LSB-first conditional-add rounds with sign-
; preserving right shifts.
scaleByPhaseFraction:
B6FA: 85 29           STA     workRam+29          ; stash the input value
B6FC: BD CC 02        LDA     workRam+2CC,X       ; 
B6FF: 29 07           AND     #$07                ; the phase's 3-bit fraction, in eighths
B701: 85 2C           STA     workRam+2C          ; 
B703: 86 2B           STX     workRam+2B          ; 
B705: A2 02           LDX     #$02                
B707: A9 00           LDA     #$00                

loc_b709:
B709: 46 2C           LSR     workRam+2C          ; shift out the fraction's low bit
B70B: 90 03           BCC     loc_b710            ; bit clear: add nothing this round
B70D: 18              CLC                         
B70E: 65 29           ADC     workRam+29          ; bit set: add the input

loc_b710:
B710: 0A              ASL     A                   ; sign-preserving halve of the accumulator
B711: 08              PHP                         
B712: 6A              ROR     A                   
B713: 28              PLP                         
B714: 6A              ROR     A                   
B715: CA              DEX                         
B716: 10 F1           BPL     loc_b709            ; 
B718: A6 2B           LDX     workRam+2B          ; 
B71A: 60              RTS                         

; draw the rim segment for slot x with an animation style: latch loc_9e
; from loc_148's sign (0x04 if negative else 0x00) and a style byte loc_29
; from $B755 indexed by ((loc_148+0x40)&0xff)>>4 (clamped to 0 when >=5),
; then split on loc_283+x's sign into
; buildSlotScreenPoint+emitTubeRimSegmentVectors(loc_29) or
; drawTubeRimSegmentFromCorner(loc_29,loc_2b9+x).
drawStyledSlotRimSegment:
B71B: A9 04           LDA     #$04                
B71D: AC 48 01        LDY     workRam+148         ; read the animation accumulator
B720: 30 02           BMI     loc_b724            ; negative phase sets the run flag
B722: A9 00           LDA     #$00                

loc_b724:
B724: 85 9E           STA     workRam+9E          ; latch the run flag
B726: AD 48 01        LDA     workRam+148         ; 
B729: 18              CLC                         
B72A: 69 40           ADC     #$40                ; bias the animation accumulator
B72C: 4A              LSR     A                   ; high nibble is the phase index
B72D: 4A              LSR     A                   
B72E: 4A              LSR     A                   
B72F: 4A              LSR     A                   
B730: C9 05           CMP     #$05                ; wrap past the five style phases
B732: 90 02           BCC     loc_b736            ; 
B734: A9 00           LDA     #$00                

loc_b736:
B736: A8              TAY                         
B737: B9 55 B7        LDA     rom+2755,Y          ; latch this phase's style byte
B73A: 85 29           STA     workRam+29          ; 
B73C: BD 83 02        LDA     workRam+283,X       ; read this slot's flag byte
B73F: 30 0B           BMI     loc_b74c            ; negative slot: compute its screen point
B741: BC B9 02        LDY     workRam+2B9,X       ; the enemy's rim corner
B744: A5 29           LDA     workRam+29          ; 
B746: 20 A0 BD        JSR     drawTubeRimSegmentFromCorner; draw the styled segment from the corner
B749: B8              CLV                         
B74A: 50 08           BVC     loc_b754            ; 

loc_b74c:
B74C: 20 34 B6        JSR     buildSlotScreenPoint; derive the slot's screen point
B74F: A4 29           LDY     workRam+29          ; 
B751: 20 CB BD        JSR     emitTubeRimSegmentVectors; emit the styled segment at the point

loc_b754:
B754: 60              RTS                         

; ---- $B755-$B75A: data ----
B755: 0D 0C 0B 0A 09 09

; emit a shape vector for each of twelve slots (index 0x37 from 0x0b
; down): for each non-empty entry 0x2d3+x seat the shape in 0x57/0x2f,
; read target 0x2ad+x, and emit via seatShapeParamsAndEmit (near value
; 0x08 for x<8, phase-derived for far slots); afterward latch a per-level
; segment colour (0x04/0x0b/0x0c by stage 0x135) into colour RAM 0x808.
drawSlotShapeList:
B75B: A2 0B           LDX     #$0B                ; walk the twelve tube slots, high-to-low
B75D: 86 37           STX     workRam+37          ; seed the slot loop index

loc_b75f:
B75F: A6 37           LDX     workRam+37          ; 
B761: BD D3 02        LDA     workRam+2D3,X       ; read the slot's occupancy/depth byte
B764: F0 1B           BEQ     loc_b781            ; empty slot, skip it
B766: 85 57           STA     workRam+57          ; seat the object depth
B768: 85 2F           STA     workRam+2F          ; carry the depth to the emitter
B76A: E0 08           CPX     #$08                ; near slots (below 8) vs far slots
B76C: BC AD 02        LDY     workRam+2AD,X       ; the slot's target tube segment
B76F: B0 05           BCS     loc_b776            ; far slot: size pulses with the frame phase
B771: A9 08           LDA     #$08                ; near slot: fixed shape size
B773: B8              CLV                         
B774: 50 08           BVC     loc_b77e            ; 

loc_b776:
B776: A5 03           LDA     workRam+3           ; far slot: read the frame phase counter
B778: 0A              ASL     A                   
B779: 29 06           AND     #$06                
B77B: 18              CLC                         
B77C: 69 20           ADC     #$20                ; build a pulsing size for distant shapes

loc_b77e:
B77E: 20 FD BC        JSR     seatShapeParamsAndEmit; seat the params and emit the shape

loc_b781:
B781: C6 37           DEC     workRam+37          ; 
B783: 10 DA           BPL     loc_b75f            ; loop until the index passes 0
B785: A0 04           LDY     #$04                
B787: AD 35 01        LDA     workRam+135         ; read the current stage
B78A: C9 06           CMP     #$06                
B78C: 90 08           BCC     loc_b796            ; below stage 6: rim colour 0x04
B78E: A0 0B           LDY     #$0B                
B790: C9 08           CMP     #$08                
B792: 90 02           BCC     loc_b796            ; below stage 8: rim colour 0x0b
B794: A0 0C           LDY     #$0C                

loc_b796:
B796: 8C 08 08        STY     colorRam+8          ; latch the per-level rim colour into colour RAM
B799: 60              RTS                         

; emit one shape record per active enemy slot (0x37 from 7 down): clear
; 0x9e, and for each non-empty 0x30a+slot seat 0x57 and 0x29(=0x2fa+slot);
; shape 1 draws specially via animateShapeOneVector, else compute a shape
; word from 0x312+slot and 0xb7e5+shape and emit via
; seatShapeParamsAndEmit; finally latch 0x9f into 0x1ff when 0x720 nonzero
; and 0x9f>=0x0d.
drawEnemyShapeList:
B79A: A0 00           LDY     #$00                
B79C: 84 9E           STY     workRam+9E          ; clear the draw scratch
B79E: A2 07           LDX     #$07                ; walk the eight shape-bank slots, high-to-low
B7A0: 86 37           STX     workRam+37          ; 

loc_b7a2:
B7A2: A6 37           LDX     workRam+37          ; 
B7A4: BD 0A 03        LDA     workRam+30A,X       ; slot's active flag -- also its object depth
B7A7: F0 29           BEQ     loc_b7d2            ; empty slot, skip
B7A9: 85 57           STA     workRam+57          ; seat the object depth
B7AB: BD FA 02        LDA     workRam+2FA,X       ; slot's tube coordinate
B7AE: 85 29           STA     workRam+29          ; 
B7B0: BC 02 03        LDY     workRam+302,X       ; slot's shape id
B7B3: C0 01           CPY     #$01                
B7B5: D0 06           BNE     loc_b7bd            ; shape id other than 1
B7B7: 20 EB B7        JSR     animateShapeOneVector; shape 1: the special animated draw path
B7BA: B8              CLV                         
B7BB: 50 15           BVC     loc_b7d2            ; 

loc_b7bd:
B7BD: BD 12 03        LDA     workRam+312,X       ; other shapes: the animation byte
B7C0: 4A              LSR     A                   
B7C1: 29 FE           AND     #$FE                ; animation base, forced even
B7C3: C0 02           CPY     #$02                
B7C5: 90 02           BCC     loc_b7c9            ; shape id below 2 keeps the base
B7C7: A9 00           LDA     #$00                ; shape id 2 or more forces base 0

loc_b7c9:
B7C9: 18              CLC                         
B7CA: 79 E5 B7        ADC     rom+27E5,Y          ; add the per-shape table offset
B7CD: A4 29           LDY     workRam+29          ; the shape's coordinate
B7CF: 20 FD BC        JSR     seatShapeParamsAndEmit; seat the params and emit

loc_b7d2:
B7D2: C6 37           DEC     workRam+37          ; 
B7D4: 10 CC           BPL     loc_b7a2            ; loop until the index passes 0
B7D6: AD 20 07        LDA     workRam+720         ; high-level guard
B7D9: F0 09           BEQ     loc_b7e4            ; guard clear, nothing to latch
B7DB: A5 9F           LDA     workRam+9F          ; the current level byte
B7DD: C9 0D           CMP     #$0D                
B7DF: 90 03           BCC     loc_b7e4            ; only levels 0x0d and up are remembered
B7E1: 8D FF 01        STA     workRam+1FF         ; latch the high-level marker

loc_b7e4:
B7E4: 60              RTS                         

; ---- $B7E5-$B7EA: data ----
B7E5: 00 00 5A 58 56 1C

; advance and emit the shape-1 enemy animation: refresh axis params
; loc_56/loc_58 from loc_435/loc_445 at loc_29, run frame updaters
; (projectPointThroughMathbox, layHeaderAndBuildRecord), tick sub-timer
; loc_13c and on wrap advance phase loc_13b and reload loc_13c from
; $B82A+loc_13b, run phase handler dispatchDrawSetup when
; $B83D+loc_13b<0x80, then emit the phase's vector-pair word from
; $CEC8/$CEC9 at ((loc_13b<<1)+0x28)&0xff.
animateShapeOneVector:
B7EB: A4 29           LDY     workRam+29          ; the enemy's lane index
B7ED: B9 35 04        LDA     workRam+435,Y       ; lane midpoint into the projection point
B7F0: 85 56           STA     workRam+56          ; 
B7F2: B9 45 04        LDA     workRam+445,Y       ; lane midpoint, second axis
B7F5: 85 58           STA     workRam+58          ; 
B7F7: 20 98 C0        JSR     projectPointThroughMathbox; project the point through the math box
B7FA: A2 61           LDX     #$61                
B7FC: 20 65 C7        JSR     layHeaderAndBuildRecord; lay the record header
B7FF: AE 3B 01        LDX     workRam+13B         ; the animation phase
B802: CE 3C 01        DEC     workRam+13C         ; age the animation sub-timer
B805: D0 0A           BNE     loc_b811            ; sub-timer not expired yet
B807: E8              INX                         ; advance the keyframe phase
B808: 8E 3B 01        STX     workRam+13B         ; 
B80B: BD 2A B8        LDA     rom+282A,X          ; reload the sub-timer from the per-phase duration table
B80E: 8D 3C 01        STA     workRam+13C         ; 

loc_b811:
B811: BC 3D B8        LDY     rom+283D,X          ; this phase's setup code
B814: 30 03           BMI     loc_b819            ; 0x80 or more: no setup handler
B816: 20 4E B8        JSR     dispatchDrawSetup   ; run the phase's setup handler

loc_b819:
B819: AD 3B 01        LDA     workRam+13B         ; 
B81C: 0A              ASL     A                   
B81D: 18              CLC                         
B81E: 69 28           ADC     #$28                ; form the template index for this phase
B820: A8              TAY                         
B821: BE C9 CE        LDX     rom+3EC9,Y          ; fetch the phase's vector-pair word, high
B824: B9 C8 CE        LDA     rom+3EC8,Y          ; vector-pair word, low
B827: 4C 57 DF        JMP     emitVectorWord      ; emit the vector word

; ---- $B82A-$B84D: data ----
B82A: 02 02 02 02 02 04 03 02 01 20 03 03 03 03 03 03
B83A: 03 3B B8 00 02 02 02 02 02 02 02 04 06 FF FF FF
B84A: FF FF FF FF

; computed-jump dispatcher: caller's Y is a byte offset (0,2,4,6)
; selecting one of four draw-setup targets $B85F/$B875/$B888/$B896, and it
; tail-returns that routine's result.
dispatchDrawSetup:
B84E: B9 58 B8        LDA     rom+2858,Y          ; push the selected setup routine's address, high
B851: 48              PHA                         
B852: B9 57 B8        LDA     rom+2857,Y          ; setup routine address, low
B855: 48              PHA                         
B856: 60              RTS                         ; jump into the chosen setup routine

; ---- $B857-$B85E: data ----
B857: 5E B8 74 B8 87 B8 95 B8

; seed the paired three-entry arrays 0x22-0x24 and 0x809-0x80b with the
; fixed values 0x00, 0x04, 0x0c.
seedTripleArrays:
B85F: A9 0C           LDA     #$0C                
B861: 8D 0B 08        STA     colorRam+B          ; colour-RAM triple, entry 2
B864: 85 24           STA     workRam+24          ; colour-cycle triple, entry 2
B866: A9 04           LDA     #$04                
B868: 8D 0A 08        STA     colorRam+A          ; colour-RAM entry 1
B86B: 85 23           STA     workRam+23          ; colour-cycle entry 1
B86D: A9 00           LDA     #$00                
B86F: 85 22           STA     workRam+22          ; colour-cycle entry 0
B871: 8D 09 08        STA     colorRam+9          ; colour-RAM entry 0
B874: 60              RTS                         

; rotate the three-entry array 0x22-0x24 down by one, threading the
; wrapped value, and mirror each new entry into paired array 0x809-0x80b.
rotateTripleArray:
B875: A4 22           LDY     workRam+22          ; capture entry 0 as the wrap-around value
B877: A2 02           LDX     #$02                ; walk the triple, top-down

loc_b879:
B879: B5 22           LDA     workRam+22,X        ; save the current occupant
B87B: 48              PHA                         
B87C: 94 22           STY     workRam+22,X        ; drop the carried value into the colour-cycle slot
B87E: 98              TYA                         
B87F: 9D 09 08        STA     colorRam+9,X        ; mirror it into visible colour RAM
B882: 68              PLA                         
B883: A8              TAY                         ; the displaced occupant carries to the next slot
B884: CA              DEX                         
B885: 10 F2           BPL     loc_b879            ; 
B887: 60              RTS                         

; rebuild the packed-nibble table via unpackLevelNibbleTables, then seat
; the vector-list tail cursor 0x139=0x7f and 0x13a=0x04.
resetVectorTailCursor:
B888: 20 96 C1        JSR     unpackLevelNibbleTables; rebuild the packed per-level nibble geometry tables
B88B: A9 7F           LDA     #$7F                
B88D: 8D 39 01        STA     workRam+139         ; seat the vector-list tail cursor low
B890: A9 04           LDA     #$04                
B892: 8D 3A 01        STA     workRam+13A         ; seat the tail cursor high -- cursor sits at 0x047f
B895: 60              RTS                         

; emit a vector-RAM tail record from cursor 0x139/0x13a — low byte to
; 0x2ffc, high byte tagged 0x70 to 0x2ffd, 0xc0 terminator to 0x2fff —
; then step the cursor down by 0x20 with a 16-bit borrow into 0x13a and
; mask the low byte to 0x7f.
emitVectorTailRecord:
B896: AD 39 01        LDA     workRam+139         ; 
B899: 8D FC 2F        STA     vectorRam+FFC       ; jump target low = the current tail cursor
B89C: AD 3A 01        LDA     workRam+13A         ; 
B89F: 09 70           ORA     #$70                ; tag with the vector-generator jump opcode bits
B8A1: 8D FD 2F        STA     vectorRam+FFD       ; jump target high
B8A4: A9 C0           LDA     #$C0                
B8A6: 8D FF 2F        STA     vectorRam+FFF       ; halt word that ends the beam scan
B8A9: AD 39 01        LDA     workRam+139         ; 
B8AC: 38              SEC                         
B8AD: E9 20           SBC     #$20                ; step the cursor down one record
B8AF: 10 05           BPL     loc_b8b6            ; 
B8B1: 29 7F           AND     #$7F                
B8B3: CE 3A 01        DEC     workRam+13A         ; borrow into the cursor high byte

loc_b8b6:
B8B6: 8D 39 01        STA     workRam+139         ; store the advanced tail cursor low
B8B9: 60              RTS                         

; draw the 16-slot moving-object cascade into the display list: reset
; accumulators (loc_6a-loc_6d, loc_202, loc_68/loc_69) with
; loc_5f=0xe0/loc_5b=0xff, cache the base draw-struct pointer from
; selectPointerPair into loc_76/loc_77, lay an opening coord
; (emitCoordinateVectorWord), then count loc_37 from 0x0f down and for
; each active slot (loc_283+x nonzero) load loc_57/loc_56/loc_58 from
; loc_283/loc_263/loc_2a3+x, integrate deltas
; (projectPointThroughMathbox), emit the record body
; (emitCoordDeltaRecord, emitBlankValueRecord, emitObjectPositionVector)
; with pointer-swap shadow passes (swapDrawPointers), and set the slot
; phase into loc_9e; closes by swapping pointers back and finishing the
; base list (emitBlankVectorWordTag70, emitRecordBodyC0).
drawMovingObjectSlots:
B8BA: A9 3F           LDA     #$3F                
B8BC: A2 F2           LDX     #$F2                
B8BE: 20 39 DF        JSR     emitCoordinateVectorWord; emit the opening framing word
B8C1: A9 00           LDA     #$00                
B8C3: 85 6A           STA     workRam+6A          ; reset the delta integrators
B8C5: 85 6B           STA     workRam+6B          ; 
B8C7: 85 6C           STA     workRam+6C          ; 
B8C9: 85 6D           STA     workRam+6D          ; 
B8CB: 8D 02 02        STA     workRam+202         ; clear the player-shot depth
B8CE: 85 68           STA     workRam+68          ; clear the x-offset accumulator
B8D0: 85 69           STA     workRam+69          ; 
B8D2: A9 E0           LDA     #$E0                
B8D4: 85 5F           STA     workRam+5F          ; depth seed high
B8D6: A9 FF           LDA     #$FF                
B8D8: 85 5B           STA     workRam+5B          ; depth seed low
B8DA: 20 67 B9        JSR     selectPointerPair   ; pick the base draw-struct pointer pair
B8DD: 85 77           STA     workRam+77          ; cache it as the alternate cursor high
B8DF: 86 76           STX     workRam+76          ; alternate cursor low
B8E1: A2 0F           LDX     #$0F                ; walk the sixteen object slots, top-down
B8E3: 86 37           STX     workRam+37          ; 

loc_b8e5:
B8E5: A6 37           LDX     workRam+37          ; 
B8E7: BD 83 02        LDA     workRam+283,X       ; slot activity/flag byte
B8EA: F0 49           BEQ     loc_b935            ; inactive slot, skip
B8EC: 85 57           STA     workRam+57          ; the flag doubles as the object depth
B8EE: BD 63 02        LDA     workRam+263,X       ; object position, axis 1
B8F1: 85 56           STA     workRam+56          ; 
B8F3: BD A3 02        LDA     workRam+2A3,X       ; object position, axis 2
B8F6: 85 58           STA     workRam+58          ; 
B8F8: 20 98 C0        JSR     projectPointThroughMathbox; project the tube position to screen
B8FB: A9 00           LDA     #$00                
B8FD: 85 73           STA     workRam+73          ; clear the record header
B8FF: 20 44 B9        JSR     swapDrawPointers    ; swap to the alternate cursor for the shadow pass
B902: 20 BA C3        JSR     emitCoordDeltaRecord; emit the coord-delta record
B905: A9 A0           LDA     #$A0                
B907: 20 6A B5        JSR     emitBlankValueRecord; emit a blank value record
B90A: 20 44 B9        JSR     swapDrawPointers    ; swap back to the base cursor
B90D: A2 61           LDX     #$61                
B90F: 20 72 C7        JSR     emitObjectPositionVector; emit the object-position vector
B912: 20 55 B9        JSR     returnConstantTwo   ; fetch the constant 2
B915: 20 6C DF        JSR     emitVectorWordTag70 ; emit the tag-70 word
B918: A5 37           LDA     workRam+37          ; 
B91A: 29 07           AND     #$07                ; 8-phase animation index from the slot
B91C: C9 07           CMP     #$07                
B91E: D0 02           BNE     loc_b922            ; 
B920: A9 00           LDA     #$00                ; fold phase 7 to 0

loc_b922:
B922: A8              TAY                         
B923: 84 9E           STY     workRam+9E          ; store the animation phase
B925: A9 08           LDA     #$08                
B927: 20 4C DF        JSR     emitTaggedVectorWord; emit the tagged style/phase word
B92A: A9 00           LDA     #$00                
B92C: 20 4A DF        JSR     emitVectorWordTag60FromKey; emit the tag-60 word
B92F: 20 67 B9        JSR     selectPointerPair   ; re-cache the pointer pair
B932: 20 39 DF        JSR     emitCoordinateVectorWord; lay the next framing word

loc_b935:
B935: C6 37           DEC     workRam+37          ; 
B937: 10 AC           BPL     loc_b8e5            ; loop until the slot index passes 0
B939: 20 44 B9        JSR     swapDrawPointers    ; restore the pointer orientation
B93C: A9 01           LDA     #$01                
B93E: 20 6A DF        JSR     emitBlankVectorWordTag70; emit the closing blank tag-70 word
B941: 20 09 DF        JSR     emitRecordBodyC0    ; emit the C0 record body

; swap the two 16-bit draw pointers 0x74/0x75 and 0x76/0x77 so the shared
; cursor addresses the other structure.
swapDrawPointers:
B944: A6 74           LDX     workRam+74          ; exchange the primary and alternate draw cursors
B946: A4 75           LDY     workRam+75          ; 
B948: A5 76           LDA     workRam+76          ; 
B94A: 85 74           STA     workRam+74          ; 
B94C: 86 76           STX     workRam+76          ; 
B94E: A5 77           LDA     workRam+77          ; 
B950: 85 75           STA     workRam+75          ; 
B952: 84 77           STY     workRam+77          ; 
B954: 60              RTS                         

; register-only leaf that always returns the constant pair A=0x02, Y=0x00
; and touches no memory.
returnConstantTwo:
B955: A5 57           LDA     workRam+57          ; 
B957: 4A              LSR     A                   
B958: 4A              LSR     A                   
B959: 4A              LSR     A                   
B95A: 4A              LSR     A                   
B95B: A0 00           LDY     #$00                

loc_b95d:
B95D: C8              INY                         
B95E: 4A              LSR     A                   
B95F: D0 FC           BNE     loc_b95d            ; 
B961: 18              CLC                         
B962: 69 02           ADC     #$02                ; hand back the constant 2 -- the shift loop above always leaves a at 0
B964: A0 00           LDY     #$00                
B966: 60              RTS                         

; select a pointer pair by flag 0x415: zero picks 0xce87/0xce86, nonzero
; picks 0xce6f/0xce6e; returns the pair as (A,X).
selectPointerPair:
B967: AD 15 04        LDA     workRam+415         ; read the draw-struct parity flag
B96A: F0 09           BEQ     loc_b975            ; even parity: the clear pointer
B96C: AD 6F CE        LDA     rom+3E6F            ; odd parity: the set draw pointer, high
B96F: AE 6E CE        LDX     rom+3E6E            ; set draw pointer, low
B972: B8              CLV                         
B973: 50 06           BVC     loc_b97b            ; 

loc_b975:
B975: AD 87 CE        LDA     rom+3E87            ; even parity: the clear draw pointer, high
B978: AE 86 CE        LDX     rom+3E86            ; clear draw pointer, low

loc_b97b:
B97B: 60              RTS                         

; ---- $B97C-$BCFC: data ----
B97C: F0 E7 CF AA 80 56 31 19 10 19 31 56 80 AA CF E7
B98C: F0 F0 F0 B8 80 48 10 10 10 10 10 48 80 B8 F0 F0
B99C: F0 F0 B8 B8 80 48 48 10 10 10 48 48 80 B8 B8 F0
B9AC: EC D5 B1 90 70 4F 2B 14 14 2B 4F 70 90 B1 D5 EC
B9BC: F0 C0 A0 94 6C 60 40 10 10 40 60 6C 94 A0 C0 F0
B9CC: D9 C2 AC 97 80 69 52 3C 27 10 35 5A 80 A6 CA F0
B9DC: EA E0 9C 80 64 20 16 50 16 20 64 80 9C E0 EA B0
B9EC: 10 1E 2C 3A 48 56 64 70 90 9E AC BA C8 D6 E4 F0
B9FC: 10 1E 2D 3C 4B 5A 69 78 87 96 A5 B4 C3 D2 E1 F0
BA0C: 10 10 10 10 16 29 46 69 97 BA D7 EA F0 F0 F0 F0
BA1C: 10 24 30 36 3E 49 5A 75 94 A4 AC BA DA E2 EA F0
BA2C: 80 70 48 20 10 20 48 70 80 90 B8 E0 F0 E0 B8 90
BA3C: DA A4 87 80 79 5C 26 10 10 20 48 80 B8 E0 F0 F0
BA4C: 10 10 30 30 50 50 70 70 90 90 B0 B0 D0 D0 F0 F0
BA5C: B0 80 50 47 18 30 18 47 50 80 B0 B9 E8 D4 E8 B9
BA6C: 10 1E 21 28 3C 55 66 73 8D 9A AB C4 D8 DF E2 F0
BA7C: 80 AA CF E7 F0 E7 CF AA 80 56 31 19 10 19 31 56
BA8C: 80 B8 F0 F0 F0 F0 F0 B8 80 48 10 10 10 10 10 48
BA9C: 80 B8 B8 F0 F0 F0 B8 B8 80 48 48 10 10 10 48 48
BAAC: 94 B0 B8 A7 A7 B8 B0 94 6C 50 48 59 59 48 50 6C
BABC: 96 A3 C5 F0 F0 C5 A3 96 6A 5D 3B 10 10 3B 5D 6A
BACC: 3D 6A 97 C4 F0 C4 97 6A 3D 10 10 10 10 10 10 10
BADC: A0 E0 EA B0 EA E0 A0 80 60 20 16 50 16 20 60 80
BAEC: F0 D0 B0 90 70 50 30 10 10 30 50 70 90 B0 D0 F0
BAFC: 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
BB0C: F0 CB A6 80 5C 39 20 12 12 20 39 5C 80 A6 CB F0
BB1C: C0 A6 8A 6A 4A 2F 14 24 20 39 59 75 72 90 B0 D0
BB2C: 80 57 48 57 80 A9 BA A9 80 57 48 57 80 A9 BA A9
BB3C: E4 E8 B7 80 B7 E8 E4 B2 7A 47 20 10 20 47 7A B2
BB4C: 90 70 70 50 50 30 30 10 10 30 30 50 50 70 70 90
BB5C: E6 D0 E6 B9 AE 80 52 47 14 30 14 47 52 80 AE B9
BB6C: 7E 6A 51 3A 2C 2C 38 4E 4E 38 2C 2C 3A 51 6A 7E
BB7C: 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 02 03 04
BB8C: 04 04 08 08 08 08 0C 0C 0C 0C 00 00 00 00 04 04
BB9C: 04 08 04 08 08 0C 08 0C 0C 00 0C 00 00 04 00 04
BBAC: 06 07 09 08 07 09 0A 0C 0E 0F 01 00 0F 01 02 04
BBBC: 07 06 05 08 0B 0A 09 0C 0F 0E 0D 00 03 02 01 04
BBCC: 05 05 05 05 0B 0B 0B 0B 0B 00 00 00 00 00 00 05
BBDC: 04 08 0B 05 08 0C 0E 09 0C 00 03 0D 00 04 07 02
BBEC: 0D 0D 0D 0D 0D 0D 0D 00 03 03 03 03 03 03 03 00
BBFC: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
BC0C: 0C 0C 0C 0D 0E 0F 0F 00 01 01 02 03 04 04 04 00
BC1C: 0E 0D 0C 0D 0D 0D 01 0F 02 03 03 00 03 03 03 00
BC2C: 0B 09 07 05 03 01 0F 0D 0D 0F 01 03 05 07 09 0B
BC3C: 08 0B 0C 04 05 08 0B 0C 0D 0E 0F 01 02 03 04 05
BC4C: 0C 00 0C 00 0C 00 0C 00 04 00 04 00 04 00 04 00
BC5C: 0A 06 0C 08 0E 0A 00 0C 02 0E 04 00 06 02 08 04
BC6C: 0E 0C 0D 0E 00 02 02 00 0E 0E 00 02 03 04 02 00
BC7C: 00 01 02 03 04 05 06 07 0D 09 08 0C 0E 0F 0A 0B
BC8C: 18 1C 18 0F 18 18 18 18 0A 18 10 0F 18 0C 14 0A
BC9C: 50 50 50 68 50 50 68 B0 A0 50 90 80 20 B0 60 A0
BCAC: 40 20 40 80 40 40 70 60 00 20 40 00 A0 40 40 00
BCBC: FF FF FF FF FF FF FF 00 01 FF 00 00 FE 01 FF 01
BCCC: 00 00 00 00 00 00 00 FF FF FF FF 00 00 FF 00 FF
BCDC: 00 00 60 40 00 00 48 40 50 28 50 00 00 50 00 40
BCEC: 04 04 03 04 04 04 03 04 05 04 04 04 04 04 04 05
BCFC: 3E

; Stashes the value byte into loc_55 and loads loc_435+y/loc_445+y into
; loc_56/loc_58, then emits the coloured shape vector (bd09).
seatShapeParamsAndEmit:
BCFD: 85 55           STA     workRam+55          ; seat the draw style/colour selector
BCFF: B9 35 04        LDA     workRam+435,Y       ; segment midpoint into the projection point
BD02: 85 56           STA     workRam+56          ; 
BD04: B9 45 04        LDA     workRam+445,Y       ; segment midpoint, second axis
BD07: 85 58           STA     workRam+58          ; 

; Folds deltas (c098), lays the header (c765), appends the pair (bd3e),
; clamps a colour/intensity nibble from loc_78 (XOR 0x07, doubled, floored
; to 0x0a, high nibble) OR'd with 0x60, and emits a template word keyed by
; loc_55.
emitColoredShapeVector:
BD09: 20 98 C0        JSR     projectPointThroughMathbox; fold the coordinate deltas through the math box
BD0C: A2 61           LDX     #$61                
BD0E: 20 65 C7        JSR     layHeaderAndBuildRecord; lay the fixed record header
BD11: A9 00           LDA     #$00                
BD13: 85 A9           STA     workRam+A9          ; reset the cursor run length
BD15: 20 3E BD        JSR     appendNormalizedMantissaExponent; append the mantissa/exponent size pair
BD18: A5 78           LDA     workRam+78          ; the interpolated colour attribute
BD1A: 49 07           EOR     #$07                ; invert the low colour bits
BD1C: 0A              ASL     A                   
BD1D: C9 0A           CMP     #$0A                
BD1F: B0 02           BCS     loc_bd23            ; 
BD21: A9 0A           LDA     #$0A                ; floor the intensity to a visible minimum

loc_bd23:
BD23: 0A              ASL     A                   ; seat the colour in the high nibble
BD24: 0A              ASL     A                   
BD25: 0A              ASL     A                   
BD26: 0A              ASL     A                   
BD27: 91 74           STA     (workRam+74),Y      ; write the colour/intensity byte
BD29: C8              INY                         
BD2A: A9 60           LDA     #$60                
BD2C: 91 74           STA     (workRam+74),Y      ; write the companion attribute byte
BD2E: C8              INY                         
BD2F: 84 A9           STY     workRam+A9          ; record the advanced run length
BD31: A4 55           LDY     workRam+55          ; the style selector indexes the template tables
BD33: BE C9 CE        LDX     rom+3EC9,Y          ; entry glyph word, high
BD36: B9 C8 CE        LDA     rom+3EC8,Y          ; entry glyph word, low
BD39: A4 A9           LDY     workRam+A9          ; 
BD3B: 4C 59 DF        JMP     emitVectorWordAtOffset; emit the templated glyph word

; Appends a (mantissa, exponent) pair for loc_57: trivial (1,0) when
; loc_57<0x10, else drives the math box on loc_57-loc_5f/loc_5b and
; normalizes loc_79 into shift count loc_78; writes the pair (exponent
; tagged 0x70) at cursor loc_a9.
appendNormalizedMantissaExponent:
BD3E: A5 57           LDA     workRam+57          ; the object depth
BD40: C9 10           CMP     #$10                
BD42: 90 48           BCC     loc_bd8c            ; near object: draw at full size
BD44: 38              SEC                         
BD45: E5 5F           SBC     workRam+5F          ; depth minus the seed -- a 16-bit difference
BD47: 8D 95 60        STA     mathboxGo+15        ; into the math-box operand low
BD4A: A9 00           LDA     #$00                
BD4C: E5 5B           SBC     workRam+5B          ; 
BD4E: 8D 96 60        STA     mathboxGo+16        ; math-box operand high
BD51: A9 18           LDA     #$18                
BD53: 8D 8C 60        STA     mathboxGo+C         ; iteration count
BD56: A5 A0           LDA     workRam+A0          ; 
BD58: 8D 8E 60        STA     mathboxGo+E         ; divisor
BD5B: 8D 94 60        STA     mathboxGo+14        ; issue the divide

loc_bd5e:
BD5E: 2C 40 60        BIT     mathboxStatus / earomControl; poll the math-box busy bit
BD61: 30 FB           BMI     loc_bd5e            ; spin until the divide is done
BD63: AD 60 60        LDA     mathboxLo           ; read the result low
BD66: 85 79           STA     workRam+79          ; 
BD68: AD 70 60        LDA     mathboxHi           ; read the result high
BD6B: 85 7A           STA     workRam+7A          ; 
BD6D: A2 0F           LDX     #$0F                
BD6F: 8E 8C 60        STX     mathboxGo+C         ; reload the iteration count
BD72: 38              SEC                         
BD73: E9 01           SBC     #$01                
BD75: D0 02           BNE     loc_bd79            ; 
BD77: A9 01           LDA     #$01                ; clamp the high byte to at least 1

loc_bd79:
BD79: A2 00           LDX     #$00                

loc_bd7b:
BD7B: E8              INX                         ; normalize: count the shifts
BD7C: 06 79           ASL     workRam+79          ; 
BD7E: 2A              ROL     A                   
BD7F: 90 FA           BCC     loc_bd7b            ; until a 1 rolls out of the top
BD81: 4A              LSR     A                   
BD82: 49 7F           EOR     #$7F                ; two's-complement fold into the exponent
BD84: 18              CLC                         
BD85: 69 01           ADC     #$01                
BD87: A8              TAY                         
BD88: 8A              TXA                         
BD89: B8              CLV                         
BD8A: 50 04           BVC     loc_bd90            ; 

loc_bd8c:
BD8C: A9 01           LDA     #$01                ; near path: mantissa 1
BD8E: A0 00           LDY     #$00                ; exponent 0 -- full size

loc_bd90:
BD90: 85 78           STA     workRam+78          ; store the mantissa
BD92: 48              PHA                         
BD93: 98              TYA                         
BD94: A4 A9           LDY     workRam+A9          ; 
BD96: 91 74           STA     (workRam+74),Y      ; write the exponent byte
BD98: C8              INY                         
BD99: 68              PLA                         
BD9A: 09 70           ORA     #$70                ; tag the mantissa with 0x70
BD9C: 91 74           STA     (workRam+74),Y      ; write the mantissa byte
BD9E: C8              INY                         
BD9F: 60              RTS                         

; draw one tube-rim segment: fetch the source corner (loc_3ce+y/loc_3de+y
; into loc_56/loc_58, loc_2f from loc_57) and the next corner ((y+1)&0x0f)
; into loc_2e/loc_30, seed run counters loc_59=0 and loc_5a=4, then fall
; into emitTubeRimSegmentVectors to emit the segment's four-byte vector
; records.
drawTubeRimSegmentFromCorner:
BDA0: 85 36           STA     workRam+36          ; stash the style/shape selector
BDA2: B9 CE 03        LDA     workRam+3CE,Y       ; first endpoint from source corner
BDA5: 85 56           STA     workRam+56          ; 
BDA7: B9 DE 03        LDA     workRam+3DE,Y       ; first endpoint, second axis
BDAA: 85 58           STA     workRam+58          ; 
BDAC: A5 57           LDA     workRam+57          ; 
BDAE: 85 2F           STA     workRam+2F          ; carry the depth to the second endpoint
BDB0: 98              TYA                         
BDB1: 18              CLC                         
BDB2: 69 01           ADC     #$01                
BDB4: 29 0F           AND     #$0F                ; next corner, wrapping the 16-corner ring
BDB6: AA              TAX                         
BDB7: BD CE 03        LDA     workRam+3CE,X       ; second endpoint
BDBA: 85 2E           STA     workRam+2E          ; 
BDBC: BD DE 03        LDA     workRam+3DE,X       ; second endpoint, second axis
BDBF: 85 30           STA     workRam+30          ; 
BDC1: A9 00           LDA     #$00                
BDC3: 85 59           STA     workRam+59          ; seed the clamp tally
BDC5: A9 04           LDA     #$04                
BDC7: 85 5A           STA     workRam+5A          ; seed the run size to 4
BDC9: A4 36           LDY     workRam+36          ; 

; emit the tube-rim segment's vector records: gate (unless loc_5b bit7,
; return when loc_57<loc_5f), read record count loc_99 and cursor loc_38
; from $BFB6/$BFC4, project both endpoints through the math box
; (projectPointThroughMathbox), form two clamped signed deltas
; (loc_79/loc_9b, loc_89/loc_9d), expand the fivefold spread, then write
; loc_99 four-byte records into (loc_74)+loc_a9 and close with
; advanceDisplayCursor.
emitTubeRimSegmentVectors:
BDCB: A5 5B           LDA     workRam+5B          ; the depth-force flag
BDCD: 30 07           BMI     loc_bdd6            ; bit 7 forces the segment to draw
BDCF: A5 57           LDA     workRam+57          ; object depth
BDD1: C5 5F           CMP     workRam+5F          ; 
BDD3: B0 01           BCS     loc_bdd6            ; 
BDD5: 60              RTS                         ; too near the rim, skip the segment

loc_bdd6:
BDD6: B9 B6 BF        LDA     rom+2FB6,Y          ; this corner's record count
BDD9: 85 99           STA     workRam+99          ; 
BDDB: B9 C4 BF        LDA     rom+2FC4,Y          ; start of this corner's packed table
BDDE: 85 38           STA     workRam+38          ; 
BDE0: A4 9E           LDY     workRam+9E          ; 
BDE2: A9 08           LDA     #$08                
BDE4: 20 4C DF        JSR     emitTaggedVectorWord; emit the run's colour/style word
BDE7: 20 98 C0        JSR     projectPointThroughMathbox; project the first endpoint
BDEA: A2 61           LDX     #$61                
BDEC: 20 65 C7        JSR     layHeaderAndBuildRecord; lay its header record
BDEF: A5 2E           LDA     workRam+2E          ; move in the second endpoint
BDF1: 85 56           STA     workRam+56          ; 
BDF3: A5 2F           LDA     workRam+2F          ; 
BDF5: 85 57           STA     workRam+57          ; 
BDF7: A5 30           LDA     workRam+30          ; 
BDF9: 85 58           STA     workRam+58          ; 
BDFB: 20 98 C0        JSR     projectPointThroughMathbox; project the second endpoint
BDFE: A4 59           LDY     workRam+59          ; 
BE00: A5 5A           LDA     workRam+5A          ; 
BE02: 20 6C DF        JSR     emitVectorWordTag70 ; emit the run's tag-70 word
BE05: A5 61           LDA     workRam+61          ; delta 1: projected Y minus previous Y
BE07: 38              SEC                         
BE08: E5 6A           SBC     workRam+6A          ; 
BE0A: 85 79           STA     workRam+79          ; the segment's direction magnitude
BE0C: A5 62           LDA     workRam+62          ; 
BE0E: E5 6B           SBC     workRam+6B          ; 
BE10: 85 9B           STA     workRam+9B          ; its sign high byte
BE12: 30 09           BMI     loc_be1d            ; 
BE14: F0 04           BEQ     loc_be1a            ; 
BE16: A9 FF           LDA     #$FF                ; clamp the magnitude on overflow
BE18: 85 79           STA     workRam+79          ; 

loc_be1a:
BE1A: B8              CLV                         
BE1B: 50 16           BVC     loc_be33            ; 

loc_be1d:
BE1D: C9 FF           CMP     #$FF                
BE1F: F0 05           BEQ     loc_be26            ; 
BE21: A9 FF           LDA     #$FF                
BE23: B8              CLV                         
BE24: 50 0B           BVC     loc_be31            ; 

loc_be26:
BE26: A5 79           LDA     workRam+79          ; 
BE28: 49 FF           EOR     #$FF                
BE2A: 18              CLC                         
BE2B: 69 01           ADC     #$01                
BE2D: 90 02           BCC     loc_be31            ; 
BE2F: A9 FF           LDA     #$FF                

loc_be31:
BE31: 85 79           STA     workRam+79          ; 

loc_be33:
BE33: A5 63           LDA     workRam+63          ; delta 2: projected X minus previous X
BE35: 38              SEC                         
BE36: E5 6C           SBC     workRam+6C          ; 
BE38: 85 89           STA     workRam+89          ; the segment's direction magnitude
BE3A: A5 64           LDA     workRam+64          ; 
BE3C: E5 6D           SBC     workRam+6D          ; 
BE3E: 85 9D           STA     workRam+9D          ; its sign high byte
BE40: 30 09           BMI     loc_be4b            ; 
BE42: F0 04           BEQ     loc_be48            ; 
BE44: A9 FF           LDA     #$FF                ; clamp on overflow
BE46: 85 89           STA     workRam+89          ; 

loc_be48:
BE48: B8              CLV                         
BE49: 50 12           BVC     loc_be5d            ; 

loc_be4b:
BE4B: C9 FF           CMP     #$FF                
BE4D: F0 05           BEQ     loc_be54            ; 
BE4F: A9 FF           LDA     #$FF                
BE51: B8              CLV                         
BE52: 50 07           BVC     loc_be5b            ; 

loc_be54:
BE54: A5 89           LDA     workRam+89          ; 
BE56: 49 FF           EOR     #$FF                
BE58: 18              CLC                         
BE59: 69 01           ADC     #$01                

loc_be5b:
BE5B: 85 89           STA     workRam+89          ; 

loc_be5d:
BE5D: A9 00           LDA     #$00                
BE5F: 85 82           STA     workRam+82          ; 
BE61: 85 92           STA     workRam+92          ; 
BE63: A5 79           LDA     workRam+79          ; scale the Y-delta into the x1..x7 offset ladder
BE65: 0A              ASL     A                   
BE66: 26 82           ROL     workRam+82          ; 
BE68: 85 7A           STA     workRam+7A          ; 
BE6A: 0A              ASL     A                   
BE6B: 85 7C           STA     workRam+7C          ; 
BE6D: A5 82           LDA     workRam+82          ; 
BE6F: 2A              ROL     A                   
BE70: 85 84           STA     workRam+84          ; 
BE72: A5 7C           LDA     workRam+7C          ; 
BE74: 65 79           ADC     workRam+79          ; 
BE76: 85 7D           STA     workRam+7D          ; 
BE78: A5 84           LDA     workRam+84          ; 
BE7A: 69 00           ADC     #$00                
BE7C: 85 85           STA     workRam+85          ; 
BE7E: A5 7A           LDA     workRam+7A          ; 
BE80: 65 79           ADC     workRam+79          ; 
BE82: 85 7B           STA     workRam+7B          ; 
BE84: A5 82           LDA     workRam+82          ; 
BE86: 69 00           ADC     #$00                
BE88: 85 83           STA     workRam+83          ; 
BE8A: 85 86           STA     workRam+86          ; 
BE8C: A5 7B           LDA     workRam+7B          ; 
BE8E: 0A              ASL     A                   
BE8F: 85 7E           STA     workRam+7E          ; 
BE91: 26 86           ROL     workRam+86          ; 
BE93: 65 79           ADC     workRam+79          ; 
BE95: 85 7F           STA     workRam+7F          ; 
BE97: A5 86           LDA     workRam+86          ; 
BE99: 69 00           ADC     #$00                
BE9B: 85 87           STA     workRam+87          ; 
BE9D: A5 89           LDA     workRam+89          ; scale the X-delta into the x1..x7 offset ladder
BE9F: 0A              ASL     A                   
BEA0: 26 92           ROL     workRam+92          ; 
BEA2: 85 8A           STA     workRam+8A          ; 
BEA4: 0A              ASL     A                   
BEA5: 85 8C           STA     workRam+8C          ; 
BEA7: A5 92           LDA     workRam+92          ; 
BEA9: 2A              ROL     A                   
BEAA: 85 94           STA     workRam+94          ; 
BEAC: A5 8C           LDA     workRam+8C          ; 
BEAE: 65 89           ADC     workRam+89          ; 
BEB0: 85 8D           STA     workRam+8D          ; 
BEB2: A5 94           LDA     workRam+94          ; 
BEB4: 69 00           ADC     #$00                
BEB6: 85 95           STA     workRam+95          ; 
BEB8: A5 8A           LDA     workRam+8A          ; 
BEBA: 65 89           ADC     workRam+89          ; 
BEBC: 85 8B           STA     workRam+8B          ; 
BEBE: A5 92           LDA     workRam+92          ; 
BEC0: 69 00           ADC     #$00                
BEC2: 85 93           STA     workRam+93          ; 
BEC4: 85 96           STA     workRam+96          ; 
BEC6: A5 8B           LDA     workRam+8B          ; 
BEC8: 0A              ASL     A                   
BEC9: 85 8E           STA     workRam+8E          ; 
BECB: 26 96           ROL     workRam+96          ; 
BECD: 65 89           ADC     workRam+89          ; 
BECF: 85 8F           STA     workRam+8F          ; 
BED1: A5 96           LDA     workRam+96          ; 
BED3: 69 00           ADC     #$00                
BED5: 85 97           STA     workRam+97          ; 
BED7: A0 00           LDY     #$00                
BED9: 84 A9           STY     workRam+A9          ; 

loc_bedb:
BEDB: A4 38           LDY     workRam+38          ; walk the packed corner table
BEDD: B9 D3 BF        LDA     rom+2FD3,Y          ; the record's vector-generator header
BEE0: C9 01           CMP     #$01                
BEE2: D0 02           BNE     loc_bee6            ; 
BEE4: A9 C0           LDA     #$C0                ; header 1 is shorthand for the 0xc0 draw-mode

loc_bee6:
BEE6: 85 73           STA     workRam+73          ; 
BEE8: B9 D2 BF        LDA     rom+2FD2,Y          ; the packed sign/index selector byte
BEEB: 85 2D           STA     workRam+2D          ; 
BEED: C8              INY                         
BEEE: C8              INY                         
BEEF: 84 38           STY     workRam+38          ; 
BEF1: AA              TAX                         
BEF2: 29 07           AND     #$07                ; low 3 bits index the spread for the point
BEF4: A8              TAY                         
BEF5: 8A              TXA                         
BEF6: 0A              ASL     A                   
BEF7: 85 2B           STA     workRam+2B          ; doubled copy carries the x sign into bit 7
BEF9: 4A              LSR     A                   
BEFA: 4A              LSR     A                   
BEFB: 4A              LSR     A                   
BEFC: 4A              LSR     A                   
BEFD: 29 07           AND     #$07                ; next 3 bits index the cross offset
BEFF: AA              TAX                         
BF00: A5 2B           LDA     workRam+2B          ; 
BF02: 45 9B           EOR     workRam+9B          ; combine the packed sign with the direction sign
BF04: 30 0B           BMI     loc_bf11            ; negate the offset when the signs differ
BF06: B9 78 00        LDA     workRam+78,Y        ; 
BF09: 85 61           STA     workRam+61          ; 
BF0B: B9 80 00        LDA     workRam+80,Y        ; 
BF0E: B8              CLV                         
BF0F: 50 11           BVC     loc_bf22            ; 

loc_bf11:
BF11: B9 78 00        LDA     workRam+78,Y        ; 
BF14: 49 FF           EOR     #$FF                
BF16: 18              CLC                         
BF17: 69 01           ADC     #$01                
BF19: 85 61           STA     workRam+61          ; 
BF1B: B9 80 00        LDA     workRam+80,Y        ; 
BF1E: 49 FF           EOR     #$FF                
BF20: 69 00           ADC     #$00                

loc_bf22:
BF22: 85 62           STA     workRam+62          ; 
BF24: A5 2D           LDA     workRam+2D          ; 
BF26: 45 9D           EOR     workRam+9D          ; combine with the second direction sign
BF28: 10 0E           BPL     loc_bf38            ; 
BF2A: B5 88           LDA     workRam+88,X        ; 
BF2C: 18              CLC                         
BF2D: 65 61           ADC     workRam+61          ; 
BF2F: 85 61           STA     workRam+61          ; 
BF31: B5 90           LDA     workRam+90,X        ; 
BF33: 65 62           ADC     workRam+62          ; 
BF35: B8              CLV                         
BF36: 50 0B           BVC     loc_bf43            ; 

loc_bf38:
BF38: A5 61           LDA     workRam+61          ; 
BF3A: 38              SEC                         
BF3B: F5 88           SBC     workRam+88,X        ; 
BF3D: 85 61           STA     workRam+61          ; 
BF3F: A5 62           LDA     workRam+62          ; 
BF41: F5 90           SBC     workRam+90,X        ; 

loc_bf43:
BF43: 85 62           STA     workRam+62          ; 
BF45: A5 2B           LDA     workRam+2B          ; 
BF47: 45 9D           EOR     workRam+9D          ; 
BF49: 30 0B           BMI     loc_bf56            ; 
BF4B: B9 88 00        LDA     workRam+88,Y        ; 
BF4E: 85 63           STA     workRam+63          ; 
BF50: B9 90 00        LDA     workRam+90,Y        ; 
BF53: B8              CLV                         
BF54: 50 11           BVC     loc_bf67            ; 

loc_bf56:
BF56: B9 88 00        LDA     workRam+88,Y        ; 
BF59: 49 FF           EOR     #$FF                
BF5B: 18              CLC                         
BF5C: 69 01           ADC     #$01                
BF5E: 85 63           STA     workRam+63          ; 
BF60: B9 90 00        LDA     workRam+90,Y        ; 
BF63: 49 FF           EOR     #$FF                
BF65: 69 00           ADC     #$00                

loc_bf67:
BF67: 85 64           STA     workRam+64          ; 
BF69: A5 2D           LDA     workRam+2D          ; 
BF6B: 45 9B           EOR     workRam+9B          ; 
BF6D: 10 0E           BPL     loc_bf7d            ; 
BF6F: A5 63           LDA     workRam+63          ; 
BF71: 38              SEC                         
BF72: F5 78           SBC     workRam+78,X        ; 
BF74: 85 63           STA     workRam+63          ; 
BF76: A5 64           LDA     workRam+64          ; 
BF78: F5 80           SBC     workRam+80,X        ; 
BF7A: B8              CLV                         
BF7B: 50 0B           BVC     loc_bf88            ; 

loc_bf7d:
BF7D: A5 63           LDA     workRam+63          ; 
BF7F: 18              CLC                         
BF80: 75 78           ADC     workRam+78,X        ; 
BF82: 85 63           STA     workRam+63          ; 
BF84: A5 64           LDA     workRam+64          ; 
BF86: 75 80           ADC     workRam+80,X        ; 

loc_bf88:
BF88: 85 64           STA     workRam+64          ; 
BF8A: A4 A9           LDY     workRam+A9          ; 
BF8C: A5 63           LDA     workRam+63          ; 
BF8E: 91 74           STA     (workRam+74),Y      ; write the record's X low
BF90: C8              INY                         
BF91: A5 64           LDA     workRam+64          ; 
BF93: 29 1F           AND     #$1F                ; clamp X high to the low 5 bits
BF95: 91 74           STA     (workRam+74),Y      ; 
BF97: C8              INY                         
BF98: A5 61           LDA     workRam+61          ; 
BF9A: 91 74           STA     (workRam+74),Y      ; write the record's Y low
BF9C: C8              INY                         
BF9D: A5 62           LDA     workRam+62          ; 
BF9F: 29 1F           AND     #$1F                
BFA1: 05 73           ORA     workRam+73          ; OR in the record header
BFA3: 91 74           STA     (workRam+74),Y      ; write the record's Y high
BFA5: C8              INY                         
BFA6: 84 A9           STY     workRam+A9          ; 
BFA8: C6 99           DEC     workRam+99          ; one record done
BFAA: F0 03           BEQ     loc_bfaf            ; 
BFAC: 4C DB BE        JMP     loc_bedb            ; loop over the corner's records

loc_bfaf:
BFAF: A4 A9           LDY     workRam+A9          ; 
BFB1: 88              DEY                         
BFB2: 4C 5F DF        JMP     advanceDisplayCursor; advance the display cursor past the run

; ---- $BFB5-$C097: data ----
BFB5: 08 08 08 08 08 08 08 08 08 09 06 07 07 04 02 00
BFC5: 10 20 30 40 50 60 70 80 92 9E AC BA C2 0C 01 8C
BFD5: 01 4A 01 09 01 CB 01 4B 01 89 01 CA 01 90 01 8A
BFE5: 01 23 01 DB 01 41 01 10 01 0A 01 CB 01 91 01 17
BFF5: 01 4B 01 8A 01 CE 01 08 01 0A 01 CB 01 92 01 16
C005: 01 4B 01 8A 01 CD 01 49 01 0A 01 CB 01 93 01 15
C015: 01 4B 01 8A 01 CC 01 4A 01 0A 01 CB 01 95 01 13
C025: 01 4B 01 8A 01 CA 01 4C 01 0A 01 CB 01 96 01 12
C035: 01 4B 01 8A 01 C9 01 4D 01 0A 01 CB 01 97 01 11
C045: 01 4B 01 8A 01 88 01 4E 01 0A 01 CB 01 0B 00 A3
C055: 01 0A 01 10 01 4B 01 8A 01 90 01 41 01 5B 01 9A
C065: 01 31 01 B1 01 31 01 B1 01 1A 01 01 00 91 01 21
C075: 01 A1 01 21 01 A1 01 11 01 01 00 89 01 11 01 91
C085: 01 11 01 91 01 09 01 01 00 8A 01 12 01 8A 01 01
C095: 00 06 01

; Projects one tube-space point into a screen-coordinate pair: forms
; clamped signed deltas from
; loc_57/loc_5f/loc_5b/loc_58/loc_60/loc_56/loc_5e into the math box, then
; folds offset pairs loc_68/loc_69 and loc_66/loc_67 into accumulators
; loc_63/64 and loc_61/62 with saturation.
projectPointThroughMathbox:
C098: A5 57           LDA     workRam+57          ; load the object depth (Z)
C09A: 38              SEC                         
C09B: E5 5F           SBC     workRam+5F          ; subtract the reference depth -- form the depth delta
C09D: 8D 95 60        STA     mathboxGo+15        ; feed the math-box divisor low byte
C0A0: A9 00           LDA     #$00                
C0A2: E5 5B           SBC     workRam+5B          ; borrow through the depth guard byte
C0A4: 8D 96 60        STA     mathboxGo+16        ; feed the math-box divisor high byte
C0A7: 10 0A           BPL     loc_c0b3            ; skip the clamp when the divisor stayed non-negative
C0A9: A9 00           LDA     #$00                
C0AB: 8D 96 60        STA     mathboxGo+16        ; clamp the divisor high byte to zero
C0AE: A9 01           LDA     #$01                
C0B0: 8D 95 60        STA     mathboxGo+15        ; floor the divisor low byte at 1 -- never divide by zero

loc_c0b3:
C0B3: A5 58           LDA     workRam+58          ; load the point X
C0B5: C5 60           CMP     workRam+60          ; compare against the X reference
C0B7: 90 07           BCC     loc_c0c0            ; 
C0B9: E5 60           SBC     workRam+60          ; X minus reference -- positive branch
C0BB: A2 00           LDX     #$00                ; mark the X delta positive
C0BD: B8              CLV                         
C0BE: 50 07           BVC     loc_c0c7            ; 

loc_c0c0:
C0C0: A5 60           LDA     workRam+60          ; 
C0C2: 38              SEC                         
C0C3: E5 58           SBC     workRam+58          ; reference minus X -- negative branch
C0C5: A2 FF           LDX     #$FF                ; mark the X delta negative

loc_c0c7:
C0C7: 8D 8E 60        STA     mathboxGo+E         ; feed the X magnitude as the math-box operand
C0CA: 8D 94 60        STA     mathboxGo+14        ; trigger the divide for the X projection
C0CD: 86 33           STX     workRam+33          ; stash the X sign
C0CF: A5 56           LDA     workRam+56          ; load the point Y
C0D1: C5 5E           CMP     workRam+5E          ; compare against the Y reference
C0D3: 90 07           BCC     loc_c0dc            ; 
C0D5: E5 5E           SBC     workRam+5E          ; Y minus reference -- positive branch
C0D7: A2 00           LDX     #$00                
C0D9: B8              CLV                         
C0DA: 50 07           BVC     loc_c0e3            ; 

loc_c0dc:
C0DC: A5 5E           LDA     workRam+5E          ; 
C0DE: 38              SEC                         
C0DF: E5 56           SBC     workRam+56          ; reference minus Y -- negative branch
C0E1: A2 FF           LDX     #$FF                

loc_c0e3:
C0E3: 85 32           STA     workRam+32          ; stash the Y magnitude
C0E5: 86 34           STX     workRam+34          ; stash the Y sign

loc_c0e7:
C0E7: 2C 40 60        BIT     mathboxStatus / earomControl; read the math-box busy status
C0EA: 30 FB           BMI     loc_c0e7            ; spin while the math box is busy
C0EC: AD 60 60        LDA     mathboxLo           ; read the math-box result low byte
C0EF: 85 63           STA     workRam+63          ; into the projected X accumulator low
C0F1: AD 70 60        LDA     mathboxHi           ; read the math-box result high byte
C0F4: 85 64           STA     workRam+64          ; into the projected X accumulator high
C0F6: A5 32           LDA     workRam+32          ; reload the Y magnitude
C0F8: 8D 8E 60        STA     mathboxGo+E         ; feed it as the math-box operand
C0FB: 8D 94 60        STA     mathboxGo+14        ; trigger the divide for the Y projection
C0FE: A5 33           LDA     workRam+33          ; test the X sign
C100: 30 18           BMI     loc_c11a            ; branch to the subtract path on a negative sign
C102: A5 63           LDA     workRam+63          ; 
C104: 18              CLC                         
C105: 65 68           ADC     workRam+68          ; add the X offset low into the projected X
C107: 85 63           STA     workRam+63          ; 
C109: A5 64           LDA     workRam+64          ; 
C10B: 65 69           ADC     workRam+69          ; add the X offset high with carry
C10D: 50 06           BVC     loc_c115            ; branch past the saturation clamp on no overflow
C10F: A9 FF           LDA     #$FF                
C111: 85 63           STA     workRam+63          ; 
C113: A9 7F           LDA     #$7F                ; saturate the projected X to positive maximum

loc_c115:
C115: 85 64           STA     workRam+64          ; 
C117: B8              CLV                         
C118: 50 15           BVC     loc_c12f            ; 

loc_c11a:
C11A: A5 68           LDA     workRam+68          ; 
C11C: 38              SEC                         
C11D: E5 63           SBC     workRam+63          ; offset minus projected X -- negative-sign path
C11F: 85 63           STA     workRam+63          ; 
C121: A5 69           LDA     workRam+69          ; 
C123: E5 64           SBC     workRam+64          ; subtract the projected-X high with borrow
C125: 50 06           BVC     loc_c12d            ; branch past the clamp on no overflow
C127: A9 00           LDA     #$00                
C129: 85 63           STA     workRam+63          ; 
C12B: A9 80           LDA     #$80                ; saturate the projected X to negative maximum

loc_c12d:
C12D: 85 64           STA     workRam+64          ; 

loc_c12f:
C12F: 2C 40 60        BIT     mathboxStatus / earomControl; read the math-box busy status
C132: 30 FB           BMI     loc_c12f            ; spin while the math box is busy
C134: AD 60 60        LDA     mathboxLo           ; read the math-box result low byte
C137: 85 61           STA     workRam+61          ; into the projected Y accumulator low
C139: AD 70 60        LDA     mathboxHi           ; read the math-box result high byte
C13C: 85 62           STA     workRam+62          ; into the projected Y accumulator high
C13E: A6 34           LDX     workRam+34          ; test the Y sign
C140: 30 16           BMI     loc_c158            ; branch to the subtract path on a negative sign
C142: A5 61           LDA     workRam+61          ; 
C144: 18              CLC                         
C145: 65 66           ADC     workRam+66          ; add the Y offset low into the projected Y
C147: 85 61           STA     workRam+61          ; 
C149: A5 62           LDA     workRam+62          ; 
C14B: 65 67           ADC     workRam+67          ; add the Y offset high with carry
C14D: 50 06           BVC     loc_c155            ; 
C14F: A9 FF           LDA     #$FF                
C151: 85 61           STA     workRam+61          ; 
C153: A9 7F           LDA     #$7F                ; saturate the projected Y to positive maximum

loc_c155:
C155: 85 62           STA     workRam+62          ; 
C157: 60              RTS                         

loc_c158:
C158: A5 66           LDA     workRam+66          ; 
C15A: 38              SEC                         
C15B: E5 61           SBC     workRam+61          ; offset minus projected Y -- negative-sign path
C15D: 85 61           STA     workRam+61          ; 
C15F: A5 67           LDA     workRam+67          ; 
C161: E5 62           SBC     workRam+62          ; 
C163: 50 06           BVC     loc_c16b            ; 
C165: A9 00           LDA     #$00                
C167: 85 61           STA     workRam+61          ; 
C169: A9 80           LDA     #$80                ; saturate the projected Y to negative maximum

loc_c16b:
C16B: 85 62           STA     workRam+62          ; 
C16D: 60              RTS                         

; build the current level's full layout: prime working flags (0x5e=0x80,
; 0x114=0xff), build the tube lane coordinates via buildTubeLaneCoords,
; clear 0x5800 only when mode trigger 0x133 was already zero then force
; 0x133=0, mirror header bytes 0xcec6/0xcec7 into display registers
; 0x2000/0x2001, and inline the 0xc1fd nibble unpack into 0x19/0x800 and
; 0x21/0x808.
buildLevelLayout:
C16E: 20 13 AA        JSR     stageTextLineWithCount; stage the level's text line into the vector buffer
C171: A9 80           LDA     #$80                
C173: 85 5E           STA     workRam+5E          ; seed the projection Y reference
C175: A9 FF           LDA     #$FF                
C177: 8D 14 01        STA     workRam+114         ; mark the display dirty for a full redraw
C17A: 20 35 C2        JSR     buildTubeLaneCoords ; build the tube lane coordinates
C17D: AD 33 01        LDA     workRam+133         ; test the one-shot level-layout trigger
C180: D0 03           BNE     loc_c185            ; 
C182: 8D 00 58        STA     avgReset            ; strobe the vector-generator reset when the trigger was already clear

loc_c185:
C185: A9 00           LDA     #$00                
C187: 8D 33 01        STA     workRam+133         ; force the one-shot layout trigger clear
C18A: AD C6 CE        LDA     rom+3EC6            ; load the first level display-list head word
C18D: 8D 00 20        STA     vectorRam           ; latch it into the vector-list header low
C190: AD C7 CE        LDA     rom+3EC7            ; load the second head word
C193: 8D 01 20        STA     vectorRam+1         ; latch it into the vector-list header high

; expand the packed ROM level table 0xc1fd into the working nibble tables:
; mask selector 0x9f with 0x70 clamped to 0x5f, form read index
; (sel>>1)|0x07, and for y=7..0 write each packed byte's low nibble into
; 0x19+y and display mirror 0x800+y and its high nibble into 0x21+y and
; mirror 0x808+y.
unpackLevelNibbleTables:
C196: A5 9F           LDA     workRam+9F          ; load the level selector
C198: 29 70           AND     #$70                ; mask the selector bits
C19A: C9 5F           CMP     #$5F                
C19C: 90 02           BCC     loc_c1a0            ; 
C19E: A9 5F           LDA     #$5F                ; clamp the selector to 0x5f

loc_c1a0:
C1A0: 4A              LSR     A                   ; halve the selector
C1A1: 09 07           ORA     #$07                ; form the packed-table read index
C1A3: AA              TAX                         
C1A4: A0 07           LDY     #$07                

loc_c1a6:
C1A6: BD FD C1        LDA     rom+31FD,X          ; read a packed level-layout byte
C1A9: 29 0F           AND     #$0F                ; take the low nibble
C1AB: 99 19 00        STA     workRam+19,Y        ; write it into the working nibble table
C1AE: 99 00 08        STA     colorRam,Y          ; mirror it into color RAM
C1B1: BD FD C1        LDA     rom+31FD,X          ; re-read the packed byte
C1B4: 4A              LSR     A                   
C1B5: 4A              LSR     A                   
C1B6: 4A              LSR     A                   
C1B7: 4A              LSR     A                   
C1B8: 99 21 00        STA     workRam+21,Y        ; write the high nibble into the second nibble table
C1BB: 99 08 08        STA     colorRam+8,Y        ; mirror it into color RAM
C1BE: CA              DEX                         
C1BF: 88              DEY                         
C1C0: 10 E4           BPL     loc_c1a6            ; loop over the eight table entries
C1C2: 60              RTS                         

; clear the working state before a tube-projection run: zero the zero-page
; scratch cells 0x78/0x80/0x81/0x88/0x90/0x91 and the math-coprocessor
; input block 0x6080/0x6081/0x6083/0x6084/0x6085/0x6086/0x6087/0x6089/0x60
; 8d/0x608e/0x608f/0x6090, then write 0x0f into the control latch 0x608c
; to arm the coprocessor.
resetMathboxInputs:
C1C3: A9 00           LDA     #$00                ; clear the zero-page projection scratch cells
C1C5: 85 81           STA     workRam+81          ; 
C1C7: 85 91           STA     workRam+91          ; 
C1C9: 85 80           STA     workRam+80          ; 
C1CB: 85 78           STA     workRam+78          ; 
C1CD: 85 90           STA     workRam+90          ; 
C1CF: 85 88           STA     workRam+88          ; 
C1D1: A9 00           LDA     #$00                
C1D3: 8D 80 60        STA     mathboxGo           ; zero the math-box coprocessor input ports
C1D6: 8D 81 60        STA     mathboxGo+1         ; 
C1D9: 8D 84 60        STA     mathboxGo+4         ; 
C1DC: 8D 85 60        STA     mathboxGo+5         ; 
C1DF: 8D 86 60        STA     mathboxGo+6         ; 
C1E2: 8D 87 60        STA     mathboxGo+7         ; 
C1E5: 8D 89 60        STA     mathboxGo+9         ; 
C1E8: 8D 83 60        STA     mathboxGo+3         ; 
C1EB: 8D 8D 60        STA     mathboxGo+D         ; 
C1EE: 8D 8E 60        STA     mathboxGo+E         ; 
C1F1: 8D 8F 60        STA     mathboxGo+F         ; 
C1F4: 8D 90 60        STA     mathboxGo+10        ; 
C1F7: A9 0F           LDA     #$0F                
C1F9: 8D 8C 60        STA     mathboxGo+C         ; load step count 0x0f to arm the math box
C1FC: 60              RTS                         

; ---- $C1FD-$C234: data ----
C1FD: 00 04 08 0C C3 07 0B 0B 00 07 0B 08 44 03 0C 0C
C20D: 00 0B 03 07 C8 0C 04 04 00 0B 08 07 C4 0C 03 03
C21D: 00 04 08 0C C3 07 0F 0B 00 0C 08 04 C3 0B 07 07
C22D: 06 03 01 04 00 05 05 05

; lay out the current level's tube: reduce table byte 0x46+0x3d through
; resolveShapeTableIndex to get shape row y (0x112), derive span cells
; (negated 0xbc8c+y into 0x5f/0x5d, 0x10-neg into 0xa0, 0x5b=0xff,
; 0x60=0xbc9c+y, 0x111=0xbccc+y), copy or shift-right the offset pair
; 0x68/0x69 (scale into 0x121) by mode 0x2, clear 0x66/0x67/0x10f/0x110
; and set 0x113=0x2c, then seed the per-lane vertex arrays
; 0x3ce/0x3de/0x3ee from ROM vertex tables 0xb97c/0xba7c/0xbb7c (clearing
; 0x31a/0x33a/0x39a) and fill midpoint arrays 0x435/0x445 by rounding-
; averaging adjacent lanes.
buildTubeLaneCoords:
C235: A6 3D           LDX     workRam+3D          ; index the player's progress slot
C237: B5 46           LDA     workRam+46,X        ; read the per-slot level value
C239: 20 E8 C2        JSR     resolveShapeTableIndex; reduce it to a shape-table index
C23C: 48              PHA                         
C23D: AC 12 01        LDY     workRam+112         ; 
C240: B9 8C BC        LDA     rom+2C8C,Y          ; read the per-shape tube-depth parameter
C243: 49 FF           EOR     #$FF                ; negate the depth
C245: 18              CLC                         
C246: 69 01           ADC     #$01                
C248: 85 5F           STA     workRam+5F          ; into the depth high byte
C24A: 85 5D           STA     workRam+5D          ; and into the depth target
C24C: A9 10           LDA     #$10                
C24E: 38              SEC                         
C24F: E5 5F           SBC     workRam+5F          ; form 0x10 minus the depth into the span cell
C251: 85 A0           STA     workRam+A0          ; 
C253: A9 FF           LDA     #$FF                
C255: 85 5B           STA     workRam+5B          ; set the depth-low guard to 0xff
C257: B9 9C BC        LDA     rom+2C9C,Y          ; read the per-shape X reference
C25A: 85 60           STA     workRam+60          ; into the projection X reference
C25C: B9 CC BC        LDA     rom+2CCC,Y          ; read the per-shape level gate flag
C25F: 8D 11 01        STA     workRam+111         ; into the tube-geometry flag
C262: A5 02           LDA     workRam+2           ; load the pending game mode
C264: C9 1E           CMP     #$1E                
C266: D0 0D           BNE     loc_c275            ; branch unless the pending mode is 0x1e
C268: B9 AC BC        LDA     rom+2CAC,Y          ; copy the per-shape offset-pair low
C26B: 85 68           STA     workRam+68          ; into the X offset low
C26D: B9 BC BC        LDA     rom+2CBC,Y          ; copy the per-shape offset-pair high
C270: 85 69           STA     workRam+69          ; into the X offset high
C272: B8              CLV                         
C273: 50 18           BVC     loc_c28d            ; 

loc_c275:
C275: B9 AC BC        LDA     rom+2CAC,Y          ; offset-pair low minus current -- geometry scale
C278: 38              SEC                         
C279: E5 68           SBC     workRam+68          ; 
C27B: 8D 21 01        STA     workRam+121         ; store the geometry scale low byte
C27E: B9 BC BC        LDA     rom+2CBC,Y          ; 
C281: ED 69 00        SBC     workRam+69          ; offset-pair high minus current
C284: A2 03           LDX     #$03                

loc_c286:
C286: 4A              LSR     A                   ; shift the 16-bit scale right by four
C287: 6E 21 01        ROR     workRam+121         ; 
C28A: CA              DEX                         
C28B: 10 F9           BPL     loc_c286            ; 

loc_c28d:
C28D: A9 00           LDA     #$00                
C28F: 85 66           STA     workRam+66          ; clear the Y offset low
C291: 85 67           STA     workRam+67          ; clear the Y offset high
C293: A9 00           LDA     #$00                
C295: 8D 0F 01        STA     workRam+10F         ; 
C298: 8D 10 01        STA     workRam+110         ; 
C29B: A9 2C           LDA     #$2C                
C29D: 8D 13 01        STA     workRam+113         ; seed the record-count cell at 0x2c
C2A0: 68              PLA                         ; restore the packed shape index
C2A1: A8              TAY                         
C2A2: A2 0F           LDX     #$0F                

loc_c2a4:
C2A4: B9 7C B9        LDA     rom+297C,Y          ; read the ROM lane vertex X
C2A7: 9D CE 03        STA     workRam+3CE,X       ; into the working lane base X
C2AA: B9 7C BA        LDA     rom+2A7C,Y          ; read the ROM lane vertex Y
C2AD: 9D DE 03        STA     workRam+3DE,X       ; into the working lane base Y
C2B0: A9 00           LDA     #$00                
C2B2: 9D 1A 03        STA     workRam+31A,X       ; clear the per-column plane-A value
C2B5: 9D 3A 03        STA     workRam+33A,X       ; clear the per-column plane-B value
C2B8: 9D 9A 03        STA     workRam+39A,X       ; clear the per-lane target flag
C2BB: B9 7C BB        LDA     rom+2B7C,Y          ; read the ROM ring-heading seed
C2BE: 9D EE 03        STA     workRam+3EE,X       ; into the segment direction table
C2C1: 88              DEY                         
C2C2: CA              DEX                         
C2C3: 10 DF           BPL     loc_c2a4            ; loop over the sixteen lanes
C2C5: A0 00           LDY     #$00                
C2C7: A2 0F           LDX     #$0F                

loc_c2c9:
C2C9: B9 CE 03        LDA     workRam+3CE,Y       ; 
C2CC: 38              SEC                         
C2CD: 7D CE 03        ADC     workRam+3CE,X       ; sum adjacent lanes' X
C2D0: 6A              ROR     A                   ; rounding-average into the midpoint X
C2D1: 9D 35 04        STA     workRam+435,X       ; store the lane midpoint X
C2D4: B9 DE 03        LDA     workRam+3DE,Y       ; 
C2D7: 38              SEC                         
C2D8: 7D DE 03        ADC     workRam+3DE,X       ; sum adjacent lanes' Y
C2DB: 6A              ROR     A                   ; rounding-average into the midpoint Y
C2DC: 9D 45 04        STA     workRam+445,X       ; store the lane midpoint Y
C2DF: 88              DEY                         
C2E0: 10 02           BPL     loc_c2e4            ; 
C2E2: A0 0F           LDY     #$0F                ; wrap the second cursor back to the top lane

loc_c2e4:
C2E4: CA              DEX                         
C2E5: 10 E2           BPL     loc_c2c9            ; loop the midpoint fill
C2E7: 60              RTS                         

; reduce an input byte into the level/shape table index: values >= 0x62
; are swapped for the POKEY random byte 0x60ca AND 0x5f, the value is
; split into quotient (>>4) and remainder (&0x0f), the remainder indexes
; ROM table 0xbc7c whose entry is stored to shape-index cell 0x112 and
; returned packed into the high nibble with the low nibble forced to 0x0f
; (quotient/remainder in X/Y).
resolveShapeTableIndex:
C2E8: A2 00           LDX     #$00                
C2EA: C9 62           CMP     #$62                ; pass values under 0x62 straight through
C2EC: 90 05           BCC     loc_c2f3            ; 
C2EE: AD CA 60        LDA     pokey1+A            ; otherwise pull the POKEY random byte
C2F1: 29 5F           AND     #$5F                ; mask it to range

loc_c2f3:
C2F3: C9 10           CMP     #$10                

loc_c2f5:
C2F5: 90 04           BCC     loc_c2fb            ; 
C2F7: E8              INX                         ; bump the quotient
C2F8: 38              SEC                         
C2F9: E9 10           SBC     #$10                ; subtract 0x10

loc_c2fb:
C2FB: C9 10           CMP     #$10                
C2FD: B0 F6           BCS     loc_c2f5            ; loop -- quotient is the value shifted right four, A the remainder
C2FF: A8              TAY                         
C300: B9 7C BC        LDA     rom+2C7C,Y          ; map the remainder through the ROM shape table
C303: 8D 12 01        STA     workRam+112         ; store the shape/level index
C306: 0A              ASL     A                   
C307: 0A              ASL     A                   
C308: 0A              ASL     A                   
C309: 0A              ASL     A                   
C30A: 09 0F           ORA     #$0F                ; force the low nibble to 0x0f
C30C: 60              RTS                         

; first-time rim-counter setup: when loc_110==0 seed loc_110/loc_10f
; through the projection integrator projectAllLanesThroughMathbox (nudging
; the low counter via snapCoordUpToReference when it lags at index 0x0f),
; always emit a header (emitBlankVectorWordTag70, loc_9e=0x06), return
; unless both counters live and loc_113!=0, then clear the record slots
; two-at-a-time via drawFramedCounterSlot and draw each counter's record
; set with drawGatedRecordLoop.
initAndDrawRimDepthCounters:
C30D: AD 10 01        LDA     workRam+110         ; test whether the rim counter is already live
C310: D0 27           BNE     loc_c339            ; skip the first-time setup when it is
C312: A9 F0           LDA     #$F0                
C314: 85 57           STA     workRam+57          ; seed the object depth at the far wall
C316: A2 4F           LDX     #$4F                
C318: 20 73 C4        JSR     projectAllLanesThroughMathbox; project all lanes -- returns the clamp count
C31B: 8D 10 01        STA     workRam+110         ; store it as the rim counter
C31E: F0 03           BEQ     loc_c323            ; 
C320: 8D 0F 01        STA     workRam+10F         ; mirror it into the second counter

loc_c323:
C323: AD 0F 01        LDA     workRam+10F         ; 
C326: D0 11           BNE     loc_c339            ; 
C328: A9 10           LDA     #$10                
C32A: 85 57           STA     workRam+57          ; seed the object depth at the near rim
C32C: 20 53 C4        JSR     snapCoordUpToReference; snap the depth up to reference
C32F: A5 57           LDA     workRam+57          ; 
C331: A2 0F           LDX     #$0F                
C333: 20 73 C4        JSR     projectAllLanesThroughMathbox; project the lanes again
C336: 8D 0F 01        STA     workRam+10F         ; store the near counter

loc_c339:
C339: A9 01           LDA     #$01                
C33B: 20 6A DF        JSR     emitBlankVectorWordTag70; emit a blank leading vector word
C33E: A0 06           LDY     #$06                
C340: 84 9E           STY     workRam+9E          ; select color mode 6
C342: AE 10 01        LDX     workRam+110         ; load the rim counter
C345: F0 01           BEQ     loc_c348            ; 
C347: 60              RTS                         ; bail when the counter is zero

loc_c348:
C348: AE 13 01        LDX     workRam+113         ; load the record-count cell
C34B: D0 01           BNE     loc_c34e            ; 
C34D: 60              RTS                         ; bail when it is zero

loc_c34e:
C34E: A2 0F           LDX     #$0F                

loc_c350:
C350: A9 C0           LDA     #$C0                
C352: 20 EE C3        JSR     drawFramedCounterSlot; clear a framed counter slot pair
C355: CA              DEX                         
C356: 10 F8           BPL     loc_c350            ; loop over sixteen slots
C358: A0 06           LDY     #$06                
C35A: 84 9E           STY     workRam+9E          ; select color mode 6
C35C: A9 08           LDA     #$08                
C35E: 20 4C DF        JSR     emitTaggedVectorWord; emit a tagged vector word
C361: A0 4F           LDY     #$4F                
C363: AD 10 01        LDA     workRam+110         ; load the far rim counter
C366: 20 6E C3        JSR     drawGatedRecordLoop ; draw its record set
C369: A0 0F           LDY     #$0F                
C36B: AD 0F 01        LDA     workRam+10F         ; load the near rim counter

; Returns when gate A is nonzero; else seats loc_61-64 from
; loc_32a/loc_31a/loc_34a/loc_33a at loc_37, emits the header (c772), and
; draws one record per pass over 0x0f passes (0x0e when loc_111 is set),
; bumping the index by 0x10 on low-nibble wrap.
drawGatedRecordLoop:
C36E: D0 49           BNE     loc_c3b9            ; return when the gate byte is nonzero
C370: 84 37           STY     workRam+37          ; seat the slot loop index
C372: B9 2A 03        LDA     workRam+32A,Y       ; load the column plane-A sub into the projected Y low
C375: 85 61           STA     workRam+61          ; 
C377: B9 1A 03        LDA     workRam+31A,Y       ; load the plane-A value into the projected Y high
C37A: 85 62           STA     workRam+62          ; 
C37C: B9 4A 03        LDA     workRam+34A,Y       ; load the plane-B sub into the projected X low
C37F: 85 63           STA     workRam+63          ; 
C381: B9 3A 03        LDA     workRam+33A,Y       ; load the plane-B value into the projected X high
C384: 85 64           STA     workRam+64          ; 
C386: A2 61           LDX     #$61                
C388: 20 72 C7        JSR     emitObjectPositionVector; emit the object's position vector
C38B: A5 74           LDA     workRam+74          ; 
C38D: 85 B0           STA     workRam+B0          ; cache the draw cursor low
C38F: A5 75           LDA     workRam+75          ; 
C391: 85 B1           STA     workRam+B1          ; cache the draw cursor high
C393: A2 0F           LDX     #$0F                
C395: AD 11 01        LDA     workRam+111         ; test the tube-geometry flag
C398: F0 01           BEQ     loc_c39b            ; 
C39A: CA              DEX                         ; drop to fifteen passes when the flag is set

loc_c39b:
C39B: A9 C0           LDA     #$C0                
C39D: 85 73           STA     workRam+73          ; set the record header to 0xc0
C39F: 86 38           STX     workRam+38          ; 

loc_c3a1:
C3A1: C6 37           DEC     workRam+37          ; step the slot index back one
C3A3: A5 37           LDA     workRam+37          ; 
C3A5: 29 0F           AND     #$0F                
C3A7: C9 0F           CMP     #$0F                
C3A9: D0 07           BNE     loc_c3b2            ; 
C3AB: A5 37           LDA     workRam+37          ; 
C3AD: 18              CLC                         
C3AE: 69 10           ADC     #$10                ; wrap the index up by 0x10 on low-nibble underflow
C3B0: 85 37           STA     workRam+37          ; 

loc_c3b2:
C3B2: 20 23 C4        JSR     emitProjectedSlotRecord; emit the projected slot record
C3B5: C6 38           DEC     workRam+38          ; 
C3B7: 10 E8           BPL     loc_c3a1            ; loop the passes

loc_c3b9:
C3B9: 60              RTS                         

; Forms two 16-bit differences of loc_61/loc_63 minus previous
; loc_6a/loc_6c into delta slots loc_6e-loc_71, emits the record, latches
; current into loc_6a-loc_6d, and sets loc_73=0xc0.
emitCoordDeltaRecord:
C3BA: A5 61           LDA     workRam+61          ; 
C3BC: 38              SEC                         
C3BD: E5 6A           SBC     workRam+6A          ; projected Y low minus previous -- delta low
C3BF: 85 6E           STA     workRam+6E          ; store the Y delta low
C3C1: A5 62           LDA     workRam+62          ; 
C3C3: E5 6B           SBC     workRam+6B          ; subtract the previous Y high
C3C5: 85 6F           STA     workRam+6F          ; store the Y delta high
C3C7: A5 63           LDA     workRam+63          ; 
C3C9: 38              SEC                         
C3CA: E5 6C           SBC     workRam+6C          ; projected X low minus previous -- delta low
C3CC: 85 70           STA     workRam+70          ; store the X delta low
C3CE: A5 64           LDA     workRam+64          ; 
C3D0: E5 6D           SBC     workRam+6D          ; subtract the previous X high
C3D2: 85 71           STA     workRam+71          ; store the X delta high
C3D4: A2 6E           LDX     #$6E                
C3D6: 20 92 DF        JSR     emitCoordinateRecord; emit the coordinate delta record
C3D9: A5 61           LDA     workRam+61          ; 
C3DB: 85 6A           STA     workRam+6A          ; latch the current Y low as previous
C3DD: A5 62           LDA     workRam+62          ; 
C3DF: 85 6B           STA     workRam+6B          ; 
C3E1: A5 63           LDA     workRam+63          ; 
C3E3: 85 6C           STA     workRam+6C          ; latch the current X low as previous
C3E5: A5 64           LDA     workRam+64          ; 
C3E7: 85 6D           STA     workRam+6D          ; 
C3E9: A9 C0           LDA     #$C0                
C3EB: 85 73           STA     workRam+73          ; reset the record header to 0xc0
C3ED: 60              RTS                         

; draw a framed counter element in two passes: emit slot loc_37 with
; colour loc_73 live ($C43C/$C423), step loc_37 back one and re-emit
; uncoloured, restore the colour and close the frame ($C3BA); return the
; stepped-back index.
drawFramedCounterSlot:
C3EE: 86 37           STX     workRam+37          ; seat the slot index
C3F0: 48              PHA                         ; save the color header
C3F1: A4 9E           LDY     workRam+9E          ; 
C3F3: A9 08           LDA     #$08                
C3F5: 20 4C DF        JSR     emitTaggedVectorWord; emit a tagged vector word
C3F8: 20 3C C4        JSR     loadSlotCoordBlock  ; load the slot's coordinate block
C3FB: A2 61           LDX     #$61                
C3FD: 20 72 C7        JSR     emitObjectPositionVector; emit the object's position vector
C400: 68              PLA                         
C401: 85 73           STA     workRam+73          ; restore the live color header
C403: 48              PHA                         
C404: 20 23 C4        JSR     emitProjectedSlotRecord; emit the colored slot record
C407: C6 37           DEC     workRam+37          ; step the slot back one
C409: A4 9E           LDY     workRam+9E          ; 
C40B: A9 00           LDA     #$00                
C40D: 85 73           STA     workRam+73          ; blank the header for the uncolored pass
C40F: A9 08           LDA     #$08                
C411: 20 4C DF        JSR     emitTaggedVectorWord; emit a tagged vector word
C414: 20 23 C4        JSR     emitProjectedSlotRecord; re-emit the slot record uncolored
C417: 68              PLA                         
C418: 85 73           STA     workRam+73          ; restore the color header
C41A: 20 3C C4        JSR     loadSlotCoordBlock  ; reload the slot's coordinate block
C41D: 20 BA C3        JSR     emitCoordDeltaRecord; close the frame via the coordinate-delta record
C420: A6 37           LDX     workRam+37          ; return the stepped-back index
C422: 60              RTS                         

; Copies slot loc_37's projected-delta cells
; loc_32a/loc_31a/loc_34a/loc_33a into loc_61-loc_64 and emits the delta
; record.
emitProjectedSlotRecord:
C423: A6 37           LDX     workRam+37          ; load the slot index
C425: BD 2A 03        LDA     workRam+32A,X       ; load plane-A sub into the projected Y low
C428: 85 61           STA     workRam+61          ; 
C42A: BD 1A 03        LDA     workRam+31A,X       ; load plane-A value into the projected Y high
C42D: 85 62           STA     workRam+62          ; 
C42F: BD 4A 03        LDA     workRam+34A,X       ; load plane-B sub into the projected X low
C432: 85 63           STA     workRam+63          ; 
C434: BD 3A 03        LDA     workRam+33A,X       ; load plane-B value into the projected X high
C437: 85 64           STA     workRam+64          ; 
C439: 4C BA C3        JMP     emitCoordDeltaRecord; emit the coordinate delta record

; Reads slot index loc_37 and copies that column of
; loc_36a/loc_35a/loc_38a/loc_37a into the working coord block
; loc_61-loc_64.
loadSlotCoordBlock:
C43C: A6 37           LDX     workRam+37          ; load the slot index
C43E: BD 6A 03        LDA     workRam+36A,X       ; load the per-object Y-delta low
C441: 85 61           STA     workRam+61          ; 
C443: BD 5A 03        LDA     workRam+35A,X       ; load the per-object Y-delta high
C446: 85 62           STA     workRam+62          ; 
C448: BD 8A 03        LDA     workRam+38A,X       ; load the per-object X-delta low
C44B: 85 63           STA     workRam+63          ; 
C44D: BD 7A 03        LDA     workRam+37A,X       ; load the per-object X-delta high
C450: 85 64           STA     workRam+64          ; 
C452: 60              RTS                         

; When guard loc_5b is clear and loc_57 sits under 0x0c above reference
; loc_5f, raises loc_57 to loc_5f+0x0f, capped at ceiling 0xf0.
snapCoordUpToReference:
C453: A5 5B           LDA     workRam+5B          ; load the depth-low guard
C455: D0 1A           BNE     loc_c471            ; bail unless the guard is clear
C457: A5 57           LDA     workRam+57          ; 
C459: 38              SEC                         
C45A: E5 5F           SBC     workRam+5F          ; object depth minus reference
C45C: 90 02           BCC     loc_c460            ; 
C45E: C9 0C           CMP     #$0C                

loc_c460:
C460: B0 0F           BCS     loc_c471            ; bail when the gap is too wide
C462: A5 5F           LDA     workRam+5F          ; 
C464: 18              CLC                         
C465: 69 0F           ADC     #$0F                ; form reference plus 0x0f
C467: B0 02           BCS     loc_c46b            ; 
C469: C9 F0           CMP     #$F0                

loc_c46b:
C46B: 90 02           BCC     loc_c46f            ; 
C46D: A9 F0           LDA     #$F0                ; cap at the ceiling 0xf0

loc_c46f:
C46F: 85 57           STA     workRam+57          ; raise the object depth

loc_c471:
C471: 60              RTS                         

; ---- $C472-$C472: data ----
C472: DB

; project the sixteen tube lanes: from loc_57=A and out index loc_38=x,
; run 16 passes loading each column's base coords from loc_3ce/loc_3de
; into loc_56/loc_58, drive projectPointThroughMathbox, clamp both signed
; high-byte results into [-4..+3] (0xfc..0x03) writing value/sign into
; loc_31a/loc_32a and loc_33a/loc_34a at loc_38, tally each clamp in
; loc_59, and return the clamp count.
projectAllLanesThroughMathbox:
C473: 85 57           STA     workRam+57          ; seed the object depth from A
C475: 86 38           STX     workRam+38          ; stash the output index
C477: A9 00           LDA     #$00                
C479: 85 59           STA     workRam+59          ; clear the clamp tally
C47B: A2 0F           LDX     #$0F                
C47D: 86 37           STX     workRam+37          ; 

loc_c47f:
C47F: A6 37           LDX     workRam+37          ; 
C481: BD CE 03        LDA     workRam+3CE,X       ; load the lane base X into the projection operand
C484: 85 56           STA     workRam+56          ; 
C486: BD DE 03        LDA     workRam+3DE,X       ; load the lane base Y into the projection operand
C489: 85 58           STA     workRam+58          ; 
C48B: 20 98 C0        JSR     projectPointThroughMathbox; project the point through the math box
C48E: A6 38           LDX     workRam+38          ; 
C490: A4 61           LDY     workRam+61          ; load the projected Y low
C492: A5 62           LDA     workRam+62          ; load the projected Y high
C494: 30 0D           BMI     loc_c4a3            ; branch to clamp the low side on a negative result
C496: C9 04           CMP     #$04                
C498: 90 06           BCC     loc_c4a0            ; within range -- keep the value
C49A: A0 FF           LDY     #$FF                
C49C: A9 03           LDA     #$03                ; clamp the high side to +3
C49E: E6 59           INC     workRam+59          ; tally the clamp

loc_c4a0:
C4A0: B8              CLV                         
C4A1: 50 0A           BVC     loc_c4ad            ; 

loc_c4a3:
C4A3: C9 FC           CMP     #$FC                ; compare against the low limit
C4A5: B0 06           BCS     loc_c4ad            ; 
C4A7: A0 01           LDY     #$01                
C4A9: A9 FC           LDA     #$FC                ; clamp the low side to -4
C4AB: E6 59           INC     workRam+59          ; tally the clamp

loc_c4ad:
C4AD: 9D 1A 03        STA     workRam+31A,X       ; store the clamped value into plane A
C4B0: 98              TYA                         
C4B1: 9D 2A 03        STA     workRam+32A,X       ; store the sign into the plane-A sub
C4B4: A4 63           LDY     workRam+63          ; load the projected X low
C4B6: A5 64           LDA     workRam+64          ; load the projected X high
C4B8: 30 0D           BMI     loc_c4c7            ; 
C4BA: C9 04           CMP     #$04                
C4BC: 90 06           BCC     loc_c4c4            ; 
C4BE: A0 FF           LDY     #$FF                
C4C0: A9 03           LDA     #$03                ; clamp the high side to +3
C4C2: E6 59           INC     workRam+59          ; tally the clamp

loc_c4c4:
C4C4: B8              CLV                         
C4C5: 50 0A           BVC     loc_c4d1            ; 

loc_c4c7:
C4C7: C9 FC           CMP     #$FC                
C4C9: B0 06           BCS     loc_c4d1            ; 
C4CB: A9 FC           LDA     #$FC                ; clamp the low side to -4
C4CD: A0 01           LDY     #$01                
C4CF: E6 59           INC     workRam+59          ; tally the clamp

loc_c4d1:
C4D1: 9D 3A 03        STA     workRam+33A,X       ; store the clamped value into plane B
C4D4: 98              TYA                         
C4D5: 9D 4A 03        STA     workRam+34A,X       ; store the sign into the plane-B sub
C4D8: C6 38           DEC     workRam+38          ; 
C4DA: C6 37           DEC     workRam+37          ; 
C4DC: 10 A1           BPL     loc_c47f            ; loop over the sixteen lanes
C4DE: A5 59           LDA     workRam+59          ; return the clamp count
C4E0: 60              RTS                         

; draw a sixteen-segment tube-shape outline: reduce the input byte via
; resolveShapeTableIndex (reduced->loc_36, quotient->loc_35), emit a
; framing record, pick a header from $C22D by loc_35&0x07 into loc_9e,
; seat the first vertex from $B97C/$BA7C (biased 0x80, seed rolled back
; 0x0f when $BCCC+loc_112 is zero), then walk 16 steps emitting each
; signed vertex delta from $B97C/$BA7C.
drawTubeShapeOutline:
C4E1: 20 E8 C2        JSR     resolveShapeTableIndex; reduce the input byte to a shape index
C4E4: 85 36           STA     workRam+36          ; save the reduced value
C4E6: 86 35           STX     workRam+35          ; save the quotient
C4E8: A9 00           LDA     #$00                
C4EA: 85 73           STA     workRam+73          ; blank the record header
C4EC: A9 05           LDA     #$05                
C4EE: 20 6A DF        JSR     emitBlankVectorWordTag70; emit a leading vector word
C4F1: A5 35           LDA     workRam+35          ; 
C4F3: 29 07           AND     #$07                
C4F5: AA              TAX                         
C4F6: BC 2D C2        LDY     rom+322D,X          ; pick the outline header from ROM
C4F9: 84 9E           STY     workRam+9E          ; stash it as the color mode
C4FB: A9 08           LDA     #$08                
C4FD: 20 4C DF        JSR     emitTaggedVectorWord; emit a tagged vector word
C500: AE 12 01        LDX     workRam+112         ; load the shape/level index
C503: A5 36           LDA     workRam+36          ; 
C505: BC CC BC        LDY     rom+2CCC,X          ; read the per-shape level gate flag
C508: D0 03           BNE     loc_c50d            ; 
C50A: 38              SEC                         
C50B: E9 0F           SBC     #$0F                ; roll the first-vertex seed back 0x0f when the gate is clear

loc_c50d:
C50D: A8              TAY                         
C50E: B9 7C BA        LDA     rom+2A7C,Y          ; read the ROM lane vertex Y
C511: 85 57           STA     workRam+57          ; seed the vertex Y
C513: 49 80           EOR     #$80                ; bias by 0x80
C515: AA              TAX                         
C516: B9 7C B9        LDA     rom+297C,Y          ; read the ROM lane vertex X
C519: 85 56           STA     workRam+56          ; seed the vertex X
C51B: 49 80           EOR     #$80                ; bias by 0x80
C51D: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the first scaled vertex record
C520: A9 C0           LDA     #$C0                
C522: 85 73           STA     workRam+73          ; set the record header to 0xc0
C524: A2 0F           LDX     #$0F                
C526: 86 38           STX     workRam+38          ; sixteen segments

loc_c528:
C528: A4 36           LDY     workRam+36          ; 
C52A: B9 7C B9        LDA     rom+297C,Y          ; read the next lane vertex X
C52D: AA              TAX                         
C52E: 38              SEC                         
C52F: E5 56           SBC     workRam+56          ; delta from the previous X
C531: 48              PHA                         
C532: 86 56           STX     workRam+56          ; cache it as the previous X
C534: B9 7C BA        LDA     rom+2A7C,Y          ; read the next lane vertex Y
C537: A8              TAY                         
C538: 38              SEC                         
C539: E5 57           SBC     workRam+57          ; delta from the previous Y
C53B: AA              TAX                         
C53C: 84 57           STY     workRam+57          ; cache it as the previous Y
C53E: 68              PLA                         
C53F: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the signed vertex delta record
C542: C6 36           DEC     workRam+36          ; 
C544: C6 38           DEC     workRam+38          ; 
C546: 10 E0           BPL     loc_c528            ; loop the sixteen segments
C548: A9 01           LDA     #$01                
C54A: 4C 6A DF        JMP     emitBlankVectorWordTag70; emit the trailing blank word and return

; draw eight slots from table 0x3fe while guard 0x115 is set (forcing
; 0x5f=0xe8, 0x5b=0xff, 0xa0=0x28): per non-empty entry seat 0x57 with
; 0x56=0x58=0x80, pick a colour mode into 0x9e (slot&7 with 7->4 when
; 0x9f>=5, else 6), emit via emitTaggedVectorWord, set style
; 0x55=((slot&3)<<1)+0x0a, and draw via emitColoredShapeVector; restore
; the forced cells; tail increments 0x200+0x40 when 0x11f set and
; 0x42>=0x15.
drawTimedObjectList:
C54D: AD 15 01        LDA     workRam+115         ; load the descending-object guard
C550: F0 5F           BEQ     loc_c5b1            ; skip the list when the guard is clear
C552: A5 5F           LDA     workRam+5F          ; 
C554: 48              PHA                         ; save the depth high byte
C555: A5 5B           LDA     workRam+5B          ; 
C557: 48              PHA                         ; save the depth guard
C558: A5 A0           LDA     workRam+A0          ; 
C55A: 48              PHA                         ; save the span cell
C55B: A9 E8           LDA     #$E8                
C55D: 85 5F           STA     workRam+5F          ; force the depth high to 0xe8
C55F: A9 FF           LDA     #$FF                
C561: 85 5B           STA     workRam+5B          ; force the depth guard to 0xff
C563: A9 28           LDA     #$28                
C565: 85 A0           STA     workRam+A0          ; force the span cell to 0x28
C567: A2 07           LDX     #$07                
C569: 86 37           STX     workRam+37          ; eight object slots

loc_c56b:
C56B: A6 37           LDX     workRam+37          ; 
C56D: BD FE 03        LDA     workRam+3FE,X       ; read the object table entry
C570: F0 32           BEQ     loc_c5a4            ; skip an empty entry
C572: 85 57           STA     workRam+57          ; entry into the object depth
C574: A9 80           LDA     #$80                
C576: 85 56           STA     workRam+56          ; center the X operand
C578: A9 80           LDA     #$80                
C57A: 85 58           STA     workRam+58          ; center the Y operand
C57C: A5 9F           LDA     workRam+9F          ; load the level
C57E: C9 05           CMP     #$05                
C580: B0 05           BCS     loc_c587            ; high levels take the per-slot color path
C582: A9 06           LDA     #$06                ; color mode 6 for low levels
C584: B8              CLV                         
C585: 50 09           BVC     loc_c590            ; 

loc_c587:
C587: 8A              TXA                         
C588: 29 07           AND     #$07                
C58A: C9 07           CMP     #$07                ; is this the last slot
C58C: D0 02           BNE     loc_c590            ; 
C58E: A9 04           LDA     #$04                ; the last slot uses color mode 4

loc_c590:
C590: 85 9E           STA     workRam+9E          ; store the color mode
C592: A8              TAY                         
C593: A9 08           LDA     #$08                
C595: 20 4C DF        JSR     emitTaggedVectorWord; emit a tagged vector word
C598: A5 37           LDA     workRam+37          ; 
C59A: 29 03           AND     #$03                
C59C: 0A              ASL     A                   ; form the draw style from the slot's low bits
C59D: 69 0A           ADC     #$0A                
C59F: 85 55           STA     workRam+55          ; store the draw style
C5A1: 20 09 BD        JSR     emitColoredShapeVector; draw the colored shape vector

loc_c5a4:
C5A4: C6 37           DEC     workRam+37          ; 
C5A6: 10 C3           BPL     loc_c56b            ; loop the eight slots
C5A8: 68              PLA                         
C5A9: 85 A0           STA     workRam+A0          ; restore the span cell
C5AB: 68              PLA                         
C5AC: 85 5B           STA     workRam+5B          ; restore the depth guard
C5AE: 68              PLA                         
C5AF: 85 5F           STA     workRam+5F          ; restore the depth high byte

loc_c5b1:
C5B1: AD 1F 01        LDA     workRam+11F         ; test the award gate
C5B4: F0 0B           BEQ     loc_c5c1            ; 
C5B6: A6 42           LDX     workRam+42          ; 
C5B8: E0 15           CPX     #$15                ; require the counter to have reached 0x15
C5BA: 90 05           BCC     loc_c5c1            ; 
C5BC: A6 40           LDX     workRam+40          ; index the player's rim segment
C5BE: FE 00 02        INC     workRam+200,X       ; bump that segment's tally

loc_c5c1:
C5C1: 60              RTS                         

; rebuild the per-frame enemy display list over up to sixteen slots (0x37
; from 0x0f, 0x0e when 0x111 set): early-out on gates 0x110/0x5b/0x5f,
; then per slot copy the fixed 4-byte header 0xc669 through cursor 0x74 at
; offset 0xa9 and append either a computed midpoint pair
; (emitSlotMidpointVertex + emitEnemySlotEntry) when 0x114 set, or a
; straight/sign-fixed coordinate block read from ($aa); restores the saved
; cursor 0xaa/0xab and flushes via advanceDisplayCursor.
buildEnemyDisplayList:
C5C2: AD 10 01        LDA     workRam+110         ; load the rim counter gate
C5C5: F0 01           BEQ     loc_c5c8            ; 
C5C7: 60              RTS                         ; bail when the counter is live

loc_c5c8:
C5C8: A5 5B           LDA     workRam+5B          ; load the depth guard
C5CA: D0 07           BNE     loc_c5d3            ; 
C5CC: A5 5F           LDA     workRam+5F          ; 
C5CE: C9 F0           CMP     #$F0                ; compare the depth against the ceiling
C5D0: 90 01           BCC     loc_c5d3            ; 
C5D2: 60              RTS                         ; bail when the depth is at the ceiling

loc_c5d3:
C5D3: A9 01           LDA     #$01                
C5D5: 20 6A DF        JSR     emitBlankVectorWordTag70; emit a blank leading vector word
C5D8: A5 74           LDA     workRam+74          ; 
C5DA: 48              PHA                         ; save the draw cursor low
C5DB: A5 75           LDA     workRam+75          ; 
C5DD: 48              PHA                         ; save the draw cursor high
C5DE: A9 00           LDA     #$00                
C5E0: 85 38           STA     workRam+38          ; clear the table cursor
C5E2: 85 A9           STA     workRam+A9          ; clear the draw-cursor offset
C5E4: A2 0F           LDX     #$0F                
C5E6: AD 11 01        LDA     workRam+111         ; test the tube-geometry flag
C5E9: F0 01           BEQ     loc_c5ec            ; 
C5EB: CA              DEX                         ; drop to fifteen slots when the flag is set

loc_c5ec:
C5EC: 86 37           STX     workRam+37          ; seat the slot loop index

loc_c5ee:
C5EE: A2 03           LDX     #$03                
C5F0: A4 A9           LDY     workRam+A9          ; 

loc_c5f2:
C5F2: BD 69 C6        LDA     rom+3669,X          ; read the fixed enemy-list header
C5F5: 91 74           STA     (workRam+74),Y      ; write the header byte through the cursor
C5F7: C8              INY                         
C5F8: CA              DEX                         
C5F9: 10 F7           BPL     loc_c5f2            ; copy the four header bytes
C5FB: 84 A9           STY     workRam+A9          ; advance the cursor offset
C5FD: AD 14 01        LDA     workRam+114         ; test the display-dirty flag
C600: D0 4A           BNE     loc_c64c            ; take the midpoint path when dirty
C602: A6 38           LDX     workRam+38          ; 
C604: BD 9A 03        LDA     workRam+39A,X       ; read the lane target flag
C607: 30 11           BMI     loc_c61a            ; a flagged lane takes the full-record path
C609: A2 0B           LDX     #$0B                
C60B: A4 A9           LDY     workRam+A9          ; 

loc_c60d:
C60D: B1 AA           LDA     (workRam+AA),Y      ; read a source record byte
C60F: 91 74           STA     (workRam+74),Y      ; copy it through the cursor
C611: C8              INY                         
C612: CA              DEX                         
C613: 10 F8           BPL     loc_c60d            ; copy the twelve-byte block
C615: 84 A9           STY     workRam+A9          ; advance the cursor offset
C617: B8              CLV                         
C618: 50 2F           BVC     loc_c649            ; 

loc_c61a:
C61A: A4 A9           LDY     workRam+A9          ; 
C61C: B1 AA           LDA     (workRam+AA),Y      ; read the source X low
C61E: 91 74           STA     (workRam+74),Y      ; copy it through the cursor
C620: 85 6C           STA     workRam+6C          ; cache it as the previous X low
C622: C8              INY                         
C623: B1 AA           LDA     (workRam+AA),Y      ; read the source X high
C625: 91 74           STA     (workRam+74),Y      ; copy it through the cursor
C627: C9 10           CMP     #$10                
C629: 90 02           BCC     loc_c62d            ; 
C62B: 09 E0           ORA     #$E0                ; sign-extend the high nibble

loc_c62d:
C62D: 85 6D           STA     workRam+6D          ; store the previous X high
C62F: C8              INY                         
C630: B1 AA           LDA     (workRam+AA),Y      ; read the source Y low
C632: 91 74           STA     (workRam+74),Y      ; copy it through the cursor
C634: 85 6A           STA     workRam+6A          ; cache it as the previous Y low
C636: C8              INY                         
C637: B1 AA           LDA     (workRam+AA),Y      ; read the source Y high
C639: 91 74           STA     (workRam+74),Y      ; copy it through the cursor
C63B: C9 10           CMP     #$10                
C63D: 90 02           BCC     loc_c641            ; 
C63F: 09 E0           ORA     #$E0                ; sign-extend the high nibble

loc_c641:
C641: 85 6B           STA     workRam+6B          ; store the previous Y high
C643: C8              INY                         
C644: 84 A9           STY     workRam+A9          ; advance the cursor offset
C646: 20 C7 C6        JSR     emitEnemySlotEntry  ; emit the enemy-slot entry

loc_c649:
C649: B8              CLV                         
C64A: 50 06           BVC     loc_c652            ; 

loc_c64c:
C64C: 20 6D C6        JSR     emitSlotMidpointVertex; emit the slot midpoint vertex
C64F: 20 C7 C6        JSR     emitEnemySlotEntry  ; emit the enemy-slot entry

loc_c652:
C652: A6 38           LDX     workRam+38          ; 
C654: 1E 9A 03        ASL     workRam+39A,X       ; shift the lane target flag left
C657: E6 38           INC     workRam+38          ; step to the next table slot
C659: C6 37           DEC     workRam+37          ; 
C65B: 10 91           BPL     loc_c5ee            ; loop the slots
C65D: 68              PLA                         
C65E: 85 AB           STA     workRam+AB          ; restore the source pointer high
C660: 68              PLA                         
C661: 85 AA           STA     workRam+AA          ; restore the source pointer low
C663: A4 A9           LDY     workRam+A9          ; 
C665: 88              DEY                         
C666: 4C 5F DF        JMP     advanceDisplayCursor; flush by advancing the display cursor

; ---- $C669-$C66C: data ----
C669: 80 40 68 05

; Round-up averages tube slot loc_38's two coord pairs (loc_36a/loc_35a,
; loc_38a/loc_37a) with its wrap neighbour (loc_38+1 & 0x0f) into
; loc_61-loc_64 and appends four midpoint bytes (high bytes masked 0x1f)
; to the display list at loc_a9.
emitSlotMidpointVertex:
C66D: A5 38           LDA     workRam+38          ; read the active tube lane (0..15)
C66F: AA              TAX                         
C670: 18              CLC                         
C671: 69 01           ADC     #$01                ; step to the next lane
C673: 29 0F           AND     #$0F                ; wrap the neighbour lane index around the 16-lane ring
C675: A8              TAY                         
C676: BD 6A 03        LDA     workRam+36A,X       ; this lane's Y coordinate
C679: 38              SEC                         
C67A: 79 6A 03        ADC     workRam+36A,Y       ; add the neighbour lane's Y, rounded up by one
C67D: 85 61           STA     workRam+61          ; stash the Y sum low byte
C67F: BD 5A 03        LDA     workRam+35A,X       ; 
C682: 79 5A 03        ADC     workRam+35A,Y       ; 
C685: 85 62           STA     workRam+62          ; carry into the Y sum high byte
C687: 0A              ASL     A                   ; halve the 16-bit Y sum, preserving its sign -- the lane-pair Y midpoint
C688: 66 62           ROR     workRam+62          ; 
C68A: 66 61           ROR     workRam+61          ; 
C68C: BD 8A 03        LDA     workRam+38A,X       ; this lane's X coordinate
C68F: 38              SEC                         
C690: 79 8A 03        ADC     workRam+38A,Y       ; add the neighbour lane's X, rounded up
C693: 85 63           STA     workRam+63          ; stash the X sum low byte
C695: BD 7A 03        LDA     workRam+37A,X       ; 
C698: 79 7A 03        ADC     workRam+37A,Y       ; 
C69B: 85 64           STA     workRam+64          ; carry into the X sum high byte
C69D: 0A              ASL     A                   ; halve the 16-bit X sum, preserving its sign -- the lane-pair X midpoint
C69E: 66 64           ROR     workRam+64          ; 
C6A0: 66 63           ROR     workRam+63          ; 
C6A2: A4 A9           LDY     workRam+A9          ; load the running byte offset into the display list
C6A4: A5 63           LDA     workRam+63          ; 
C6A6: 91 74           STA     (workRam+74),Y      ; write the X midpoint low byte to the display list
C6A8: C8              INY                         
C6A9: 85 6C           STA     workRam+6C          ; mirror it as the previous-point X low
C6AB: A5 64           LDA     workRam+64          ; 
C6AD: 85 6D           STA     workRam+6D          ; previous-point X high
C6AF: 29 1F           AND     #$1F                ; strip the vector-opcode tag bits from the high byte
C6B1: 91 74           STA     (workRam+74),Y      ; write the X midpoint high byte
C6B3: C8              INY                         
C6B4: A5 61           LDA     workRam+61          ; 
C6B6: 91 74           STA     (workRam+74),Y      ; write the Y midpoint low byte
C6B8: C8              INY                         
C6B9: 85 6A           STA     workRam+6A          ; mirror it as the previous-point Y low
C6BB: A5 62           LDA     workRam+62          ; 
C6BD: 85 6B           STA     workRam+6B          ; previous-point Y high
C6BF: 29 1F           AND     #$1F                ; strip the vector-opcode tag bits
C6C1: 91 74           STA     (workRam+74),Y      ; write the Y midpoint high byte
C6C3: C8              INY                         
C6C4: 84 A9           STY     workRam+A9          ; commit the advanced list offset -- four bytes appended
C6C6: 60              RTS                         

; Emits one enemy-slot vector entry keyed by kind byte loc_3ac+loc_38:
; four blank+0x71 pairs when inactive, else seats loc_57/loc_56/loc_58,
; clamps depth via c453, runs c098/c73c, and appends a random ($60CA-
; selected) or fixed marker word per loc_39a bit6.
emitEnemySlotEntry:
C6C7: A6 38           LDX     workRam+38          ; index by the active enemy slot
C6C9: BD AC 03        LDA     workRam+3AC,X       ; read the slot's depth/kind byte
C6CC: D0 16           BNE     loc_c6e4            ; live slot -> project it; a zero byte means the slot is empty
C6CE: A4 A9           LDY     workRam+A9          ; empty slot: load the list offset
C6D0: A2 03           LDX     #$03                

loc_c6d2:
C6D2: A9 00           LDA     #$00                
C6D4: 91 74           STA     (workRam+74),Y      ; write a blank byte
C6D6: C8              INY                         
C6D7: A9 71           LDA     #$71                ; write the 0x71 blanking word
C6D9: 91 74           STA     (workRam+74),Y      ; 
C6DB: C8              INY                         
C6DC: CA              DEX                         
C6DD: 10 F3           BPL     loc_c6d2            ; repeat for four placeholder pairs -- keeps the list stride fixed
C6DF: 84 A9           STY     workRam+A9          ; commit the advanced offset
C6E1: B8              CLV                         
C6E2: 50 57           BVC     loc_c73b            ; done

loc_c6e4:
C6E4: 85 57           STA     workRam+57          ; live slot: seat the depth as the projection input
C6E6: 20 53 C4        JSR     snapCoordUpToReference; clamp the depth to the reference point
C6E9: BD 35 04        LDA     workRam+435,X       ; seat this slot's segment midpoint as the point to project (one axis)
C6EC: 85 56           STA     workRam+56          ; 
C6EE: BD 45 04        LDA     workRam+445,X       ; seat the other axis of the point
C6F1: 85 58           STA     workRam+58          ; 
C6F3: 20 98 C0        JSR     projectPointThroughMathbox; project the point through the math box
C6F6: 20 3C C7        JSR     emitDeltaVectorPair ; emit the projected delta vectors
C6F9: A6 38           LDX     workRam+38          ; reload the slot index
C6FB: BD 9A 03        LDA     workRam+39A,X       ; read the slot's target flag
C6FE: 29 40           AND     #$40                ; test target-flag bit6
C700: F0 1F           BEQ     loc_c721            ; flag clear -> draw the fixed marker word
C702: 20 3E BD        JSR     appendNormalizedMantissaExponent; flag set: append a normalized mantissa/exponent pair, advancing the cursor
C705: AD CA 60        LDA     pokey1+A            ; read a bit of the sound chip's random register
C708: 29 02           AND     #$02                
C70A: 18              CLC                         
C70B: 69 1C           ADC     #$1C                ; pick one of two enemy template words at random
C70D: AA              TAX                         
C70E: BD C9 CE        LDA     rom+3EC9,X          ; write the template word high byte
C711: C8              INY                         
C712: 91 74           STA     (workRam+74),Y      ; 
C714: 88              DEY                         
C715: BD C8 CE        LDA     rom+3EC8,X          ; write the template word low byte
C718: 91 74           STA     (workRam+74),Y      ; 
C71A: C8              INY                         
C71B: C8              INY                         
C71C: 84 A9           STY     workRam+A9          ; commit the offset -- a four-byte record
C71E: B8              CLV                         
C71F: 50 1A           BVC     loc_c73b            ; done

loc_c721:
C721: A4 A9           LDY     workRam+A9          ; marker path: load the list offset
C723: A9 00           LDA     #$00                
C725: 91 74           STA     (workRam+74),Y      ; write a blank byte
C727: C8              INY                         
C728: A9 68           LDA     #$68                ; write the 0x68 marker word
C72A: 91 74           STA     (workRam+74),Y      ; 
C72C: C8              INY                         
C72D: AD B2 3D        LDA     vectorRom+DB2       ; write the blank-slot vector low byte
C730: 91 74           STA     (workRam+74),Y      ; 
C732: C8              INY                         
C733: AD B3 3D        LDA     vectorRom+DB3       ; write the blank-slot vector high byte
C736: 91 74           STA     (workRam+74),Y      ; 
C738: C8              INY                         
C739: 84 A9           STY     workRam+A9          ; commit the advanced offset

loc_c73b:
C73B: 60              RTS                         

; Emits two 16-bit differences (loc_63:64 minus loc_6c:6d, then loc_61:62
; minus loc_6a:6b) as vector words, high bytes masked to five bits and the
; second OR'd with opcode 0xa0, advancing cursor loc_a9 by four.
emitDeltaVectorPair:
C73C: A4 A9           LDY     workRam+A9          ; load the running list offset
C73E: A5 63           LDA     workRam+63          ; X delta: projected X minus the previous point's X
C740: 38              SEC                         
C741: E5 6C           SBC     workRam+6C          ; 
C743: 91 74           STA     (workRam+74),Y      ; write the X delta low byte
C745: C8              INY                         
C746: A5 64           LDA     workRam+64          ; X delta high byte, clipped to five bits
C748: E5 6D           SBC     workRam+6D          ; 
C74A: 29 1F           AND     #$1F                
C74C: 91 74           STA     (workRam+74),Y      ; write it
C74E: C8              INY                         
C74F: A5 61           LDA     workRam+61          ; Y delta: projected Y minus the previous point's Y
C751: 38              SEC                         
C752: E5 6A           SBC     workRam+6A          ; 
C754: 91 74           STA     (workRam+74),Y      ; write the Y delta low byte
C756: C8              INY                         
C757: A5 62           LDA     workRam+62          ; Y delta high byte, clipped and stamped with the 0xa0 vector opcode
C759: E5 6B           SBC     workRam+6B          ; 
C75B: 29 1F           AND     #$1F                
C75D: 09 A0           ORA     #$A0                
C75F: 91 74           STA     (workRam+74),Y      ; write it
C761: C8              INY                         
C762: 84 A9           STY     workRam+A9          ; commit the offset -- two delta words appended
C764: 60              RTS                         

; Writes the fixed header word 0x00/0x71 at the draw cursor start
; (loc_74/loc_75) then resumes the shared vector-record builder from
; cursor slot 2.
layHeaderAndBuildRecord:
C765: A0 00           LDY     #$00                ; start the write at the cursor head
C767: 98              TYA                         
C768: 91 74           STA     (workRam+74),Y      ; write the fixed header byte 0x00
C76A: A9 71           LDA     #$71                ; write the fixed header byte 0x71
C76C: C8              INY                         
C76D: 91 74           STA     (workRam+74),Y      ; 
C76F: C8              INY                         
C770: D0 02           BNE     emitObjectPositionRecord; fall into the position-record builder past the header

; emit an object's position vector: from cursor offset 0, write the
; {0x40,0x80} header, an X word from zero-page pair 0x2/0x3 indexed by X
; and a Y word from pair 0x00/0x1 (each high byte masked to 5 bits),
; caching the raw bytes as the previous point (X low/high at 0x6c/0x6d, Y
; low/high at 0x6a/0x6b), then advance the cursor 0x74/0x75 past the six
; emitted bytes.
emitObjectPositionVector:
C772: A0 00           LDY     #$00                ; public entry: start the record at cursor offset 0

; emit a six-byte object position record into the vector list at write
; cursor loc_74/loc_75 offset Y: a fixed header (0x40,0x80) then the
; object's X pair (loc_2+x low, loc_3+x high masked to 5 bits) and Y pair
; (loc_00+x low, loc_1+x high masked to 5 bits), caching the raw bytes
; into loc_6c/loc_6d/loc_6a/loc_6b, then advancing the cursor past the
; bytes via advanceDisplayCursor (entry emitObjectPositionVector starts
; the offset at 0).
emitObjectPositionRecord:
C774: A9 40           LDA     #$40                ; write position-record header byte 0x40
C776: 91 74           STA     (workRam+74),Y      ; 
C778: A9 80           LDA     #$80                ; write position-record header byte 0x80
C77A: C8              INY                         
C77B: 91 74           STA     (workRam+74),Y      ; 
C77D: C8              INY                         
C77E: B5 02           LDA     workRam+2,X         ; object X low, cached as the previous point's X low
C780: 85 6C           STA     workRam+6C          ; 
C782: 91 74           STA     (workRam+74),Y      ; write it
C784: C8              INY                         
C785: B5 03           LDA     workRam+3,X         ; object X high, cached, clipped to five bits
C787: 85 6D           STA     workRam+6D          ; 
C789: 29 1F           AND     #$1F                
C78B: 91 74           STA     (workRam+74),Y      ; write it
C78D: B5 00           LDA     workRam,X           ; object Y low, cached as the previous point's Y low
C78F: 85 6A           STA     workRam+6A          ; 
C791: C8              INY                         
C792: 91 74           STA     (workRam+74),Y      ; write it
C794: B5 01           LDA     workRam+1,X         ; object Y high, cached, clipped to five bits
C796: 85 6B           STA     workRam+6B          ; 
C798: 29 1F           AND     #$1F                
C79A: C8              INY                         
C79B: 91 74           STA     (workRam+74),Y      ; write it
C79D: 4C 5F DF        JMP     advanceDisplayCursor; advance the draw cursor past the six emitted bytes

; the main frame loop (a generator): after a one-time board-init pass
; (resetBothPokeyChips) and seeding 0x00=0, free-run forever -- each pass
; yields until the interrupt counter 0x53 reaches 9, clears it, and runs
; the three per-update passes dispatchFramePhaseHandler,
; seedFramePhaseAndTick, buildFrameVectors (~26.5Hz).
runMainFrameLoop:
C7A0: 20 95 CD        JSR     resetBothPokeyChips ; one-time: reset both sound/IO chips
C7A3: A9 00           LDA     #$00                
C7A5: 85 00           STA     workRam             ; seed the live game mode to 0

loc_c7a7:
C7A7: A5 53           LDA     workRam+53          ; frame boundary: wait until nine interrupts have accumulated (~26.5Hz)
C7A9: C9 09           CMP     #$09                
C7AB: 90 FA           BCC     loc_c7a7            ; 
C7AD: A9 00           LDA     #$00                ; consume the interrupt count, start the next frame
C7AF: 85 53           STA     workRam+53          ; 
C7B1: 20 BD C7        JSR     dispatchFramePhaseHandler; run the current mode's phase handler
C7B4: 20 91 C8        JSR     seedFramePhaseAndTick; seed the next phase and tick the frame clock
C7B7: 20 B6 B1        JSR     buildFrameVectors   ; build this frame's vector display list
C7BA: 18              CLC                         
C7BB: 90 EA           BCC     loc_c7a7            ; loop back forever to the frame wait

; DSW-gated per-frame handler dispatch: do nothing when the coinage dip
; (0xd00 & 0x83) reads 0x82; otherwise run a pre-pass ($A7D2), set bit7 of
; 0x4e, and select one of eighteen per-frame handlers by the byte offset
; in 0x00 (index offset>>1 into a 19-entry table whose one slot is
; unused).
dispatchFramePhaseHandler:
C7BD: AD 00 0D        LDA     dsw1                ; coinage dip config 0x82 disables this whole update pass
C7C0: 29 83           AND     #$83                
C7C2: C9 82           CMP     #$82                
C7C4: F0 13           BEQ     loc_c7d9            ; 
C7C6: 20 D2 A7        JSR     stepSpikeTableCollapse; pre-pass: step the spike-table collapse
C7C9: A6 00           LDX     workRam             ; select the phase handler by the current game mode
C7CB: A5 4E           LDA     workRam+4E          ; 
C7CD: 09 80           ORA     #$80                ; mark this frame's edge state -- set bit7
C7CF: 85 4E           STA     workRam+4E          ; 
C7D1: BD DB C7        LDA     rom+37DB,X          ; push the handler's address high byte from the mode dispatch table
C7D4: 48              PHA                         
C7D5: BD DA C7        LDA     rom+37DA,X          ; push its low byte
C7D8: 48              PHA                         

loc_c7d9:
C7D9: 60              RTS                         ; jump to the selected phase handler

; ---- $C7DA-$C7FF: data ----
C7DA: 0B C9 3F C9 0A 97 AE C9 F0 C9 FF C7 00 00 8B C9
C7EA: 3E AC 6D AD 17 CA 48 91 4A 90 E6 B0 07 91 7A C9
C7FA: 28 97 E0 D7 17 A6

; while the guard loc_3 & loc_16b is set do nothing; otherwise run delay
; counter loc_4 down and, on the frame it reaches zero, load the live mode
; loc_0 from the pending mode loc_2 and clear the guard loc_16b; every
; path tail-delegates the spinner update rotateBlasterAroundRim.
commitPendingModeAfterDelay:
C800: A5 03           LDA     workRam+3           ; while the guarded frame-counter bit is set, suppress the countdown
C802: 2D 6B 01        AND     workRam+16B         ; 
C805: D0 11           BNE     loc_c818            ; 
C807: A5 04           LDA     workRam+4           ; tick the mode-change delay timer toward zero
C809: F0 02           BEQ     loc_c80d            ; 
C80B: C6 04           DEC     workRam+4           ; 

loc_c80d:
C80D: D0 09           BNE     loc_c818            ; not expired yet -> just service the shooter
C80F: A5 02           LDA     workRam+2           ; delay expired: commit the pending mode as the live mode
C811: 85 00           STA     workRam             ; 
C813: A9 00           LDA     #$00                
C815: 8D 6B 01        STA     workRam+16B         ; clear the arming guard

loc_c818:
C818: 4C 49 97        JMP     rotateBlasterAroundRim; advance the player's shooter around the tube rim

; from the 2-bit gate loc_4e&0x60 (then cleared) and a >=2 test on counter
; loc_6 derive a step 0..2 and drain loc_6; when the gate is clear
; optionally seed intro cells (loc_1/loc_4/loc_0/loc_2, gated by loc_50
; and loc_5 bit7); when the step is nonzero set loc_5|=0xc0, zero
; loc_16/loc_18/loc_0, bump the 16-bit tally loc_40c,x/loc_40d,x, and
; advance the level cell loc_100 by step+1 clamped to 0x63.
advanceLevelCounter:
C81B: A5 06           LDA     workRam+6           ; read the phase counter -- phases still owed
C81D: A0 00           LDY     #$00                
C81F: C9 02           CMP     #$02                ; note whether at least two phases remain
C821: A5 4E           LDA     workRam+4E          ; extract the pending-advance request bits (6..5)
C823: 29 60           AND     #$60                
C825: 84 4E           STY     workRam+4E          ; consume the request, clearing the flags
C827: F0 48           BEQ     loc_c871            ; no advance requested -> the queued-intro path
C829: B0 05           BCS     loc_c830            ; two or more phases owed -> the double-step branch
C82B: 29 20           AND     #$20                ; otherwise test request bit5 only
C82D: B8              CLV                         
C82E: 50 05           BVC     loc_c835            ; 

loc_c830:
C830: C8              INY                         ; count one step and drain a phase
C831: C6 06           DEC     workRam+6           ; 
C833: 29 40           AND     #$40                ; request bit6 -> a second step

loc_c835:
C835: F0 03           BEQ     loc_c83a            ; 
C837: C6 06           DEC     workRam+6           ; drain another phase for the double step
C839: C8              INY                         

loc_c83a:
C83A: 98              TYA                         
C83B: 85 3E           STA     workRam+3E          ; store the derived 0..2 level step
C83D: F0 2F           BEQ     loc_c86e            ; a zero step -> nothing to advance
C83F: A5 05           LDA     workRam+5           ; latch the advance in the status flags (top two bits)
C841: 09 C0           ORA     #$C0                
C843: 85 05           STA     workRam+5           ; 
C845: A9 00           LDA     #$00                
C847: 85 16           STA     workRam+16          ; clear the heartbeat accumulator low byte
C849: 85 18           STA     workRam+18          ; clear its overflow byte
C84B: A9 00           LDA     #$00                
C84D: 85 00           STA     workRam             ; reset the game mode to 0
C84F: C6 3E           DEC     workRam+3E          ; index the level tally by the step: 1 -> offset 0, 2 -> offset 3
C851: A6 3E           LDX     workRam+3E          ; 
C853: F0 02           BEQ     loc_c857            ; 
C855: A2 03           LDX     #$03                

loc_c857:
C857: FE 0C 04        INC     workRam+40C,X       ; bump the 16-bit level tally, carrying into the high byte
C85A: D0 03           BNE     loc_c85f            ; 
C85C: FE 0D 04        INC     workRam+40D,X       ; 

loc_c85f:
C85F: AD 00 01        LDA     workRam+100         ; advance the on-screen level number by the step (1 or 2)
C862: 38              SEC                         
C863: 65 3E           ADC     workRam+3E          ; 
C865: C9 63           CMP     #$63                ; clamp it to 0x63
C867: 90 02           BCC     loc_c86b            ; 
C869: A9 63           LDA     #$63                

loc_c86b:
C86B: 8D 00 01        STA     workRam+100         ; store the new level number

loc_c86e:
C86E: B8              CLV                         ; done
C86F: 50 1F           BVC     loc_c890            ; 

loc_c871:
C871: A5 50           LDA     workRam+50          ; no-advance path: nothing latched -> exit
C873: F0 1B           BEQ     loc_c890            ; 
C875: 24 05           BIT     workRam+5           ; status bit7 set -> exit
C877: 30 17           BMI     loc_c890            ; 
C879: A9 10           LDA     #$10                ; arm the intro dispatch selector
C87B: 85 01           STA     workRam+1           ; 
C87D: A9 20           LDA     #$20                ; set the mode-delay countdown
C87F: 85 04           STA     workRam+4           ; 
C881: A9 0A           LDA     #$0A                ; enter mode 0x0a
C883: 85 00           STA     workRam             ; 
C885: A9 14           LDA     #$14                ; queue the next mode 0x14
C887: 85 02           STA     workRam+2           ; 
C889: A9 00           LDA     #$00                
C88B: 85 50           STA     workRam+50          ; disarm the latched spinner value
C88D: 8D 23 01        STA     workRam+123         ; reset the spike tally

loc_c890:
C890: 60              RTS                         

; per-frame mode/timing driver: from coin input 0xc00, mode flag 0x5 and
; phase counters 0xa/0x6, seed the phase/speed cells 0x00/0x1/0xa2
; (running the setup step advanceLevelCounter on the appropriate phase),
; then a common tail advances the frame counter 0x3, fires the EAROM step
; stepEaromTransfer on odd frames and the sound-register step
; requestActiveSoundCue when 0xc is live, and trims bit7 of 0x4e.
seedFramePhaseAndTick:
C891: AD 00 0C        LDA     in0                 ; coin input bit4 clear -> force game mode 0x22
C894: 29 10           AND     #$10                
C896: D0 07           BNE     loc_c89f            ; 
C898: A9 22           LDA     #$22                ; force mode 0x22
C89A: 85 00           STA     workRam             ; 
C89C: B8              CLV                         
C89D: 50 44           BVC     loc_c8e3            ; skip to the common tail

loc_c89f:
C89F: 24 05           BIT     workRam+5           ; status bit6 set -> skip to the tail
C8A1: 70 40           BVS     loc_c8e3            ; 
C8A3: A5 0A           LDA     workRam+A           ; even phase -> straight to the level-counter step
C8A5: 29 01           AND     #$01                
C8A7: F0 29           BEQ     loc_c8d2            ; 
C8A9: A4 06           LDY     workRam+6           ; read the phase counter
C8AB: D0 04           BNE     loc_c8b1            ; 
C8AD: A9 80           LDA     #$80                ; an expired counter arms the phase gate
C8AF: 85 A2           STA     workRam+A2          ; 

loc_c8b1:
C8B1: 24 A2           BIT     workRam+A2          ; gate clear -> the level-counter step
C8B3: 10 1D           BPL     loc_c8d2            ; 
C8B5: C0 02           CPY     #$02                ; two or more phases owed -> the mode-0x14 branch
C8B7: B0 11           BCS     loc_c8ca            ; 
C8B9: 98              TYA                         
C8BA: F0 08           BEQ     loc_c8c4            ; zero phases -> skip the mode seed
C8BC: A9 16           LDA     #$16                ; one phase: select dispatch 0x16
C8BE: 85 01           STA     workRam+1           ; 
C8C0: A9 0A           LDA     #$0A                ; enter mode 0x0a
C8C2: 85 00           STA     workRam             ; 

loc_c8c4:
C8C4: 4C D9 C8        JMP     loc_c8d9            ; continue to the frame tick

; ---- $C8C7-$C8C9: data ----
C8C7: B8 50 08

loc_c8ca:
C8CA: A9 14           LDA     #$14                ; set game mode 0x14
C8CC: 85 00           STA     workRam             ; 
C8CE: A9 00           LDA     #$00                ; clear the phase gate
C8D0: 85 A2           STA     workRam+A2          ; 

loc_c8d2:
C8D2: A5 06           LDA     workRam+6           ; run the level-advance bookkeeper only when a phase is owed
C8D4: F0 03           BEQ     loc_c8d9            ; 
C8D6: 20 1B C8        JSR     advanceLevelCounter ; step the level-advance bookkeeper

loc_c8d9:
C8D9: A5 09           LDA     workRam+9           ; every fourth frame...
C8DB: 29 03           AND     #$03                
C8DD: D0 04           BNE     loc_c8e3            ; 
C8DF: A9 02           LDA     #$02                ; ...reseed the phase counter to 2
C8E1: 85 06           STA     workRam+6           ; 

loc_c8e3:
C8E3: E6 03           INC     workRam+3           ; advance the master frame counter
C8E5: A5 03           LDA     workRam+3           ; on odd frames...
C8E7: 29 01           AND     #$01                
C8E9: F0 03           BEQ     loc_c8ee            ; 
C8EB: 20 1B DE        JSR     stepEaromTransfer   ; ...step the non-volatile high-score store transfer

loc_c8ee:
C8EE: A5 0C           LDA     workRam+C           ; when a sound step is queued...
C8F0: F0 03           BEQ     loc_c8f5            ; 
C8F2: 20 FA CC        JSR     requestActiveSoundCue; ...register the active sound

loc_c8f5:
C8F5: AD 6C 01        LDA     workRam+16C         ; read the decimal-mode guard
C8F8: F0 07           BEQ     loc_c901            ; 
C8FA: A9 13           LDA     #$13                
C8FC: C5 9F           CMP     workRam+9F          ; 
C8FE: B0 01           BCS     loc_c901            ; 
C900: F8              SED                         ; arm decimal mode when the guard opens

loc_c901:
C901: A5 4E           LDA     workRam+4E          ; if this frame's edge bit is set...
C903: 29 80           AND     #$80                
C905: F0 04           BEQ     loc_c90b            ; 
C907: A9 00           LDA     #$00                ; ...clear the edge flags
C909: 85 4E           STA     workRam+4E          ; 

loc_c90b:
C90B: 60              RTS                         

; reset the per-slot playfield state: run setup passes
; rebuildControlBlocksIfRequested/buildLevelLayout (and
; clearChannelStagingBlock when 0x5 negative), clear 0x49, walk every slot
; from 0x3e down to 0 seeding 0x48,slot from 0x158 and 0x46,slot=0xff,
; clear 0x3f and 0x115, reload 0x3d from 0x3e, then tail-delegate to
; selectWaveStartSlot.
resetLevelPlayfieldSlots:
C90C: 20 A2 AB        JSR     rebuildControlBlocksIfRequested; rebuild the control blocks if requested
C90F: 20 6E C1        JSR     buildLevelLayout    ; build the level's geometry layout
C912: A5 05           LDA     workRam+5           ; only when the status byte is negative...
C914: 10 03           BPL     loc_c919            ; 
C916: 20 62 CA        JSR     clearChannelStagingBlock; ...clear the channel staging block

loc_c919:
C919: A9 00           LDA     #$00                ; clear the slot-countdown high byte
C91B: 85 49           STA     workRam+49          ; 
C91D: A6 3E           LDX     workRam+3E          ; start the loop index at the top live slot
C91F: 86 3D           STX     workRam+3D          ; 

loc_c921:
C921: A6 3D           LDX     workRam+3D          ; 
C923: AD 58 01        LDA     workRam+158         ; seed this slot's countdown from the bonus config
C926: 9D 48 00        STA     workRam+48,X        ; 
C929: A9 FF           LDA     #$FF                ; mark the slot's level entry unassigned
C92B: 9D 46 00        STA     workRam+46,X        ; 
C92E: C6 3D           DEC     workRam+3D          ; walk down through every live slot
C930: 10 EF           BPL     loc_c921            ; 
C932: A9 00           LDA     #$00                
C934: 85 3F           STA     workRam+3F          ; clear the level id
C936: 8D 15 01        STA     workRam+115         ; clear the spike-table guard
C939: A5 3E           LDA     workRam+3E          ; reload the index to the top slot for the next consumer
C93B: 85 3D           STA     workRam+3D          ; 
C93D: 4C C4 90        JMP     selectWaveStartSlot ; hand off to wave-start slot selection

; set up the level: seed sizing/timer cells loc_1=0, loc_00=30, loc_2=30,
; and when the level id loc_3f differs from last-seen loc_3d latch loc_3d
; and (with loc_5 negative) install the new-level timers
; loc_1=14/loc_00=10/loc_4=40or80 (by loc_117) via swapParallelTables,
; then run selectProjectionScale, index loc_46 by loc_3d into loc_9f, run
; runLevelInit and tail-delegate to the readout reset resetBothPokeyChips.
setupLevelTimers:
C940: A9 00           LDA     #$00                ; clear the dispatch selector
C942: 85 01           STA     workRam+1           ; 
C944: A9 1E           LDA     #$1E                ; seed the game mode and pending mode to 30
C946: 85 00           STA     workRam             ; 
C948: 85 02           STA     workRam+2           ; 
C94A: A5 3F           LDA     workRam+3F          ; only on a genuine level change...
C94C: C5 3D           CMP     workRam+3D          ; 
C94E: F0 1C           BEQ     loc_c96c            ; 
C950: 85 3D           STA     workRam+3D          ; latch the new level id
C952: A5 05           LDA     workRam+5           ; ...and only when the status byte is negative
C954: 10 16           BPL     loc_c96c            ; 
C956: A9 0E           LDA     #$0E                ; install the new-level dispatch selector
C958: 85 01           STA     workRam+1           ; 
C95A: A9 0A           LDA     #$0A                ; set game mode 10
C95C: 85 00           STA     workRam             ; 
C95E: A9 50           LDA     #$50                ; pick the level-delay timer -- 40 or 80
C960: AC 17 01        LDY     workRam+117         ; 
C963: F0 02           BEQ     loc_c967            ; 
C965: A9 28           LDA     #$28                

loc_c967:
C967: 85 04           STA     workRam+4           ; store the delay timer
C969: 20 B2 92        JSR     swapParallelTables  ; swap the paired geometry tables

loc_c96c:
C96C: 20 48 CA        JSR     selectProjectionScale; size the projection scale
C96F: A6 3D           LDX     workRam+3D          ; 
C971: B5 46           LDA     workRam+46,X        ; copy this level's per-slot level into the working cell
C973: 85 9F           STA     workRam+9F          ; 
C975: 20 25 90        JSR     runLevelInit        ; run the level startup init
C978: 4C 95 CD        JMP     resetBothPokeyChips ; reset the sound chips

; alternate state-entry seeder: write the four config cells loc_2=0x04,
; loc_1=0x00, loc_0=0x0a and loc_4=0x14.
seedModeParamsMinimal:
C97B: A9 04           LDA     #$04                ; queue mode 0x04 to promote after the delay
C97D: 85 02           STA     workRam+2           ; 
C97F: A9 00           LDA     #$00                
C981: 85 01           STA     workRam+1           ; clear the dispatch selector
C983: A9 0A           LDA     #$0A                
C985: 85 00           STA     workRam             ; enter the live mode 0x0a
C987: A9 14           LDA     #$14                
C989: 85 04           STA     workRam+4           ; set the promotion countdown
C98B: 60              RTS                         

; ramp the per-level enemy quota: while the loc_46-slot indexed by level
; loc_3d is below 0x62, bump that slot and the working copy loc_9f
; together, seed loc_00=0x18, and when the loc_102-slot is nonzero run the
; handler chain (seatInPagePointer, addBcdScoreAndAwardAtThreshold,
; requestScoreAwardSound), then tail-delegate to init runWaveInit.
bumpLevelEnemyQuota:
C98C: A6 3D           LDX     workRam+3D          ; index this level's enemy-quota cell
C98E: B5 46           LDA     workRam+46,X        ; 
C990: C9 62           CMP     #$62                ; quota below the cap?
C992: B0 04           BCS     loc_c998            ; at the cap -> skip the bump
C994: F6 46           INC     workRam+46,X        ; bump the level's enemy quota
C996: E6 9F           INC     workRam+9F          ; bump its working copy in lockstep

loc_c998:
C998: A9 18           LDA     #$18                ; enter wave-active mode
C99A: 85 00           STA     workRam             ; 
C99C: BD 02 01        LDA     workRam+102,X       ; if this level carries a bonus trigger...
C99F: F0 0B           BEQ     loc_c9ac            ; 
C9A1: 20 B5 91        JSR     seatInPagePointer   ; seat it as an in-page pointer
C9A4: A2 FF           LDX     #$FF                
C9A6: 20 6C CA        JSR     addBcdScoreAndAwardAtThreshold; add it to the score at the top threshold
C9A9: 20 B9 CC        JSR     requestScoreAwardSound; cue the score-award sound

loc_c9ac:
C9AC: 4C 09 90        JMP     runWaveInit         ; bring the wave up

; clear loc_4, decrement the active slot's countdown loc_48[loc_3d]; when
; the loc_48/loc_49 pair is fully spent finalize via
; reloadPacingFromPeakSlot, otherwise (flag loc_1=0x0c and loc_4=0x28 if
; this tick hit zero) toggle loc_3f to pick the next non-empty slot, arm
; its timer loc_2 (0x1c when loc_46[x]+1 wraps, else 0x02), and request
; mode loc_0=0x0a.
tickEnemyPacingCountdown:
C9AF: A9 00           LDA     #$00                ; clear the mode-delay timer
C9B1: 85 04           STA     workRam+4           ; 
C9B3: A6 3D           LDX     workRam+3D          ; tick the active slot's pacing countdown low byte
C9B5: D6 48           DEC     workRam+48,X        ; 
C9B7: A5 48           LDA     workRam+48          ; if the whole countdown pair is spent...
C9B9: 05 49           ORA     workRam+49          ; 
C9BB: D0 06           BNE     loc_c9c3            ; 
C9BD: 20 F1 C9        JSR     reloadPacingFromPeakSlot; ...reload the wave's pacing from the peak slot
C9C0: B8              CLV                         ; and return
C9C1: 50 2D           BVC     loc_c9f0            ; 

loc_c9c3:
C9C3: A6 3D           LDX     workRam+3D          ; 
C9C5: B5 48           LDA     workRam+48,X        ; if this tick zeroed the active slot...
C9C7: D0 08           BNE     loc_c9d1            ; 
C9C9: A9 0C           LDA     #$0C                ; ...raise the dispatch selector
C9CB: 85 01           STA     workRam+1           ; 
C9CD: A9 28           LDA     #$28                ; and set the mode delay
C9CF: 85 04           STA     workRam+4           ; 

loc_c9d1:
C9D1: A5 3E           LDA     workRam+3E          ; 
C9D3: F0 06           BEQ     loc_c9db            ; 
C9D5: A5 3F           LDA     workRam+3F          ; toggle to the other pacing slot
C9D7: 49 01           EOR     #$01                
C9D9: 85 3F           STA     workRam+3F          ; 

loc_c9db:
C9DB: A6 3F           LDX     workRam+3F          ; 
C9DD: B5 48           LDA     workRam+48,X        ; keep toggling until a non-empty slot is found
C9DF: F0 F0           BEQ     loc_c9d1            ; 
C9E1: A9 02           LDA     #$02                ; pick the slot's arm value -- 0x1c on wrap, else 0x02
C9E3: B4 46           LDY     workRam+46,X        ; 
C9E5: C8              INY                         
C9E6: D0 02           BNE     loc_c9ea            ; 
C9E8: A9 1C           LDA     #$1C                

loc_c9ea:
C9EA: 85 02           STA     workRam+2           ; queue it as the pending mode
C9EC: A9 0A           LDA     #$0A                ; request the pacing mode 0x0a
C9EE: 85 00           STA     workRam             ; 

loc_c9f0:
C9F0: 60              RTS                         

; scan the zero-page window loc_46[loc_3e..0] for its maximum, store it
; decremented-once (when nonzero) into loc_126, and set the mode-request
; cell loc_0 to 0x14 (or 0x10 when the status byte loc_5 is negative).
reloadPacingFromPeakSlot:
C9F1: A9 00           LDA     #$00                ; reset the running peak
C9F3: 8D 26 01        STA     workRam+126         ; 
C9F6: A6 3E           LDX     workRam+3E          ; scan from the top live slot down

loc_c9f8:
C9F8: B5 46           LDA     workRam+46,X        ; keep the highest per-slot level seen
C9FA: CD 26 01        CMP     workRam+126         ; 
C9FD: 90 03           BCC     loc_ca02            ; 
C9FF: 8D 26 01        STA     workRam+126         ; 

loc_ca02:
CA02: CA              DEX                         ; across the whole live window
CA03: 10 F3           BPL     loc_c9f8            ; 
CA05: AC 26 01        LDY     workRam+126         ; seed the pacing floor with peak-1 -- zero stays zero
CA08: F0 03           BEQ     loc_ca0d            ; 
CA0A: CE 26 01        DEC     workRam+126         ; 

loc_ca0d:
CA0D: A9 14           LDA     #$14                ; pick the next mode -- 0x10 when the status byte is negative, else 0x14
CA0F: 24 05           BIT     workRam+5           ; 
CA11: 10 02           BPL     loc_ca15            ; 
CA13: A9 10           LDA     #$10                

loc_ca15:
CA15: 85 00           STA     workRam             ; request it
CA17: 60              RTS                         

; alternate state-entry seeder: mask loc_5 to its low six bits (clearing
; the top two flag bits), then write loc_3e=0x00, loc_2=0x1a, loc_0=0x0a,
; loc_4=0xa0, loc_16b=0x01 and loc_1=0x0a.
seedModeParamsFromMaskedFlags:
CA18: A5 05           LDA     workRam+5           ; load the status flags
CA1A: 29 3F           AND     #$3F                ; keep only the low six gating bits -- clear the play/active-state flags
CA1C: 85 05           STA     workRam+5           ; store the masked status back
CA1E: A9 00           LDA     #$00                
CA20: 85 3E           STA     workRam+3E          ; clear the active-slot count
CA22: A9 1A           LDA     #$1A                
CA24: 85 02           STA     workRam+2           ; queue the mode to promote once the delay expires
CA26: A9 0A           LDA     #$0A                
CA28: 85 00           STA     workRam             ; set the live game mode
CA2A: A9 A0           LDA     #$A0                
CA2C: 85 04           STA     workRam+4           ; arm the mode-promotion countdown
CA2E: A9 01           LDA     #$01                
CA30: 8D 6B 01        STA     workRam+16B         ; arm the guard holding the pending transition
CA33: A9 0A           LDA     #$0A                
CA35: 85 01           STA     workRam+1           ; set the pre-doubled dispatch selector paired with the mode
CA37: 60              RTS                         

; ---- $CA38-$CA47: data ----
CA38: 80 40 20 10 08 04 02 01 01 02 04 08 10 20 40 80

; choose the projection scale and mode bit from gates loc_117/loc_3d:
; default value 0x00/scale 0x10, but when both gates are nonzero value
; 0x04/scale 0x08; copy bit2 of the value into flag loc_a1 (preserving the
; rest) and store the scale into loc_b4.
selectProjectionScale:
CA48: A0 10           LDY     #$10                ; default projection scale
CA4A: AD 17 01        LDA     workRam+117         ; read the spinner heartbeat flag
CA4D: F0 08           BEQ     loc_ca57            ; skip the alternate regime if the flag is clear
CA4F: A5 3D           LDA     workRam+3D          ; read the active seat
CA51: F0 04           BEQ     loc_ca57            ; skip the alternate regime if no live seat
CA53: A9 04           LDA     #$04                ; alternate mode-bit source
CA55: A0 08           LDY     #$08                ; alternate projection scale

loc_ca57:
CA57: 45 A1           EOR     workRam+A1          ; masked bit-merge -- fold only bit 2 of the chosen value into the vector-mode flag
CA59: 29 04           AND     #$04                
CA5B: 45 A1           EOR     workRam+A1          ; 
CA5D: 85 A1           STA     workRam+A1          ; store the updated vector-mode flag
CA5F: 84 B4           STY     workRam+B4          ; publish the projection scale for the depth math
CA61: 60              RTS                         

; zero the six-byte working block loc_40..loc_45 (a pre-clear used before
; that block is staged with fresh channel entries).
clearChannelStagingBlock:
CA62: A9 00           LDA     #$00                ; value to write
CA64: A2 05           LDX     #$05                ; index over the six staging cells

loc_ca66:
CA66: 95 40           STA     workRam+40,X        ; zero one sound-request staging cell
CA68: CA              DEX                         
CA69: 10 FB           BPL     loc_ca66            ; loop until the whole staging block is blank
CA6B: 60              RTS                         

; add a three-byte BCD amount into the score triplet at
; loc_40/loc_41/loc_42 (offset 0 when loc_3d==0 else 3) using fixed table
; bytes $CAF1+x/$CAF9+x when index x<0x08 else the live operand triplet
; loc_29/loc_2a/loc_2b, then range-check against threshold loc_156; on
; qualifying, if the per-slot counter loc_48+loc_3d is under 0x06 bump it,
; fire sound 0x4f via requestSoundIfEnabled and set loc_124=0x20; gated
; off unless loc_5 bit7 is set.
addBcdScoreAndAwardAtThreshold:
CA6C: F8              SED                         ; switch to BCD arithmetic for the score add
CA6D: 24 05           BIT     workRam+5           ; test the scoring-armed flag -- bit 7 of the status byte
CA6F: 10 7E           BPL     loc_caef            ; bail out unless scoring is armed
CA71: A4 3D           LDY     workRam+3D          ; read the active seat
CA73: F0 02           BEQ     loc_ca77            ; 
CA75: A0 03           LDY     #$03                ; player-two score bank offset

loc_ca77:
CA77: E0 08           CPX     #$08                ; small index selects a fixed point value, else the live operand triplet
CA79: 90 16           BCC     loc_ca91            ; branch to the fixed score-value table path
CA7B: A5 29           LDA     workRam+29          ; live operand low byte
CA7D: 18              CLC                         
CA7E: 79 40 00        ADC     workRam+40,Y        ; add it into the score low byte
CA81: 99 40 00        STA     workRam+40,Y        ; store the running score low byte
CA84: A5 2A           LDA     workRam+2A          ; live operand mid byte
CA86: 79 41 00        ADC     workRam+41,Y        ; add into the score mid byte with carry
CA89: 99 41 00        STA     workRam+41,Y        ; store the running score mid byte
CA8C: A5 2B           LDA     workRam+2B          ; live operand high byte
CA8E: B8              CLV                         
CA8F: 50 15           BVC     loc_caa6            ; 

loc_ca91:
CA91: BD F1 CA        LDA     rom+3AF1,X          ; low byte of the point value from the score-value table
CA94: 18              CLC                         
CA95: 79 40 00        ADC     workRam+40,Y        ; add into the score low byte
CA98: 99 40 00        STA     workRam+40,Y        ; store the running score low byte
CA9B: BD F9 CA        LDA     rom+3AF9,X          ; high byte of the point value from the score-value table
CA9E: 79 41 00        ADC     workRam+41,Y        ; add into the score mid byte with carry
CAA1: 99 41 00        STA     workRam+41,Y        ; store the running score mid byte
CAA4: A9 00           LDA     #$00                ; fixed-table value has no third byte

loc_caa6:
CAA6: 08              PHP                         
CAA7: 79 42 00        ADC     workRam+42,Y        ; add the third byte into the score high cell with carry
CAAA: 99 42 00        STA     workRam+42,Y        ; store the running score high byte
CAAD: 28              PLP                         
CAAE: F0 0B           BEQ     loc_cabb            ; 
CAB0: AE 56 01        LDX     workRam+156         ; read the bonus-life interval
CAB3: F0 06           BEQ     loc_cabb            ; 
CAB5: E4 2B           CPX     workRam+2B          ; compare it against the operand high byte
CAB7: F0 23           BEQ     loc_cadc            ; exact landing -- grant the award
CAB9: 90 21           BCC     loc_cadc            ; 

loc_cabb:
CABB: 90 32           BCC     loc_caef            ; 
CABD: AE 56 01        LDX     workRam+156         ; reload the bonus-life interval
CAC0: F0 2C           BEQ     loc_caee            ; 
CAC2: E0 03           CPX     #$03                ; tiny interval takes a direct compare, else repeated subtraction
CAC4: 90 0B           BCC     loc_cad1            ; 

loc_cac6:
CAC6: 38              SEC                         ; repeatedly subtract the bonus interval from the score high byte
CAC7: ED 56 01        SBC     workRam+156         ; 
CACA: F0 10           BEQ     loc_cadc            ; exact multiple of the interval -- grant the award
CACC: B0 F8           BCS     loc_cac6            ; keep subtracting while it still fits
CACE: B8              CLV                         
CACF: 50 1D           BVC     loc_caee            ; 

loc_cad1:
CAD1: E0 02           CPX     #$02                
CAD3: D0 07           BNE     loc_cadc            ; 
CAD5: 29 01           AND     #$01                
CAD7: F0 03           BEQ     loc_cadc            ; 
CAD9: B8              CLV                         
CADA: 50 12           BVC     loc_caee            ; 

loc_cadc:
CADC: A6 3D           LDX     workRam+3D          ; read the active seat
CADE: B5 48           LDA     workRam+48,X        ; load this seat's bonus-life counter
CAE0: C9 06           CMP     #$06                ; cap the counter at six
CAE2: B0 0A           BCS     loc_caee            ; skip the award if already at the cap
CAE4: F6 48           INC     workRam+48,X        ; grant the extra life -- bump the counter
CAE6: 20 B9 CC        JSR     requestScoreAwardSound; fire the score-award chime
CAE9: A9 20           LDA     #$20                
CAEB: 8D 24 01        STA     workRam+124         ; kick the rim colour animation

loc_caee:
CAEE: 38              SEC                         

loc_caef:
CAEF: D8              CLD                         ; back to binary arithmetic
CAF0: 60              RTS                         

; ---- $CAF1-$CCAF: data ----
CAF1: 00 50 00 00 50 50 00 50 00 01 02 01 00 02 05 07
CB01: 00 00 00 00 00 00 00 00 35 38 00 00 00 00 00 00
CB11: 00 00 47 4A 00 00 00 00 00 00 00 00 00 00 00 00
CB21: 00 00 00 00 0D 10 00 00 00 00 00 00 00 00 00 00
CB31: 00 00 00 00 00 00 00 00 00 00 65 68 00 00 00 00
CB41: 00 00 00 00 00 00 21 32 00 00 00 00 00 00 00 00
CB51: 13 1A 00 00 00 00 00 00 00 00 00 00 00 00 00 00
CB61: 00 00 00 00 00 00 00 00 00 00 53 56 00 00 00 00
CB71: 00 00 00 00 00 00 00 00 00 00 59 5C 00 00 00 00
CB81: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 3B 3E
CB91: 00 00 00 00 00 00 00 00 00 00 00 00 41 44 00 00
CBA1: 4D 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00
CBB1: 5F 62 00 00 00 00 00 00 00 00 00 00 00 00 00 00
CBC1: 00 00 00 00 00 00 00 00 00 00 6D 6D 00 00 00 00
CBD1: C0 08 04 10 00 00 A6 20 F8 04 00 00 40 08 04 10
CBE1: 00 00 A6 20 FE 04 00 00 10 01 07 20 00 00 A2 01
CBF1: F8 20 00 00 08 04 20 0A 08 04 01 09 10 0D 04 0C
CC01: 00 00 08 04 00 0A 68 04 00 09 68 12 FF 09 00 00
CC11: 40 01 00 01 40 01 FF 40 30 01 FF 30 20 01 FF 20
CC21: 18 01 FF 18 14 01 FF 14 12 01 FF 12 10 01 FF 10
CC31: 00 00 A8 93 00 02 00 00 0F 04 00 01 00 00 A2 04
CC41: 40 01 00 00 00 03 02 09 00 00 08 03 FF 09 00 00
CC51: 80 01 E8 05 00 00 A1 01 01 05 00 00 01 08 02 10
CC61: 00 00 86 20 00 04 00 00 18 04 00 FF 00 00 AF 04
CC71: 00 FF 00 00 C0 02 FF FF 00 00 28 02 00 F0 00 00
CC81: 10 0B 01 40 00 00 86 40 00 0B 00 00 20 80 00 03
CC91: 00 00 A8 40 F8 06 00 00 B0 02 00 FF 00 00 C8 01
CCA1: 02 FF C8 01 02 FF 00 00 C0 01 00 01 00 00 00

; cue the fixed sound id 0x5f through the sound gate, carrying the
; caller's X/Y.
gateSound5f:
CCB0: A9 5F           LDA     #$5F                ; sound id -- fixed effect 0x5f
CCB2: 4C C3 CC        JMP     requestSoundIfEnabled; hand it to the sound-enable gate

; load fixed sound id 0x0f and pass it through the sound gate $CCC3
; (keeping caller X/Y); rung by $9749 when the coarse rim angle changes,
; i.e. the spinner-rotation sound cue.
cueRimRotationSound:
CCB5: A9 0F           LDA     #$0F                ; sound id -- rim-rotation click
CCB7: D0 0A           BNE     requestSoundIfEnabled; branch into the enable gate

; one-line cue: request fixed sound id 0x4f through the enable gate $CCC3;
; caller $C98C fires it on the bonus/level-advance handler chain, and the
; same id 0x4f is fired by the score-award path $CA6C, so it voices a
; score/bonus award.
requestScoreAwardSound:
CCB9: A9 4F           LDA     #$4F                ; sound id -- score-award chime
CCBB: D0 06           BNE     requestSoundIfEnabled; branch into the enable gate

; cue the fixed sound id 0x8f through the sound gate, carrying the
; caller's X/Y.
gateSound8f:
CCBD: A9 8F           LDA     #$8F                ; sound id -- effect 0x8f
CCBF: D0 02           BNE     requestSoundIfEnabled; branch into the enable gate

; cue the fixed sound id 0x1f through the sound gate, carrying the
; caller's X/Y.
gateSound1f:
CCC1: A9 1F           LDA     #$1F                ; sound id -- enemy-spawn cue

; sound enable gate: only when bit7 of the enable flag loc_5 is set,
; forward the sound id in A (with X/Y) to the loader loadSoundVoiceSlots;
; with sound disabled it returns making no write.
requestSoundIfEnabled:
CCC3: 24 05           BIT     workRam+5           ; test the sound-enable flag -- bit 7 of the status byte
CCC5: 10 22           BPL     loc_cce9            ; muted: drop the cue and return

; sound loader: stash caller X/Y into loc_31/loc_32, read the sound's row
; of bytes from $CB01 by descending id, and for each nonzero table byte
; claim its slot -- mark slot in loc_bf, write the byte to loc_c0,x, set
; fast/slow flags loc_e0,x and loc_f0,x to 1, then restore the 0xff
; sentinel to loc_bf.
loadSoundVoiceSlots:
CCC7: 86 31           STX     workRam+31          ; stash the caller's X/Y
CCC9: 84 32           STY     workRam+32          ; 
CCCB: A8              TAY                         ; sound id becomes the starting index into the voice table
CCCC: A2 0F           LDX     #$0F                ; walk all sixteen voice slots

loc_ccce:
CCCE: B9 01 CB        LDA     rom+3B01,Y          ; read this slot's byte from the voice table
CCD1: F0 0E           BEQ     loc_cce1            ; zero byte is a gap -- leave the slot untouched
CCD3: 86 BF           STX     workRam+BF          ; mark the slot being claimed
CCD5: 95 C0           STA     workRam+C0,X        ; load the voice value into the slot
CCD7: A9 01           LDA     #$01                
CCD9: 95 E0           STA     workRam+E0,X        ; arm the fast timer
CCDB: 95 F0           STA     workRam+F0,X        ; arm the slow timer
CCDD: A9 FF           LDA     #$FF                
CCDF: 85 BF           STA     workRam+BF          ; restore the idle sentinel

loc_cce1:
CCE1: 88              DEY                         
CCE2: CA              DEX                         
CCE3: 10 E9           BPL     loc_ccce            ; loop over the remaining slots
CCE5: A6 31           LDX     workRam+31          ; restore the caller's X/Y
CCE7: A4 32           LDY     workRam+32          ; 

loc_cce9:
CCE9: 60              RTS                         

; one-line cue: request fixed sound id 0x2f through the enable gate $CCC3,
; forwarding slot index X; its sole caller is the enemy spawner $A23F, so
; it voices a new enemy entering the tube.
requestEnemySpawnSound:
CCEA: A9 2F           LDA     #$2F                ; sound id -- new enemy entering the tube
CCEC: D0 D5           BNE     requestSoundIfEnabled; branch into the enable gate

; register the fixed sound id 0x6f through the sound-enable gate $CCC3 --
; the moving spike's start cue, fired by $97F8 at trigger height
; loc_202==0x10.
cueMovingSpikeStartSound:
CCEE: A9 6F           LDA     #$6F                ; sound id -- moving-spike start cue
CCF0: D0 D1           BNE     requestSoundIfEnabled; branch into the enable gate

; register the fixed sound id 0x7f through the sound-enable gate $CCC3 --
; the moving spike's end cue, fired by $97F8 when the height overflows the
; ceiling.
cueMovingSpikeEndSound:
CCF2: A9 7F           LDA     #$7F                ; sound id -- moving-spike end cue
CCF4: D0 CD           BNE     requestSoundIfEnabled; branch into the enable gate

; one-line cue: request fixed sound id 0x9f through the enable gate $CCC3,
; forwarding X/Y; its sole caller is the per-slot hit/award routine $A1FA,
; so it chimes when a segment/enemy is hit.
requestSegmentHitSound:
CCF6: A9 9F           LDA     #$9F                ; sound id -- segment-hit chime
CCF8: D0 C9           BNE     requestSoundIfEnabled; branch into the enable gate

; request fixed sound id 0xaf by jumping straight into the loader $CCC7,
; bypassing the enable gate; the frame dispatcher $C891 calls it every
; frame while loc_c is live, so it (re)voices the currently-active held
; cue regardless of the loc_5 enable flag.
requestActiveSoundCue:
CCFA: A9 AF           LDA     #$AF                ; sound id -- currently-held active cue
CCFC: D0 C9           BNE     loadSoundVoiceSlots ; branch straight to the voice loader -- bypass the enable gate

; one-line cue: request fixed sound id 0xbf through the enable gate $CCC3;
; its sole caller $90C4 fires it at phase 3 of the start-slot pick / wave
; working-set reseed, so it voices the level-intro / skill-step start.
requestLevelIntroSound:
CCFE: A9 BF           LDA     #$BF                ; sound id -- level-intro / skill-step start
CD00: D0 C1           BNE     requestSoundIfEnabled; branch into the enable gate

; one-line cue: request fixed sound id 0x3f through the enable gate $CCC3,
; threading X; its sole caller $9B1E fires it as a correction when the
; per-slot motion accumulator loc_148 sign-flips, so it voices an enemy
; direction reversal on the rim.
requestMotionFlipSound:
CD02: A9 3F           LDA     #$3F                ; load fixed sound id 0x3f -- the enemy direction-reversal cue on the rim
CD04: D0 BD           BNE     requestSoundIfEnabled; hand the id to the sound-enable gate -- queued only while sound is on

; register the fixed sound id 0xcf through the sound-enable gate $CCC3,
; threading X/Y -- the spike-collision cue fired by $97F8 when the spike
; reaches the player segment.
cueSpikeCollisionSound:
CD06: A9 CF           LDA     #$CF                ; load fixed sound id 0xcf -- the spike-strikes-blaster hit
CD08: D0 B9           BNE     requestSoundIfEnabled; hand the id to the sound-enable gate

; per-frame voice engine: scan 16 slots 0x0f..0, skip idle slots
; (loc_c0,x==0) and the reserved slot loc_bf; decrement fast timer
; loc_e0,x then slow timer loc_f0,x, step the slot through the $CBCC/$CCCC
; animation tables (single step, or walk to a nonzero frame when both
; expire), fold the result into level byte loc_d0,x (odd slots keep the
; prior high nibble), and publish loc_d0,x to POKEY audio register $60C0+x
; for slots<8 or $60C8+x for the upper slots.
stepSoundVoices:
CD0A: A2 0F           LDX     #$0F                ; start at sound slot 15 and walk down to 0

loc_cd0c:
CD0C: B5 C0           LDA     workRam+C0,X        ; read this slot's envelope pointer
CD0E: F0 7E           BEQ     loc_cd8e            ; skip an idle slot (pointer 0)
CD10: E4 BF           CPX     workRam+BF          ; is this the reserved slot
CD12: F0 7A           BEQ     loc_cd8e            ; skip the reserved slot
CD14: D6 E0           DEC     workRam+E0,X        ; age the slot's fast timer
CD16: D0 76           BNE     loc_cd8e            ; still running -- nothing more for this slot this frame
CD18: D6 F0           DEC     workRam+F0,X        ; fast timer expired -- age the slow timer too
CD1A: D0 38           BNE     loc_cd54            ; slow timer still running -- take a single envelope step

loc_cd1c:
CD1C: F6 C0           INC     workRam+C0,X        ; both timers expired: advance the envelope pointer by two
CD1E: F6 C0           INC     workRam+C0,X        ; (second half of the two-step advance to the next frame)
CD20: B5 C0           LDA     workRam+C0,X        ; reload the envelope pointer
CD22: 0A              ASL     A                   ; double it into a word-stride table index
CD23: A8              TAY                         
CD24: B0 10           BCS     loc_cd36            ; top bit set -- read the frame from the high envelope table
CD26: B9 CB CB        LDA     rom+3BCB,Y          ; low table: load the frame's level byte
CD29: 95 D0           STA     workRam+D0,X        ; store it as the slot's level
CD2B: B9 CE CB        LDA     rom+3BCE,Y          ; load the frame's slow-timer reload
CD2E: 95 F0           STA     workRam+F0,X        ; store the slow timer
CD30: B9 CC CB        LDA     rom+3BCC,Y          ; load the frame's fast-timer reload
CD33: B8              CLV                         
CD34: 50 0D           BVC     loc_cd43            ; 

loc_cd36:
CD36: B9 CB CC        LDA     rom+3CCB,Y          ; high table: load the frame's level byte
CD39: 95 D0           STA     workRam+D0,X        ; store it as the slot's level
CD3B: B9 CE CC        LDA     rom+3CCE,Y          ; load the frame's slow-timer reload
CD3E: 95 F0           STA     workRam+F0,X        ; store the slow timer
CD40: B9 CC CC        LDA     rom+3CCC,Y          ; load the frame's fast-timer reload

loc_cd43:
CD43: 95 E0           STA     workRam+E0,X        ; store the fast timer
CD45: D0 0A           BNE     loc_cd51            ; nonzero fast timer -- a real frame, publish it
CD47: 95 C0           STA     workRam+C0,X        ; zero fast timer: park the envelope pointer at 0
CD49: B5 D0           LDA     workRam+D0,X        ; reload the level byte
CD4B: F0 04           BEQ     loc_cd51            ; level 0 is a terminator -- publish
CD4D: 95 C0           STA     workRam+C0,X        ; otherwise treat the level as the next pointer
CD4F: D0 CB           BNE     loc_cd1c            ; keep walking the envelope

loc_cd51:
CD51: B8              CLV                         
CD52: 50 2B           BVC     loc_cd7f            ; 

loc_cd54:
CD54: 0A              ASL     A                   ; single-step path: double the pointer into a table index
CD55: A8              TAY                         
CD56: B0 0B           BCS     loc_cd63            ; top bit set -- read from the high envelope table
CD58: B9 CC CB        LDA     rom+3BCC,Y          ; low table: load the fast-timer reload
CD5B: 95 E0           STA     workRam+E0,X        ; reload the fast timer
CD5D: B9 CD CB        LDA     rom+3BCD,Y          ; load the level increment
CD60: B8              CLV                         
CD61: 50 08           BVC     loc_cd6b            ; 

loc_cd63:
CD63: B9 CC CC        LDA     rom+3CCC,Y          ; high table: load the fast-timer reload
CD66: 95 E0           STA     workRam+E0,X        ; reload the fast timer
CD68: B9 CD CC        LDA     rom+3CCD,Y          ; load the level increment

loc_cd6b:
CD6B: B4 D0           LDY     workRam+D0,X        ; fetch the running level
CD6D: 18              CLC                         
CD6E: 75 D0           ADC     workRam+D0,X        ; add the increment into the running level
CD70: 95 D0           STA     workRam+D0,X        ; store the stepped level
CD72: 8A              TXA                         
CD73: 4A              LSR     A                   ; test whether this is an odd slot
CD74: 90 09           BCC     loc_cd7f            ; even slot -- publish as is
CD76: 98              TYA                         
CD77: 55 D0           EOR     workRam+D0,X        ; odd slot: preserve the prior high nibble of the level byte
CD79: 29 F0           AND     #$F0                ; (mask off the low nibble of the delta)
CD7B: 55 D0           EOR     workRam+D0,X        ; (merge back the retained high nibble)
CD7D: 95 D0           STA     workRam+D0,X        ; store the merged level

loc_cd7f:
CD7F: B5 D0           LDA     workRam+D0,X        ; load the slot's level to publish
CD81: E0 08           CPX     #$08                ; slots below 8 use the first sound chip
CD83: 90 06           BCC     loc_cd8b            ; 
CD85: 9D C8 60        STA     pokey1+8,X          ; publish the level to the second sound chip's voice register
CD88: B8              CLV                         
CD89: 50 03           BVC     loc_cd8e            ; 

loc_cd8b:
CD8B: 9D C0 60        STA     pokey1,X            ; publish the level to the first sound chip's voice register

loc_cd8e:
CD8E: CA              DEX                         ; step down to the next slot
CD8F: 30 03           BMI     loc_cd94            ; done all 16 slots
CD91: 4C 0C CD        JMP     loc_cd0c            ; loop to the next slot

loc_cd94:
CD94: 60              RTS                         

; dual-POKEY reset: zero both serial-control regs $60CF/$60DF and the
; scratch flag loc_720, poll random regs $60CA/$60DA across five
; iterations latching the first sample into loc_720 the moment either
; changes, set both serial-control regs to 7, clear both chips' eight
; audio regs $60C0..7/$60D0..7 and the software arrays loc_c0,x/loc_d0,x,
; then zero both control regs $60C8/$60D8.
resetBothPokeyChips:
CD95: A9 00           LDA     #$00                
CD97: 8D CF 60        STA     pokey1+F            ; hold the first sound chip in serial reset (control latch 0)
CD9A: 8D DF 60        STA     pokey2+F            ; hold the second sound chip in serial reset
CD9D: 8D 20 07        STA     workRam+720         ; clear the random-seed scratch cell
CDA0: A2 04           LDX     #$04                ; poll the entropy registers up to five times
CDA2: AD CA 60        LDA     pokey1+A            ; snapshot the first chip's free-running random register
CDA5: AC DA 60        LDY     pokey2+A            ; snapshot the second chip's random register

loc_cda8:
CDA8: CD CA 60        CMP     pokey1+A            ; has the first chip's random register advanced
CDAB: D0 03           BNE     loc_cdb0            ; 
CDAD: CC DA 60        CPY     pokey2+A            ; has the second chip's advanced

loc_cdb0:
CDB0: F0 05           BEQ     loc_cdb7            ; neither moved -- keep polling
CDB2: 8D 20 07        STA     workRam+720         ; one moved -- latch the snapshot as the random seed
CDB5: A2 00           LDX     #$00                ; stop polling

loc_cdb7:
CDB7: CA              DEX                         
CDB8: 10 EE           BPL     loc_cda8            ; 
CDBA: A9 07           LDA     #$07                ; release value for the serial-control latch
CDBC: 8D CF 60        STA     pokey1+F            ; release the first chip (control latch 7, the init handshake)
CDBF: 8D DF 60        STA     pokey2+F            ; release the second chip
CDC2: A2 07           LDX     #$07                ; walk the eight audio registers of each chip
CDC4: A9 00           LDA     #$00                

loc_cdc6:
CDC6: 9D C0 60        STA     pokey1,X            ; silence a first-chip audio register
CDC9: 9D D0 60        STA     pokey2,X            ; silence a second-chip audio register
CDCC: 95 C0           STA     workRam+C0,X        ; clear the slot's software voice-value mirror
CDCE: 95 D0           STA     workRam+D0,X        ; clear the slot's software voice-level mirror
CDD0: CA              DEX                         
CDD1: 10 F3           BPL     loc_cdc6            ; 
CDD3: A9 00           LDA     #$00                
CDD5: 8D C8 60        STA     pokey1+8            ; clear the first chip's control register
CDD8: A9 00           LDA     #$00                
CDDA: 8D D8 60        STA     pokey2+8            ; clear the second chip's control register
CDDD: 60              RTS                         

; ---- $CDDE-$CF23: data ----
CDDE: 0B 5D 22 74 0C 5E 34 50 00 71 C5 68 40 80 6C 01
CDEE: 40 1E 00 71 B4 A8 B4 A8 B4 A8 B4 A8 B4 A8 65 A8
CDFE: 00 00 70 1F 00 71 00 58 C1 68 3F A9 3F A9 3F A9
CE0E: 3F A9 3F A9 3F A9 30 00 D0 1F C5 68 B4 A8 B4 A8
CE1E: B4 A8 B4 A8 B4 A8 B4 A8 DC 1F 00 00 C7 68 B4 A8
CE2E: B4 A8 C5 68 24 00 E8 1F B4 A8 B4 A8 B4 A8 00 71
CE3E: E0 1F 28 00 00 71 B4 A8 B4 A8 B4 A8 B4 A8 B4 A8
CE4E: 65 A8 00 00 70 1F 00 71 00 58 C1 68 3F A9 3F A9
CE5E: 3F A9 3F A9 3F A9 3F A9 55 7F 06 20 02 22 0C 24
CE6E: 92 26 00 29 56 2A D8 2C BE 2D 24 2E 04 21 06 23
CE7E: 4E 25 C8 27 AA 29 96 2B 4A 2D F0 2D A6 2E 04 20
CE8E: 00 22 0A 24 90 26 FE 28 54 2A D6 2C BC 2D 22 2E
CE9E: 03 E0 01 E1 06 E2 49 E3 80 E4 2B E5 6C E6 DF E6
CEAE: 12 E7 82 E0 83 E1 A7 E2 E4 E3 D5 E4 CB E5 A5 E6
CEBE: F8 E6 53 E7 DA EE E4 EE E6 EE 61 AA 7C AA 91 AA
CECE: AD AA CA AA 14 AB 6F AB C0 AB 15 AC 66 AC 7D AC
CEDE: 94 AC AB AC D8 AC FA AC 0D AD 20 AD 39 AD 51 AD
CEEE: 6A AD 8C AD 8A AD 88 AD 86 AD 84 AD 82 AD 86 AD
CEFE: 8A AD 8C AD D7 AD C2 AD C5 AD C8 AD CB AD CE AD
CF0E: D1 AD D4 AD C2 AC CB AC 35 AE 59 AE 7E AE A2 AE
CF1E: C5 AE CB AE D2 AE

; on every interrupt advance the three timebase counter lanes gated by
; loc_8: step each enabled lane's wrapped position loc_d/loc_e/loc_f
; (masked to 0x1f, wrapping the 0x20 boundary), run its down-timer
; loc_10,x (reload 0x78 on zero), fold a small per-lane increment into the
; running accumulator pair loc_16/loc_17 and bump counter loc_13,x; then
; subtract a $CFD9-table amount from loc_16 (advancing overflow tally
; loc_18), nudge loc_6, store loc_17, and run two clamp passes over the
; loc_13 triple.
tickHeartbeatCounters:
CF24: A2 02           LDX     #$02                ; step the three timebase lanes, x = 2,1,0

loc_cf26:
CF26: AD 08 00        LDA     workRam+8           ; read the latched input port for this lane's control bits
CF29: E0 01           CPX     #$01                
CF2B: F0 03           BEQ     loc_cf30            ; 
CF2D: B0 02           BCS     loc_cf31            ; 
CF2F: 4A              LSR     A                   ; shift out this lane's control bits

loc_cf30:
CF30: 4A              LSR     A                   

loc_cf31:
CF31: 4A              LSR     A                   
CF32: B5 0D           LDA     workRam+D,X         ; load this lane's wrapped position
CF34: 29 1F           AND     #$1F                ; mask to the 0..0x1f rail
CF36: B0 37           BCS     loc_cf6f            ; control bit set -- take the wrap-up path
CF38: F0 10           BEQ     loc_cf4a            ; position at 0 -- store as-is
CF3A: C9 1B           CMP     #$1B                ; near the top rail
CF3C: B0 0A           BCS     loc_cf48            ; 
CF3E: A8              TAY                         
CF3F: A5 07           LDA     workRam+7           ; read the interrupt sub-timer
CF41: 29 07           AND     #$07                ; take its low phase
CF43: C9 07           CMP     #$07                ; pace the step to sub-timer phase 7
CF45: 98              TYA                         
CF46: 90 02           BCC     loc_cf4a            ; 

loc_cf48:
CF48: E9 01           SBC     #$01                ; decrement the position with borrow

loc_cf4a:
CF4A: 95 0D           STA     workRam+D,X         ; store the updated lane position
CF4C: AD 08 00        LDA     workRam+8           ; test input bit 3
CF4F: 29 08           AND     #$08                
CF51: D0 04           BNE     loc_cf57            ; 
CF53: A9 F0           LDA     #$F0                ; prime the sound-step gate to 0xf0
CF55: 85 0C           STA     workRam+C           ; 

loc_cf57:
CF57: A5 0C           LDA     workRam+C           ; read the sound-step gate
CF59: F0 08           BEQ     loc_cf63            ; 
CF5B: C6 0C           DEC     workRam+C           ; tick the sound-step gate down
CF5D: A9 00           LDA     #$00                
CF5F: 95 0D           STA     workRam+D,X         ; reset this lane's position on gate drain
CF61: 95 10           STA     workRam+10,X        ; reset this lane's down-timer

loc_cf63:
CF63: 18              CLC                         
CF64: B5 10           LDA     workRam+10,X        ; read the lane down-timer
CF66: F0 23           BEQ     loc_cf8b            ; 
CF68: D6 10           DEC     workRam+10,X        ; decrement the down-timer
CF6A: D0 1F           BNE     loc_cf8b            ; 
CF6C: 38              SEC                         ; timer reached 0 -- flag an event tick
CF6D: B0 1C           BCS     loc_cf8b            ; 

loc_cf6f:
CF6F: C9 1B           CMP     #$1B                ; wrap-up path: position near the top rail
CF71: B0 09           BCS     loc_cf7c            ; 
CF73: B5 0D           LDA     workRam+D,X         ; load the position
CF75: 69 20           ADC     #$20                ; add 0x20 to the position
CF77: 90 D1           BCC     loc_cf4a            ; 
CF79: F0 01           BEQ     loc_cf7c            ; 
CF7B: 18              CLC                         

loc_cf7c:
CF7C: A9 1F           LDA     #$1F                ; rail the position at 0x1f
CF7E: B0 CA           BCS     loc_cf4a            ; 
CF80: 95 0D           STA     workRam+D,X         ; store the railed position
CF82: B5 10           LDA     workRam+10,X        ; read the down-timer
CF84: F0 01           BEQ     loc_cf87            ; 
CF86: 38              SEC                         

loc_cf87:
CF87: A9 78           LDA     #$78                ; reload the lane down-timer to 0x78
CF89: 95 10           STA     workRam+10,X        ; store the reloaded down-timer

loc_cf8b:
CF8B: 90 2A           BCC     loc_cfb7            ; no event this lane -- skip the accumulator fold
CF8D: A9 00           LDA     #$00                
CF8F: E0 01           CPX     #$01                ; select the increment by lane
CF91: 90 16           BCC     loc_cfa9            ; 
CF93: F0 0C           BEQ     loc_cfa1            ; 
CF95: A5 09           LDA     workRam+9           ; lane 2: take option bits 2-3 as the increment
CF97: 29 0C           AND     #$0C                
CF99: 4A              LSR     A                   
CF9A: 4A              LSR     A                   
CF9B: F0 0C           BEQ     loc_cfa9            ; 
CF9D: 69 02           ADC     #$02                ; bias the increment
CF9F: D0 08           BNE     loc_cfa9            ; 

loc_cfa1:
CFA1: A5 09           LDA     workRam+9           ; lane 1: option bit 4 selects
CFA3: 29 10           AND     #$10                
CFA5: F0 02           BEQ     loc_cfa9            ; 
CFA7: A9 01           LDA     #$01                ; a +1 increment

loc_cfa9:
CFA9: 38              SEC                         
CFAA: 48              PHA                         
CFAB: 65 16           ADC     workRam+16          ; fold the increment into the accumulator low byte
CFAD: 85 16           STA     workRam+16          ; store the accumulator low byte
CFAF: 68              PLA                         
CFB0: 38              SEC                         
CFB1: 65 17           ADC     workRam+17          ; carry into the accumulator high byte
CFB3: 85 17           STA     workRam+17          ; store the accumulator high byte
CFB5: F6 13           INC     workRam+13,X        ; bump this lane's event counter

loc_cfb7:
CFB7: CA              DEX                         ; advance to the next lane
CFB8: 30 03           BMI     loc_cfbd            ; 
CFBA: 4C 26 CF        JMP     loc_cf26            ; loop to the next lane

loc_cfbd:
CFBD: A5 09           LDA     workRam+9           ; option top three bits index the reduction table
CFBF: 4A              LSR     A                   
CFC0: 4A              LSR     A                   
CFC1: 4A              LSR     A                   
CFC2: 4A              LSR     A                   
CFC3: 4A              LSR     A                   
CFC4: A8              TAY                         
CFC5: A5 16           LDA     workRam+16          ; load the accumulator low byte
CFC7: 38              SEC                         
CFC8: F9 D9 CF        SBC     rom+3FD9,Y          ; drain it by the option-indexed reduction amount
CFCB: 30 14           BMI     loc_cfe1            ; 
CFCD: 85 16           STA     workRam+16          ; store the drained low byte
CFCF: E6 18           INC     workRam+18          ; advance the overflow tally
CFD1: C0 03           CPY     #$03                ; at the maximum reduction index
CFD3: D0 0C           BNE     loc_cfe1            ; 
CFD5: E6 18           INC     workRam+18          ; advance the overflow tally a second time
CFD7: D0 08           BNE     loc_cfe1            ; 

; A short data table subtracted from a running value via sbc $cfd9,y
; (y=0..3) by the routine just above; a fall-through mis-decoded its first
; byte as an undefined opcode. Real code resumes at $cfe1.
; ---- $CFD9-$CFE0: data table ----
CFD9: 7F 02 04 04 05 03 7F 7F

loc_cfe1:
CFE1: A5 09           LDA     workRam+9           ; option low two bits select a high-byte correction
CFE3: 29 03           AND     #$03                
CFE5: A8              TAY                         
CFE6: F0 1A           BEQ     loc_d002            ; 
CFE8: 4A              LSR     A                   
CFE9: 69 00           ADC     #$00                
CFEB: 49 FF           EOR     #$FF                
CFED: 38              SEC                         
CFEE: 65 17           ADC     workRam+17          ; apply the correction to the accumulator high byte
CFF0: B0 08           BCS     loc_cffa            ; 
CFF2: 65 18           ADC     workRam+18          ; carry it through the overflow tally
CFF4: 30 0E           BMI     loc_d004            ; 
CFF6: 85 18           STA     workRam+18          ; store the overflow tally
CFF8: A9 00           LDA     #$00                

loc_cffa:
CFFA: C0 02           CPY     #$02                
CFFC: B0 02           BCS     loc_d000            ; 
CFFE: E6 06           INC     workRam+6           ; step the phase counter

loc_d000:
D000: E6 06           INC     workRam+6           ; step the phase counter again

loc_d002:
D002: 85 17           STA     workRam+17          ; commit the accumulator high byte

loc_d004:
D004: A5 07           LDA     workRam+7           ; gate the clamp passes on the sub-timer's low bit
D006: 4A              LSR     A                   
D007: B0 27           BCS     loc_d030            ; odd sub-timer -- skip the clamp
D009: A0 00           LDY     #$00                
D00B: A2 02           LDX     #$02                ; first clamp pass over the three lane counters

loc_d00d:
D00D: B5 13           LDA     workRam+13,X        ; read a lane counter
D00F: F0 09           BEQ     loc_d01a            ; 
D011: C9 10           CMP     #$10                ; is it 0x10 or more
D013: 90 05           BCC     loc_d01a            ; 
D015: 69 EF           ADC     #$EF                ; reduce it by 0x11
D017: C8              INY                         ; tally that a lane was reduced
D018: 95 13           STA     workRam+13,X        ; store the reduced counter

loc_d01a:
D01A: CA              DEX                         
D01B: 10 F0           BPL     loc_d00d            ; 
D01D: 98              TYA                         ; any lane reduced
D01E: D0 10           BNE     loc_d030            ; yes -- skip the second pass
D020: A2 02           LDX     #$02                ; second clamp pass over the lane counters

loc_d022:
D022: B5 13           LDA     workRam+13,X        ; read a lane counter
D024: F0 07           BEQ     loc_d02d            ; 
D026: 18              CLC                         
D027: 69 EF           ADC     #$EF                ; reduce it by 0x11
D029: 95 13           STA     workRam+13,X        ; store it
D02B: 30 03           BMI     loc_d030            ; stop at the first that goes negative

loc_d02d:
D02D: CA              DEX                         
D02E: 10 F2           BPL     loc_d022            ; 

loc_d030:
D030: 60              RTS                         

; ---- $D031-$D6BA: data ----
D031: 5D D1 8F D1 8F D1 B1 D1 EB D1 03 D2 61 D2 CB D2
D041: 33 D3 66 D3 B0 D3 E6 D3 FF D3 17 D4 1D D4 34 D4
D051: 4C D4 60 D4 A1 D4 AB D4 EF D4 30 D5 75 D5 85 D5
D061: A1 D5 A8 D5 E9 D5 1C D6 62 D6 7A D6 67 D1 97 D1
D071: 97 D1 BD D1 F0 D1 17 D2 75 D2 E0 D2 3F D3 79 D3
D081: BE D3 E6 D3 FF D3 17 D4 22 D4 3A D4 51 D4 6D D4
D091: A1 D4 BA D4 FD D4 3F D5 75 D5 85 D5 A1 D5 B9 D5
D0A1: F6 D5 29 D6 68 D6 7A D6 75 D1 9F D1 9F D1 CF D1
D0B1: F6 D1 30 D2 94 D2 FB D2 50 D3 8B D3 CB D3 F5 D3
D0C1: 0E D4 17 D4 28 D4 41 D4 5B D4 83 D4 A1 D4 CC D4
D0D1: 0E D5 51 D5 75 D5 8E D5 A1 D5 C8 D5 04 D6 3E D6
D0E1: 6F D6 8F D6 7F D1 A8 D1 A8 D1 DE D1 FC D1 4D D2
D0F1: AE D2 16 D3 5E D3 A0 D3 DA D3 ED D3 06 D4 17 D4
D101: 2D D4 46 D4 56 D4 92 D4 A1 D4 DD D4 1F D5 63 D5
D111: 75 D5 97 D5 A1 D5 D9 D5 10 D6 51 D6 74 D6 A1 D6
D121: 51 56 00 1A 01 20 31 56 01 38 31 B0 41 00 11 F6
D131: 30 38 31 CE 51 0A 31 E2 31 E2 51 BA 51 98 51 D8
D141: 51 C9 31 56 51 80 51 80 51 80 51 80 71 92 51 80
D151: 31 B0 51 89 41 89 00 00 71 5A 71 A0 E5 22 16 2E
D161: 1E 00 32 40 1E B8 D9 20 26 30 00 1C 1E 00 34 16
D171: 38 3C 26 9E E5 3A 34 26 1E 2C 1E 30 1C 9E D3 28
D181: 3E 1E 22 32 00 3C 1E 38 2E 26 30 16 1C B2 CD 34
D191: 2C 16 46 1E 38 80 C6 28 32 3E 1E 3E 38 80 C6 3A
D1A1: 34 26 1E 2C 1E 38 80 C6 28 3E 22 16 1C 32 38 80
D1B1: DF 34 38 1E 3A 3A 00 3A 3C 16 38 BC CD 16 34 34
D1C1: 3E 46 1E 48 00 3A 3E 38 00 3A 3C 16 38 BC D6 3A
D1D1: 3C 16 38 3C 00 1C 38 3E 1E 1A 2A 1E B0 DC 34 3E
D1E1: 2C 3A 16 38 00 3A 3C 16 38 BC F4 34 2C 16 C6 F1
D1F1: 28 32 3E 1E C8 F1 3A 34 26 1E AC EE 28 3E 1E 22
D201: 3E 9E C7 1E 30 3C 1E 38 00 46 32 3E 38 00 26 30
D211: 26 3C 26 16 2C BA B8 3A 40 34 00 1E 30 3C 38 1E
D221: 48 00 40 32 3A 00 26 30 26 3C 26 16 2C 1E BA AC
D231: 22 1E 18 1E 30 00 3A 26 1E 00 26 24 38 1E 00 26
D241: 30 26 3C 26 16 2C 1E 30 00 1E 26 B0 C7 1E 30 3C
D251: 38 1E 00 3A 3E 3A 00 26 30 26 1A 26 16 2C 1E BA
D261: C7 3A 34 26 30 00 2A 30 32 18 00 3C 32 00 1A 24
D271: 16 30 22 9E A6 3C 32 3E 38 30 1E 48 00 2C 1E 00
D281: 18 32 3E 3C 32 30 00 34 32 3E 38 00 1A 24 16 30
D291: 22 1E B8 B5 2A 30 32 34 20 00 1C 38 1E 24 1E 30
D2A1: 00 48 3E 2E 00 42 1E 1A 24 3A 1E 2C B0 AC 22 26
D2B1: 38 1E 00 2C 16 00 34 1E 38 26 2C 2C 16 00 34 16
D2C1: 38 16 00 1A 16 2E 18 26 16 B8 C4 34 38 1E 3A 3A
D2D1: 00 20 26 38 1E 00 3C 32 00 3A 1E 2C 1E 1A BC B2
D2E1: 34 32 3E 3A 3A 1E 48 00 20 1E 3E 00 36 3E 16 30
D2F1: 1C 00 1A 32 38 38 1E 1A 3C 9E B2 20 26 38 1E 00
D301: 1C 38 3E 1E 1A 2A 1E 30 00 42 1E 30 30 00 38 26
D311: 1A 24 3C 26 A2 AC 32 34 38 26 2E 16 00 20 26 38
D321: 1E 00 34 16 38 16 00 3A 1E 2C 1E 1A 1A 26 32 30
D331: 16 B8 BC 24 26 22 24 00 3A 1A 32 38 1E BA 9E 2E
D341: 1E 26 2C 2C 1E 3E 38 3A 00 3A 1A 32 38 1E BA B0
D351: 24 32 1E 1A 24 3A 3C 48 16 24 2C 1E B0 D4 38 1E
D361: 1A 32 38 1C BA C2 38 16 30 2A 26 30 22 00 20 38
D371: 32 2E 00 04 00 3C 32 80 C2 34 2C 16 1A 1E 2E 1E
D381: 30 3C 00 1C 1E 00 04 00 16 80 BC 38 16 30 22 2C
D391: 26 3A 3C 1E 00 40 32 30 00 04 00 48 3E 2E 80 C8
D3A1: 38 16 30 2A 26 30 22 00 1C 1E 00 04 00 16 80 D9
D3B1: 38 16 3C 1E 00 46 32 3E 38 3A 1E 2C A0 DC 1E 40
D3C1: 16 2C 3E 1E 48 4C 40 32 3E BA D6 3A 1E 2C 18 3A
D3D1: 3C 00 38 1E 1A 24 30 1E B0 DF 1A 16 2C 26 20 26
D3E1: 36 3E 1E 3A 9E AA 30 32 40 26 1A 9E AA 30 32 40
D3F1: 26 1A 26 B2 AA 16 30 20 16 1E 30 22 1E B8 4A 1E
D401: 44 34 1E 38 BC 45 1E 44 34 1E 38 3C B2 40 1E 38
D411: 20 16 24 38 1E B0 8B 18 32 30 3E BA E8 3C 26 2E
D421: 9E E0 1C 3E 38 1E 9E E8 48 1E 26 BC E4 3C 26 1E
D431: 2E 34 B2 8B 2C 1E 40 1E AC 8B 30 26 40 1E 16 BE
D441: 8B 22 38 16 9C 8B 30 26 40 1E AC 8B 24 32 2C 9E
D451: 8B 3C 38 32 BE 8B 24 32 46 B2 8B 2C 32 1A A4 DC
D461: 26 30 3A 1E 38 3C 00 1A 32 26 30 BA C1 26 30 3C
D471: 38 32 1C 3E 26 38 1E 00 2C 1E 3A 00 34 26 1E 1A
D481: 1E BA D6 22 1E 2C 1C 00 1E 26 30 42 1E 38 20 1E
D491: B0 D6 26 30 3A 1E 38 3C 1E 00 20 26 1A 24 16 BA
D4A1: 00 20 38 1E 1E 00 34 2C 16 C6 0E 04 00 1A 32 26
D4B1: 30 00 06 00 34 2C 16 46 BA FA 04 00 34 26 1E 1A
D4C1: 1E 00 06 00 28 32 3E 1E 3E 38 BA 00 04 00 2E 3E
D4D1: 1E 30 48 00 06 00 3A 34 26 1E 2C 9E FA 04 00 2E
D4E1: 32 30 1E 1C 16 00 06 00 28 3E 1E 22 32 BA 14 04
D4F1: 00 1A 32 26 30 00 04 00 34 2C 16 C6 00 04 00 34
D501: 26 1E 1A 1E 00 04 00 28 32 3E 1E 3E B8 00 04 00
D511: 2E 3E 1E 30 48 1E 00 04 00 3A 34 26 1E AC 00 04
D521: 00 2E 32 30 1E 1C 16 00 04 00 28 3E 1E 22 B2 0E
D531: 06 00 1A 32 26 30 3A 00 04 00 34 2C 16 C6 FA 06
D541: 00 34 26 1E 1A 1E 3A 00 04 00 28 32 3E 1E 3E B8
D551: FA 06 00 2E 3E 1E 30 48 1E 30 00 04 00 3A 34 26
D561: 1E AC FA 06 00 2E 32 30 1E 1C 16 3A 00 04 00 28
D571: 3E 1E 22 B2 D3 50 00 2E 1A 2E 2C 44 44 44 00 16
D581: 3C 16 38 A6 A0 1A 38 1E 1C 26 3C 3A 80 A0 2A 38
D591: 1E 1C 26 3C 1E 80 A0 1A 38 1E 1C 26 3C 32 3A 80
D5A1: DA 18 32 30 3E 3A 80 D0 06 00 1A 38 1E 1C 26 3C
D5B1: 00 2E 26 30 26 2E 3E AE D6 06 00 28 1E 3E 44 00
D5C1: 2E 26 30 26 2E 3E AE D0 06 00 3A 34 26 1E 2C 1E
D5D1: 00 2E 26 30 26 2E 3E AE D3 06 00 28 3E 1E 22 32
D5E1: 3A 00 2E 26 30 26 2E B2 C8 18 32 30 3E 3A 00 1E
D5F1: 40 1E 38 46 80 CE 18 32 30 3E 3A 00 1A 24 16 36
D601: 3E 1E 80 CE 18 32 30 3E 3A 00 28 1E 1C 1E 80 C8
D611: 18 32 30 3E 3A 00 1A 16 1C 16 80 B8 16 40 32 26
D621: 1C 00 3A 34 26 2A 1E BA 88 16 3C 3C 1E 30 3C 26
D631: 32 30 00 16 3E 44 00 2C 16 30 1A 1E BA 96 3A 34
D641: 26 3C 48 1E 30 00 16 3E 3A 42 1E 26 1A 24 1E B0
D651: A0 1E 40 26 3C 1E 00 2C 16 3A 00 34 3E 30 3C 16
D661: BA E0 2C 1E 40 1E AC DA 30 26 40 1E 16 BE E2 22
D671: 38 16 9C E0 30 26 40 1E AC C4 3A 3E 34 1E 38 48
D681: 16 34 34 1E 38 00 38 1E 1A 24 16 38 22 9E CD 30
D691: 1E 3E 1E 38 00 3A 3E 34 1E 38 48 16 34 34 1E B8
D6A1: CD 30 3E 1E 40 32 00 3A 3E 34 1E 38 48 16 34 34
D6B1: 1E B8 31 D0 6D D0 A9 D0 E5 D0

; decode the option (DIP) switch ports into game config: read port loc_e00
; into loc_a, index $D6F7 by bits 5-3 into loc_156, $D6FF by bits 7-6 into
; loc_158, and the a0&0x06 field into $D6B3/$D6B4 -> loc_ac/loc_ad; store
; the other port loc_d00 (bit1 toggled) into loc_9, and fold loc_ad
; through assemblePotStatusByte recording the merge in loc_16a.
decodeOptionSwitches:
D6BB: AD 00 0E        LDA     dsw2                ; read the options DIP-switch port
D6BE: 85 0A           STA     workRam+A           ; snapshot the options byte
D6C0: 29 38           AND     #$38                ; bits 5-3 select the bonus-life interval
D6C2: 4A              LSR     A                   
D6C3: 4A              LSR     A                   
D6C4: 4A              LSR     A                   
D6C5: AA              TAX                         
D6C6: BD F7 D6        LDA     rom+46F7,X          ; read the bonus-interval table
D6C9: 8D 56 01        STA     workRam+156         ; store the live bonus-life interval
D6CC: AD 00 0D        LDA     dsw1                ; read the coinage DIP-switch port
D6CF: 49 02           EOR     #$02                ; toggle bit 1
D6D1: 85 09           STA     workRam+9           ; store the coinage snapshot
D6D3: A5 0A           LDA     workRam+A           ; bits 7-6 select the bonus config
D6D5: 2A              ROL     A                   
D6D6: 2A              ROL     A                   
D6D7: 2A              ROL     A                   
D6D8: 29 03           AND     #$03                
D6DA: AA              TAX                         
D6DB: BD FF D6        LDA     rom+46FF,X          ; read the bonus-config table
D6DE: 8D 58 01        STA     workRam+158         ; store the bonus config
D6E1: A5 0A           LDA     workRam+A           ; take the two-bit difficulty field
D6E3: 29 06           AND     #$06                
D6E5: A8              TAY                         
D6E6: B9 B3 D6        LDA     rom+46B3,Y          ; read paired config entry A
D6E9: 85 AC           STA     workRam+AC          ; store config A
D6EB: B9 B4 D6        LDA     rom+46B4,Y          ; read paired config entry B
D6EE: 85 AD           STA     workRam+AD          ; store config B
D6F0: 20 E0 DB        JSR     assemblePotStatusByte; fold config B through the pot/status merge
D6F3: 8D 6A 01        STA     workRam+16A         ; store the merged difficulty config
D6F6: 60              RTS                         

; ---- $D6F7-$D703: data ----
D6F7: 02 01 03 04 05 06 07 00 03 04 05 02 7C

serviceHeartbeatInterrupt:
D704: 48              PHA                         
D705: 8A              TXA                         
D706: 48              PHA                         
D707: 98              TYA                         
D708: 48              PHA                         
D709: D8              CLD                         
D70A: BA              TSX                         ; read the stack pointer for the depth guard
D70B: E0 D0           CPX     #$D0                ; stack too shallow
D70D: 90 04           BCC     loc_d713            ; 
D70F: A5 53           LDA     workRam+53          ; read the heartbeat counter for the sign guard
D711: 10 04           BPL     loc_d717            ; heartbeat still positive -- proceed

loc_d713:
D713: 00              BRK                         ; guard tripped -- force a reset
D714: 4C 3F D9        JMP     bootMachineFromReset; divert to the power-on reset

loc_d717:
D717: 8D 00 50        STA     wdclr / irqAck      ; kick the watchdog and acknowledge the interrupt
D71A: 8D CB 60        STA     pokey1+B            ; pulse the first chip's pot-scan start
D71D: AD C8 60        LDA     pokey1+8            ; read the spinner pot
D720: 49 0F           EOR     #$0F                ; invert the pot reading
D722: A8              TAY                         
D723: 29 10           AND     #$10                ; stash the pot's bit-4 flag
D725: 8D 17 01        STA     workRam+117         ; 
D728: 98              TYA                         
D729: 38              SEC                         
D72A: E5 52           SBC     workRam+52          ; form the delta versus the previous pot reading
D72C: 29 0F           AND     #$0F                ; keep the low nibble
D72E: C9 08           CMP     #$08                ; is the delta's sign bit set
D730: 90 02           BCC     loc_d734            ; 
D732: 09 F0           ORA     #$F0                ; sign-extend the 4-bit delta

loc_d734:
D734: 18              CLC                         
D735: 65 50           ADC     workRam+50          ; accumulate the delta into the spinner accumulator
D737: 85 50           STA     workRam+50          ; store the spinner accumulator
D739: 84 52           STY     workRam+52          ; save this frame's pot reading
D73B: 8D DB 60        STA     pokey2+B            ; mirror the accumulator to the second chip's pot register
D73E: AC D8 60        LDY     pokey2+8            ; read the coin/switch input
D741: AD 00 0C        LDA     in0                 ; latch the raw input port
D744: 85 08           STA     workRam+8           ; store the input latch
D746: A5 4C           LDA     workRam+4C          ; last frame's inputs
D748: 84 4C           STY     workRam+4C          ; latch this frame's coin/switch inputs
D74A: A8              TAY                         
D74B: 25 4C           AND     workRam+4C          ; 
D74D: 05 4D           ORA     workRam+4D          ; 
D74F: 85 4D           STA     workRam+4D          ; update the debounced-input cell
D751: 98              TYA                         
D752: 05 4C           ORA     workRam+4C          ; 
D754: 25 4D           AND     workRam+4D          ; 
D756: 85 4D           STA     workRam+4D          ; settle the debounced inputs
D758: A8              TAY                         
D759: 45 4F           EOR     workRam+4F          ; compare against the previous held state for edges
D75B: 25 4D           AND     workRam+4D          ; 
D75D: 05 4E           ORA     workRam+4E          ; 
D75F: 85 4E           STA     workRam+4E          ; record the newly-pressed edge flags
D761: 84 4F           STY     workRam+4F          ; save the held state
D763: A5 B4           LDA     workRam+B4          ; base the output flags on the vector scale
D765: A4 13           LDY     workRam+13          ; lane-0 status counter
D767: 10 02           BPL     loc_d76b            ; 
D769: 09 04           ORA     #$04                ; set a bit for an active lane-0 counter

loc_d76b:
D76B: A4 14           LDY     workRam+14          ; 
D76D: 10 02           BPL     loc_d771            ; 
D76F: 09 02           ORA     #$02                ; set a bit for an active lane-1 counter

loc_d771:
D771: A4 15           LDY     workRam+15          ; 
D773: 10 02           BPL     loc_d777            ; 
D775: 09 01           ORA     #$01                ; set a bit for an active lane-2 counter

loc_d777:
D777: 8D 00 40        STA     coin                ; write the coin-counter / screen-flip output latch
D77A: A6 3E           LDX     workRam+3E          ; 
D77C: E8              INX                         
D77D: A4 05           LDY     workRam+5           ; status flags set -- use the phase index + 1
D77F: D0 10           BNE     loc_d791            ; 
D781: A2 00           LDX     #$00                
D783: A4 07           LDY     workRam+7           ; idle: sub-timer still low -- index 0
D785: C0 40           CPY     #$40                
D787: 90 08           BCC     loc_d791            ; 
D789: A6 06           LDX     workRam+6           ; else the phase counter (0 or 1)
D78B: E0 02           CPX     #$02                
D78D: 90 02           BCC     loc_d791            ; 
D78F: A2 03           LDX     #$03                ; else a fixed index 3

loc_d791:
D791: BD DD D7        LDA     rom+47DD,X          ; read the vector-generator state-code table
D794: 45 A1           EOR     workRam+A1          ; fold its low two bits into the draw-mode flag
D796: 29 03           AND     #$03                
D798: 45 A1           EOR     workRam+A1          ; 
D79A: 85 A1           STA     workRam+A1          ; 
D79C: 8D E0 60        STA     led                 ; mirror the draw mode to the LED / screen-flip latch
D79F: 20 24 CF        JSR     tickHeartbeatCounters; run the timebase lane engine
D7A2: 20 0A CD        JSR     stepSoundVoices     ; run the per-frame sound engine
D7A5: E6 53           INC     workRam+53          ; advance the heartbeat counter
D7A7: E6 07           INC     workRam+7           ; advance the interrupt sub-timer
D7A9: D0 1E           BNE     loc_d7c9            ; only on the sub-timer wrap, run the timer cascades
D7AB: EE 06 04        INC     workRam+406         ; carry into timer-1 low byte
D7AE: D0 08           BNE     loc_d7b8            ; 
D7B0: EE 07 04        INC     workRam+407         ; carry into timer-1 mid byte
D7B3: D0 03           BNE     loc_d7b8            ; 
D7B5: EE 08 04        INC     workRam+408         ; carry into timer-1 high byte

loc_d7b8:
D7B8: 24 05           BIT     workRam+5           ; the second cascade is gated by a status flag
D7BA: 50 0D           BVC     loc_d7c9            ; 
D7BC: EE 09 04        INC     workRam+409         ; carry into timer-2 low byte
D7BF: D0 08           BNE     loc_d7c9            ; 
D7C1: EE 0A 04        INC     workRam+40A         ; carry into timer-2 mid byte
D7C4: D0 03           BNE     loc_d7c9            ; 
D7C6: EE 0B 04        INC     workRam+40B         ; carry into timer-2 high byte

loc_d7c9:
D7C9: 2C 00 0C        BIT     in0                 ; is the vector-generator-done input asserted
D7CC: 50 09           BVC     loc_d7d7            ; 
D7CE: EE 33 01        INC     workRam+133         ; bump the redraw counter
D7D1: 8D 00 58        STA     avgReset            ; strobe the vector-generator reset
D7D4: 8D 00 48        STA     avgGo               ; strobe the vector-generator go -- launch the redraw

loc_d7d7:
D7D7: 68              PLA                         
D7D8: A8              TAY                         
D7D9: 68              PLA                         
D7DA: AA              TAX                         
D7DB: 68              PLA                         
D7DC: 40              RTI                         

; ---- $D7DD-$D7E0: data ----
D7DD: FF FD FE FC

; arm mode bytes loc_5=0x00 and loc_1=0x02, then rebuild only while idle
; and enabled: bail if loc_1ca is nonzero (busy) or bit4 of option port
; loc_c00 is clear (disabled), write loc_0=0x00, and run
; rebuildControlBlocksFromTemplate only when loc_1c9's low two bits show a
; pending request.
armModeAndRebuildIfEnabled:
D7E1: A9 00           LDA     #$00                
D7E3: 85 05           STA     workRam+5           ; clear the status/flags byte
D7E5: A9 02           LDA     #$02                
D7E7: 85 01           STA     workRam+1           ; arm the dispatch selector to 2
D7E9: AD CA 01        LDA     workRam+1CA         ; high-score store busy
D7EC: D0 15           BNE     loc_d803            ; busy -- bail
D7EE: AD 00 0C        LDA     in0                 ; read the feature-enable input
D7F1: 29 10           AND     #$10                
D7F3: F0 0E           BEQ     loc_d803            ; feature disabled -- bail
D7F5: A9 00           LDA     #$00                
D7F7: 85 00           STA     workRam             ; set idle game mode
D7F9: AD C9 01        LDA     workRam+1C9         ; check the pending-work bits
D7FC: 29 03           AND     #$03                
D7FE: F0 03           BEQ     loc_d803            ; nothing queued -- bail
D800: 20 AC AB        JSR     rebuildControlBlocksFromTemplate; rebuild the control blocks from their template

loc_d803:
D803: 60              RTS                         

; a trampoline target that assembles the frame's vector item list into
; display RAM: four setup passes, a header pair, a marker emitted 0x158
; times (dec-counted via 0x37), then table-indexed coordinate records
; selected by state cells 0x16a/0x200/0x4d and the 0xd8b6/0xd8ba/0xd8c2
; tables, with a mask-table gated branch
; (eraseEaromLowRegions/queueEaromRegionErase).
buildVectorItemList:
D804: 20 BB D6        JSR     decodeOptionSwitches; read the option DIP switches
D807: 20 A8 AA        JSR     drawOverlayFrame    ; draw the overlay frame
D80A: 20 0D DD        JSR     buildPotReadoutVectorList; build the pot readout list
D80D: 20 41 DD        JSR     buildLargeDecimalNumber; build the large decimal number
D810: AD 58 01        LDA     workRam+158         ; marker repeat count from the bonus config
D813: 85 37           STA     workRam+37          ; 
D815: 20 53 DF        JSR     emitVectorHeaderWord; emit the vector-list header word
D818: A9 E8           LDA     #$E8                
D81A: A2 C0           LDX     #$C0                
D81C: 20 75 DF        JSR     emitScaledCoordinateRecord; emit a scaled coordinate record

loc_d81f:
D81F: A9 32           LDA     #$32                
D821: A2 6C           LDX     #$6C                
D823: 20 39 DF        JSR     emitCoordinateVectorWord; emit a marker coordinate word
D826: C6 37           DEC     workRam+37          ; decrement the marker count
D828: D0 F5           BNE     loc_d81f            ; repeat the marker that many times
D82A: AD 6A 01        LDA     workRam+16A         ; difficulty (0..3) doubled into a table index
D82D: 29 03           AND     #$03                
D82F: 0A              ASL     A                   
D830: A8              TAY                         
D831: B9 1F 3F        LDA     vectorRom+F1F,Y     ; read the difficulty coordinate pair (high byte)
D834: BE 1E 3F        LDX     vectorRom+F1E,Y     ; read the difficulty coordinate pair (low byte)
D837: 20 39 DF        JSR     emitCoordinateVectorWord; emit the difficulty coordinate word
D83A: AD 00 02        LDA     workRam+200         ; read the player's rim segment
D83D: 20 CE AD        JSR     foldStepIntoFraction; fold it through the step-into-fraction helper
D840: 8D 00 02        STA     workRam+200         ; write the folded segment back
D843: 29 06           AND     #$06                ; segment-selected table index
D845: 48              PHA                         
D846: A8              TAY                         
D847: B9 17 3F        LDA     vectorRom+F17,Y     ; read the segment coordinate pair (high byte)
D84A: BE 16 3F        LDX     vectorRom+F16,Y     ; read the segment coordinate pair (low byte)
D84D: 20 39 DF        JSR     emitCoordinateVectorWord; emit the segment coordinate word
D850: 68              PLA                         
D851: 4A              LSR     A                   
D852: AA              TAX                         
D853: A5 4D           LDA     workRam+4D          ; load the debounced inputs
D855: 3D B6 D8        AND     rom+48B6,X          ; mask them with the diagnostic mask-table entry
D858: DD B6 D8        CMP     rom+48B6,X          ; compare against the mask
D85B: D0 1A           BNE     loc_d877            ; not every mask bit present -- skip the erase work
D85D: CA              DEX                         
D85E: CA              DEX                         
D85F: 10 03           BPL     loc_d864            ; 
D861: 4C 3F D9        JMP     bootMachineFromReset; slot underflow -- force a full reset

loc_d864:
D864: D0 06           BNE     loc_d86c            ; 
D866: 20 E9 DD        JSR     queueEaromRegionErase; erase the low high-score-store regions
D869: B8              CLV                         
D86A: 50 0B           BVC     loc_d877            ; 

loc_d86c:
D86C: 20 ED DD        JSR     eraseEaromLowRegions; queue a single high-score-store region erase
D86F: AD C9 01        LDA     workRam+1C9         ; 
D872: 09 03           ORA     #$03                ; arm the pending erase-work bits
D874: 8D C9 01        STA     workRam+1C9         ; store the pending-work byte

loc_d877:
D877: AD CA 01        LDA     workRam+1CA         ; read the high-score-store mode flag
D87A: 2D C6 01        AND     workRam+1C6         ; and it with the blank flag
D87D: F0 07           BEQ     loc_d886            ; flags disagree -- skip the extra word
D87F: A9 34           LDA     #$34                ; emit an extra coordinate word (high byte)
D881: A2 6E           LDX     #$6E                
D883: 20 39 DF        JSR     emitCoordinateVectorWord; 

loc_d886:
D886: 20 53 DF        JSR     emitVectorHeaderWord; emit the second header word
D889: A5 09           LDA     workRam+9           ; coinage bits 2-4 select the low diagnostic digit
D88B: 29 1C           AND     #$1C                
D88D: 4A              LSR     A                   
D88E: 4A              LSR     A                   
D88F: AA              TAX                         
D890: BD BA D8        LDA     rom+48BA,X          ; read the low digit value
D893: A0 EE           LDY     #$EE                
D895: A2 1B           LDX     #$1B                
D897: 20 A9 D8        JSR     emitScaledByteDigit ; draw it as a scaled digit
D89A: A5 09           LDA     workRam+9           ; coinage top three bits select the high digit
D89C: 4A              LSR     A                   
D89D: 4A              LSR     A                   
D89E: 4A              LSR     A                   
D89F: 4A              LSR     A                   
D8A0: 4A              LSR     A                   
D8A1: AA              TAX                         
D8A2: BD C2 D8        LDA     rom+48C2,X          ; read the high digit value
D8A5: A0 32           LDY     #$32                
D8A7: A2 F8           LDX     #$F8                

; Stashes A into loc_29, scales the two coordinates (y, x), and emits that
; one stashed byte as a single-entry digit run.
emitScaledByteDigit:
D8A9: 85 29           STA     workRam+29          ; stash the byte to draw
D8AB: 98              TYA                         
D8AC: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the scaled coordinate record for the anchor
D8AF: A9 29           LDA     #$29                ; point at the stashed byte
D8B1: A0 01           LDY     #$01                ; one digit
D8B3: 4C B1 DF        JMP     emitNibbleDigitRun  ; lay the byte down as a single-digit glyph run

; ---- $D8B6-$D8C9: data ----
D8B6: 18 18 30 50 11 14 15 16 21 24 25 26 00 12 14 24
D8C6: 15 13 00 00

; power-on tone-and-delay entry: store the passed byte at loc_79 then
; drive POKEY chip-0 through a descending run of tone bursts (writing
; $60C1/$60C0/$60E0 and strobing output latch $5000 while draining fixed
; counts), before tail-delegating to the checksum/self-test
; checksumRomAndSettleEntropy.
playPowerOnTone:
D8CA: A8              TAY                         ; hold the passed byte as the burst count
D8CB: A9 00           LDA     #$00                ; seed the pass count to 0

; second entry of the power-on tone driver (same file as playPowerOnTone):
; store count at loc_79, derive the pass total from a ((a>>2)<<1, +1
; when count's low nibble is zero), then loop the descending POKEY tone
; bursts ($60C1/$60C0/$60E0, watchdog strobe $5000) switching to the low
; tone on the last pass, and tail-delegate to checksumRomAndSettleEntropy.
runPowerOnToneBursts:
D8CD: 84 79           STY     workRam+79          ; store the burst count
D8CF: 4A              LSR     A                   
D8D0: 4A              LSR     A                   
D8D1: 0A              ASL     A                   
D8D2: AA              TAX                         
D8D3: 98              TYA                         
D8D4: 29 0F           AND     #$0F                ; one extra pass when the count's low nibble is 0
D8D6: D0 01           BNE     loc_d8d9            ; 
D8D8: E8              INX                         

loc_d8d9:
D8D9: 9A              TXS                         

loc_d8da:
D8DA: A9 A2           LDA     #$A2                
D8DC: 8D C1 60        STA     pokey1+1            ; set voice-1 control -- tone on
D8DF: BA              TSX                         
D8E0: D0 07           BNE     loc_d8e9            ; is this the final pass
D8E2: A9 60           LDA     #$60                ; final pass: low tone, nine drains
D8E4: A0 09           LDY     #$09                
D8E6: B8              CLV                         
D8E7: 50 04           BVC     loc_d8ed            ; 

loc_d8e9:
D8E9: A9 C0           LDA     #$C0                ; normal pass: high tone, one drain
D8EB: A0 01           LDY     #$01                

loc_d8ed:
D8ED: 8D C0 60        STA     pokey1              ; write voice-1 frequency -- the tone
D8F0: A9 03           LDA     #$03                
D8F2: 8D E0 60        STA     led                 ; strobe the LED / screen-flip latch
D8F5: A2 00           LDX     #$00                

loc_d8f7:
D8F7: 2C 00 0C        BIT     in0                 ; sync to the 3kHz line
D8FA: 30 FB           BMI     loc_d8f7            ; 

loc_d8fc:
D8FC: 2C 00 0C        BIT     in0                 ; 
D8FF: 10 FB           BPL     loc_d8fc            ; 
D901: 8D 00 50        STA     wdclr / irqAck      ; kick the watchdog through the tone drain
D904: CA              DEX                         
D905: D0 F0           BNE     loc_d8f7            ; 
D907: 88              DEY                         
D908: D0 ED           BNE     loc_d8f7            ; 
D90A: 8E C1 60        STX     pokey1+1            ; silence voice-1
D90D: A9 00           LDA     #$00                
D90F: 8D E0 60        STA     led                 ; clear the LED / screen-flip latch
D912: A0 09           LDY     #$09                

loc_d914:
D914: 2C 00 0C        BIT     in0                 ; 
D917: 30 FB           BMI     loc_d914            ; 

loc_d919:
D919: 2C 00 0C        BIT     in0                 ; 
D91C: 10 FB           BPL     loc_d919            ; 
D91E: 8D 00 50        STA     wdclr / irqAck      ; kick the watchdog through the silent gap
D921: CA              DEX                         
D922: D0 F0           BNE     loc_d914            ; 
D924: 88              DEY                         
D925: D0 ED           BNE     loc_d914            ; 
D927: BA              TSX                         
D928: CA              DEX                         
D929: 9A              TXS                         
D92A: 10 AE           BPL     loc_d8da            ; loop until the pass counter underflows
D92C: 4C 0A DA        JMP     checksumRomAndSettleEntropy; continue into the ROM checksum

; fold one table byte (read through the zero-page pointer at loc_0,
; indexed by the incoming cursor y) into the running byte a by XOR, then
; continue into the tone-burst count path seedToneBurstCount with that
; result.
foldToneTableByte:
D92F: 51 00           EOR     (workRam),Y         ; fold one table byte into the running tone byte

; carry the incoming byte through as the tone-burst count and derive the
; pass-seed from loc_1 (values >=0x20 fold down by 0x18, then masked to
; five bits), handing both to the power-on tone burst
; runPowerOnToneBursts.
seedToneBurstCount:
D931: A8              TAY                         ; carry the folded byte as the burst count
D932: A5 01           LDA     workRam+1           ; read the dispatch selector as the pass seed
D934: C9 20           CMP     #$20                ; is the seed 0x20 or more
D936: 90 02           BCC     loc_d93a            ; 
D938: E9 18           SBC     #$18                ; fold high seed values back into range

loc_d93a:
D93A: 29 1F           AND     #$1F                ; keep the low five bits
D93C: 4C CD D8        JMP     runPowerOnToneBursts; drive the power-on tone bursts

; power-on RESET entry (a generator): wipe the two mapped RAM windows (2KB
; from 0x00, 4KB from 0x2000), seed the control block (0x1=0, 0x60e0=0,
; 0x60cf=7, 0x60df=7, and the 0x60c0/0x60d0 runs zeroed), then fork on
; self-test switch 0xc00 bit4 -- released seeds 0xb4=0x10, runs the
; device-init chain and becomes the main loop; held spins the operator
; diagnostic.
bootMachineFromReset:
D93F: 78              SEI                         ; mask interrupts at the reset entry
D940: 8D 00 50        STA     wdclr / irqAck      ; kick the watchdog
D943: 8D 00 58        STA     avgReset            ; strobe the vector-generator reset
D946: A2 FF           LDX     #$FF                
D948: 9A              TXS                         ; seat the stack
D949: D8              CLD                         
D94A: E8              INX                         
D94B: 8A              TXA                         
D94C: A8              TAY                         

loc_d94d:
D94D: 84 00           STY     workRam             ; set up a rolling zero-page pointer for the RAM wipe
D94F: 86 01           STX     workRam+1           ; (pointer high byte)
D951: A0 00           LDY     #$00                

loc_d953:
D953: 91 00           STA     (workRam),Y         ; zero a work-RAM byte
D955: C8              INY                         
D956: D0 FB           BNE     loc_d953            ; wipe the whole page
D958: E8              INX                         
D959: E0 08           CPX     #$08                ; reached the unmapped gap
D95B: D0 02           BNE     loc_d95f            ; 
D95D: A2 20           LDX     #$20                ; resume at the 0x2000 vector-RAM window

loc_d95f:
D95F: E0 30           CPX     #$30                ; wipe through 0x2fff
D961: 8D 00 50        STA     wdclr / irqAck      ; kick the watchdog during the wipe
D964: 90 E7           BCC     loc_d94d            ; continue the wipe
D966: 85 01           STA     workRam+1           ; 
D968: 8D E0 60        STA     led                 ; clear the LED / screen-flip latch
D96B: 8D CF 60        STA     pokey1+F            ; hold the first sound chip in reset
D96E: 8D DF 60        STA     pokey2+F            ; hold the second sound chip in reset
D971: A2 07           LDX     #$07                
D973: 8E CF 60        STX     pokey1+F            ; release the first sound chip (control latch 7)
D976: 8E DF 60        STX     pokey2+F            ; release the second sound chip
D979: E8              INX                         

loc_d97a:
D97A: 9D C0 60        STA     pokey1,X            ; silence a first-chip audio register
D97D: 9D D0 60        STA     pokey2,X            ; silence a second-chip audio register
D980: CA              DEX                         
D981: 10 F7           BPL     loc_d97a            ; 
D983: AD 00 0C        LDA     in0                 ; read the self-test switch
D986: 29 10           AND     #$10                
D988: F0 1F           BEQ     loc_d9a9            ; switch held -- run the RAM diagnostic

loc_d98a:
D98A: 8D 00 50        STA     wdclr / irqAck      ; normal boot: kick the watchdog through a settle delay
D98D: CE 00 01        DEC     workRam+100         ; settle-delay countdown low byte
D990: D0 F8           BNE     loc_d98a            ; 
D992: CE 01 01        DEC     workRam+101         ; settle-delay countdown high byte
D995: D0 F3           BNE     loc_d98a            ; 
D997: A9 10           LDA     #$10                
D999: 85 B4           STA     workRam+B4          ; set the vector-generator scale
D99B: 20 11 DE        JSR     armEaromReadback    ; arm the high-score-store readback
D99E: 20 AC AB        JSR     rebuildControlBlocksFromTemplate; rebuild the control blocks from their template
D9A1: 20 6E C1        JSR     buildLevelLayout    ; build the level layout
D9A4: 58              CLI                         ; enable interrupts
D9A5: 4C A0 C7        JMP     runMainFrameLoop    ; enter the main frame loop

; ---- $D9A8-$D9A8: data ----
D9A8: A0

loc_d9a9:
D9A9: A2 11           LDX     #$11                

loc_d9ab:
D9AB: 9A              TXS                         
D9AC: A0 00           LDY     #$00                

loc_d9ae:
D9AE: BA              TSX                         
D9AF: 96 00           STX     workRam,Y           ; RAM diagnostic: write a walking pattern to a cell
D9B1: A2 01           LDX     #$01                

loc_d9b3:
D9B3: C8              INY                         
D9B4: B9 00 00        LDA     workRam,Y           ; read the cell back
D9B7: F0 03           BEQ     loc_d9bc            ; 

loc_d9b9:
D9B9: 4C CA D8        JMP     playPowerOnTone     ; verify failed -- sound the error tone

loc_d9bc:
D9BC: E8              INX                         
D9BD: D0 F4           BNE     loc_d9b3            ; 
D9BF: BA              TSX                         
D9C0: 8A              TXA                         
D9C1: 8D 00 50        STA     wdclr / irqAck      ; kick the watchdog during the test
D9C4: C8              INY                         
D9C5: 59 00 00        EOR     workRam,Y           ; check the read-back byte against the pattern
D9C8: D0 EF           BNE     loc_d9b9            ; mismatch -- error tone
D9CA: 99 00 00        STA     workRam,Y           ; advance the pattern
D9CD: C8              INY                         
D9CE: D0 DE           BNE     loc_d9ae            ; 
D9D0: BA              TSX                         
D9D1: 8A              TXA                         
D9D2: 0A              ASL     A                   
D9D3: AA              TAX                         
D9D4: 90 D5           BCC     loc_d9ab            ; 
D9D6: A0 00           LDY     #$00                
D9D8: A2 01           LDX     #$01                

loc_d9da:
D9DA: 84 00           STY     workRam             ; 
D9DC: 86 01           STX     workRam+1           ; 
D9DE: A0 00           LDY     #$00                

loc_d9e0:
D9E0: B1 00           LDA     (workRam),Y         ; second pass: read a byte, expect zero
D9E2: F0 03           BEQ     loc_d9e7            ; 
D9E4: 4C 31 D9        JMP     seedToneBurstCount  ; nonzero -- error tone

loc_d9e7:
D9E7: A9 11           LDA     #$11                

loc_d9e9:
D9E9: 91 00           STA     (workRam),Y         ; write a walking-ones pattern
D9EB: D1 00           CMP     (workRam),Y         ; read it back
D9ED: F0 03           BEQ     loc_d9f2            ; 
D9EF: 4C 2F D9        JMP     foldToneTableByte   ; mismatch -- error tone

loc_d9f2:
D9F2: 0A              ASL     A                   
D9F3: 90 F4           BCC     loc_d9e9            ; 
D9F5: A9 00           LDA     #$00                
D9F7: 91 00           STA     (workRam),Y         ; clear the cell again
D9F9: C8              INY                         
D9FA: D0 E4           BNE     loc_d9e0            ; 
D9FC: 8D 00 50        STA     wdclr / irqAck      ; kick the watchdog
D9FF: E8              INX                         
DA00: E0 08           CPX     #$08                ; reached the unmapped gap
DA02: D0 02           BNE     loc_da06            ; 
DA04: A2 20           LDX     #$20                ; resume at the 0x2000 window

loc_da06:
DA06: E0 30           CPX     #$30                
DA08: 90 D0           BCC     loc_d9da            ; cover through 0x2fff

; power-on ROM checksum + entropy settle: walk 12 banks of 8 pages, XOR
; every byte into a per-bank checksum seeded with the bank index (strobing
; watchdog 0x5000 per page), land the 12 checksums from 0x7d; if bank 0's
; checksum is nonzero arm the error tone (0x60c4/0x60c5); then settle each
; entropy register (0x60ca->0x7a, 0x60da->0x7b) storing only when six re-
; reads match, and continue into runSelfTestLoop.
checksumRomAndSettleEntropy:
DA0A: A9 00           LDA     #$00                
DA0C: A8              TAY                         
DA0D: AA              TAX                         
DA0E: 85 3B           STA     workRam+3B          ; set the ROM-scan pointer low byte
DA10: A9 30           LDA     #$30                
DA12: 85 3C           STA     workRam+3C          ; pointer high byte -- first ROM bank

loc_da14:
DA14: A9 08           LDA     #$08                
DA16: 85 38           STA     workRam+38          ; eight pages per bank
DA18: 8A              TXA                         ; seed this bank's checksum with the bank index

loc_da19:
DA19: 51 3B           EOR     (workRam+3B),Y      ; fold each ROM byte into the checksum
DA1B: C8              INY                         
DA1C: D0 FB           BNE     loc_da19            ; 
DA1E: E6 3C           INC     workRam+3C          ; advance to the next page
DA20: 8D 00 50        STA     wdclr / irqAck      ; kick the watchdog once per page
DA23: C6 38           DEC     workRam+38          ; eight pages per bank
DA25: D0 F2           BNE     loc_da19            ; 
DA27: 95 7D           STA     workRam+7D,X        ; store this bank's checksum
DA29: E8              INX                         ; next bank
DA2A: E0 02           CPX     #$02                
DA2C: D0 04           BNE     loc_da32            ; 
DA2E: A9 90           LDA     #$90                ; after two banks, jump to the high ROM window
DA30: 85 3C           STA     workRam+3C          ; 

loc_da32:
DA32: E0 0C           CPX     #$0C                ; twelve banks total
DA34: 90 DE           BCC     loc_da14            ; 
DA36: A5 7D           LDA     workRam+7D          ; bank-0 checksum nonzero -- a bad ROM
DA38: F0 0A           BEQ     loc_da44            ; 
DA3A: A9 40           LDA     #$40                ; arm the error tone -- voice-3 frequency
DA3C: A2 A4           LDX     #$A4                
DA3E: 8D C4 60        STA     pokey1+4            ; 
DA41: 8E C5 60        STX     pokey1+5            ; error-tone voice-3 control

loc_da44:
DA44: A2 05           LDX     #$05                
DA46: AD CA 60        LDA     pokey1+A            ; sample the first chip's random register

loc_da49:
DA49: CD CA 60        CMP     pokey1+A            ; re-read until it reads stable
DA4C: D0 05           BNE     loc_da53            ; 
DA4E: CA              DEX                         
DA4F: 10 F8           BPL     loc_da49            ; 
DA51: 85 7A           STA     workRam+7A          ; store the settled entropy sample

loc_da53:
DA53: A2 05           LDX     #$05                
DA55: AD DA 60        LDA     pokey2+A            ; sample the second chip's random register

loc_da58:
DA58: CD DA 60        CMP     pokey2+A            ; re-read until stable
DA5B: D0 05           BNE     runSelfTestLoop     ; 
DA5D: CA              DEX                         
DA5E: 10 F8           BPL     loc_da58            ; 
DA60: 85 7B           STA     workRam+7B          ; store the second entropy sample

; run the self-test session: seed the state machine (armEaromReadback),
; forward a pending request (queueEaromEraseAllRegions), copy the 8-byte
; colour table $DAF9 into colour RAM at 0x800, idle coin/flip 0x4000; then
; each pass strobe watchdog 0x5000/display-reset 0x5800, sample option
; switches 0x60c8 and diagnostics 0xc00, build/show a frame via
; dispatchDrawHandler/emitHeaderedBodyRecord, call stepEaromTransfer every
; fourth frame, until the self-test switch (0xc00 bit4) is released.
runSelfTestLoop:
DA62: 20 11 DE        JSR     armEaromReadback    ; arm the high-score-store readback
DA65: A0 02           LDY     #$02                
DA67: AD C9 01        LDA     workRam+1C9         ; branch on the pending-work byte
DA6A: F0 0A           BEQ     loc_da76            ; 
DA6C: 85 7C           STA     workRam+7C          ; stash the pending-work value
DA6E: 20 F1 DD        JSR     queueEaromEraseAllRegions; queue an erase of every high-score-store region
DA71: A0 00           LDY     #$00                
DA73: 8C C9 01        STY     workRam+1C9         ; clear the pending-work byte

loc_da76:
DA76: 84 00           STY     workRam             ; set the diagnostic mode state
DA78: A2 07           LDX     #$07                

loc_da7a:
DA7A: BD F9 DA        LDA     rom+4AF9,X          ; copy the self-test colour table
DA7D: 9D 00 08        STA     colorRam,X          ; into colour RAM
DA80: CA              DEX                         
DA81: 10 F7           BPL     loc_da7a            ; 
DA83: A9 00           LDA     #$00                
DA85: 8D E0 60        STA     led                 ; blank the LED / screen-flip latch
DA88: A9 10           LDA     #$10                
DA8A: 8D 00 40        STA     coin                ; idle the coin-counter / flip output latch

loc_da8d:
DA8D: A0 04           LDY     #$04                

loc_da8f:
DA8F: A2 14           LDX     #$14                

loc_da91:
DA91: 2C 00 0C        BIT     in0                 ; busy-wait on the video sync line
DA94: 10 FB           BPL     loc_da91            ; 

loc_da96:
DA96: 2C 00 0C        BIT     in0                 ; 
DA99: 30 FB           BMI     loc_da96            ; 
DA9B: CA              DEX                         
DA9C: 10 F3           BPL     loc_da91            ; 
DA9E: 88              DEY                         
DA9F: 30 08           BMI     loc_daa9            ; 
DAA1: 8D 00 50        STA     wdclr / irqAck      ; kick the watchdog
DAA4: 2C 00 0C        BIT     in0                 ; 
DAA7: 50 E6           BVC     loc_da8f            ; loop on the vector-generator-done line

loc_daa9:
DAA9: 8D 00 58        STA     avgReset            ; strobe the vector-generator reset
DAAC: A9 00           LDA     #$00                
DAAE: 85 74           STA     workRam+74          ; reset the draw cursor low byte
DAB0: A9 20           LDA     #$20                
DAB2: 85 75           STA     workRam+75          ; draw cursor high byte -- cursor at 0x2000
DAB4: 8D CB 60        STA     pokey1+B            ; pulse the first chip's pot scan
DAB7: AD C8 60        LDA     pokey1+8            ; read the spinner pot
DABA: 85 52           STA     workRam+52          ; save the pot reading as the previous value
DABC: 29 0F           AND     #$0F                ; keep the low nibble
DABE: 85 50           STA     workRam+50          ; store it
DAC0: AD 00 0C        LDA     in0                 ; read and invert the input port
DAC3: 49 FF           EOR     #$FF                
DAC5: 29 2F           AND     #$2F                ; mask the input edges
DAC7: 85 4E           STA     workRam+4E          ; store the edge flags
DAC9: 29 28           AND     #$28                ; either diagnostic-select bit set
DACB: F0 0B           BEQ     loc_dad8            ; 
DACD: 06 4C           ASL     workRam+4C          ; shift the diagnostic-select state
DACF: 90 04           BCC     loc_dad5            ; 
DAD1: E6 00           INC     workRam             ; advance the diagnostic page by two
DAD3: E6 00           INC     workRam             ; 

loc_dad5:
DAD5: B8              CLV                         
DAD6: 50 04           BVC     loc_dadc            ; 

loc_dad8:
DAD8: A9 20           LDA     #$20                ; reset the select state
DADA: 85 4C           STA     workRam+4C          ; 

loc_dadc:
DADC: 20 0F DB        JSR     dispatchDrawHandler ; build the diagnostic frame
DADF: 20 0D DF        JSR     emitHeaderedBodyRecord; emit the headered body record
DAE2: 8D 00 48        STA     avgGo               ; strobe the vector-generator go
DAE5: E6 03           INC     workRam+3           ; tick the frame counter
DAE7: A5 03           LDA     workRam+3           ; 
DAE9: 29 03           AND     #$03                ; every fourth frame
DAEB: D0 03           BNE     loc_daf0            ; 
DAED: 20 1B DE        JSR     stepEaromTransfer   ; service one high-score-store transfer step

loc_daf0:
DAF0: AD 00 0C        LDA     in0                 ; read the self-test switch
DAF3: 29 10           AND     #$10                
DAF5: F0 96           BEQ     loc_da8d            ; still held -- run another diagnostic frame

loc_daf7:
DAF7: D0 FE           BNE     loc_daf7            ; switch released -- spin until the watchdog resets the board

; ---- $DAF9-$DB0E: data ----
DAF9: 00 04 08 0C 03 07 0B 0B 59 DB F6 DB 83 DB 99 DB
DB09: 7D DB 6E DB 21 DB

; draw-handler dispatch on the display-finalize/self-test path: byte
; offset in 0x00 (0,2,..,12) selects one of seven per-frame draw handlers
; ($DB5A, $DBF7,...) at offset>>1; an out-of-range offset (>=0x0e) is
; clamped to 0x02 and the clamp persisted to 0x00, then tail-returns the
; handler's result.
dispatchDrawHandler:
DB0F: A6 00           LDX     workRam             ; read the per-frame draw-handler selector from the game-mode cell
DB11: E0 0E           CPX     #$0E                ; range-check the handler offset against the table size
DB13: 90 04           BCC     loc_db19            ; in range -- dispatch the selected handler
DB15: A2 02           LDX     #$02                ; out of range -- clamp the selector to handler entry 1
DB17: 86 00           STX     workRam             ; persist the clamped selector back to the game-mode cell

loc_db19:
DB19: BD 02 DB        LDA     rom+4B02,X          ; push the selected handler's address high byte from the dispatch table
DB1C: 48              PHA                         
DB1D: BD 01 DB        LDA     rom+4B01,X          ; push the handler's address low byte from the dispatch table
DB20: 48              PHA                         
DB21: 60              RTS                         ; rts-dispatch into the chosen draw handler

; reset the $60xx vector-display register bank (zero
; 0x60e0/0x6080/0x60c0/0x60d0/0x6000/0x6040, four settling reads, then
; raise 0x60e0 to 0x08), march a single set bit across the 32 slots at
; 0x6080, and emit one framing word via
; emitCoordinateVectorWord(0x34,0xa6).
initVectorDisplayRegisters:
DB22: A9 00           LDA     #$00                
DB24: 8D E0 60        STA     led                 ; zero the LED/flip/coin latch
DB27: 8D 80 60        STA     mathboxGo           ; zero the mathbox R0-low load register
DB2A: 8D C0 60        STA     pokey1              ; zero POKEY 1 pitch
DB2D: 8D D0 60        STA     pokey2              ; zero POKEY 2 pitch
DB30: 8D 00 60        STA     earomWrite          ; zero the EAROM data latch
DB33: 8D 40 60        STA     mathboxStatus / earomControl; zero the EAROM control latch (0x6040 write) -- a write here hits EAROM control, not the read-only mathbox status
DB36: AD 40 60        LDA     mathboxStatus / earomControl; dummy read to let the mathbox status flip-flop settle
DB39: AD 60 60        LDA     mathboxLo           ; dummy read of the mathbox result low byte
DB3C: AD 70 60        LDA     mathboxHi           ; dummy read of the mathbox result high byte
DB3F: AD 50 60        LDA     earomRead           ; dummy read of the EAROM read latch
DB42: A9 08           LDA     #$08                
DB44: 8D E0 60        STA     led                 ; raise the LED/flip latch
DB47: A9 01           LDA     #$01                ; seed a single set bit for the walking-bit march
DB49: A2 1F           LDX     #$1F                ; prepare to march across 32 mathbox load slots
DB4B: 18              CLC                         

loc_db4c:
DB4C: 9D 80 60        STA     mathboxGo,X         ; write the walking-bit pattern into mathbox load slot x
DB4F: 2A              ROL     A                   ; rotate the set bit up through carry
DB50: CA              DEX                         
DB51: 10 F9           BPL     loc_db4c            ; loop the 32-slot bit march
DB53: A9 34           LDA     #$34                
DB55: A2 A6           LDX     #$A6                
DB57: 4C 39 DF        JMP     emitCoordinateVectorWord; emit one framing coordinate word (tail)

; When guards loc_1ca and loc_1c7 are both clear, runs the EAROM seeder
; (de11) and stamps loc_7c=loc_1c9 and loc_0=0x02.
beginEaromSequenceIfIdle:
DB5A: AD CA 01        LDA     workRam+1CA         ; read the EAROM mode/active-operation flag
DB5D: 0D C7 01        ORA     workRam+1C7         ; OR in the queued-region (pending) flag -- bail if a sequence is pending or running
DB60: D0 0C           BNE     loc_db6e            ; a sequence is already pending or running -- bail
DB62: 20 11 DE        JSR     armEaromReadback    ; arm the EAROM readback state machine
DB65: AD C9 01        LDA     workRam+1C9         ; copy the pending-work flags...
DB68: 85 7C           STA     workRam+7C          ; ...into the walk's scratch cell
DB6A: A9 02           LDA     #$02                
DB6C: 85 00           STA     workRam             ; advance the game-mode cell

loc_db6e:
DB6E: 60              RTS                         

; emit a header from the halved slot count via emitTaggedVectorWord(0x68,
; 0x50>>1), then run $DB88(0x33,0x4e) to emit its header and blank the
; four even slots of 0x60c1 and 0x60d1.
emitHalvedCountHeaderAndClearVectorSlots:
DB6F: A5 50           LDA     workRam+50          ; read the spinner accumulator (player rotation count)
DB71: 4A              LSR     A                   ; halve it
DB72: A8              TAY                         
DB73: A9 68           LDA     #$68                
DB75: 20 4C DF        JSR     emitTaggedVectorWord; emit a 0x68-tagged header word carrying half the spinner count
DB78: A2 4E           LDX     #$4E                
DB7A: A9 33           LDA     #$33                
DB7C: D0 0A           BNE     emitVectorHeaderAndClearSlots; emit the fixed header and clear the slot banks

; front onto emitVectorHeaderAndClearSlots with the fixed header pair
; (0x32,0xb6): emit that header word then blank the four even slots of
; 0x60c1 and 0x60d1.
emitPrimedHeaderAndClearVectorSlots:
DB7E: A2 B6           LDX     #$B6                
DB80: A9 32           LDA     #$32                
DB82: D0 04           BNE     emitVectorHeaderAndClearSlots; emit the primed header (pair 0x32,0xb6) and clear the slots

; emit a fixed framing word via emitCoordinateVectorWord(0x33,0x0a), then
; blank the four even-indexed slots of output tables 0x60c1 and 0x60d1.
emitFixedHeaderAndClearVectorSlots:
DB84: A9 33           LDA     #$33                
DB86: A2 0A           LDX     #$0A                

; emit a caller-supplied header word via emitCoordinateVectorWord(a,x),
; then blank the four even-indexed slots of output tables 0x60c1 and
; 0x60d1.
emitVectorHeaderAndClearSlots:
DB88: 20 39 DF        JSR     emitCoordinateVectorWord; emit the caller's framing word into the display list
DB8B: A2 06           LDX     #$06                
DB8D: A9 00           LDA     #$00                

loc_db8f:
DB8F: 9D C1 60        STA     pokey1+1,X          ; silence POKEY 1 voice control at even slot x
DB92: 9D D1 60        STA     pokey2+1,X          ; silence POKEY 2 voice control at even slot x
DB95: CA              DEX                         
DB96: CA              DEX                         
DB97: 10 F6           BPL     loc_db8f            ; loop the four even AUDC slots
DB99: 60              RTS                         

; advance phase counter 0x39 (only while frame gate 0x3 & 0x3f is clear),
; index three parallel ROM rows ($DBD5/$DBD6/$DFDC) by 0x39 & 0x07 to seed
; output cells (clear 0x60c1+slotA, write ROM value into 0x60c0+slotB and
; 0xa8 into 0x60c1+slotB), then emit three words via
; emitCoordinateVectorWord(0x34,0x56)/emitVectorWordTag70(0x01, 0x3 &
; 0x7f)/emitCoordinateVectorWord(0x34,0xaa).
stepVectorPhaseAnimation:
DB9A: A5 03           LDA     workRam+3           ; read the frame counter
DB9C: 29 3F           AND     #$3F                ; gate on the low six bits -- once every 64 frames
DB9E: D0 02           BNE     loc_dba2            ; 
DBA0: E6 39           INC     workRam+39          ; advance the 8-phase sound/vector sequencer

loc_dba2:
DBA2: A5 39           LDA     workRam+39          ; 
DBA4: 29 07           AND     #$07                ; mask the phase to 0..7 for the sequencer row index
DBA6: AA              TAX                         
DBA7: BC D5 DB        LDY     rom+4BD5,X          ; pick this phase's voice to silence from the row table
DBAA: A9 00           LDA     #$00                
DBAC: 99 C1 60        STA     pokey1+1,Y          ; silence that POKEY 1 voice
DBAF: BC D6 DB        LDY     rom+4BD6,X          ; pick this phase's voice to fire from the row table
DBB2: BD DC DF        LDA     rom+4FDC,X          ; read this phase's value byte from the row table
DBB5: 99 C0 60        STA     pokey1,Y            ; set the fired voice's frequency
DBB8: A9 A8           LDA     #$A8                
DBBA: 99 C1 60        STA     pokey1+1,Y          ; fire it at fixed volume and distortion
DBBD: A9 34           LDA     #$34                
DBBF: A2 56           LDX     #$56                
DBC1: 20 39 DF        JSR     emitCoordinateVectorWord; emit the opening coordinate word
DBC4: A5 03           LDA     workRam+3           ; take the low seven bits of the frame counter
DBC6: 29 7F           AND     #$7F                
DBC8: A8              TAY                         
DBC9: A9 01           LDA     #$01                
DBCB: 20 6C DF        JSR     emitVectorWordTag70 ; emit a tag-70 word that scrolls with time
DBCE: A9 34           LDA     #$34                
DBD0: A2 AA           LDX     #$AA                
DBD2: 4C 39 DF        JMP     emitCoordinateVectorWord; emit the closing coordinate word (tail)

; ---- $DBD5-$DBDF: data ----
DBD5: 16 00 10 02 12 04 14 06 16 00 EA

; Assembles a pot-status byte from the low three bits of $60D8 (mirrored
; into loc_37 and $60CB) merged with one relocated bit lifted from $60C8.
assemblePotStatusByte:
DBE0: 8D DB 60        STA     pokey2+B            ; strobe POKEY 2 pot scan with the incoming value
DBE3: AD D8 60        LDA     pokey2+8            ; read POKEY 2 pot lines (ALLPOT at 0x60d8)
DBE6: 29 07           AND     #$07                ; keep the low three control bits
DBE8: 85 37           STA     workRam+37          ; mirror them into scratch
DBEA: 8D CB 60        STA     pokey1+B            ; and into the POKEY 1 pot-scan strobe
DBED: AD C8 60        LDA     pokey1+8            ; read POKEY 1 pot lines (ALLPOT at 0x60c8)
DBF0: 29 20           AND     #$20                ; isolate bit 5
DBF2: 4A              LSR     A                   
DBF3: 4A              LSR     A                   ; shift it down to bit 3
DBF4: 05 37           ORA     workRam+37          ; merge with the low three bits
DBF6: 60              RTS                         ; return the assembled pot-status byte

; per-frame vector-list emit (handler index 1 of dispatchDrawHandler):
; when the 16-bit counter 0x2e/0x2f is nonzero, seed the POKEY operand
; cells and run the math-coprocessor scan (runMathboxDivide) to decide
; 0x78=0xff and the POKEY status byte; advance the 15-bit counter
; 0x2e/0x2f; build the POKEY work word from 0x4d/0x4e; fire the readout
; draws; conditionally emit the 0x52-bit marker; walk the 0x7d (x=11..0)
; and 0x78 (x=4..0) emit tables; then tail-delegate the 0x50-indexed
; colour pair to the colour-pair emitter emitKeyedScaledCoordinateRecord.
emitReadoutVectorList:
DBF7: A5 2E           LDA     workRam+2E          ; read the sweep counter low byte
DBF9: F0 1E           BEQ     loc_dc19            ; counter zero -- skip the mathbox scan
DBFB: 8D 95 60        STA     mathboxGo+15        ; seed mathbox R7 low with the counter
DBFE: 8D 8D 60        STA     mathboxGo+D         ; and mathbox RA low
DC01: A5 2F           LDA     workRam+2F          ; read the sweep counter high byte
DC03: 8D 96 60        STA     mathboxGo+16        ; seed mathbox R7 high
DC06: A2 00           LDX     #$00                
DC08: 20 E6 DC        JSR     runMathboxDivide    ; run the mathbox divide
DC0B: C9 01           CMP     #$01                ; result A == 1?
DC0D: D0 06           BNE     loc_dc15            ; A != 1 -- force the status byte to 0xff
DC0F: 98              TYA                         
DC10: D0 03           BNE     loc_dc15            ; A == 1 but Y != 0 -- force the status byte to 0xff
DC12: 8A              TXA                         
DC13: 10 04           BPL     loc_dc19            ; remainder positive -- clear (keep X as the status byte)

loc_dc15:
DC15: A9 FF           LDA     #$FF                
DC17: 85 78           STA     workRam+78          ; force the spread/scan flag to 0xff

loc_dc19:
DC19: A2 00           LDX     #$00                
DC1B: 86 73           STX     workRam+73          ; clear the vector-record header
DC1D: E6 2E           INC     workRam+2E          ; advance the sweep counter low byte
DC1F: D0 06           BNE     loc_dc27            ; 
DC21: E6 2F           INC     workRam+2F          ; carry into the sweep counter high byte
DC23: 10 02           BPL     loc_dc27            ; 
DC25: 86 2F           STX     workRam+2F          ; wrap the high byte at bit 7 (7-bit sweep)

loc_dc27:
DC27: 8D DB 60        STA     pokey2+B            ; write the decided status byte to the POKEY 2 pot-scan strobe
DC2A: AD D8 60        LDA     pokey2+8            ; read POKEY 2 pot lines (ALLPOT at 0x60d8)
DC2D: 29 78           AND     #$78                ; keep the option bits
DC2F: 85 4D           STA     workRam+4D          ; stash the debounced input
DC31: F0 05           BEQ     loc_dc38            ; no bits set -- leave voice 1 silent
DC33: 8D C0 60        STA     pokey1              ; set POKEY 1 voice 1 frequency
DC36: A2 A4           LDX     #$A4                ; arm voice 1 control

loc_dc38:
DC38: 8E C1 60        STX     pokey1+1            ; write POKEY 1 voice 1 control
DC3B: A2 00           LDX     #$00                
DC3D: A5 4E           LDA     workRam+4E          ; read the input edge flags
DC3F: F0 06           BEQ     loc_dc47            ; none set -- leave voice 2 silent
DC41: 0A              ASL     A                   
DC42: 8D C2 60        STA     pokey1+2            ; set POKEY 1 voice 2 frequency (edge flags << 1)
DC45: A2 A4           LDX     #$A4                ; arm voice 2 control

loc_dc47:
DC47: 8E C3 60        STX     pokey1+3            ; write POKEY 1 voice 2 control
DC4A: 20 0D DD        JSR     buildPotReadoutVectorList; build the DIP/pot diagnostic readout
DC4D: A4 4D           LDY     workRam+4D          ; 
DC4F: A9 D0           LDA     #$D0                
DC51: A2 F0           LDX     #$F0                
DC53: 20 2B DD        JSR     emitByteBitsAsDigits; render the debounced-input byte as eight per-bit digits
DC56: A4 4E           LDY     workRam+4E          ; 
DC58: 20 27 DD        JSR     emitByteBitsAsDigitsFixed; render the edge-flags byte as eight per-bit digits
DC5B: A5 52           LDA     workRam+52          ; read the previous spinner-pot sample
DC5D: 29 10           AND     #$10                ; test bit 4
DC5F: F0 1D           BEQ     loc_dc7e            ; clear -- skip the marker and latch writes
DC61: A9 34           LDA     #$34                
DC63: A2 82           LDX     #$82                
DC65: 20 39 DF        JSR     emitCoordinateVectorWord; emit a marker coordinate word
DC68: A0 10           LDY     #$10                
DC6A: A5 4D           LDA     workRam+4D          ; read the debounced input
DC6C: 29 60           AND     #$60                ; test the coin/mode bits
DC6E: F0 0E           BEQ     loc_dc7e            ; none set -- skip the latch write
DC70: 49 20           EOR     #$20                ; flip bit 5 -- result zero iff only the coin bit (bit 5) was set
DC72: F0 04           BEQ     loc_dc78            ; exactly bit 5 -- keep the default latch value
DC74: A9 04           LDA     #$04                ; otherwise use the alternate mode value...
DC76: A0 08           LDY     #$08                ; ...and the alternate latch value

loc_dc78:
DC78: 8D E0 60        STA     led                 ; write the LED/flip latch
DC7B: 8C 00 40        STY     coin                ; write the coin-counter/flip output latch

loc_dc7e:
DC7E: A9 34           LDA     #$34                
DC80: A2 92           LDX     #$92                
DC82: 20 39 DF        JSR     emitCoordinateVectorWord; emit a coordinate mark
DC85: A2 0B           LDX     #$0B                ; walk the 12-slot spread table (x = 11..0)

loc_dc87:
DC87: B5 7D           LDA     workRam+7D,X        ; read spread slot x
DC89: F0 19           BEQ     loc_dca4            ; empty slot -- skip
DC8B: 85 35           STA     workRam+35          ; stash the slot value
DC8D: 86 38           STX     workRam+38          ; save the loop index
DC8F: 8A              TXA                         
DC90: 20 1F DF        JSR     emitStrokeWordFromNibblePlusOne; emit a stroke word for slot index + 1
DC93: A0 F4           LDY     #$F4                
DC95: A2 F4           LDX     #$F4                
DC97: A5 35           LDA     workRam+35          ; 
DC99: 20 A9 D8        JSR     emitScaledByteDigit ; emit the slot value as a scaled digit
DC9C: A9 0C           LDA     #$0C                
DC9E: AA              TAX                         
DC9F: 20 75 DF        JSR     emitScaledCoordinateRecord; emit a scaled coordinate record
DCA2: A6 38           LDX     workRam+38          ; restore the loop index

loc_dca4:
DCA4: CA              DEX                         
DCA5: 10 E0           BPL     loc_dc87            ; next spread-table slot
DCA7: 20 53 DF        JSR     emitVectorHeaderWord; emit the header word
DCAA: A9 00           LDA     #$00                
DCAC: A2 16           LDX     #$16                
DCAE: 20 75 DF        JSR     emitScaledCoordinateRecord; emit a scaled coordinate record
DCB1: A2 04           LDX     #$04                ; walk the 5-slot spread table (x = 4..0)
DCB3: 86 37           STX     workRam+37          ; 

loc_dcb5:
DCB5: A6 37           LDX     workRam+37          ; 
DCB7: A0 00           LDY     #$00                
DCB9: B5 78           LDA     workRam+78,X        ; read spread slot x
DCBB: F0 03           BEQ     loc_dcc0            ; empty slot -- use word index 0
DCBD: BC E1 DC        LDY     rom+4CE1,X          ; pick this slot's coordinate-word index from the table

loc_dcc0:
DCC0: B9 E4 31        LDA     vectorRom+1E4,Y     ; read the coordinate word low byte from the glyph table
DCC3: BE E5 31        LDX     vectorRom+1E5,Y     ; read the coordinate word high byte from the glyph table
DCC6: 20 57 DF        JSR     emitVectorWord      ; emit the coordinate word
DCC9: C6 37           DEC     workRam+37          ; 
DCCB: 10 E8           BPL     loc_dcb5            ; next slot
DCCD: A2 AC           LDX     #$AC                
DCCF: A9 30           LDA     #$30                
DCD1: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the final coordinate mark
DCD4: A4 50           LDY     workRam+50          ; index by the spinner accumulator
DCD6: B9 E8 DF        LDA     rom+4FE8,Y          ; read the colour-pair high byte
DCD9: BE E4 DF        LDX     rom+4FE4,Y          ; read the colour-pair low byte
DCDC: A0 C0           LDY     #$C0                
DCDE: 4C 73 DF        JMP     emitKeyedScaledCoordinateRecord; emit the keyed scaled coordinate record (tail)

; ---- $DCE1-$DCE5: data ----
DCE1: 2E 38 34 36 1E

; Primes the math-coprocessor operand/count registers $608C-$6096 from A
; and X, kicks its divide, spins a 16-step window on $6040 for the first
; ready result, and returns the $6060/$6070 low/high pair.
runMathboxDivide:
DCE6: A0 00           LDY     #$00                
DCE8: 84 73           STY     workRam+73          ; clear the vector-record header staging cell
DCEA: 8C 14 04        STY     workRam+414         ; clear the second staging cell
DCED: 8D 8E 60        STA     mathboxGo+E         ; load mathbox Ra high operand
DCF0: 8E 8F 60        STX     mathboxGo+F         ; load mathbox Rb low operand
DCF3: 8C 90 60        STY     mathboxGo+10        ; clear mathbox Rb high
DCF6: A2 10           LDX     #$10                
DCF8: 8E 8C 60        STX     mathboxGo+C         ; seed the mathbox iteration count
DCFB: 8E 94 60        STX     mathboxGo+14        ; strobe the divide to start

loc_dcfe:
DCFE: CA              DEX                         ; count down the poll window
DCFF: 30 0B           BMI     loc_dd0c            ; window exhausted -- no result ready
DD01: AD 40 60        LDA     mathboxStatus / earomControl; read the mathbox status
DD04: 30 F8           BMI     loc_dcfe            ; still busy -- keep polling
DD06: AD 60 60        LDA     mathboxLo           ; ready -- latch the result low byte
DD09: AC 70 60        LDY     mathboxHi           ; latch the result high byte

loc_dd0c:
DD0C: 60              RTS                         

; Builds a diagnostic readout vector list: a fixed header word ($DF53), a
; zero word (emitBlankVectorWordTag70), then eight-digit runs keyed by DIP
; ports loc_d00/loc_e00 and a pot-status byte.
buildPotReadoutVectorList:
DD0D: 20 53 DF        JSR     emitVectorHeaderWord; emit the fixed header word
DD10: A9 00           LDA     #$00                
DD12: 20 6A DF        JSR     emitBlankVectorWordTag70; emit a zero-valued tag-70 framing word
DD15: A9 E8           LDA     #$E8                
DD17: AC 00 0D        LDY     dsw1                ; read the coinage DIP bank
DD1A: 20 29 DD        JSR     emitByteBitsAsDigitsAtF8; render it as an eight-bit digit run at column 0xe8
DD1D: AC 00 0E        LDY     dsw2                ; read the options DIP bank
DD20: 20 27 DD        JSR     emitByteBitsAsDigitsFixed; render it as an eight-bit digit run
DD23: 20 E0 DB        JSR     assemblePotStatusByte; pulse the POKEY pot scan and fold to a status byte
DD26: A8              TAY                         

; Fixed-position front feeding the coordinate deltas 0xd0/0xf8 into the
; eight-bit byte-digit emit, displaying the byte in y.
emitByteBitsAsDigitsFixed:
DD27: A9 D0           LDA     #$D0                ; inject the fixed value byte 0xd0

; Fixed-position front feeding coordinate x = 0xf8 into the eight-bit
; byte-digit emit.
emitByteBitsAsDigitsAtF8:
DD29: A2 F8           LDX     #$F8                ; inject the fixed screen-X column 0xf8

; Stashes the byte y into loc_35, scales the two coordinates, then shifts
; loc_35 out MSB-first emitting each of its eight bits as one vector
; digit.
emitByteBitsAsDigits:
DD2B: 84 35           STY     workRam+35          ; stash the byte to render
DD2D: 20 75 DF        JSR     emitScaledCoordinateRecord; position the eight-bit row on screen
DD30: A2 07           LDX     #$07                ; eight bits to draw
DD32: 86 37           STX     workRam+37          ; 

loc_dd34:
DD34: 06 35           ASL     workRam+35          ; shift the byte's top bit out
DD36: A9 00           LDA     #$00                
DD38: 2A              ROL     A                   ; capture the shifted-out (MSB-first) bit
DD39: 20 1F DF        JSR     emitStrokeWordFromNibblePlusOne; emit one stroke word for that bit
DD3C: C6 37           DEC     workRam+37          ; 
DD3E: 10 F4           BPL     loc_dd34            ; next bit
DD40: 60              RTS                         

; Doubles-and-adds two little-endian input pairs into the math-box
; operands $6095/$6096 (floored to one), seeds a divide, emits a header,
; then makes repeated passes of binary-to-BCD double-dabble over the
; three-byte source at loc_3b/loc_3c emitting each pass's digits with a
; scaled coordinate record.
buildLargeDecimalNumber:
DD41: AD 0F 04        LDA     workRam+40F         ; read the input pair low byte
DD44: 0A              ASL     A                   ; double it
DD45: 85 29           STA     workRam+29          ; 
DD47: AD 10 04        LDA     workRam+410         ; read the input pair high byte
DD4A: 2A              ROL     A                   ; double it, threading the carry
DD4B: 85 2A           STA     workRam+2A          ; 
DD4D: AD 0C 04        LDA     workRam+40C         ; read the coordinate accumulator low byte
DD50: 18              CLC                         
DD51: 65 29           ADC     workRam+29          ; add the doubled input
DD53: 8D 95 60        STA     mathboxGo+15        ; store as mathbox R7 low operand
DD56: 85 29           STA     workRam+29          ; 
DD58: AD 0D 04        LDA     workRam+40D         ; read the coordinate accumulator high byte
DD5B: 65 2A           ADC     workRam+2A          ; add with carry
DD5D: 8D 96 60        STA     mathboxGo+16        ; store as mathbox R7 high operand
DD60: 05 29           ORA     workRam+29          ; test whether the whole operand is zero
DD62: D0 05           BNE     loc_dd69            ; nonzero -- keep it
DD64: A9 01           LDA     #$01                
DD66: 8D 95 60        STA     mathboxGo+15        ; floor the operand to one so the divide never sees zero

loc_dd69:
DD69: AD 09 04        LDA     workRam+409         ; load mathbox Ra low from the timer low byte
DD6C: 8D 8D 60        STA     mathboxGo+D         ; 
DD6F: AD 0A 04        LDA     workRam+40A         ; read the timer mid byte
DD72: AE 0B 04        LDX     workRam+40B         ; read the timer high byte
DD75: 20 E6 DC        JSR     runMathboxDivide    ; run the mathbox divide
DD78: 8D 12 04        STA     workRam+412         ; stash the quotient
DD7B: 8C 13 04        STY     workRam+413         ; stash the remainder
DD7E: A9 3D           LDA     #$3D                
DD80: A2 CE           LDX     #$CE                
DD82: 20 39 DF        JSR     emitCoordinateVectorWord; emit the fixed coordinate header word
DD85: A9 06           LDA     #$06                
DD87: 85 3B           STA     workRam+3B          ; set the source pointer low -> 0x0406
DD89: A9 04           LDA     #$04                
DD8B: 85 3C           STA     workRam+3C          ; set the source pointer high
DD8D: 85 37           STA     workRam+37          ; outer loop -- five numbers (post-decrement bpl runs 5 passes; the 5th draws the divide quotient/remainder at 0x0412/0x0413)

loc_dd8f:
DD8F: A0 00           LDY     #$00                
DD91: 84 31           STY     workRam+31          ; clear the 4-byte BCD accumulator
DD93: 84 32           STY     workRam+32          ; 
DD95: 84 33           STY     workRam+33          ; 
DD97: 84 34           STY     workRam+34          ; 
DD99: B1 3B           LDA     (workRam+3B),Y      ; load the first binary source byte
DD9B: 85 56           STA     workRam+56          ; 
DD9D: E6 3B           INC     workRam+3B          ; 
DD9F: B1 3B           LDA     (workRam+3B),Y      ; load the second binary source byte
DDA1: 85 57           STA     workRam+57          ; 
DDA3: E6 3B           INC     workRam+3B          ; 
DDA5: B1 3B           LDA     (workRam+3B),Y      ; load the third binary source byte
DDA7: 85 58           STA     workRam+58          ; 
DDA9: E6 3B           INC     workRam+3B          ; 
DDAB: F8              SED                         ; enter decimal mode for double-dabble
DDAC: A0 17           LDY     #$17                ; 24 source bits
DDAE: 84 38           STY     workRam+38          ; 

loc_ddb0:
DDB0: 26 56           ROL     workRam+56          ; shift the 24-bit binary source left one bit...
DDB2: 26 57           ROL     workRam+57          ; ...carry chained low to high...
DDB4: 26 58           ROL     workRam+58          ; ...through the third byte
DDB6: A0 03           LDY     #$03                
DDB8: A2 00           LDX     #$00                

loc_ddba:
DDBA: B5 31           LDA     workRam+31,X        ; BCD-double each accumulator byte with the shifted-out bit
DDBC: 75 31           ADC     workRam+31,X        ; 
DDBE: 95 31           STA     workRam+31,X        ; 
DDC0: E8              INX                         
DDC1: 88              DEY                         
DDC2: 10 F6           BPL     loc_ddba            ; across the four BCD bytes
DDC4: C6 38           DEC     workRam+38          ; 
DDC6: 10 E8           BPL     loc_ddb0            ; next of the 24 bits
DDC8: D8              CLD                         ; leave decimal mode
DDC9: A9 31           LDA     #$31                
DDCB: A0 04           LDY     #$04                
DDCD: 20 B1 DF        JSR     emitNibbleDigitRun  ; emit the converted decimal digit run
DDD0: A9 D0           LDA     #$D0                
DDD2: A2 F8           LDX     #$F8                
DDD4: 20 75 DF        JSR     emitScaledCoordinateRecord; emit the number's scaled coordinate record
DDD7: C6 37           DEC     workRam+37          ; 
DDD9: 10 B4           BPL     loc_dd8f            ; next of the five numbers
DDDB: 60              RTS                         

; ---- $DDDC-$DDE8: data ----
DDDC: 73 00 09 0A 15 16 22 15 06 15 07 06 04

; queue a blanked write (erase) of the single region on bit 0x04 by
; feeding mask 0x04 into the blank-mode merge $DDF3 (which forces blank-
; flag 0x1c6=0xff then ORs the mask into 0x1c7/0x1c8).
queueEaromRegionErase:
DDE9: A9 04           LDA     #$04                ; select the EAROM region on bit mask 0x04
DDEB: D0 06           BNE     requestEaromBlankWrite; request a blanked (erase) write of that region

; Branch-only trampoline requesting a blanked EAROM write of the two low
; regions via ddf3 with mask 0x03; downstream loc_1c7/loc_1c8 observed
; changing.
eraseEaromLowRegions:
DDED: A9 03           LDA     #$03                ; load region mask 0x03 -- the two low high-score regions
DDEF: D0 02           BNE     requestEaromBlankWrite; jump into the blank-write request builder with the mask

; queue a blanked write (erase) of all three EAROM regions: stamp 0xff
; into blank-flag 0x1c6 and OR mask 0x07 into region-pending 0x1c7 and
; direction 0x1c8.
queueEaromEraseAllRegions:
DDF1: A9 07           LDA     #$07                ; load region mask 0x07 -- all three high-score regions -- then build a blank/erase request

; Forces the EAROM index byte to 0xff (blank mode) then merges the
; caller's mask, requesting a blanked write of those regions.
requestEaromBlankWrite:
DDF3: A0 FF           LDY     #$FF                ; force the index byte to 0xff, the blank/erase sentinel
DDF5: D0 08           BNE     queueEaromRequest   ; jump to the shared request tail, mask still in a

; request a (non-blanked) EAROM write of the two low NVRAM regions: pass
; the fixed mask 0x03 to the shared zeroed-index merge tail $DDFD, which
; ORs the mask into the region-pending loc_1c7 and direction loc_1c8 cells
; with a cleared blank-index loc_1c6.
requestWriteLowRegions:
DDF7: A9 03           LDA     #$03                ; load region mask 0x03 -- the two low regions -- for a live save
DDF9: D0 02           BNE     queueEaromRequestAtIndexZero; jump into the index-zero (live-save) tail

; queue a plain (non-blanked) EAROM save of the region on bit 0x04: store
; index 0x00 into blank-flag 0x1c6 and OR mask 0x04 into region-pending
; 0x1c7 and direction 0x1c8.
queueEaromRegionSave:
DDFB: A9 04           LDA     #$04                ; load region mask 0x04 -- the third region -- then fall into the live-save tail

; queue an EAROM (high-score NVRAM) request at cell index 0: store 0x00
; into the target-index cell loc_1c6 and OR the caller's mask A into both
; request-flag cells loc_1c7 and loc_1c8 (shared tail queueEaromRequest);
; entry queueEaromRegionSave presets mask 0x04.
queueEaromRequestAtIndexZero:
DDFD: A0 00           LDY     #$00                ; force the index byte to 0 -- live save, not erase

; queue an EAROM (high-score NVRAM) request: store Y into the target-index
; cell loc_1c6 and OR the request mask A into both request-flag cells
; loc_1c7 and loc_1c8.
queueEaromRequest:
DDFF: 8C C6 01        STY     workRam+1C6         ; store the index/blank byte (0 = live save, 0xff = erase)
DE02: 48              PHA                         
DE03: 0D C7 01        ORA     workRam+1C7         ; merge the mask into the region-pending bits
DE06: 8D C7 01        STA     workRam+1C7         ; store the regions awaiting service
DE09: 68              PLA                         
DE0A: 0D C8 01        ORA     workRam+1C8         ; merge the mask into the per-region direction bits
DE0D: 8D C8 01        STA     workRam+1C8         ; store direction (set = write out, clear = read back)
DE10: 60              RTS                         

; Sets EAROM mode byte loc_1c7=0x07 and clears loc_1c8=0x00, then drives
; the step machine to arm a read-back of all regions.
armEaromReadback:
DE11: A9 07           LDA     #$07                ; command all three regions
DE13: 8D C7 01        STA     workRam+1C7         ; set the region-pending bits to read every region back
DE16: A9 00           LDA     #$00                ; clear the direction bits
DE18: 8D C8 01        STA     workRam+1C8         ; so all regions are read back in, then fall into the transfer step

; drain one entry of the queued EAROM save/read: when mode 0x1ca is idle
; and pending 0x1c7 is set, isolate one region bit into 0x1ce, seed cursor
; 0x1cc/limit 0x1cd and row pointer 0xbd/0xbe from packed ROM rows
; ($DDDD/ddde/dde3/dde4) and arm 0x1ca write(0x80)/read(0x20) per 0x1c8;
; each pass folds the RAM/read-back byte into checksum 0x1cf, moves it
; through the 0x6000 data window with the 0x6040/0x6050 handshake, at the
; limit writes/compares the checksum (recording failures into 0x1c9), and
; clocks 0x6040 to signal re-enter or stop.
stepEaromTransfer:
DE1B: AD CA 01        LDA     workRam+1CA         ; read the EAROM step-machine mode/busy byte
DE1E: D0 4B           BNE     loc_de6b            ; a pass is already active: skip region setup
DE20: AD C7 01        LDA     workRam+1C7         ; read the region-pending bits
DE23: F0 46           BEQ     loc_de6b            ; nothing queued: skip setup
DE25: A2 00           LDX     #$00                
DE27: 8E CB 01        STX     workRam+1CB         ; clear the per-region pass counter
DE2A: 8E CF 01        STX     workRam+1CF         ; clear the running checksum accumulator
DE2D: 8E CE 01        STX     workRam+1CE         ; clear the single-region walking mask
DE30: A2 08           LDX     #$08                ; 8 bits to rotate -- x also becomes the region index
DE32: 38              SEC                         

loc_de33:
DE33: 6E CE 01        ROR     workRam+1CE         ; rotate a set bit into the walking mask
DE36: 0A              ASL     A                   ; shift the pending bits left, hunting the highest (most-significant) set region bit
DE37: CA              DEX                         ; count down toward the region index
DE38: 90 F9           BCC     loc_de33            ; keep rotating until a set bit falls out
DE3A: A0 80           LDY     #$80                ; default to write mode (0x80)
DE3C: AD CE 01        LDA     workRam+1CE         ; 
DE3F: 2D C8 01        AND     workRam+1C8         ; test the isolated region mask against the direction bits
DE42: D0 02           BNE     loc_de46            ; region flagged write: keep write mode
DE44: A0 20           LDY     #$20                ; else read mode (0x20)

loc_de46:
DE46: 8C CA 01        STY     workRam+1CA         ; arm the step-machine mode for this region
DE49: AD CE 01        LDA     workRam+1CE         ; 
DE4C: 4D C7 01        EOR     workRam+1C7         ; drop this region's bit out of the pending set
DE4F: 8D C7 01        STA     workRam+1C7         ; so it is not serviced again
DE52: 8A              TXA                         
DE53: 0A              ASL     A                   ; word-stride the region index into the packed tables
DE54: AA              TAX                         
DE55: BD DD DD        LDA     rom+4DDD,X          ; read the region's start cursor from the packed table
DE58: 8D CC 01        STA     workRam+1CC         ; seed the region byte cursor
DE5B: BD DE DD        LDA     rom+4DDE,X          ; read the region's end/limit from the packed table
DE5E: 8D CD 01        STA     workRam+1CD         ; seed the checksum-position limit
DE61: BD E3 DD        LDA     rom+4DE3,X          ; read the region RAM-copy pointer low byte
DE64: 85 BD           STA     workRam+BD          ; seed the region walk-pointer low
DE66: BD E4 DD        LDA     rom+4DE4,X          ; read the region RAM-copy pointer high byte
DE69: 85 BE           STA     workRam+BE          ; seed the region walk-pointer high

loc_de6b:
DE6B: A0 00           LDY     #$00                
DE6D: 8C 40 60        STY     mathboxStatus / earomControl; reset the math-box / EAROM control port
DE70: AD CA 01        LDA     workRam+1CA         ; read the mode byte
DE73: D0 01           BNE     loc_de76            ; mode active: run a pass
DE75: 60              RTS                         

loc_de76:
DE76: AC CB 01        LDY     workRam+1CB         ; y = which entry of the region is being serviced
DE79: AE CC 01        LDX     workRam+1CC         ; x = position in the EAROM data window
DE7C: 0A              ASL     A                   ; shift the mode byte to select the sub-operation
DE7D: 90 0D           BCC     loc_de8c            ; 
DE7F: 9D 00 60        STA     earomWrite,X        ; stage a data byte into the EAROM data window
DE82: A9 40           LDA     #$40                
DE84: 8D CA 01        STA     workRam+1CA         ; arm the write sub-mode (0x40)
DE87: A0 0E           LDY     #$0E                
DE89: B8              CLV                         
DE8A: 50 73           BVC     loc_deff            ; 

loc_de8c:
DE8C: 10 25           BPL     loc_deb3            ; 
DE8E: A9 80           LDA     #$80                ; set write mode
DE90: 8D CA 01        STA     workRam+1CA         ; 
DE93: AD C6 01        LDA     workRam+1C6         ; read the blank flag
DE96: F0 04           BEQ     loc_de9c            ; not blanking: keep the RAM byte
DE98: A9 00           LDA     #$00                
DE9A: 91 BD           STA     (workRam+BD),Y      ; blank the RAM byte through the region walk pointer

loc_de9c:
DE9C: B1 BD           LDA     (workRam+BD),Y      ; read the RAM byte through the region walk pointer
DE9E: EC CD 01        CPX     workRam+1CD         ; reached the region limit?
DEA1: 90 08           BCC     loc_deab            ; not yet
DEA3: A9 00           LDA     #$00                
DEA5: 8D CA 01        STA     workRam+1CA         ; clear mode: region done
DEA8: AD CF 01        LDA     workRam+1CF         ; at the limit, emit the running checksum instead

loc_deab:
DEAB: 9D 00 60        STA     earomWrite,X        ; stage the byte into the EAROM data window
DEAE: A0 0C           LDY     #$0C                
DEB0: B8              CLV                         
DEB1: 50 3F           BVC     loc_def2            ; 

loc_deb3:
DEB3: A9 08           LDA     #$08                
DEB5: 8D 40 60        STA     mathboxStatus / earomControl; begin the EAROM read handshake on the control port
DEB8: 9D 00 60        STA     earomWrite,X        ; 
DEBB: A9 09           LDA     #$09                
DEBD: 8D 40 60        STA     mathboxStatus / earomControl; clock the control port
DEC0: EA              NOP                         
DEC1: A9 08           LDA     #$08                
DEC3: 8D 40 60        STA     mathboxStatus / earomControl; return the control port
DEC6: EC CD 01        CPX     workRam+1CD         ; reached the region limit?
DEC9: AD 50 60        LDA     earomRead           ; read the EAROM read-back port
DECC: 90 20           BCC     loc_deee            ; not at limit: store the byte
DECE: 4D CF 01        EOR     workRam+1CF         ; at the limit, xor the read-back against the running checksum
DED1: F0 13           BEQ     loc_dee6            ; zero means the region verified
DED3: A9 00           LDA     #$00                
DED5: AC CB 01        LDY     workRam+1CB         ; 

loc_ded8:
DED8: 91 BD           STA     (workRam+BD),Y      ; checksum mismatch: blank the region's RAM bytes back to front
DEDA: 88              DEY                         
DEDB: 10 FB           BPL     loc_ded8            ; loop until the whole region is blanked
DEDD: AD CE 01        LDA     workRam+1CE         ; 
DEE0: 0D C9 01        ORA     workRam+1C9         ; record the failure by re-queuing the region bit
DEE3: 8D C9 01        STA     workRam+1C9         ; into the pending-work flags

loc_dee6:
DEE6: A9 00           LDA     #$00                
DEE8: 8D CA 01        STA     workRam+1CA         ; retire the mode byte -- region done
DEEB: B8              CLV                         
DEEC: 50 02           BVC     loc_def0            ; 

loc_deee:
DEEE: 91 BD           STA     (workRam+BD),Y      ; store the read-back byte through the region walk pointer

loc_def0:
DEF0: A0 00           LDY     #$00                

loc_def2:
DEF2: 18              CLC                         
DEF3: 6D CF 01        ADC     workRam+1CF         ; fold the byte into the running checksum
DEF6: 8D CF 01        STA     workRam+1CF         ; store the checksum accumulator
DEF9: EE CB 01        INC     workRam+1CB         ; bump the per-region pass counter
DEFC: EE CC 01        INC     workRam+1CC         ; bump the region byte cursor

loc_deff:
DEFF: 8C 40 60        STY     mathboxStatus / earomControl; write the exit code to the control port
DF02: 98              TYA                         
DF03: D0 03           BNE     loc_df08            ; nonzero exit: return, re-enter later
DF05: 4C 1B DE        JMP     stepEaromTransfer   ; else loop to drain the next entry

loc_df08:
DF08: 60              RTS                         

; Stores the fixed body byte 0xc0 at the draw cursor origin loc_74 and
; runs the shared record-tail emit.
emitRecordBodyC0:
DF09: A9 C0           LDA     #$C0                ; fixed body byte 0xc0 -- the vector-generator opcode for this record class
DF0B: D0 05           BNE     emitRecordBodyByte  ; jump to the body-write step

; build a two-byte-header record: emit the {0x40,0x80} header via 0x74,
; then store the fixed body byte 0x20 at the cursor origin and run the
; shared record tail.
emitHeaderedBodyRecord:
DF0D: 20 53 DF        JSR     emitVectorHeaderWord; lay the record header word through the draw cursor
DF10: A9 20           LDA     #$20                ; fixed body byte 0x20, then fall into the body-write step

; store one body byte (A) at the display cursor origin (0x74)+0 and
; continue into the shared record tail (0xdfac).
emitRecordBodyByte:
DF12: A0 00           LDY     #$00                
DF14: 91 74           STA     (workRam+74),Y      ; store the body byte at the draw-cursor origin
DF16: 4C AC DF        JMP     emitRecordTailByte  ; continue into the shared record tail

; emit a stroke-table word: form a word index from A's low nibble (0 when
; carry set and nibble is zero, else nibble+1), double it, copy the two
; bytes of table 0x31e4[index] into the display list at cursor 0x74, and
; advance two.
emitStrokeWordFromNibble:
DF19: 90 04           BCC     emitStrokeWordFromNibblePlusOne; carry clear: skip the zero-terminator test, use nibble+1
DF1B: 29 0F           AND     #$0F                ; keep the low nibble -- the glyph selector
DF1D: F0 05           BEQ     emitStrokeWordByIndex; carry set and nibble zero: select terminator glyph 0

; thin index wrapper: form the stroke-table index (A&0x0f)+1 and share the
; 0x31e4 copy-and-advance emit tail, laying that word into the display
; list at cursor 0x74.
emitStrokeWordFromNibblePlusOne:
DF1F: 29 0F           AND     #$0F                ; keep the low nibble
DF21: 18              CLC                         
DF22: 69 01           ADC     #$01                ; index = nibble + 1

; emit one glyph/stroke vector word by table index A: copy the two bytes
; of ROM stroke-table $31E4 entry ($31E4 + (A<<1)) into the vector list at
; write cursor mem16[loc_74] and step the cursor past them via
; advanceDisplayCursor (entry emitStrokeWordFromNibblePlusOne first maps a
; low nibble to index (nibble&0x0f)+1).
emitStrokeWordByIndex:
DF24: 08              PHP                         
DF25: 0A              ASL     A                   ; double the index -- stroke entries are 16-bit words
DF26: A0 00           LDY     #$00                
DF28: AA              TAX                         
DF29: BD E4 31        LDA     vectorRom+1E4,X     ; read the glyph's first stroke byte from the vector-ROM stroke table
DF2C: 91 74           STA     (workRam+74),Y      ; copy it into the display list at the draw cursor
DF2E: BD E5 31        LDA     vectorRom+1E5,X     ; read the glyph's second stroke byte
DF31: C8              INY                         
DF32: 91 74           STA     (workRam+74),Y      ; copy it to draw cursor +1
DF34: 20 5F DF        JSR     advanceDisplayCursor; advance the draw cursor two bytes past the word
DF37: 28              PLP                         
DF38: 60              RTS                         

; Emits a coordinate vector word: high byte is a's upper nibble tagged
; 0xa0, low byte is x shifted right with a's carry rotated into bit7,
; written through loc_74 then advanced (or, on cursor wrap, tailing
; emitTaggedVectorWord with key loc_73).
emitCoordinateVectorWord:
DF39: 4A              LSR     A                   ; shift a right, low bit into carry
DF3A: 29 0F           AND     #$0F                ; keep a's upper nibble
DF3C: 09 A0           ORA     #$A0                ; tag the high byte with the 0xa0 vector-generator opcode
DF3E: A0 01           LDY     #$01                
DF40: 91 74           STA     (workRam+74),Y      ; store the tagged high byte at draw cursor +1
DF42: 88              DEY                         
DF43: 8A              TXA                         
DF44: 6A              ROR     A                   ; rotate x right with a's old bit0 into bit7 -- the low byte
DF45: 91 74           STA     (workRam+74),Y      ; store the low byte at the draw-cursor origin
DF47: C8              INY                         
DF48: D0 15           BNE     advanceDisplayCursor; advance the draw cursor past the word

; Emits a vector word tagged 0x60 using the key byte loc_73 as its data
; byte through the draw cursor loc_74.
emitVectorWordTag60FromKey:
DF4A: A4 73           LDY     workRam+73          ; take the second payload byte from the current record-header key

; emit one vector word through cursor 0x74: first byte the Y payload,
; second byte the A payload tagged with header bits 0x60 (via $DF57).
emitTaggedVectorWord:
DF4C: 09 60           ORA     #$60                ; OR the 0x60 header tag into the payload byte
DF4E: AA              TAX                         
DF4F: 98              TYA                         
DF50: 4C 57 DF        JMP     emitVectorWord      ; lay the tagged pair into the display list via the shared vector-word writer

; emit the fixed {0x40,0x80} vector header word at the cursor 0x74 (0x40
; then 0x80 into display RAM) and advance the cursor two.
emitVectorHeaderWord:
DF53: A9 40           LDA     #$40                ; load the canonical beam-position header low byte 0x40
DF55: A2 80           LDX     #$80                ; load its high byte 0x80 -- the fixed record-opening header word

; lay one vector word into the display list: store A at cursor (0x74)+0
; and X at +1, then advance the cursor two via 0x74/0x75.
emitVectorWord:
DF57: A0 00           LDY     #$00                ; point at the draw cursor origin (offset 0)

; indexed word emit: store A at cursor 0x74 offset Y and X at Y+1, then
; advance the cursor past them by (Y+1)+1.
emitVectorWordAtOffset:
DF59: 91 74           STA     (workRam+74),Y      ; store the word's low byte at draw cursor + y
DF5B: C8              INY                         
DF5C: 8A              TXA                         
DF5D: 91 74           STA     (workRam+74),Y      ; store the word's high byte at draw cursor + y+1

; advance the 16-bit display cursor 0x74/0x75 by Y+1 (carry forced set),
; storing the new low byte at 0x74 and bumping high byte 0x75 on overflow;
; returns the new low byte in A.
advanceDisplayCursor:
DF5F: 98              TYA                         ; stride for the cursor advance = the offset just consumed
DF60: 38              SEC                         ; force the carry so the add yields stride + 1
DF61: 65 74           ADC     workRam+74          ; add stride+1 to the draw cursor low byte
DF63: 85 74           STA     workRam+74          ; store the advanced draw cursor low byte
DF65: 90 02           BCC     loc_df69            ; if no page overflow, done
DF67: E6 75           INC     workRam+75          ; carry the draw cursor into the next page

loc_df69:
DF69: 60              RTS                         

; Emits a 0x70-tagged vector word with a zero data byte through the draw
; cursor loc_74.
emitBlankVectorWordTag70:
DF6A: A0 00           LDY     #$00                ; zero the data byte for a blank 0x70-tagged record opener

; Emits one vector word through the draw cursor loc_74 -- first byte the y
; payload, second byte the a payload OR 0x70 -- then advances.
emitVectorWordTag70:
DF6C: 09 70           ORA     #$70                ; OR the 0x70 header tag into the payload byte
DF6E: AA              TAX                         
DF6F: 98              TYA                         
DF70: 4C 57 DF        JMP     emitVectorWord      ; lay the tagged word via the shared vector-word writer

; Stashes the index byte into the key cell loc_73, then scales the two
; coordinates and emits the scaled coordinate record.
emitKeyedScaledCoordinateRecord:
DF73: 84 73           STY     workRam+73          ; stash the record's key/index byte into the header cell (loc_73)

; Widens two input values by four with sign extension into the delta pairs
; loc_6e/loc_6f and loc_70/loc_71, then emits the coordinate record they
; anchor.
emitScaledCoordinateRecord:
DF75: A0 00           LDY     #$00                
DF77: 0A              ASL     A                   ; shift the first coordinate left (x2)
DF78: 90 01           BCC     loc_df7b            ; if no sign bit shifted out, leave the sign fill zero
DF7A: 88              DEY                         ; else set an all-ones negative sign fill

loc_df7b:
DF7B: 84 6F           STY     workRam+6F          ; store the first delta's high-byte sign fill (loc_6f)
DF7D: 0A              ASL     A                   ; shift again -- x4 total
DF7E: 26 6F           ROL     workRam+6F          ; roll the shifted-out bit into the delta high byte
DF80: 85 6E           STA     workRam+6E          ; store the first coordinate's scaled low byte (loc_6e)
DF82: 8A              TXA                         ; second coordinate into a
DF83: 0A              ASL     A                   ; shift the second coordinate left (x2)
DF84: A0 00           LDY     #$00                
DF86: 90 01           BCC     loc_df89            ; sign check for the second coordinate
DF88: 88              DEY                         ; set an all-ones negative sign fill

loc_df89:
DF89: 84 71           STY     workRam+71          ; store the second delta's high-byte sign fill (loc_71)
DF8B: 0A              ASL     A                   ; shift again -- x4 total
DF8C: 26 71           ROL     workRam+71          ; roll the shifted-out bit into the delta high byte
DF8E: 85 70           STA     workRam+70          ; store the second coordinate's scaled low byte (loc_70)
DF90: A2 6E           LDX     #$6E                ; anchor the coordinate record at the first delta pair (loc_6e)

; Emits a four-byte coordinate record from the zeropage slots off x --
; loc_2+x, loc_3+x masked 0x1f, loc_0+x, and a key-folded 5-bit loc_1+x --
; through the draw cursor loc_74.
emitCoordinateRecord:
DF92: A0 00           LDY     #$00                ; point at the draw cursor origin
DF94: B5 02           LDA     workRam+2,X         ; read coordinate source byte (loc_2 + x)
DF96: 91 74           STA     (workRam+74),Y      ; write it at draw cursor +0
DF98: B5 03           LDA     workRam+3,X         ; read source byte (loc_3 + x)
DF9A: 29 1F           AND     #$1F                ; clip to five bits
DF9C: C8              INY                         
DF9D: 91 74           STA     (workRam+74),Y      ; write at draw cursor +1
DF9F: B5 00           LDA     workRam,X           ; read source byte (loc_0 + x)
DFA1: C8              INY                         
DFA2: 91 74           STA     (workRam+74),Y      ; write at draw cursor +2
DFA4: B5 01           LDA     workRam+1,X         ; read the key-folded source byte (loc_1 + x)
DFA6: 45 73           EOR     workRam+73          ; XOR with the record header key (loc_73)
DFA8: 29 1F           AND     #$1F                ; keep only the low five bits re-keyed
DFAA: 45 73           EOR     workRam+73          ; XOR the key back so the top three bits come from the key

; Stores one final record byte at the next draw-cursor slot loc_74 and
; either advances the cursor or, on wrap to zero, runs the terminating
; nibble run.
emitRecordTailByte:
DFAC: C8              INY                         ; advance to the record's tail slot
DFAD: 91 74           STA     (workRam+74),Y      ; write the key-folded fourth byte at the tail slot
DFAF: D0 AE           BNE     advanceDisplayCursor; if the slot index has not wrapped to zero, advance the cursor -- else terminate the run

; Emits a run of y zeropage bytes from the top index a+y-1 downward, each
; byte as its high then low nibble via the glyph-word lookup, chaining
; carry so only the final low nibble sees carry cleared as the terminator.
emitNibbleDigitRun:
DFB1: 38              SEC                         ; seed the carry set for the digit run
DFB2: 08              PHP                         
DFB3: 88              DEY                         ; run counter = length - 1
DFB4: 84 AE           STY     workRam+AE          ; store the run counter (loc_ae)
DFB6: 18              CLC                         
DFB7: 65 AE           ADC     workRam+AE          ; index = run base + count -- the top byte of the run
DFB9: 28              PLP                         
DFBA: AA              TAX                         ; working index into x

loc_dfbb:
DFBB: 08              PHP                         
DFBC: 86 AF           STX     workRam+AF          ; save the working index (loc_af)
DFBE: B5 00           LDA     workRam,X           ; fetch the packed byte at the run base + x
DFC0: 4A              LSR     A                   ; shift the packed byte's high nibble down into the low four bits
DFC1: 4A              LSR     A                   
DFC2: 4A              LSR     A                   
DFC3: 4A              LSR     A                   
DFC4: 28              PLP                         
DFC5: 20 19 DF        JSR     emitStrokeWordFromNibble; emit the high nibble as a glyph stroke word
DFC8: A5 AE           LDA     workRam+AE          ; reload the run counter
DFCA: D0 01           BNE     loc_dfcd            ; if not the last byte, keep the propagating carry
DFCC: 18              CLC                         ; last byte -- force the carry clear as the run terminator marker

loc_dfcd:
DFCD: A6 AF           LDX     workRam+AF          ; restore the working index
DFCF: B5 00           LDA     workRam,X           ; re-fetch the packed byte for its low nibble
DFD1: 20 19 DF        JSR     emitStrokeWordFromNibble; emit the low nibble as a glyph stroke word
DFD4: A6 AF           LDX     workRam+AF          ; 
DFD6: CA              DEX                         ; step to the previous byte of the run
DFD7: C6 AE           DEC     workRam+AE          ; decrement the run counter
DFD9: 10 E0           BPL     loc_dfbb            ; loop while the counter stays non-negative
DFDB: 60              RTS                         

; ---- $DFDC-$DFFF: data ----
DFDC: 10 10 40 40 90 90 FF FF 00 0C 16 1E 20 1E 16 0C
DFEC: 00 F4 EA E2 E0 E2 EA F4 00 0C 16 1E 00 00
  
DFFA: 04 D7  ; NMI vector to D704
DFFC: 3F D9  ; RESET vector to D93F
DFFE: 04 D7  ; IRQ vector to D704