.segment "CODE"

;; Assuming that the 'x' register indexes an item on its pool, increment the
;; register as many times as to point to the next one. Bound checking is not
;; performed, it's up to the caller to implement that.
.macro NEXT_ITEM_INDEX_X
    inx
    inx
    inx
.endmacro

.scope Items
    ;;;
    ;; Shadowed variables from the Player scope. This is mainly to be
    ;; compatible with cl65.
    zp_player_screen_y = $40    ; asan:ignore
    zp_player_screen_x = $45    ; asan:ignore
    PLAYER_WAIST  = $0C

    ;; Maximum amount of items allowed on screen at the same time.
    POOL_CAPACITY = 3

    ;; The amount of bytes each pool item takes.
    SIZEOF_POOL_ITEM = 3

    ;; The capacity of the items pool in bytes.
    POOL_CAPACITY_BYTES = POOL_CAPACITY * SIZEOF_POOL_ITEM

    ;; 1. State: $FF for invalid, otherwise:
    ;;   |PFD- CKKK|; where:
    ;;   |
    ;;   |- P: following the player
    ;;   |- F: falling.
    ;;   |- D: dropping: together with 'falling', but the player cannot re-grab it.
    ;;   |- C: 1: collectable (i.e. disappears on collision); 0: part (i.e. follows the player)
    ;;   |- K: object kind (00: high shuttle; 01: mid shuttle; 10: fuel; 11: regular item; 100: coin)
    ;;
    ;; 2. Y coordinate.
    ;; 3. X coordinate.
    zp_pool_base = $C0          ; asan:reserve POOL_CAPACITY_BYTES

    ;; Preserves the index on 'zp_pool_base' in Items::update().
    zp_pool_index = $C9

    ;; TODO: stabilize and document.
    ;;
    ;; Y tile | X tile | palette/tile ID
    zp_current_tiles = $E7       ; asan:reserve POOL_CAPACITY_BYTES

    ;;
    ;; TODO: stabilize and document.
    ;;
    ;; |GNS- --FF|
    ;; |
    ;; |- G: the player is grabbing an item
    ;; |- N: a fuel tank is needed.
    ;; |- S: there is a fuel tank on screen. (TODO: needed?)
    ;; |- F: number of falling items.
    zp_state = $CA

    ;; Number of shuttle parts (or fuel tanks) that have been collected so far.
    zp_collected = $CB

    ;; Coordinate where the dropping of items takes place. This comes in two
    ;; versions, as the "collision" is done in the tile coordinates so to give
    ;; some leeway to the player; but the dropping itself has to fall from the
    ;; exact screen coordinates or otherwise the dropping would feel weird.z
    DROPPING_SCREEN_X = $A8
    DROPPING_TILE_X = $15

    ;; Y screen coordinates in order for various parts to be considered as
    ;; "collected".
    MID_SHUTTLE_Y = $A7
    HIGH_SHUTTLE_Y = $97
    FUEL_SHUTTLE_Y = $B8

    ;; Constants for 'Items::zp_timer'.
    ITEM_TIMER = HZ * 4
    ITEM_TIMER_LO = ITEM_TIMER & $00FF
    ITEM_TIMER_HI = (ITEM_TIMER & $FF00) >> 4

    ;; Timer that determines when to drop a new item from the sky. It is
    ;; initialized on screen entry or after time out.
    ;;
    ;; NOTE: 16-bit integer in little-endian format.
    zp_timer = $CC              ; asan:reserve $02

    ;; Initialize variables just before switching to the current level.
    ;;
    ;; NOTE: variables initialized here are supposed to live after
    ;; deaths. Hence, they will only be re-initialized on either after a game
    ;; over, or switching to a new level.
    .proc init_level
        ldx #0

        lda Globals::zp_level_kind
        bne @other_screens

        lda #0
        sta Items::zp_state

        ;; We haven't collected anything yet, but it's convenient for us to mock
        ;; that the ship part on the right side of the screen is actually
        ;; collected.
        lda #1
        sta Items::zp_collected

        ;; State for the top part of the shuttle.
        lda #0
        sta Items::zp_pool_base, x

        ;; Screen and tile coordinates for the top part of the shuttle.
        lda #$4F
        sta Items::zp_pool_base + 1, x
        lsr
        lsr
        lsr
        sta Items::zp_current_tiles, x
        lda #$29
        sta Items::zp_pool_base + 2, x
        lsr
        lsr
        lsr
        sta Items::zp_current_tiles + 1, x

        ;; Palettes.
        lda #0
        sta Items::zp_current_tiles + 2, x

        ;; State of the middle part of the shuttle.
        lda #1
        sta Items::zp_pool_base + 3, x

        ;; Screen and tile coordinates for the middle part of the shuttle.
        lda #$67
        sta Items::zp_pool_base + 4, x
        lsr
        lsr
        lsr
        sta Items::zp_current_tiles + 3, x
        lda #$81
        sta Items::zp_pool_base + 5, x
        lsr
        lsr
        lsr
        sta Items::zp_current_tiles + 4, x

        ;; Palettes.
        lda #0
        sta Items::zp_current_tiles + 6, x

        beq @invalidate_third

    @other_screens:
        ;; Fuel tanks are needed, that's all.
        lda #%01000000
        sta Items::zp_state

        ;; Shuttle parts are counted as "collected". This makes the
        ;; implementation on other parts easier.
        lda #3
        sta Items::zp_collected

        ;; Invalidate the first and the second slots.
        lda #$FF
        sta Items::zp_pool_base, x
        sta Items::zp_pool_base + 3, x

    @invalidate_third:
        ;; Always invalidate the third item.
        lda #$FF
        sta Items::zp_pool_base + 6, x

        rts
    .endproc

    ;; Initialize a fresh screen.
    ;;
    ;; NOTE: this should _only_ be called whenever we initialize the
    ;; screen. This happens either when switching to it for the first time, but
    ;; also after a death. That is, unlike Items::init_level() things here are
    ;; reset for good.
    .proc init
        ;; Initialize the timer.
        lda #ITEM_TIMER_LO
        sta Items::zp_timer
        lda #ITEM_TIMER_HI
        sta Items::zp_timer + 1

        rts
    .endproc

    ;; Allocate an item indexed by 'x' from the `zp_pool_base` buffer, and set
    ;; it to OAM-reserved space indexed via 'y'.
    ;;
    ;; The 'y' register will be updated by increasing its value by 16,
    ;; indicating the amount of bytes allocated in OAM space.
    ;;
    ;; The 'x' register will be _preserved_.
    ;;
    ;; NOTE: this function assumes that the item is in a valid state. That's up
    ;; to the caller to check before calling this function.
    .proc allocate_x_y
        lda Items::zp_pool_base, x
        and #$07

        ;; Should we allocate a part from the shuttle?
        bne @try_next_shuttle
        lda #$04
        bne @no_attributes
    @try_next_shuttle:
        cmp #$01
        bne @do_fuel_or_regular
        lda #$06
        bne @no_attributes

    @do_fuel_or_regular:
        ;; Is it a fuel tank?
        lda Items::zp_pool_base, x
        and #$03
        cmp #2
        bne @regular
        cmp #4
        beq @coin

        ;; Then just pick the tile from the fuel tank and pick the right
        ;; palette.
        lda #$0C
        sta Globals::zp_arg0
        lda #2
        sta Globals::zp_arg1
        JAL allocate_metasprite_x_y

    @regular:
        ;; This is a regular item
        lda Items::zp_current_tiles + 2, x
        lsr
        lsr
        lsr
        lsr
        sta Globals::zp_arg1
        lda Items::zp_current_tiles + 2, x
        and #$0F
        stx Globals::zp_tmp0
        tax
        lda regular_items, x
        ldx Globals::zp_tmp0
        sta Globals::zp_arg0
        JAL allocate_metasprite_x_y

    @coin:
        lda #$0A

    @no_attributes:
        sta Globals::zp_arg0
        lda #0
        sta Globals::zp_arg1
        JAL allocate_metasprite_x_y

    regular_items:
        ;; Tile IDs for all collectible items. Note that some of them are
        ;; repeated. This is in part to get to 8 items in total which makes the
        ;; implementation easier, but it also give more chances to some items
        ;; than some other more rare.
        .byte $62, $62, $64, $64, $66, $68, $68, $6A
    .endproc

    ;; Allocate a meta-sprite (made of 4 sprites) on the same terms as
    ;; Items::allocate_x_y(). Moreover, it expects the following parameters:
    ;;
    ;;  - Globals::zp_arg0: the tile ID.
    ;;  - Globals::zp_arg1: the attributes for each sprite.
    .proc allocate_metasprite_x_y
        ;; Y coordinates
        lda Items::zp_pool_base + 1, x
        sta OAM::m_sprites, y                       ; top left
        sta OAM::m_sprites + 4, y                   ; top right
        clc
        adc #8
        sta OAM::m_sprites + 8, y                   ; bottom left
        sta OAM::m_sprites + 12, y                  ; bottom right

        ;; Tile IDs
        lda Globals::zp_arg0
        sta OAM::m_sprites + 1, y                   ; top left
        clc
        adc #1
        sta OAM::m_sprites + 5, y                   ; top right

        lda Globals::zp_arg0
        clc
        adc #$10
        sta OAM::m_sprites + 9, y                   ; bottom left
        clc
        adc #1
        sta OAM::m_sprites + 13, y                  ; bottom right

        ;; Attributes
        lda Globals::zp_arg1
        sta OAM::m_sprites + 2, y                   ; top left
        sta OAM::m_sprites + 6, y                   ; top right
        sta OAM::m_sprites + 10, y                  ; bottom left
        sta OAM::m_sprites + 14, y                  ; bottom right

        ;; X coordinates.
        lda Items::zp_pool_base + 2, x              ; top left
        sta OAM::m_sprites + 3, y
        sta OAM::m_sprites + 11, y                  ; bottom left
        clc
        adc #8
        sta OAM::m_sprites + 7, y                   ; top right
        sta OAM::m_sprites + 15, y                  ; bottom right

        ;; And update the 'y' register.
        tya
        clc
        adc #16
        tay

        rts
    .endproc

    .proc update
        ldx #0

        ;; In 'Globals::zp_arg3' we will store the index of a free item slot. If
        ;; a free slot is found, at the end of this function the timer will be
        ;; decremented and, if it times out, then a new item will be allocated
        ;; at this stored index.
        lda #$FF
        sta Globals::zp_arg3

        ;; The loop index is kept on memory so the 'y' register can be abused
        ;; inside of it.
        ldy #POOL_CAPACITY
        sty Globals::zp_idx

        ;; The player's coordinates are cached into arguments in memory so they
        ;; can be used for collision checking. Note that we are targetting for
        ;; the center of the player, which feels at a fair point for item
        ;; interactions.
        lda zp_player_screen_y
        clc
        adc #PLAYER_WAIST
        lsr
        lsr
        lsr
        sta Globals::zp_arg0
        lda zp_player_screen_x
        lsr
        lsr
        lsr
        clc
        adc #1
        sta Globals::zp_arg1

    @loop:
        ;; This index will be valid throughout the iteration so different
        ;; functions can rely on it.
        stx Items::zp_pool_index

        ;; Is it valid?
        lda Items::zp_pool_base, x
        cmp #$FF
        bne @check_status

        ;; Save the index of this free slot.
        stx Globals::zp_arg3
        jmp @next

    @check_status:
        ;; If it's resting, then just check for collision. Otherwise, we either
        ;; fall/drop or follow the player.
        and #$C0
        bne @check_fall
        jmp @check_collision
    @check_fall:
        cmp #$40
        beq @do_fall

        ;;;
        ;; Follow the player.

        ;; Neither of the above. Then, just follow the player.
        lda zp_player_screen_y
        clc
        adc #8
        sta Items::zp_pool_base + 1, x
        lda zp_player_screen_x
        sta Items::zp_pool_base + 2, x

        ;; Are we at the zone where we must drop items?
        ldy Globals::zp_arg1
        dey
        cpy #DROPPING_TILE_X
        beq @drop
        jmp @next

    @drop:
        ;; Yeah! Then the item stops being in 'following player' mode and is
        ;; dropped (F & D set).
        lda Items::zp_pool_base, x
        and #$7F
        ora #%01100000
        sta Items::zp_pool_base, x

        ;; Unset the 'grabbing' bit and increase the number of falling items.
        lda Items::zp_state
        and #$7F
        sta Items::zp_state
        inc Items::zp_state

        ;; And we force the item to be on the exact X screen position so to
        ;; adjust from the player's subpixel movement.
        lda #DROPPING_SCREEN_X
        sta Items::zp_pool_base + 2, x

        jmp @next

        ;;;
        ;; Fall/drop.

    @do_fall:
        ;; Update the Y screen/tile coordinates so the item is falling.
        inc Items::zp_pool_base + 1, x
        lda Items::zp_pool_base + 1, x
        lsr
        lsr
        lsr
        sta Items::zp_current_tiles, x

        ;; Is the item being dropped? If not, then we just check for collision.
        lda Items::zp_pool_base, x
        and #$20
        beq @check_collision

        ;; This is a fuel tank or a shuttle part that is aligned with the
        ;; shuttle platform. We will load in 'a' the exact screen coordinates
        ;; where each part should stop.
        lda Items::zp_pool_base, x
        and #$07
        beq @high_shuttle
        cmp #1
        beq @mid_shuttle
        lda #FUEL_SHUTTLE_Y
        bne @drop_check
    @mid_shuttle:
        lda #MID_SHUTTLE_Y
        bne @drop_check
    @high_shuttle:
        lda #HIGH_SHUTTLE_Y

    @drop_check:
        ;; Does this item reach its dropping limit? If not just go to the next
        ;; item. Note that this also works if it's below it, as the player can
        ;; drop things from the ground too.
        cmp Items::zp_pool_base + 1, x
        bcc @is_dropped
        jmp @next

    @is_dropped:
        ;; Enable the 'ppu' and the 'shuttle' flags.
        lda Globals::zp_flags
        ora #%01100000
        sta Globals::zp_flags

        ;; Increase the number of collected items.
        inc Items::zp_collected

        ;; Now we unset the 'S' bit, which is unconditionally true regardless of
        ;; the collection state. That being said, if we still need to collect
        ;; more fuel tanks (the rocket has all its parts and we have not filled
        ;; it with all tanks), then we set the 'N' bit.
        lda Items::zp_state
        ldy Items::zp_collected
        cpy #3
        bcc @set_new_state
        cpy #9
        beq @set_new_state
        ora #$40
    @set_new_state:
        and #%11011111
        sta Items::zp_state

        ;; Decrease the number of falling items.
        dec Items::zp_state

        ;; Save the index of this free slot.
        stx Globals::zp_arg3

        ;; And invalidate this item.
        lda #$FF
        sta Items::zp_pool_base, x

        ;; All collision checks that were needed for 'collision' mode have been
        ;; done. We can just move to the next item.
        jmp @next

    ;;;
    ;; Collision checks.

    @check_collision:
        ;; Check collision with the player if it's alive. Otherwise let's check
        ;; for background collision.
        lda Globals::zp_flags
        and #$10
        bne @background
        jsr Items::collides_with_player
        beq @background

        ;; A collision happened! Get collected or follow the player (if possible).
        lda Items::zp_pool_base, x
        sta Globals::zp_tmp0
        and #$08
        beq @try_to_follow_player
        jsr Items::collect
        jmp @next

    @try_to_follow_player:
        ;; If the player is already grabbing another item, don't even try it.
        bit Items::zp_state
        bmi @next

        ;; We don't need extra precautions except when the level kind is the
        ;; first one. In that case we must guarantee the right shuttle order.
        lda Globals::zp_level_kind
        bne @do_follow_player

        ;; Is this the first shuttle part to be collected?
        lda Items::zp_collected
        cmp #1
        bne @do_follow_player

        ;; Yes! Then it _must_ be the middle part.
        lda Items::zp_pool_base, x
        and #$07
        cmp #$01
        bne @next

    @do_follow_player:
        ;; Mark this item to be in 'following' mode.
        lda Globals::zp_tmp0
        ora #$80

        ;; Moreover, if the 'falling' flag was set, unset it now, and decrease
        ;; the number of falling items.
        bit Globals::zp_tmp0
        bvc @set_modes
        and #%10111111
        dec Items::zp_state
    @set_modes:
        sta Items::zp_pool_base, x

        ;; Mark the player's to be already grabbing an item.
        lda Items::zp_state
        ora #$80
        sta Items::zp_state
        bne @next

    @background:
        ;; If it's not falling, then there's nothing to be done.
        lda Items::zp_pool_base, x
        and #$40
        beq @next

        ;; Check background collision with the bottom part of the item.
        ldy Items::zp_current_tiles, x
        iny
        iny
        sty Globals::zp_arg0
        ldy Items::zp_current_tiles + 1, x
        iny
        sty Globals::zp_arg1
        jsr Background::collides
        beq @preserve_and_next

        ;; It collides with the background! Cancel the previous downwards
        ;; movement.
        ldx Items::zp_pool_index
        dec Items::zp_pool_base + 1, x
        lda Items::zp_pool_base + 1, x
        lsr
        lsr
        lsr
        sta Items::zp_current_tiles, x

        ;; We have to unset the P, F, and D flags.
        lda Items::zp_pool_base, x
        and #$1F
        sta Items::zp_pool_base, x

        ;; And we need to decrease the number of falling items.
        dec Items::zp_state

    @preserve_and_next:
        ldx Items::zp_pool_index
    @next:
        NEXT_ITEM_INDEX_X
        dec Globals::zp_idx
        beq @decrement_timer
        jmp @loop

    @decrement_timer:
        ;; Do we have a free item slot? If not then quit.
        lda Globals::zp_arg3
        cmp #$FF
        beq @end
        tax

        ;; Yes! Decrement the counter.
        lda Items::zp_timer
        sec
        sbc #1
        sta Items::zp_timer
        lda Items::zp_timer + 1
        sbc #0
        sta Items::zp_timer + 1

        ;; If it times out, initialize a new item at this position.
        lda Items::zp_timer
        bne @end
        lda Items::zp_timer + 1
        bne @end
        stx Items::zp_pool_index
        JAL init_item_x

    @end:
        rts
    .endproc

    ;; Initialize an ite from the pool as indexed by the 'x' register.
    ;;
    ;; NOTE: the 'x' register is modified, but the 'y' register is not touched.
    .proc init_item_x
        ;; Reset the timer.
        lda #ITEM_TIMER_LO
        sta Items::zp_timer
        lda #ITEM_TIMER_HI
        sta Items::zp_timer + 1

        ;; We start by generating a new state. If the state is asking for a fuel
        ;; tank, let it be. Otherwise it will be a regular item.
        ;;
        ;; TODO: coin support.
        lda Items::zp_state
        and #$40
        beq @regular

        ;; While we are on the topic of having a fuel tank, update the state for
        ;; items by unsetting the N bit, and setting the S one.
        lda Items::zp_state
        and #%10111111
        ora #$20
        sta Items::zp_state

        lda #$42
        bne @set_state
    @regular:
        lda #$4B
    @set_state:
        sta Items::zp_pool_base, x

        ;; As for the Y coordinate, the sky is the limit ;)
        lda #Background::UPPER_MARGIN_Y_COORD
        sta Items::zp_pool_base + 1, x
        lsr
        lsr
        lsr
        sta Items::zp_current_tiles, x

        ;; For the X coordinate we pick a random value, mask it so we only get
        ;; four possible values, and we get the place from there.
        jsr Prng::random_valid_y_coordinate
        and #$03
        tax
        lda possible_x_positions, x
        ldx Items::zp_pool_index
        sta Items::zp_pool_base + 2, x
        lsr
        lsr
        lsr
        sta Items::zp_current_tiles + 1, x

        ;; Set the palette and a tile ID by assuming it's a regular item. Both
        ;; of these values will be picked at random. Other items will simply
        ;; ignore this byte, but for a regular item it's important. We could've
        ;; done this conditionally, but adding an extra check while preserving
        ;; some guaranteed registers and what not means more trouble than just
        ;; doing things unconditionally.
        jsr Prng::random_valid_y_coordinate
        and #$03
        asl
        asl
        asl
        asl
        sta Globals::zp_tmp0
        jsr Prng::random_valid_y_coordinate
        and #$07
        ora Globals::zp_tmp0
        ldx Items::zp_pool_index
        sta Items::zp_current_tiles + 2, x

        ;; Update the state to reflect a new falling item.
        inc Items::zp_state

        rts

    possible_x_positions:
        ;; In this order: top left platform, between left-mid platform, mid
        ;; platform, and the right platform.
        .byte $29, $50, $7F, $D0
    .endproc

    ;; Check if the item pointed by the 'x' register is colliding with the
    ;; player.
    ;;
    ;; NOTE: this assumes a 4-sprite meta-sprite, as all items are.
    .proc collides_with_player
        ldx Items::zp_pool_index
        lda Items::zp_current_tiles, x
        cmp #$FF
        beq @no

        ;; Check for the Y tile coordinate. If it's not the same on either the
        ;; upper or the bottom parts of the item, then it's a no.
        cmp Globals::zp_arg0
        beq @check_x
        clc
        adc #1
        cmp Globals::zp_arg0
        bne @no

    @check_x:
        ;; If the Y tile coordinate checks out, let's narrow it down to the X
        ;; coordinate.
        lda Items::zp_current_tiles + 1, x
        cmp Globals::zp_arg1
        beq @yes
        clc
        adc #1
        cmp Globals::zp_arg1
        bne @no

    @yes:
        lda #1
        rts
    @no:
        lda #0
        rts
    .endproc

    ;; Let go the item from the player if there is one being grabbed.
    .proc let_go_on_death
        ;; First of all, we need do check if the player was actually holding an
        ;; item.
        ldx #0
        ldy #Items::POOL_CAPACITY

    @loop:
        lda Items::zp_pool_base, x
        cmp #$FF
        beq @next
        and #$80
        bne @found

    @next:
        NEXT_ITEM_INDEX_X
        dey
        bne @loop
        rts

    @found:
        ;; The player was indeed grabbing an item. Then unset the P flag and set
        ;; the F one.
        lda Items::zp_pool_base, x
        and #$7F
        ora #$40
        sta Items::zp_pool_base, x
        ;; TODO: what if it died while on the ground

        ;; Unset the 'grabbing' bit, and increase the number of falling items.
        lda Items::zp_state
        and #$7F
        sta Items::zp_state
        inc Items::zp_state

        rts
    .endproc

    ;; Collect an item as indexed by 'zp_pool_index'. This function assumes that
    ;; the item is already valid.
    ;;
    ;; NOTE: the 'y' register is preserved.
    .proc collect
        ldx Items::zp_pool_index

        ;; If the collected item was actually falling down, decrease the number
        ;; of falling items.
        lda Items::zp_pool_base, x
        and #$40
        beq @invalidate
        dec Items::zp_state

    @invalidate:
        lda #$FF
        sta Items::zp_pool_base, x

        ;; TODO: score

        rts
    .endproc

    ;; Prepare the background scenary for items. Namely, the rocket parts which
    ;; belong to the background.
    ;;
    ;; NOTE: this has to be called with the PPU disabled.
    .proc prepare_background_scene
        ;; The low part of the rocket.
        bit PPU::m_status
        lda #$2A
        sta PPU::m_address
        lda #$F5
        sta PPU::m_address
        ldx #$0C
        stx PPU::m_data
        inx
        stx PPU::m_data

        lda #$2B
        sta PPU::m_address
        lda #$15
        sta PPU::m_address
        inx
        stx PPU::m_data
        inx
        stx PPU::m_data

        lda Globals::zp_level_kind
        beq @end

    @rest_of_the_rocket:
        jsr draw_high_part_shuttle
        jsr draw_middle_part_shuttle

    @end:
        rts
    .endproc

    ;; Update the background scenary for the shuttle.
    ;;
    ;; NOTE: this has to be called with the PPU disabled.
    .proc update_shuttle
        lda Items::zp_collected
        cmp #9
        bne @high_middle
        bit PPU::m_status
        lda #$2B
        sta PPU::m_address
        lda #$E5
        sta PPU::m_address
        lda #%10100000
        sta PPU::m_data
        jmp @end

    @high_middle:
        lda Items::zp_collected
        cmp #8
        bcc @half_high_middle
        bit PPU::m_status
        lda #$2B
        sta PPU::m_address
        lda #$ED
        sta PPU::m_address
        lda #%10101010
        sta PPU::m_data
        bne @end

    @half_high_middle:
        lda Items::zp_collected
        cmp #7
        bcc @low_middle
        bit PPU::m_status
        lda #$2B
        sta PPU::m_address
        lda #$ED
        sta PPU::m_address
        lda #%10100010
        sta PPU::m_data
        bne @end

    @low_middle:
        lda Items::zp_collected
        cmp #6
        bcc @half_low_middle
        bit PPU::m_status
        lda #$2B
        sta PPU::m_address
        lda #$ED
        sta PPU::m_address
        lda #%10100000
        sta PPU::m_data
        bne @end

    @half_low_middle:
        lda Items::zp_collected
        cmp #5
        bcc @low
        bit PPU::m_status
        lda #$2B
        sta PPU::m_address
        lda #$ED
        sta PPU::m_address
        lda #%00100000
        sta PPU::m_data
        bne @end

    @low:
        lda Items::zp_collected
        cmp #4
        bcc @just_top
        bit PPU::m_status
        lda #$2B
        sta PPU::m_address
        lda #$F5
        sta PPU::m_address
        lda #%10101010
        sta PPU::m_data

    @just_top:
        cmp #3
        bcc @just_middle
        jsr draw_high_part_shuttle

    @just_middle:
        jsr draw_middle_part_shuttle

    @end:
        rts
    .endproc

    ;; Update the background scenary to show the middle part of the shuttle.
    ;;
    ;; NOTE: this has to be called with the PPU disabled.
    .proc draw_middle_part_shuttle
        ldx #$08
        ldy #$2A

        bit PPU::m_status
        sty PPU::m_address
        lda #$B5
        sta PPU::m_address
        stx PPU::m_data
        inx
        stx PPU::m_data

        bit PPU::m_status
        sty PPU::m_address
        lda #$D5
        sta PPU::m_address
        inx
        stx PPU::m_data
        inx
        stx PPU::m_data

        rts
    .endproc

    ;; Update the background scenary to show the high part of the shuttle.
    ;;
    ;; NOTE: this has to be called with the PPU disabled.
    .proc draw_high_part_shuttle
        ldx #$04
        ldy #$2A

        bit PPU::m_status
        sty PPU::m_address
        lda #$75
        sta PPU::m_address
        stx PPU::m_data
        inx
        stx PPU::m_data

        bit PPU::m_status
        sty PPU::m_address
        lda #$95
        sta PPU::m_address
        inx
        stx PPU::m_data
        inx
        stx PPU::m_data

        rts
    .endproc
.endscope
