            REL

            use   Locator.Macs
            use   Load.Macs
            use   Mem.Macs
            use   Misc.Macs
            use   Util.Macs
            use   EDS.GSOS.Macs
            use   GTE.Macs
            use   Externals.s

; Keycodes
LEFT_ARROW      equ   $08
RIGHT_ARROW     equ   $15
UP_ARROW        equ   $0B
DOWN_ARROW      equ   $0A

; Nametable queue
NT_QUEUE_LEN  equ $1000
NT_QUEUE_SIZE equ {2*NT_QUEUE_LEN}
NT_QUEUE_MOD   equ {NT_QUEUE_SIZE-1}

            mx    %00

; Direct page space
MyUserId    equ   0
ROMStk      equ   2
ROMZeroPg   equ   4
LastScroll  equ   6
TileX       equ   10                      ; GTE tile store coordinates that correspond to the PPUSCROLL edge
TileY       equ   12
ROMScreenEdge equ 14
ROMScrollEdge equ 16
ROMScrollDelta equ 18
OldROMScrollEdge equ 20
CurrScrollEdge equ 22
CurrNTQueueEnd equ 40
BGToggle    equ   44
LastEnable  equ   46
ShowFPS     equ   48

OldOneSec   equ   100

Tmp0        equ   240
Tmp1        equ   242
Tmp2        equ   244
Tmp3        equ   246
Tmp4        equ   248
Tmp5        equ   250
Tmp6        equ   252

FTblPtr     equ   224
FTblTmp     equ   228

            phk
            plb
            sta   MyUserId                ; GS/OS passes the memory manager user ID for the application into the program
            _MTStartUp                    ; GTE requires the miscellaneous toolset to be running

            stz   LastScroll
            stz   TileX
            stz   TileY
            stz   ROMScreenEdge
            stz   ROMScrollEdge
            stz   ROMScrollDelta
            stz   OldROMScrollEdge
            stz   LastAreaType
            stz   ShowFPS

            lda   #1
            sta   BGToggle

            lda   #$0008
            sta   LastEnable

; The next two direct pages will be used by GTE, so get another 2 pages beyond that for the ROM.  We get
; 4K of DP/Stack space by default, so there is plenty to share

            tdc
            sta   DPSave
            clc
            adc   #$300
            sta   ROMZeroPg
            clc
            adc   #$1FF                   ; Stack starts at the top of the page
            sta   ROMStk

; Initialize the sound hardware for APU emulation

            jsr   APUStartUp

; Start up GTE to drive the graphics
;            brl   :debug

            lda   #ENGINE_MODE_USER_TOOL  ; Engine in Fast Mode as a User Tool
            jsr   GTEStartUp              ; Load and install the GTE User Tool

; Install a VBL callback task that we will use to invoke the NMI routine in the ROM
            pea   vblCallback
            pea   #^nmiTask
            pea   #nmiTask
            _GTESetAddress

; Install a custom sprite renderer that will read directly off of the OAM table
            pea   extSpriteRenderer
            pea   #^drawOAMSprites
            pea   #drawOAMSprites
            _GTESetAddress

; Install a custom callback to update the tile store as the screen scrolls
            pea   extBG0TileUpdate
            pea   #^UpdateFromPPU
            pea   #UpdateFromPPU
            _GTESetAddress

; Install a custom tile blitter to merge PPU attributes with the extracted tile data
            pea   userTileCallback
            pea   #^NESTileBlitter
            pea   #NESTileBlitter
            _GTESetAddress

; Get the address of a low-level routine that can be used to draw a tile directly to the graphics screen
;            pea   rawDrawTile
;            _GTEGetAddress
;            lda   1,s
;            sta   drawTilePatch+1
;            lda   2,s
;            sta   drawTilePatch+2
;            pla
;            plx

; Initialize the graphics screen playfield (256x160).  The NES is 240 lines high, so 160
; is a reasonable compromise.

            pea   #128
            pea   #200
;            pea   #80
;            pea   #144
            _GTESetScreenMode

            ldx   #Area1Palette
            lda   #TmpPalette
            jsr   NESColorToIIgs

            pea   $0000
            pea   #^TmpPalette
            pea   #TmpPalette
            _GTESetPalette

; Convert the CHR ROM from the cart into GTE tiles
:debug
            ldx   #0
            ldy   #0
:tloop
            phx
            phy

            lda   #TileBuff
            jsr  ConvertROMTile2

            lda  1,s

            pha                  ; start
            inc
            pha                  ; finish
            pea   #^TileBuff     ; pointer
            pea   #TileBuff
            _GTELoadTileSet

            ply
            iny

            pla
            clc
            adc  #16                         ; NES tiles are 16 bytes
            tax
            cpx  #512*16
            bcc  :tloop

; Start the FPS counter
            pha
            _GTEGetSeconds
            pla
            sta   OldOneSec

; Set an internal flag to tell the VBL interrupt handler that it is
; ok to start invoking the game logic.  The ROM code has to be run
; at 60 Hz because it controls the audio.  Bad audio is way worse
; than a choppy refresh rate.
;
; Call the boot code in the ROM

            ldx   #SMBStart
            jsr   romxfer

; Apply hacks
;WorldNumber                 =     $075f
;LevelNumber                 =     $075c
;AreaNumber                  =     $0760
;OffScr_WorldNumber    = $0766
;OffScr_AreaNumber     = $0767
;OffScr_LevelNumber    = $0763

EvtLoop
:spin       lda   nmiCount
            beq   :spin
            stz   nmiCount

;            sep   #$20
;            lda   #0
;            stal  ROMBase+$075f
;            stal  ROMBase+$0766

;            lda   #3
;            stal  ROMBase+$0763
;            stal  ROMBase+$075c

;            lda   #4
;            stal  ROMBase+$0767
;            stal  ROMBase+$0760
;            rep   #$30

; The GTE playfield is 41 tiles wide, but the NES is 32 tiles wide.  Fortunately, the game
; keeps track of the global coordinates of each level at
;
; ScreenEdge_PageLoc          =     $071a
; ScreenEdge_X_Pos            =     $071c
;
; So we can keep our scrolling in sync with the game.  In order to efficiently update the
; GTE tile store, we handle this in two stages
;
; 1. When new column(s) are exposed, set the tiles directly from the PPU nametable memory
; 2. When the PPU nametable memory is updated in an area that is already on-screen, set the tile

            lda   singleStepMode
            bne   :skip_render
            jsr   RenderFrame

            lda   ShowFPS
            beq   :no_fps

            inc   frameCount
            pha
            _GTEGetSeconds
            pla
            cmp   OldOneSec
            beq   :skip_render
            sta   OldOneSec
            
            ldx   #0
            ldy   #$FFFF
            lda   frameCount
            
            jsr   DrawByte
            lda   frameCount
            stz   frameCount
:no_fps
:skip_render

            lda   lastKey

            bit   #PAD_KEY_DOWN
            beq   EvtLoop

            and   #$007F

; Put the game in single-step mode
            cmp   #'s'
            bne   :not_s

            lda   #1                         ; Stop the VBL interrupt from running the game logic
            sta   singleStepMode

            jsr   triggerNMI
            jsr   RenderFrame
            brl   EvtLoop
:not_s

            cmp   #'f'
            bne   :not_f
            lda   #1
            eor   ShowFPS
            sta   ShowFPS
            bne   :no_clear
            ldx   #0
            jsr   ClearWord
:no_clear
            brl   EvtLoop
:not_f

            cmp   #'b'                       ; Togget background flag
            bne   :not_b
            lda   BGToggle
            eor   #$0001
            sta   BGToggle
            pha
            _GTEEnableBackground
            brl   EvtLoop
:not_b

            cmp   #'g'                       ; Re-enable VBL-drive game logic
            bne   :not_g
            stz   singleStepMode
            brl   EvtLoop
:not_g

            cmp   #'a'                       ; Show how much time APU simulation is taking
            bne   :not_a
            lda   show_border
            eor   #$0001
            sta   show_border
            brl   EvtLoop
:not_a

            cmp   #'0'
            bne   :not_0
            stz   APU_FORCE_OFF
            brl   EvtLoop
:not_0

            cmp   #'1'
            bne   :not_1
            lda   #$01
            jsr   ToggleAPUChannel
            brl   EvtLoop
:not_1

            cmp   #'2'
            bne   :not_2
            lda   #$02
            jsr   ToggleAPUChannel
            brl   EvtLoop
:not_2

            cmp   #'3'
            bne   :not_3
            lda   #$04
            jsr   ToggleAPUChannel
            brl   EvtLoop
:not_3

            cmp   #'4'
            bne   :not_4
            lda   #$08
            jsr   ToggleAPUChannel
            brl   EvtLoop
:not_4

            cmp   #'r'               ; Refresh
            bne   :not_r
            jsr   CopyStatus

            lda   ROMScreenEdge      ; global tile index
            and   #$003F             ; mod the mirrored nametable size
            ldx   #33
            ldy   #0
            jsr   CopyNametable
            brl   EvtLoop
:not_r

            cmp   #'v'
            bne   :not_v
            lda   ROMScreenEdge
            clc
            adc   #33
            and   #$003F
            ldx   #1
            ldy   #33
            jsr   CopyNametable
            brl   EvtLoop

:not_v
            cmp   #'q'
            beq   Exit
            brl   EvtLoop

Exit
            _GTEShutDown
            jsr   APUShutDown
Quit
            _QuitGS    qtRec
qtRec       adrl  $0000
            da    $00
Greyscale   dw    $0000,$5555,$AAAA,$FFFF
            dw    $0000,$5555,$AAAA,$FFFF
            dw    $0000,$5555,$AAAA,$FFFF
            dw    $0000,$5555,$AAAA,$FFFF

TmpPalette  ds    32

lastKey     dw    0
singleStepMode dw 0
nmiCount    dw    0
DPSave      dw    0
LastAreaType dw   0
frameCount  dw    0 
; Toggle an APU control bit
ToggleAPUChannel
            pha
            lda   #$0001
            stal  APU_FORCE_OFF
            pla

            sep   #$30
            eorl  APU_STATUS
            jsl   APU_STATUS_FORCE
            rep   #$30
            rts

; Convert NES palette entries to IIgs
; X = NES palette (16 color indices)
; A = 32 byte array to write results
NESColorToIIgs
            sta   Tmp0
            stz   Tmp1

:loop       lda:  0,x
            asl
            tay
            lda   nesPalette,y
            ldy   Tmp1
            sta   (Tmp0),y

            inx
            inx

            iny
            iny
            sty   Tmp1
            cpy   #32
            bcc   :loop
            rts

; Helper to perform the essential functions of rendering a frame
RenderFrame

; Get the current global coordinates

            sei
            lda   nt_queue_end      ; Freeze the end of the queue that contains updates up until "now"
            sta   CurrNTQueueEnd
            lda   ROMScrollEdge     ; This is set in the VBL IRQ
            sta   CurrScrollEdge    ; Freeze it, then we can let the IRQs continue
            cli

            lsr
            lsr
            lsr
            sta   ROMScreenEdge

; Calculate how many blocks have been scrolled into view

            lda   CurrScrollEdge
            sec
            sbc   OldROMScrollEdge
            sta   Tmp1             ; This is the raw number of pixels moved

            lda   OldROMScrollEdge ; This is the number of partial pixels the old scroll position occupied
            and   #7
            sta   Tmp0
            lda   #7
            sec
            sbc   Tmp0             ; This account for situations where going from 8 -> 9 reveals a new column
            clc
            adc   Tmp1
            lsr
            lsr
            lsr
            sta   ROMScrollDelta   ; This many columns have been revealed

            lda   CurrScrollEdge
            sta   OldROMScrollEdge ; Stash a copy for the next round through
            lsr
            pha
            pea   $0000
            _GTESetBG0Origin

            lda   ppumask
            and   #$0008     ; Isolate background enable/disable bit
            cmp   LastEnable
            beq   :bghop
            sta   LastEnable
            pha
            _GTEEnableBackground
:bghop

            pea   $FFFF      ; NES mode
            _GTERender

; Check the AreaType and see if the palette needs to be changed. We do this after the screen is blitted
; so the palette does not get changed too eary while old pixels are still on the screen.

            ldal  ROMBase+$074E
            and   #$00FF
            cmp   LastAreaType
            beq   :no_area_change
            sta   LastAreaType
            jsr   SetAreaType
:no_area_change

            rts

SetAreaType
            cmp   #5
            bcs   :out

            asl
            tay
            ldx   AreaPalettes,y      ; First parameter to NESColorToIIgs

            asl
            tay
            lda   SwizzleTables,y
            sta   SwizzlePtr
            lda   SwizzleTables+2,y
            sta   SwizzlePtr+2

            lda   #TmpPalette
            jsr   NESColorToIIgs

; Special copy routine; do not touch color indices 0, 1, 14 or 15 -- we let the NES PPU handle those

            ldx   #4
:loop
            lda   TmpPalette,x
            stal  $E19E00,x
            inx
            inx
            cpx   #2*14
            bcc   :loop
:out
            rts

AreaPalettes  dw   WaterPalette,Area1Palette,Area2Palette,Area3Palette,Area4Palette
SwizzleTables adrl AT0_T0,AT1_T0,AT2_T0,AT3_T0,AT2_T0
SwizzlePtr    adrl AT1_T0

; Take a PPU address and convert it to a tile store coordinate
;
; Inputs
;   A = PPU address
;   X = Global Address in GTE bytes

; Outputs
;   X = relative tile store column
;   Y = relative tile store row
PPUAddrToTileStore
:PPUAddr    equ   Tmp0
:PPUTopLeft equ   Tmp1

            sta   :PPUAddr

; Based on the global coordiate, figure out whhat the left column in the PPU RAM is
            txa
            lsr                        ; Convert from bytes to tiles
            lsr
            and   #$003F               ; Logically there are 64 tiles in the mirrored PPU RAM
            sta   :PPUTopLeft

; Now we have the PPU address of the column that corresponds to the left edge of the GTE
; playfield.  Now, calculate the relative coordinates of the passed PPU address

; The y-coordinate is easy. Since the top-left address is always on the top row (row = 0),
; we just have to extract the row that the PPU address occupies.

            lda   :PPUAddr
            and   #$03E0               ; Take the middle 5 bits (ignore nametable)
            lsr
            lsr
            lsr
            lsr
            lsr
            tay                        ; Save the y-index here

; The GTE playfield is positioned with the third PPU row as it's origin and is 25 tiles high.
; If the PPU tile is in rows 0, 1, 27, 28 or 29 then we can ignore it

            cpy  #2
            bcc  :outOfRange
            cpy  #27
            bcs  :outOfRange

; Adjust the relative position down by 2

            dey
            dey

; The horizontal coordinate is a bit trickier. We need to add 32 to the horizontal
; coordinate in it's in the second nametable

            lda   :PPUAddr
            and   #$041F               ; Project it to the top row
            bit   #$0400
            beq   *+5
            ora   #$0020               ; Add 32
            and   #$003F               ; Clamp to range of 0 - 63

; If we're in the top two row, they don't scroll, so skip the displacement
            cpy   #2
            bcc   :noshift
 
; Now calculate the difference between the PPUTopLeft index and this value

            cmp   :PPUTopLeft
            bcs   :ahead               ; If the provided address is > than the origin, just calc the difference
            adc   #64                  ; Else distance is (a - 0) + (64 - b) = a + 64 - b
            sec
:ahead      sbc   :PPUTopLeft
:noshift

; If this value is larger than the payfield + 1, then we have the carry set or clear

            tax
            cmp   #33
            rts

:outOfRange
            sec
            rts

; If there is some other reason to draw the full screen, this will empty the queue
ClearNTQueue
            lda  CurrNTQueueEnd
            sta  nt_queue_front
            rts

; Scan through the queue of tiles that need to be updated before applying the scroll change
DrainNTQueue
:GTELeftEdge equ Tmp3
:PPUAddr     equ Tmp4
;:Count       equ Tmp5

;            stz  :Count

; Prep item -- get the logical block of the left edge of the scroll window

            lda   CurrScrollEdge             ; Global position that the GTE playfield was set to
            lsr 
            sta   :GTELeftEdge

            lda   nt_queue_front
            cmp   CurrNTQueueEnd
            beq   :out

:loop
            tax
            phx                              ; Save the x register

            lda   nt_queue,x                 ; get the PPU address that was stored
            sta   :PPUAddr                   ; save for later if we draw this tile

            lda   :PPUAddr
            ldx   :GTELeftEdge               ; get the global coordinate
            jsr   PPUAddrToTileStore         ; convert the PPU address to relative tile store coordinates
            bcc   :set_tile                  ; if it's onscreen, draw it

:skip
            pla                             ; Pop the saved x-register into the accumulator
            inc
            inc
            and   #NT_QUEUE_MOD
            cmp   CurrNTQueueEnd
            bne   :loop

:out
            sta   nt_queue_front
            rts

:set_tile
; Now we have the relative position from the left edge of the tile.  Add the origin
; tile to it (unless we're in rows 0 or 1)

            txa
            cpy   #2
            bcc   :toprow
            clc
            adc   TileX
            cmp   #41
            bcc   *+5
            sbc   #41
:toprow
            pha                             ; Tile Store horizontal tile coordinate
            phy                             ; No translation needed for y

            ldx   :PPUAddr
            lda   PPU_MEM,x
            and   #$00FF
            ora   #$0100+TILE_USER_BIT
            pha
            jsr   GetPaletteSelect
            ora   1,s                      ; Merge bits 9 and 10 into the Tile ID that's on the stack
            sta   1,s

; NOTE: Better to draw this into the PEA field directly.  Calling GTESetTile queues up the tile and can cause
;       issues because many frames can pass before Render gets control again.  We need to expose a 
;       _SetTileImmediate function in the list of function callbacks....

            _GTESetTile
;            inc   :Count
            brl   :skip


; Do the calculation to get the palette select bits from the attribute byte that corresponds to the
; PPU address in the x-registers
GetPaletteSelect

; Get the palette select bits.  We need to calculate both the address of the attribute value and 
; which bits to isolate from the byte and then merge into the TileId.  The most straighforward way
; is to identify the quadrant right away and have alternate code paths

            txa
            and   #$2C00
            ora   #$23C0                   ; Make sure to put the addr in the $2xxx range
            sta   Tmp6                     ; Base attribute table address

; Not calculate the byte within the attribute table

            txa
            and   #$001F                   ; 32 byte rows, divide by 4
            lsr
            lsr
            ora   Tmp6
            sta   Tmp6

            txa
            and   #$0380                   ; Isolate the top 3 bits
            lsr
            lsr
            lsr
            lsr
            ora   Tmp6
            tay

            lda   PPU_MEM,y                ; This is the attribute byte
            and   #$00FF
            pha                            ; Which we save for a minute

; Now figure out the quadrant that this address is in for the attribute byte value

            txa
            bit  #%01000010
            beq  :top_left
            bit  #%01000000
            beq  :top_right
            bit  #%00000010
            beq  :bot_left

:bot_right
            pla
            and  #$00C0
            asl
            asl
            asl
            bra  :set_palette

:bot_left
            pla
            and  #$0030
            xba
            lsr
            lsr
            lsr
            bra  :set_palette

:top_right
            pla
            and  #$000C
            xba
            lsr
            bra  :set_palette

:top_left
            pla
            and  #$0003
            xba
            asl

:set_palette
            rts

; Copy the necessary columns into the TileStore when setting a new scroll position
UpdateFromPPU
:StartXMod164 equ   36

            phb
            phd

; Snag the StartXmod164 value from the GTE direct page so we can calulate the tile origin
; ourselves

            ldx  :StartXMod164

            phk
            plb
            lda   DPSave
            tcd

            txa
            lsr
            lsr
            sta   TileX              ; Tile column of playfield origin

; Debug the PPU writes

*             ldy   #0
*             ldx   #0
*             lda   #0
* :log_loop
*             phy
*             pha

*             cpy   ppu_write_log_len
*             bcc   :write_val

*             pha
*             tax
*             ldy   #$FFFF
*             jsr   ClearWord

*             pla
*             clc
*             adc   #160-16
*             tax
*             jsr   ClearWord

*             bra   :next

* :write_val
*             pha
*             phy

*             tax
*             lda   ppu_write_log,y
*             ldy   #$FFFF
*             jsr   DrawWord

*             ply
*             pla
*             clc
*             adc   #160-16
*             tax
*             lda   ppu_write_log+50,y
*             ldy   #$FFFF
*             jsr   DrawWord

* :next       pla
*             ply

*             iny
*             iny

*             clc
*             adc   #8*160

*             cpy   #50
*             bcc   :log_loop

*             stz   ppu_write_log_len

; Show the queue depth

;            lda   CurrNTQueueEnd
;            sec
;            sbc   nt_queue_front
;            bpl   *+5
;            adc   #NT_QUEUE_SIZE
;            lsr                      ; Number of items in the queue
;            ldx   #0
;            ldy   #$FFFF
;            jsr   DrawWord

; Check the scroll delta, if it's negative or just large enough, do a whole copy of the current PPU
; memory into the TileStore

            lda   ROMScrollDelta
            beq   :queue

            cmp   #32
            bcc   :partial

            jsr   ClearNTQueue       ; kill any pending updates
            lda   ROMScreenEdge      ; global tile index
            and   #$003F             ; mod the mirrored nametable size
            ldx   #33                ; do the full width
            ldy   #0
            jsr   CopyNametable
            bra   :done

; Calculate the difference between the old and new
:partial
            jsr   DrainNTQueue

            lda   #33
            sec
            sbc   ROMScrollDelta
            tay

            ldx   ROMScrollDelta
            inx
            inx

            lda   ROMScreenEdge
            clc
            adc   #33
            sec
            sbc   ROMScrollDelta
            and   #$003F

            jsr   CopyNametable
:done
            pld
            plb
            rtl

; Just drain the queue of any on-screen changes and then exit
:queue
            jsr   DrainNTQueue
            pld
            plb
            rtl

CopyStatus
; Copy the first two rows from $2400 because they don't scroll

            ldy   #0
:yloop
            ldx   #0
            tya
            clc
            adc   #2
            asl
            asl
            asl
            asl
            asl
            sta   Tmp2
            stz   Tmp3
:xloop
            phx                            ; Save X and Y
            phy

            phx                            ; x = GTE tile index = PPU tile index
            phy                            ; No vertical scroll, so screen_y = tile_y

            ldx   Tmp2                     ; Nametable address
            lda   PPU_MEM+$2000,x
            and   #$00FF
            ora   #$0100+TILE_USER_BIT
            pha
            jsr   GetPaletteSelect
            ora   1,s                      ; Merge bits 9 and 10 into the Tile ID that's on the stack
            sta   1,s

; Advance to the next tile (no wrapping needed)

            inx
            stx   Tmp2

            _GTESetTile

            ply
            plx

            inx
            cpx   #33
            bcc   :xloop

            iny
            cpy   #2
            bcc   :yloop
            rts

; Copy the tile and attribute bytes into the GTE buffer
;
; A = logical column in mirrored PPU memory (0 - 63)
; X = number of columns to copy
; Y = number of GTE tiles to offset
CopyNametable
            sta   Tmp2
            bit   #$0020                  ; Is it >32?
            beq   *+5
            ora   #$0400                  ; Move to the next nametable
            and   #$041F                  ; Mask to the top of a valid column

            clc                           ; Add in the offset since we only copy rows 2 - 27
            adc   #4*32
            sta   Tmp0                    ; base address offset into nametable memory

            stx   Tmp4

            tya
            clc
            adc   TileX
            cmp   #41
            bcc   *+5
            sbc   #41
            sta   Tmp5

; NES RAM $6D = page, $86 = player_x_in_page can be used to get a global position in the level, then subtracting the 
; player's x coordinate will give us the global coordinate of the left edge of the screen and allow us to map between
; the GTE tile buffer and the PPU nametables

; Skip the first two rows -- call CopyStatus to get those

            ldy   #2
:yloop
            ldx   #0

            lda   Tmp0                    ; Get the base address for this row
            sta   Tmp2                    ; coarse x-scroll

            lda   Tmp5
            sta   Tmp3                    ; Keep a separate variable for the GTE tile position
:xloop
            phx                            ; Save X and Y
            phy

            pei   Tmp3                     ; Wrap-around tile column
            phy                            ; No vertical scroll, so screen_y = tile_y

            ldx   Tmp2                     ; Nametable address
            lda   PPU_MEM+$2000,x
            and   #$00FF
            ora   #$0100+TILE_USER_BIT     ; USe top 256 tiles and set as a user-defined tile
            pha
            jsr   GetPaletteSelect
            ora   1,s                      ; Merge bits 9 and 10 into the Tile ID that's on the stack
            sta   1,s

; Advance to the next tile (handle nametable wrapping)

            lda   #$001F
            and   Tmp2
            cmp   #$001F
            bne   :inc_x
            txa
            and   #$FFE0
            eor   #$0400
            sta   Tmp2
            bra   :x_hop

:inc_x      inx
            stx   Tmp2
:x_hop

            _GTESetTile

            ply
            plx

            lda   Tmp3
            inc
            cmp   #41
            bcc   *+5
            lda   #0
            sta   Tmp3

            inx
            cpx   Tmp4
            bcc   :xloop

            lda   Tmp0
            clc
            adc   #32
            sta   Tmp0

            iny
            cpy   #25
            bcc   :yloop

            rts

; Trigger an NMI in the ROM
triggerNMI
            ldal  ppuctrl               ; If the ROM has not enabled VBL NMI, also skip
            bit   #$80
            beq   :skip

            ldal  ppustatus             ; Set the bit that the VBL has started
            ora   #$80
            stal  ppustatus

            ldx   #NonMaskableInterrupt
            jsr   romxfer

; Immediately after the NMI returns, freeze some of the global state variables so we can sync up with this frame when
; we render the next frame.  Since we're in an interrupt handler here, sno change of the variables changing under
; our nose

            sep   #$20
            ldal  ROMBase+$071a
            xba
            ldal  ROMBase+$071c
            rep   #$20
            sta   ROMScrollEdge

:skip       rts

; Expose joypad bits from GTE to the ROM: A-B-Select-Start-Up-Down-Left-Right
native_joy  ENT
            db   0,0

; X = address in the rom file
; A = address to write
;
; This keeps the tile in 2-bit mode in a format that makes it easy to look up pixel data
; based on a dynamic palette selection
;
; Tiles are stored in a pre-shifted, 16-bit format (2 bits per pixel): 0000000w wxxyyzz0
; When rendered, the 2-bit palette selection is passed in bits 9 and 10 and ORed with
; the palette data to create a single word of 00000ppw wxxyyzz0.  This value is used
; to index directly into a 2048-byte swizzel table that will load the appropriate
; pixel data for the word.  There are 2 swizzle tables, one for tiles and one for sprites
; that take care of mapping the 25 possible on-screen colors to a 16-color palette.
ConvertROMTile2
:DPtr        equ   Tmp1
:MPtr        equ   Tmp2

            jsr   ROMTileToLookup

; Now we have 32 bytes (4 x 8) with each byte being a 4-bit value that holds two pairs of bits
; from the PPU pattern table.  We use these 4-bit values as lookup indices into tables
; that decode the values differently depending on the use case.

            sta   :DPtr
            clc
            adc   #32                ; Move to the mask
            sta   :MPtr

            lda   #0                 ; Zero out high byte
            sep   #$30               ; 8-bit mode
            ldy   #0

:loop
            lda   (:DPtr),y           ; Load the index for the initial high nibble
            tax
            lda   MLUT4,x             ; Look up the mask value for this byte. This table decodes the 4 bits into an 8-bit mask
            sta   (:MPtr),y

            lda   DLUT2,x             ; Look up the two, 2-bit pixel values for this quad of bits.  This remains a 4-bit value
            asl
            asl
            asl
            asl
            sta   Tmp3

            iny
            lda   (:DPtr),y
            tax
            lda   DLUT2,x             ; Look up the two, 2-bit pixel values for next quad of bits
            ora   Tmp3                ; Move it int othe top nibble since it will decode to the top-byte on the SHR screen

            dey
            sta   (:DPtr),y           ; Put in low byte
            iny
            lda   #0
            sta   (:DPtr),y           ; Zero high byte

            lda   MLUT4,x
            sta   (:MPtr),y

            iny
            cpy   #32
            bcc   :loop


; Reverse and shift the data

            rep    #$30
            ldy    #8
            ldx    :DPtr

:rloop
            lda:   0,x              ; Load the word: xx00
            jsr    reverse2         ; Reverse the bottom byte in chunks of 2 bits
            asl                     ; Shift by 1 for indexing
            sta:   66,x
            asl:   0,x              ; Shift the original word, too

            lda:   2,x
            jsr    reverse2
            asl
            sta:   64,x
            asl:   2,x

            lda:   32,x
            jsr    reverse4
            sta:   98,x
            lda:   34,x
            jsr    reverse4
            sta:   96,x

            inx
            inx
            inx
            inx
            dey
            bne    :rloop
            rts

; X = address in the rom file
; A = address to write

ConvertROMTile
:DPtr        equ   Tmp1
:MPtr        equ   Tmp2

            jsr   ROMTileToLookup

            sta   :DPtr
            clc
            adc   #32                ; Move to the mask
            sta   :MPtr

            sep   #$30               ; 8-bit mode
            ldy   #0

:loop
            lda   (:DPtr),y           ; Load the index for this tile byte
            tax
            lda   DLUT4,x             ; Look up the two, 4-bit pixel values for this quad of bits
            sta   (:DPtr),y
            lda   MLUT4,x             ; Look up the mask value for this byte
            sta   (:MPtr),y
            iny
            cpy   #32
            bcc   :loop

; Switch back to 16-bit mode and flip the tile data before returning

            rep    #$20
            ldy    #16
            ldx    :DPtr

:rloop
            lda:   0,x
            jsr    reverse4
            sta:   66,x
            lda:   2,x
            jsr    reverse4
            sta:   64,x
            inx
            inx
            inx
            inx
            dey
            bne    :rloop
            rts

; Build a table of index values for the ROM tile data.  The different routines
; can mix and match the lookup table information as they see fit
;
; X = address in the rom file
; A = address to write
;
; For each byte of pattern table memory, we create two bytes in the DPtr with
; a lookup value for the pixels corresponding to bits in that location
;
; Example:
;   Tile 0: $03,$0F,$1F,$1F,$1C,$24,$26,$66, $00,$00,$00,$00,$1F,$3F,$3F,$7F
;
;                                      0,1  2,3  4,5  6,7
;
;   $03 | 00000011 | 00000000 | $00 -> 0000 0000 0000 0011 -> 00 00 05 00 
;   $0F | 00001111 | 00000000 | $00 -> 0000 0000 0011 0011 -> 00 00 55 00
;   $1F | 00011111 | 00000000 | $00 -> 0000 0001 0011 0011 -> 01 00 55 00
;   $1F | 00011111 | 00000000 | $00 -> 0000 0001 0011 0011 -> 01 00 55 00
;   $1C | 00011100 | 00011111 | $1F -> 0000 0101 1111 1100 -> 03 00 FA 00
;   $24 | 00100100 | 00111111 | $3F -> 0000 1110 1101 1100 -> 0E 00 BA 00
;   $26 | 00100110 | 00111111 | $3F -> 0000 1110 1101 1110 -> 0E 00 BE 00
;   $66 | 01100110 | 01111111 | $7F -> 0101 1110 1101 1110 -> 3E 00 BE 00
;
;   
; e.g. Plane 0   = 0101 0001 (LSB)
;      Plane 1   = 1001 0001 (MSB)
;
;      For speed, use a table and convert one pair at a time
;
;      Pair 1 = 1001 -> 1001
;      Pair 2 = 0101 -> 0011
;      Pair 3 = 0000 -> 0000
;      Pair 4 = 0101 -> 0011
;
;      Lookup[0] = 10 01 00 11
;      Lookup[1] = 00 00 00 11
;
;      Tile Data  = 63 00 03 00
;      Pixel Data = 12 03 00 03
            mx   %00
ROMTileToLookup
:DPtr       equ   Tmp1
            pha
            phx

            sta   :DPtr
            lda   #0                 ; Clear A and B

            sep   #$20               ; 8-bit mode
            ldy   #0

:loop

; Top two bits from each byte defines the two left-most pixels

            lda   CHR_ROM,x          ; Load the low bits
            and   #$C0
            lsr
            lsr
            sta   Tmp0

            lda   CHR_ROM+8,x        ; Load the high bits
            and   #$C0
            ora   Tmp0
            lsr
            lsr
            lsr
            lsr
            sta   (:DPtr),y          ; First byte
            iny

; Repeat for bits 4 & 5

            lda   CHR_ROM,x
            and   #$30
            lsr
            lsr
            sta   Tmp0

            lda   CHR_ROM+8,x
            and   #$30
            ora   Tmp0
            lsr
            lsr
            sta   (:DPtr),y
            iny

; Repeat for bits 2 & 3

            lda   CHR_ROM,x
            and   #$0C
            lsr
            lsr
            sta   Tmp0

            lda   CHR_ROM+8,x
            and   #$0C
            ora   Tmp0               ; Combine the two and create a lookup value
            sta   (:DPtr),y
            iny

; Repeat for bits 0 & 1

            lda   CHR_ROM,x          ; Load the high bits
            and   #$03
            sta   Tmp0

            lda   CHR_ROM+8,x
            and   #$03
            asl
            asl
            ora   Tmp0                ; Combine the two and create a lookup value
            sta   (:DPtr),y
            iny

            inx
            cpy   #32
            bcc   :loop

            rep    #$20
            plx
            pla
            rts

; Reverse the 2-bit fields in a byte
            mx   %00
reverse2
            php
            sta  Tmp0
            stz  Tmp1

            sep  #$20

            and  #$C0
            lsr
            lsr
            lsr
            lsr
            lsr
            lsr
            tsb  Tmp1

            lda  Tmp0
            and  #$30
            lsr
            lsr
            tsb  Tmp1

            lda  Tmp0
            and  #$0C
            asl
            asl
            tsb  Tmp1

            lda  Tmp0
            and  #$03
            asl
            asl
            asl
            asl
            asl
            asl
            ora  Tmp1

            plp
            rts

; Reverse the nibbles in a word
            mx   %00
reverse4
            xba
            sta   Tmp0
            and   #$0F0F
            asl
            asl
            asl
            asl
            sta   Tmp1
            lda   Tmp0
            and   #$F0F0
            lsr
            lsr
            lsr
            lsr
            ora   Tmp1
            rts

; Look up the 2-bit indexes for the data words
DLUT2       db    $00,$01,$04,$05    ; CHR_ROM[0] = xy, CHR_ROM[8] = 00 -> 0x0y
            db    $02,$03,$06,$07    ; CHR_ROM[0] = xy, CHR_ROM[8] = 01 -> 0x1y
            db    $08,$09,$0C,$0D    ; CHR_ROM[0] = xy, CHR_ROM[8] = 10 ->
            db    $0A,$0B,$0E,$0F    ; CHR_ROM[0] = xy, CHR_ROM[8] = 11

; Look up the 4-bit indexes for the data words
DLUT4       db    $00,$01,$10,$11    ; CHR_ROM[0] = xx, CHR_ROM[8] = 00
            db    $02,$03,$12,$13    ; CHR_ROM[0] = xx, CHR_ROM[8] = 01
            db    $20,$21,$30,$31    ; CHR_ROM[0] = xx, CHR_ROM[8] = 10
            db    $22,$23,$32,$33    ; CHR_ROM[0] = xx, CHR_ROM[8] = 11

MLUT4       db    $FF,$F0,$0F,$00
            db    $F0,$F0,$00,$00
            db    $0F,$00,$0F,$00
            db    $00,$00,$00,$00

; Inverted mask for using eor/and/eor rendering
;MLUT4       db    $00,$0F,$F0,$FF
;            db    $0F,$0F,$FF,$FF
;            db    $F0,$FF,$F0,$FF
;            db    $FF,$FF,$FF,$FF

; Extracted tiles
TileBuff    ds    128

GTEStartUp
            pha                           ; Save engine mode

            pea   $0000
            _LoaderStatus
            pla

            pea   $0000
            pea   $0000
            pea   $0000
            pea   $0000
            pea   $0000                   ; result space

            lda   MyUserId
            pha

            pea   #^ToolPath
            pea   #ToolPath
            pea   $0001                   ; do not load into special memory
            _InitialLoad
            bcc    *+4
            brk    $01

            ply
            pla                           ; Address of the loaded tool
            plx
            ply
            ply

            pea   $8000                   ; User toolset
            pea   $00A0                   ; Set the tool set number
            phx
            pha                           ; Address of function pointer table
            _SetTSPtr
            bcc    *+4
            brk    $02

            plx                            ; Pop the Engine Mode value

            clc                            ; Give GTE two pages of direct page memory
            tdc
            adc   #$0100
            pha
            phx
            lda   MyUserId                 ; Pass the userId for memory allocation
            pha
            _GTEStartUp
            bcc    *+4
            brk    $03

            rts

ToolPath    str   '1/Tool160'

* ; Store sprite and tile data as 0000000w wxxyyzz0 to facilitate swizzle loads

* ; sprite high priority (8-bit acc, compiled)
*             ldy   #PPU_DATA
*             lda   screen
*             andl  tilemask,x
*             ora   (palptr),y          ; 512 byte lookup table per palette
*             sta   screen

* ; sprite low (this is just slow) ....
*             lda   screen
*             beq   empty
*             ; do 4 bits to figure out a mask and then


*             bit   #$FF00
*             ...
*             ...
*             ldy   #PPU_DATA
*             lda   (palptr),y
*             eor   screen
*             andl  tilemask,x
*             and   bgmask
*             eor   screen
*             sta   screen

* ; tile
*             ldy   tiledata,x
*             lda   (palptr),y
*             ldy   tmp
*             sta   abs,y


* ; Custom tile renderer that swizzles the tile data based on the PPU attribute tables. This
* ; is more complicate than just combining the palette select bits with the tile index bits
* ; because the NES can have >16 colors on screen at once, we remap the possible colors
* ; onto a smaller set of indices.
* SwizzleTile
*                  tax
* ]line            equ             0
*                  lup             8
*                  ldal            tiledata+{]line*4},x     ; Tile data is 00ww00xx 00yy00zz
*                  ora             metatile                 ; Pre-calculated metatile mask
*                  and             tilemask+{]line*4},x     ; Set any zero indices to actual zero
*                  sta:            $0004+{]line*$1000},y
*                  ldal            tiledata+{]line*4}+2,x
*                  sta:            $0001+{]line*$1000},y
* ]line            equ             ]line+1
*                  --^
*                  plb
*                  rts



; Transfer control to the ROM.  This function is trampoline that is responsible for
; setting up the direct page and stack for the ROM and then passing control into
; the ROM wrapped in a JSL/RTL vector stashed in the ROM space.
;
; X = ROM Address
romxfer     phb                             ; Save the bank and direct page
            phd
            tsc
            sta   StkSave+1                 ; Save the current stack in the main program
            pea   #^ExtIn                   ; Set the bank to the ROM
            plb

            lda   ROMStk                    ; Set the ROM stack address
            tcs
            lda   ROMZeroPg                 ; Set the ROM zero page
            tcd

            jml   ExtIn
ExtRtn      ENT
            tsx                             ; Copy the stack address returned by the emulator
StkSave     lda   #$0000
            tcs

            pld
            plb
            stx   ROMStk                    ; Keep an updated copy of the stack address
            rts

; VBL Interrupt task (called in native 8-bit mode)
            mx    %11
nmiTask
            ldal  nmiCount
            inc
            stal  nmiCount

            php
            rep   #$30
            phb
            phd

            phk
            plb
            lda   DPSave
            tcd

            jsr   readInput

            ldal  singleStepMode
            bne   :no_nmi
;            lda   #1
;            jsr   setborder

            jsr   triggerNMI

;            lda   #0
;            jsr   setborder
:no_nmi

            pld
            plb
            plp
:skip
            rtl
            mx    %00

readInput
            pha
            _GTEReadControl
            pla
            stal  lastKey                          ; Cache for other code

; Map the GTE field to the NES controller format: A-B-Select-Start-Up-Down-Left-Right

            pha
            and   #PAD_BUTTON_A+PAD_BUTTON_B        ; bits 0x200 and 0x100
            lsr
            lsr
            sta  native_joy

            sep   #$20
            lda   1,s
            cmp   #'n'
            bne   *+6
            lda   #$20
            bra   :nes_merge
            cmp   #'m'
            bne   *+6
            lda   #$10
            bra   :nes_merge
            cmp   #UP_ARROW
            bne   *+6
            lda   #$08
            bra   :nes_merge
            cmp   #DOWN_ARROW
            bne   *+6
            lda   #$04
            bra   :nes_merge
            cmp   #LEFT_ARROW
            bne   *+6
            lda   #$02
            bra   :nes_merge
            cmp   #RIGHT_ARROW
            bne   *+6
            lda   #$01
            bra   :nes_merge
            lda   #0
:nes_merge  ora  native_joy 
            sta  native_joy
            sta  native_joy+1

:nes_done
            rep   #$20
            pla
            rts

            put   App.Msg.s
            put   font.s
            put   palette.s
            put   ppu.s

            ds    \,$00                      ; pad to the next page boundary
PPU_MEM
CHR_ROM     put   chr2.s         ; 8K of CHR-ROM at PPU memory $0000 - $2000
PPU_NT      ds    $2000          ; Nametable memory from $2000 - $3000, $3F00 - $3F14 is palette RAM
PPU_OAM     ds    256            ; 256 bytes of separate OAM RAM

; If AreaStyle is 1 then load an alternate palette 'b'
;
; Palettes of NES color indexes
Area1Palette dw     $22, $00, $29, $1A, $0F, $36, $17, $30, $21, $27, $1A, $16, $00, $00, $16, $18
Area4Palette

; Underground
Area2Palette dw     $0F, $00, $29, $1A, $09, $3C, $1C, $30, $21, $17, $27, $36, $16, $1D, $16, $18

; Castle
Area3Palette dw     $0F, $00, $30, $10, $00, $16, $17, $27, $1C, $36, $1D, $00,  $00, $00, $16, $18

WaterPalette dw     $22, $00, $15, $12, $25, $3A, $1A, $0F, $30, $12, $27, $10,  $16, $00, $16, $18
; Palette remapping
            put   pal_w11.s
            put   apu.s