aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2025-02-04 11:00:22 +0100
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-02-04 22:00:07 +0100
commit6c4ac7727094259b3b5a25e8c168245983e70b2f (patch)
tree089c185a436f57a58f43b6c0ed3eed53e8b93da5
parentfdf44365e56c3741f47cbd421a57f0d074a5492c (diff)
downloadcode.nes-6c4ac7727094259b3b5a25e8c168245983e70b2f.tar.gz
code.nes-6c4ac7727094259b3b5a25e8c168245983e70b2f.zip
scroll: Implement a roulette-like game
Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
-rw-r--r--.gitignore1
-rw-r--r--Makefile5
-rw-r--r--README.md24
-rw-r--r--basics/sprite.s17
-rw-r--r--docs/blink.gifbin0 -> 57479 bytes
-rw-r--r--docs/roulette.gifbin0 -> 1330387 bytes
-rw-r--r--fx/README.md2
-rw-r--r--fx/blink.s11
-rw-r--r--scroll/README.md21
-rw-r--r--scroll/roulette.s521
-rw-r--r--shared/diskun.s6
-rw-r--r--shared/ppu.s11
12 files changed, 580 insertions, 39 deletions
diff --git a/.gitignore b/.gitignore
index 89f9ac0..088ff7b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
out/
+TODO
diff --git a/Makefile b/Makefile
index 461c0b5..066f663 100644
--- a/Makefile
+++ b/Makefile
@@ -54,9 +54,12 @@ space:
.PHONY: scroll
scroll:
- $(E) " CC scroll"
+ $(E) " CC scroll/level"
$(Q) $(CC65) $(CCOPTS) scroll/level.s -C config/nrom.cfg -o out/scroll/level.nes
+ $(E) " CC scroll/roulette"
+ $(Q) $(CC65) $(CCOPTS) scroll/roulette.s -C config/mmc3.cfg -o out/scroll/roulette.nes
+
.PHONY: fx
fx:
$(E) " CC fx/blink"
diff --git a/README.md b/README.md
index 729a8cb..42302a5 100644
--- a/README.md
+++ b/README.md
@@ -26,19 +26,17 @@ watchers or a full debugger.
The examples are distributed like this:
-- `basics`: simple examples which cover basic stuff for NES development. These
- examples are self-contained and supposed to be read by absolute newcomers. The
- description for each example is covered by an initial comment on each file,
- and you can also find more info in the [basics/README.md](./basics/README.md)
- file.
-- `space`: example in which you can move a spaceship with subpixel movement and
- shoot bullets. Consider this an evolution from the `basics/sprite.s` and
- `basics/input.s` examples. That is, we are no longer just showing a sprite,
- but we make it move and perform an action like shooting bullets.
-- `scroll`: different scrolling tactics. Read the
- [scroll/README.md](./scroll/README.md) file for more info.
-- `fx`: simple graphical effects that can be pulled off. Read the
- [fx/README.md](./fx/README.md) file for more info.
+- [basics](./basics/README.md): simple examples which cover basic stuff for NES
+ development. These examples are self-contained and supposed to be read by
+ absolute newcomers. The description for each example is covered by an initial
+ comment on each file.
+- [space](./space/README.md): example in which you can move a spaceship with
+ subpixel movement and shoot bullets. Consider this an evolution from the
+ `basics/sprite.s` and `basics/input.s` examples. That is, we are no longer
+ just showing a sprite, but we make it move and perform an action like shooting
+ bullets.
+- [scroll](./scroll/README.md): different scrolling tactics.
+- [fx](./fx/README.md): simple graphical effects that can be pulled off.
## Other projects
diff --git a/basics/sprite.s b/basics/sprite.s
index 1fa55ad..1892ce5 100644
--- a/basics/sprite.s
+++ b/basics/sprite.s
@@ -386,17 +386,8 @@ palettes:
.byte $0F, $00, $00, $00
.endproc
-;; WRITE_PPU_DATA is a macro that will write into PPUADDR the given address and
-;; into PPUDATA the given byte value.
-.macro WRITE_PPU_DATA address, value
- bit $2002 ; PPUSTATUS
- lda #.HIBYTE(address)
- sta $2006 ; PPUADDR
- lda #.LOBYTE(address)
- sta $2006 ; PPUADDR
- lda #value
- sta $2007 ; PPUDATA
-.endmacro
+;; Include the WRITE_PPU_DATA macro. More on this below.
+.include "../shared/ppu.s"
;; init_nametable loads the relevant data to the nametable that is then going to
;; be used in order to build up the background.
@@ -468,8 +459,8 @@ palettes:
;; Now we have to do the same for the rest of the elements. I have gone
;; through the same process of fetching the offset from a canvas I drew on
;; NEXXT and here's the result. Instead of repeating te same code over and
- ;; over but with slightly different values, I am using this macro I created
- ;; to do the same.
+ ;; over but with slightly different values, I am using the WRITE_PPU_DATA
+ ;; from `shared/ppu.s` to do the same.
WRITE_PPU_DATA $20B9, $04
WRITE_PPU_DATA $21CE, $04
WRITE_PPU_DATA $21BA, $04
diff --git a/docs/blink.gif b/docs/blink.gif
new file mode 100644
index 0000000..a54d560
--- /dev/null
+++ b/docs/blink.gif
Binary files differ
diff --git a/docs/roulette.gif b/docs/roulette.gif
new file mode 100644
index 0000000..805f9f0
--- /dev/null
+++ b/docs/roulette.gif
Binary files differ
diff --git a/fx/README.md b/fx/README.md
index e7194dc..f42f323 100644
--- a/fx/README.md
+++ b/fx/README.md
@@ -10,3 +10,5 @@ the data.
A very simple example is provided in [blink.s](./blink.s) where a character
blinks periodically.
+
+![blink.gif](../docs/blink.gif)
diff --git a/fx/blink.s b/fx/blink.s
index c4d141d..adf8553 100644
--- a/fx/blink.s
+++ b/fx/blink.s
@@ -8,8 +8,9 @@
;; `basics/unrom`, which also perform bank switching albeit with simpler
;; hardware.
;;
-;; This examples basically makes use of the bank switching capabilities of the
-;; MMC3 chip, so the handling of IRQs is left to other examples.
+;; This example basically makes use of the bank switching capabilities of the
+;; MMC3 chip, so the handling of IRQs is left to other examples (e.g. see
+;; `scroll/roulette.s`).
;;
;; On the context of the MMC3 chip, the CPU ROM space is divided into 4 regions
;; of 8KB each. Two of these regions are swappable, the rest are fixed. This is
@@ -154,9 +155,9 @@ reset:
sta MMC3::MIRRORING
;; Interrupts are a whole topic of their own on the MMC3 chip. Here we just
- ;; disable them and leave it for other examples. Again, this is tied to a
- ;; register mapped to a region in memory (even addresses on $E000-$FFFE in
- ;; this case).
+ ;; disable them and leave it for other examples (e.g. see
+ ;; `scroll/roulette.s`). Again, this is tied to a register mapped to a
+ ;; region in memory (even addresses on $E000-$FFFE in this case).
sta MMC3::IRQ_DISABLE
;; PRG RAM can be protected from writes, but this is a feature that many
diff --git a/scroll/README.md b/scroll/README.md
index d24117d..be33f87 100644
--- a/scroll/README.md
+++ b/scroll/README.md
@@ -15,7 +15,20 @@ information), while allowing the rest of the screen to scroll as expected. In
the end, it's the same example as `level.s` (same level to scroll), but the top
part does not move and shows a "THIS DOES NOT MOVE" message.
-Last but not least, the `mmc3.s` example shows how to configure the MMC3 chip
-(e.g. Super Mario Bros. 3, Kirby's Adventure) to have better control on which
-parts of the screen to scroll or not. To showcase this the example implements a
-roulette mini-game (similar to Super Mario Bros. 3 but simpler).
+## Scrolling in different ways in the same frame
+
+Some chips like the MMC3 give programmers a lot of flexibility when it comes to
+mid-frame customization. That is, chips like the MMC3 give an interface in which
+programmers can ask the chip to submit an IRQ on a given exact scanline. One
+main usage of this was to allow a top section of the screen to scroll, while
+leaving a small section at the bottom not to scroll. This way, games were no
+longer required to have a status bar at the top and they could have it at the
+bottom. But these chips allow for a lot of flexibility, so programmers can get
+playful with it. One simple example is the roulette mini-game from Super Mario
+Bros. 3. In here the game asks for two scanline IRQs and then the scroll
+direction is changed on each given IRQ. This way, the background is split in
+three sections that move in different directions/speed. Something similar (but
+more simple) has been reproduced in [roulette.s](./roulette.s), giving the
+following result:
+
+![roulette.png](../docs/roulette.gif)
diff --git a/scroll/roulette.s b/scroll/roulette.s
new file mode 100644
index 0000000..ecf2569
--- /dev/null
+++ b/scroll/roulette.s
@@ -0,0 +1,521 @@
+;;;
+;; Divide the screen in three rows and make them move in different
+;; directions/speed. This is achieved thanks to the MMC3 chip (check the
+;; `fx/blink.s` for further information on this chip). In particular, we are
+;; using the scanline IRQ mechanism provided by this chip to react to different
+;; parts of the screen being rendered:
+;;
+;; 1. The top of the screen moves fast on one direction.
+;; 2. The center of the screen moves fast on the opposite direction.
+;; 3. The bottom of the screen moves on the same direction as 1. but slower.
+;;
+;; This is achieved by setting an IRQ that hits on points 2. and 3., and then we
+;; manipulate the PPU scroll register mid frame. That is, all you see are just
+;; background elements being scrolled in different ways.
+;;
+;; This trick was used, for example, on Super Mario Bros. 3 for the roulette
+;; mini-game. That being said, usually games used this capability to handle
+;; scroll on the top part of the screen, and then resetting the scroll on the
+;; lower part, so they could show a status section (again, as Super Mario Bros.
+;; 3 does inside of a level).
+
+;; Include helpful definitions.
+.include "../shared/mmc3.s"
+
+;; Variables used on this example.
+.scope Vars
+ top_scroll = $00
+ center_scroll = $01
+ bottom_scroll = $02
+ is_bottom = $04 ; 0 -> scroll center; 1 -> scroll bottom
+.endscope
+
+.segment "HEADER"
+ .byte 'N', 'E', 'S', $1A
+ .byte $10 ; 16 * 16 PRG-ROM (256KB)
+ .byte $10 ; 16 * 8 CHR-ROM (128KB)
+ .byte $42, $08 ; Mapper 4, battery present, iNES 2.0 header
+ .res 8, 0
+
+.segment "VECTORS"
+ .addr nmi, reset, irq
+
+;;; NOTE: lots of banks, all of them empty since we don't need them :)
+
+.segment "PRG0_00"
+.byte $FF
+.segment "PRG0_01"
+.byte $FF
+.segment "PRG0_02"
+.byte $FF
+.segment "PRG0_03"
+.byte $FF
+.segment "PRG0_04"
+.byte $FF
+.segment "PRG0_05"
+.byte $FF
+.segment "PRG0_06"
+.byte $FF
+.segment "PRG0_07"
+.byte $FF
+.segment "PRG0_08"
+.byte $FF
+.segment "PRG0_09"
+.byte $FF
+.segment "PRG0_0A"
+.byte $FF
+.segment "PRG0_0B"
+.byte $FF
+.segment "PRG0_0C"
+.byte $FF
+.segment "PRG0_0D"
+.byte $FF
+.segment "PRG0_0E"
+.byte $FF
+.segment "PRG1_00"
+.byte $FF
+.segment "PRG1_01"
+.byte $FF
+.segment "PRG1_02"
+.byte $FF
+.segment "PRG1_03"
+.byte $FF
+.segment "PRG1_04"
+.byte $FF
+.segment "PRG1_05"
+.byte $FF
+.segment "PRG1_06"
+.byte $FF
+.segment "PRG1_07"
+.byte $FF
+.segment "PRG1_08"
+.byte $FF
+.segment "PRG1_09"
+.byte $FF
+.segment "PRG1_0A"
+.byte $FF
+.segment "PRG1_0B"
+.byte $FF
+.segment "PRG1_0C"
+.byte $FF
+.segment "PRG1_0D"
+.byte $FF
+.segment "PRG1_0E"
+.byte $FF
+
+;;; NOTE: the first fixed PRG bank will simply contain utilities for moving the
+;;; player around.
+.segment "FIXED"
+.include "../shared/diskun.s"
+
+;;; NOTE: the main bulk of this example. Comments only for the parts which are
+;;; specific to this example.
+.segment "TAIL"
+.include "../shared/ppu.s"
+
+reset:
+ sei
+ cld
+
+ ;; NOTE: as explained on the `basics/sprite.s` example, this is done to
+ ;; disable the APU frame IRQ. This is usually done without giving it a
+ ;; second thought, but it's specially relevant on the MMC3 chip because
+ ;; disabling this allows the `irq` handler to be able to assume that the
+ ;; only kind of IRQ available is a scanline one.
+ ldx #$40
+ stx $4017
+
+ ldx #$ff
+ txs
+
+ inx
+ stx $2000
+ stx $2001
+ stx $4010
+
+ ;;;
+ ;; NOTE: Setup MMC3. Nothing different from `fx/blink.s`. Take that example
+ ;; as a reference on how to configure the MMC3 chip and bank switching on
+ ;; it.
+
+ lda #$00
+ sta MMC3::MIRRORING
+ sta MMC3::IRQ_DISABLE
+
+ lda #$80
+ sta MMC3::RAM_PROTECT
+
+ BANK_REGISTER_SET 0, 0
+ BANK_REGISTER_SET 1, 2
+ BANK_REGISTER_SET 2, 4
+ BANK_REGISTER_SET 3, 5
+ BANK_REGISTER_SET 4, 6
+ BANK_REGISTER_SET 5, 7
+ BANK_REGISTER_SET 6, 0
+ BANK_REGISTER_SET 7, 1
+
+@vblankwait1:
+ bit $2002
+ bpl @vblankwait1
+
+ ldx #0
+ lda #0
+@ram_reset_loop:
+ sta $000, x
+ sta $100, x
+ sta $300, x
+ sta $400, x
+ sta $500, x
+ sta $600, x
+ sta $700, x
+ inx
+ bne @ram_reset_loop
+
+ lda #$ef
+@sprite_reset_loop:
+ sta $200, x
+ inx
+ bne @sprite_reset_loop
+
+ lda #$00
+ sta $2003
+ lda #$02
+ sta $4014
+
+@vblankwait2:
+ bit $2002
+ bpl @vblankwait2
+
+ lda #$3F
+ sta $2006
+ lda #$00
+ sta $2006
+
+ lda #$0F
+ ldx #$20
+@palettes_reset_loop:
+ sta $2007
+ dex
+ bne @palettes_reset_loop
+ jmp main
+
+;; The main function is used here only for further initialization purposes.
+.proc main
+ ;; Initialize both the palettes and the nametables.
+ jsr Diskun::init_palettes
+ jsr init_nametables
+
+ ;; NOTE: enable back interrupts so we can set them up later on `nmi` code.
+ cli
+
+ lda #%10010000
+ sta $2000
+ lda #%00011110
+ sta $2001
+
+@main_game_loop:
+ ;; NOTE: nothing :D
+
+ lda #%10000000
+ ora $20
+ sta $20
+@wait_for_render:
+ bit $20
+ bmi @wait_for_render
+
+ ;; NOTE: no game logic, everything happens on NMI and IRQ handlers.
+
+ jmp @main_game_loop
+.endproc
+
+;; NOTE: everything displayed on this example only happens on the background.
+;; Moreover, to give a more accurate illusion of the scrolling, the same
+;; background elements are repeated on the other nametable. That's why we have
+;; to set the data twice on each section: once for each nametable.
+.proc init_nametables
+ ;; Top left
+ WRITE_PPU_DATA $20A6, $01
+ WRITE_PPU_DATA $20C6, $11
+ WRITE_PPU_DATA $20A7, $02
+ WRITE_PPU_DATA $20C7, $12
+
+ WRITE_PPU_DATA $24A6, $01
+ WRITE_PPU_DATA $24C6, $11
+ WRITE_PPU_DATA $24A7, $02
+ WRITE_PPU_DATA $24C7, $12
+
+ ;; Top center
+ WRITE_PPU_DATA $20AF, $01
+ WRITE_PPU_DATA $20CF, $11
+ WRITE_PPU_DATA $23CB, %01000100
+ WRITE_PPU_DATA $20B0, $02
+ WRITE_PPU_DATA $20D0, $12
+ WRITE_PPU_DATA $23CC, %00010001
+
+ WRITE_PPU_DATA $24AF, $01
+ WRITE_PPU_DATA $24CF, $11
+ WRITE_PPU_DATA $27CB, %01000100
+ WRITE_PPU_DATA $24B0, $02
+ WRITE_PPU_DATA $24D0, $12
+ WRITE_PPU_DATA $27CC, %00010001
+
+ ;; Top right
+ WRITE_PPU_DATA $20B8, $01
+ WRITE_PPU_DATA $20D8, $11
+ WRITE_PPU_DATA $23CE, %00100010
+ WRITE_PPU_DATA $20B9, $02
+ WRITE_PPU_DATA $20D9, $12
+
+ WRITE_PPU_DATA $24B8, $01
+ WRITE_PPU_DATA $24D8, $11
+ WRITE_PPU_DATA $27CE, %00100010
+ WRITE_PPU_DATA $24B9, $02
+ WRITE_PPU_DATA $24D9, $12
+
+ ;; Center left
+ WRITE_PPU_DATA $21E6, $01
+ WRITE_PPU_DATA $2206, $11
+ WRITE_PPU_DATA $21E7, $02
+ WRITE_PPU_DATA $2207, $12
+
+ WRITE_PPU_DATA $25E6, $01
+ WRITE_PPU_DATA $2606, $11
+ WRITE_PPU_DATA $25E7, $02
+ WRITE_PPU_DATA $2607, $12
+
+ ;; Center center
+ WRITE_PPU_DATA $21EF, $01
+ WRITE_PPU_DATA $220F, $11
+ WRITE_PPU_DATA $21F0, $02
+ WRITE_PPU_DATA $2210, $12
+ WRITE_PPU_DATA $23DB, %01000000
+ WRITE_PPU_DATA $23E3, %00000100
+ WRITE_PPU_DATA $23DC, %00010000
+ WRITE_PPU_DATA $23E4, %00000001
+
+ WRITE_PPU_DATA $25EF, $01
+ WRITE_PPU_DATA $260F, $11
+ WRITE_PPU_DATA $25F0, $02
+ WRITE_PPU_DATA $2610, $12
+ WRITE_PPU_DATA $27DB, %01000000
+ WRITE_PPU_DATA $27E3, %00000100
+ WRITE_PPU_DATA $27DC, %00010000
+ WRITE_PPU_DATA $27E4, %00000001
+
+ ;; Center right
+ WRITE_PPU_DATA $21F8, $01
+ WRITE_PPU_DATA $2218, $11
+ WRITE_PPU_DATA $21F9, $02
+ WRITE_PPU_DATA $2219, $12
+ WRITE_PPU_DATA $23DE, %00100000
+ WRITE_PPU_DATA $23E6, %00000010
+
+ WRITE_PPU_DATA $25F8, $01
+ WRITE_PPU_DATA $2618, $11
+ WRITE_PPU_DATA $25F9, $02
+ WRITE_PPU_DATA $2619, $12
+ WRITE_PPU_DATA $27DE, %00100000
+ WRITE_PPU_DATA $27E6, %00000010
+
+ ;; Bottom left
+ WRITE_PPU_DATA $2326, $01
+ WRITE_PPU_DATA $2327, $02
+ WRITE_PPU_DATA $2346, $11
+ WRITE_PPU_DATA $2347, $12
+
+ WRITE_PPU_DATA $2726, $01
+ WRITE_PPU_DATA $2727, $02
+ WRITE_PPU_DATA $2746, $11
+ WRITE_PPU_DATA $2747, $12
+
+ ;; Bottom center
+ WRITE_PPU_DATA $232F, $01
+ WRITE_PPU_DATA $234F, $11
+ WRITE_PPU_DATA $2330, $02
+ WRITE_PPU_DATA $2350, $12
+ WRITE_PPU_DATA $23F3, %01000100
+ WRITE_PPU_DATA $23F4, %00010001
+
+ WRITE_PPU_DATA $272F, $01
+ WRITE_PPU_DATA $274F, $11
+ WRITE_PPU_DATA $2730, $02
+ WRITE_PPU_DATA $2750, $12
+ WRITE_PPU_DATA $27F3, %01000100
+ WRITE_PPU_DATA $27F4, %00010001
+
+ ;; Bottom right
+ WRITE_PPU_DATA $2338, $01
+ WRITE_PPU_DATA $2358, $11
+ WRITE_PPU_DATA $2339, $02
+ WRITE_PPU_DATA $2359, $12
+ WRITE_PPU_DATA $23F6, %00100010
+
+ WRITE_PPU_DATA $2738, $01
+ WRITE_PPU_DATA $2758, $11
+ WRITE_PPU_DATA $2739, $02
+ WRITE_PPU_DATA $2759, $12
+ WRITE_PPU_DATA $27F6, %00100010
+
+ rts
+.endproc
+
+;;;
+;; NOTE: for this example the NMI is a bit different than on other examples. It
+;; has to do mainly two things:
+;; 1. Set up a scanline IRQ so the scroll at the center/bottom is different.
+;; 2. Set the scroll for the top region.
+nmi:
+ bit $20
+ bpl @next
+
+ pha
+ txa
+ pha
+ tya
+ pha
+
+ ;; NOTE: no DMA transfer as usual since there are no sprites involved.
+
+ ;;;
+ ;; NOTE: setting up IRQs for scanline counting.
+
+ ;; Disable scanline IRQs and acknowledge any previous one. Technically
+ ;; speaking this is not needed because the only times we set up an IRQ we
+ ;; know it's going to be acknowledge where it is needed. That being said,
+ ;; let's be safe.
+ ldx #$00
+ stx MMC3::IRQ_DISABLE
+
+ ;; The screen is made up of 240 scan lines. Since we are dividing the screen
+ ;; by 3: 240 / 3 = 80. Hence, the next IRQ should happen on scanline 80,
+ ;; where we would need to update the scroll value through the
+ ;; `{center/bottom}_scroll` values instead. Moreover, note that
+ ;; `MMC3::IRQ_ENABLE` accepts any value, so the same value as the two other
+ ;; registers is just fine.
+ lda #80
+ sta MMC3::IRQ_LATCH
+ sta MMC3::IRQ_RELOAD
+ sta MMC3::IRQ_ENABLE
+
+ ;; The IRQ for scanline 80 has been set up. Now proceed with the scroll for
+ ;; the top section. The scroll will only happen on the X axis and it's going
+ ;; to be a bit fast.
+ bit $2002
+ lda Vars::top_scroll
+ clc
+ adc #2
+ sta Vars::top_scroll
+ sta $2005
+ lda #$00
+ sta $2005
+
+ ;; NOTE: the rest as usual.
+
+ lda #%01111111
+ and $20
+ sta $20
+
+ pla
+ tay
+ pla
+ tax
+ pla
+@next:
+ rti
+
+;;;
+;; NOTE: handle a scanline IRQ.
+;;
+;; Notice that we cannot at first glance know what kind of IRQ is hitting at the
+;; moment, but we have disabled the frame counter on our `reset` code, so on the
+;; context of the MMC3 chip the only thing left are scanline IRQs, which we have
+;; set up on `nmi` code.
+irq:
+ ;; Save current context.
+ pha
+ txa
+ pha
+ tya
+ pha
+
+ ;; Disable IRQs and acknowledge the current one.
+ ldx #$00
+ stx MMC3::IRQ_DISABLE
+
+ ;; What are we trying to scroll, exactly?
+ lda Vars::is_bottom
+ beq @scroll_right
+
+ ;; We are scrolling the bottom section, which scrolls in the same direction
+ ;; as the top one but a bit slower at that. Load the next scroll value on
+ ;; the `a` register and `Vars::bottom_scroll`.
+ lda Vars::bottom_scroll
+ clc
+ adc #1
+ sta Vars::bottom_scroll
+ ldy #0
+ sty Vars::is_bottom
+ jmp @do_scroll
+
+@scroll_right:
+ ;; We are scrolling the center, which works by going on the opposite
+ ;; direction as the top and bottom sections. Load the next scroll value on
+ ;; the `a` register and `Vars::center_scroll`.
+ lda Vars::center_scroll
+ sec
+ adc #$FD
+ sta Vars::center_scroll
+ ldy #1
+ sty Vars::is_bottom
+
+ ;; We are at the center, but there is still the bottom section to be
+ ;; scrolled differently. Hence, set a new scanline IRQ 80 lines ahead of
+ ;; where we are now. This is done in pretty much the same way as we did in
+ ;; `nmi` code.
+ ldx #80
+ stx MMC3::IRQ_LATCH
+ stx MMC3::IRQ_RELOAD
+ stx MMC3::IRQ_ENABLE
+
+@do_scroll:
+ ;; Regardless of the path, the `a` register contains the value for the
+ ;; scroll on the X axis. Store this value now and leave.
+ sta $2005
+ lda #$00
+ sta $2005
+
+ ;; Restore previous context.
+ pla
+ tay
+ pla
+ tax
+ pla
+
+ rti
+
+;;; NOTE: pretty much the same as `fx/blink.s` but we also copy the same data on
+;;; the second pattern table as that's the one being used for background
+;;; elements.
+
+.segment "CHARS"
+.incbin "../assets/diskun0.chr"
+.incbin "../assets/diskun1.chr"
+.incbin "../assets/diskun0.chr"
+.incbin "../assets/diskun1.chr"
+
+;; The 15 other 8KB portions are left empty.
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
+.res $2000, $00
diff --git a/shared/diskun.s b/shared/diskun.s
index d8bab90..8349748 100644
--- a/shared/diskun.s
+++ b/shared/diskun.s
@@ -26,9 +26,9 @@
;; Background
.byte DEFAULT_COLOR, $36, $17, $0F
- .byte DEFAULT_COLOR, $00, $00, $00
- .byte DEFAULT_COLOR, $00, $00, $00
- .byte DEFAULT_COLOR, $00, $00, $00
+ .byte DEFAULT_COLOR, $28, $0F, $30
+ .byte DEFAULT_COLOR, $20, $0F, $30
+ .byte DEFAULT_COLOR, $2F, $20, $30
;; Foreground
.byte DEFAULT_COLOR, $28, $0F, $30
diff --git a/shared/ppu.s b/shared/ppu.s
new file mode 100644
index 0000000..42acb06
--- /dev/null
+++ b/shared/ppu.s
@@ -0,0 +1,11 @@
+;; WRITE_PPU_DATA is a macro that will write into PPUADDR the given address and
+;; into PPUDATA the given byte value.
+.macro WRITE_PPU_DATA address, value
+ bit $2002
+ lda #.HIBYTE(address)
+ sta $2006
+ lda #.LOBYTE(address)
+ sta $2006
+ lda #value
+ sta $2007
+.endmacro