├── gif ├── rolloff.gif ├── rollon.gif ├── smearon.gif ├── xsine.gif ├── xysine.gif ├── ysine.gif └── smearoff.gif ├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── DeadCScroll.asm /gif/rolloff.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gb-archive/DeadCScroll/HEAD/gif/rolloff.gif -------------------------------------------------------------------------------- /gif/rollon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gb-archive/DeadCScroll/HEAD/gif/rollon.gif -------------------------------------------------------------------------------- /gif/smearon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gb-archive/DeadCScroll/HEAD/gif/smearon.gif -------------------------------------------------------------------------------- /gif/xsine.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gb-archive/DeadCScroll/HEAD/gif/xsine.gif -------------------------------------------------------------------------------- /gif/xysine.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gb-archive/DeadCScroll/HEAD/gif/xysine.gif -------------------------------------------------------------------------------- /gif/ysine.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gb-archive/DeadCScroll/HEAD/gif/ysine.gif -------------------------------------------------------------------------------- /gif/smearoff.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gb-archive/DeadCScroll/HEAD/gif/smearoff.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.DS_Store 2 | /DeadCScroll.map 3 | /DeadCScroll.obj 4 | /DeadCScroll.sym 5 | /DeadCScroll.gb 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | ############################################################################################################################ 3 | # Project Symbols 4 | ############################################################################################################################ 5 | 6 | ### Project Environment #################################################################################################### 7 | PROJECT_NAME = DeadCScroll 8 | ROM_NAME = $(PROJECT_NAME).gb 9 | MAP_NAME = $(PROJECT_NAME).map 10 | SYM_NAME = $(PROJECT_NAME).sym 11 | 12 | SYS_INCLUDE_DIR = ../../Include 13 | SOURCE_DIR = . 14 | OBJECT_DIR = . 15 | ROM_DIR = . 16 | 17 | ### Development Tools ###################################################################################################### 18 | PADDING_VALUE = 0xFF 19 | 20 | ASSEMBLER = rgbasm 21 | ASSEMBLER_OPTS = -Weverything -p $(PADDING_VALUE) -i $(SYS_INCLUDE_DIR)/ -i $(SOURCE_DIR)/ 22 | 23 | LINKER = rgblink 24 | LINKER_OPTS = -p $(PADDING_VALUE) 25 | 26 | CARTMAKER = rgbfix 27 | CARTMAKER_OPTS = -v -p $(PADDING_VALUE) 28 | 29 | 30 | ############################################################################################################################ 31 | # File Lists 32 | ############################################################################################################################ 33 | 34 | .SUFFIXES := .gb .map .sym .obj .asm .inc 35 | 36 | ### Paths ################################################################################################################## 37 | vpath %.asm $(SOURCE_DIR) 38 | vpath %.inc $(SOURCE_DIR) $(SYS_INCLUDE_DIR) 39 | vpath %.def $(SOURCE_DIR) 40 | vpath %.obj $(OBJECT_DIR) 41 | vpath %.gb $(ROM_DIR) 42 | vpath %.map $(ROM_DIR) 43 | vpath %.sym $(ROM_DIR) 44 | 45 | ### Lists ################################################################################################################## 46 | SOURCE_BASE_NAMES = $(basename $(notdir $(wildcard $(SOURCE_DIR)/*.asm))) 47 | SOURCE_FILES = $(addprefix $(SOURCE_DIR)/,$(addsuffix .asm,$(SOURCE_BASE_NAMES))) 48 | OBJECT_FILES = $(addprefix $(OBJECT_DIR)/,$(addsuffix .obj,$(SOURCE_BASE_NAMES))) 49 | 50 | 51 | ############################################################################################################################ 52 | # Main Targets 53 | ############################################################################################################################ 54 | 55 | $(ROM_DIR)/$(ROM_NAME): $(OBJECT_FILES) 56 | @echo 57 | @echo Linking 58 | $(LINKER) $(LINKER_OPTS) -m $(ROM_DIR)/$(MAP_NAME) -n $(ROM_DIR)/$(SYM_NAME) -o $(ROM_DIR)/$(ROM_NAME) $(OBJECT_FILES) 59 | @echo 60 | @echo Fixing 61 | $(CARTMAKER) $(CARTMAKER_OPTS) $(ROM_DIR)/$(ROM_NAME) 62 | 63 | 64 | ############################################################################################################################ 65 | # Pattern Rules 66 | ############################################################################################################################ 67 | 68 | ### Explicit Rules ######################################################################################################### 69 | 70 | 71 | ### Implicit Rules ######################################################################################################### 72 | 73 | $(OBJECT_DIR)/%.obj : %.asm 74 | @echo Assembling $( Note: The scroll registers only affect background rendering. They do not change how objects are displayed. 64 | 65 | ## Implementation 66 | 67 | There are three main states that drive the display on the Game Boy: the Horizontal Blank (HBlank), the Vertical Blank (VBlank), and drawing. The HBlank starts when a line of pixels is completely drawn. There is an opportunity to do some work\* before the next line of pixels starts drawing, and there is one HBlank for every line, all the way down the screen. 68 | 69 | > \**The exact amount of time you have depends on several things; most notably how many objects are being drawn on that line. The [PanDocs](https://gbdev.io/pandocs/#pixel-fifo) has a detailed explanation of the timing. (Indeed, read that entire document because it's great!)* 70 | 71 | When all of the lines are completely drawn, the VBlank starts. This interval is always 10 lines high so there is much more time to do some work compared to the HBlank. The VBlank is only secondary to this system though; the focus is the HBlank since we want to change the screen as it draws. The problem that needs to be solved is reliably knowing what value to set for a specific line. 72 | 73 | As previously mentioned, there is a small amount of time that HBlanks give you to do work. This means that the handler has to be as fast as possible. On a limited system like the Game Boy, that usually equates to judicious use of table lookups and buffers. 74 | 75 | There are two key elements to make this system very stable and very fast: 76 | 1. A double-buffering system that holds the data that feeds the HBlank handler 77 | 2. How the buffers are arranged 78 | 79 | ### Double Buffering 80 | 81 | The idea of the double-buffer is that while one buffer is being used by the hardware to draw the screen, you modify (fill) values in the other. When the screen is done drawing, you switch buffers so the one you were just modifying is being used for drawing and you start modifying the other. 82 | 83 | While the Draw Buffer (A) is used to render the screen, you change values in the Fill Buffer (B). 84 | ``` 85 | ┌───────┐ ┌───────┐ 86 | │Draw │ │Fill │ 87 | │Buffer │ │Buffer │ 88 | │ │ │ │ 89 | │ │ │ │ 90 | │ │ │ │ 91 | │ │ │ │ 92 | │A │ │B │ 93 | └───────┘ └───────┘ 94 | ``` 95 | 96 | When the screen is done being drawn (and you know this because the VBlank interrupt would have triggered or the value in `LY` changed to 144), you switch the buffers. 97 | ``` 98 | ┌───────┐ ┌───────┐ 99 | │Fill │ │Draw │ 100 | │Buffer │ │Buffer │ 101 | │ │ │ │ 102 | │ │ │ │ 103 | │ │ │ │ 104 | │ │ │ │ 105 | │A │ │B │ 106 | └───────┘ └───────┘ 107 | ``` 108 | 109 | Here, "switch buffers" means to switch the *purpose* of each buffer. It doesn't mean to copy buffers. Remember, we need this to be as fast as possible so to change buffers, you simply change pointers: 110 | ``` 111 | Draw-->┌───────┐ Fill-->┌───────┐ 112 | Ptr │Buffer │ Ptr │Buffer │ 113 | │A │ │B │ 114 | │ │ │ │ 115 | │ │ │ │ 116 | │ │ │ │ 117 | │ │ │ │ 118 | │ │ │ │ 119 | └───────┘ └───────┘ 120 | ``` 121 | Becomes: 122 | ``` 123 | Fill-->┌───────┐ Draw-->┌───────┐ 124 | Ptr │Buffer │ Ptr │Buffer │ 125 | │A │ │B │ 126 | │ │ │ │ 127 | │ │ │ │ 128 | │ │ │ │ 129 | │ │ │ │ 130 | │ │ │ │ 131 | └───────┘ └───────┘ 132 | ``` 133 | 134 | #### Buffer Size 135 | 136 | The size of each buffer (indeed, any buffer) depends on two things: 137 | - how many elements are needed 138 | - how much data is needed per element 139 | 140 | We know that the buffers exist to support the HBlank handler, so the number of elements in the buffer are however many times the HBlank can trigger. We said earlier that the HBlank starts at the end of every screen line, so the number of elements is at least that many. However, remember *when* the HBlank starts: at the *end* of every line. What do we do if we need to change the 0th line (before *any* line has started drawing)? Well, we need to change that value *before* line 0 starts, which means it has to be done in the VBlank. And **that** means we need one more element. In short, we need the height of the screen plus one (144+1=145) elements in each buffer. 141 | 142 | This tutorial is only concerned with the scroll registers, so it only needs to store 2 values per line: one for `SCY` and one for `SCX`. (You can store more data per line, of course, but this tutorial doesn't require it.) 143 | 144 | In summary: each buffer is 145 2-byte elements (290 bytes), and we need two of them, so the total buffer memory size is 580 bytes. 145 | 146 | #### Location in Memory 147 | 148 | Assume for a moment that you put the buffers physically next to each other in memory. For example, Buffer A is at `$C000` and Buffer B is at `$C122` (the buffer size is 290 bytes). We said earlier that in order to swap buffers, we just swap pointers, so the code that does that might look like this: 149 | 150 | ``` 151 | ; assume the pointers are next to each other in memory 152 | wDrawBuffer: DS 2 ; buffer currently being drawn 153 | wFillBuffer: DS 2 ; buffer currently being modified 154 | 155 | ; swap the contents of each pointer (28 cycles) 156 | ld hl,wDrawBuffer 157 | ld a,[hl+] 158 | ld b,[hl] 159 | ld c,a ; bc = contents of wDrawBuffer 160 | inc hl 161 | ld a,[hl+] ; a = LOW(contents of wFillBuffer) 162 | ld d,[hl] ; d = HIGH(contents of wFillBuffer) 163 | ld hl,wDrawBuffer 164 | ld [hl+],a 165 | ld [hl],d 166 | inc hl 167 | ld a,c 168 | ld [hl+],a 169 | ld [hl],b 170 | ``` 171 | To use a pointer, that code looks like this: 172 | ``` 173 | ; use a pointer (8 cycles) 174 | ld hl,wFillBuffer 175 | ld a,[hl+] 176 | ld h,[hl] 177 | ld l,a ; hl = contents of wFillBuffer ($C000 or $C122) 178 | ``` 179 | 180 | You could certainly implement the system like this, but there is a way to gain some efficiency when swapping buffers and even with the actual pointers themselves. 181 | 182 | Consider this: other than the memory locations, the buffers are identical. Since we're only really concerned with pointers, *where* the buffers reside in memory doesn't really matter. This can be exploited (and optimized!) 183 | 184 | We can keep Buffer A at `$C000`. The buffer size is `$122` bytes, but instead of putting Buffer B at `$C122`, what if we put it at `$C200`? This would make the pointer values `$C000` and `$C200`. Literally a 1-bit difference. This, too, can be exploited! Both pointers end in `$00` so we don't need to store those, which saves 2 bytes. This leaves us with two 1-byte 'pointers': `$C0` and `$C2`. 185 | 186 | To swap the pointers, literally just one bit has to be toggled: 187 | ``` 188 | ; swap the contents of each 'pointer' (11 cycles) 189 | ldh a,[hFillBuffer] 190 | ldh [hDrawBuffer],a 191 | xor $02 192 | ldh [hFillBuffer],a 193 | ``` 194 | And to use a pointer, we only need to do this: 195 | ``` 196 | ; use a 'pointer' (6 cycles) 197 | ldh a,[hFillBuffer] 198 | ld h,a 199 | ld l,0 ; hl = contents of hFillBuffer ($C000 or $C200) 200 | ``` 201 | 202 | You'll notice that the name of the pointers have changed. This is because they were moved into HRAM. (Also notice that they don't have to be next to each other in memory.) They were moved to HRAM for a couple of reasons: it allows an optimization in the swapping code (11 cycles vs 28), and it makes the use code slightly faster. There are only 2 bytes used now so that is a better candidate for moving to HRAM than 4 bytes. 203 | 204 | ### VBlank 205 | 206 | In this system, code in the VBlank is responsible for two things: 207 | - swapping the pointers 208 | - setting the data for line 0 209 | 210 | We've already seen what swapping the pointers looks like, but how is the data set for line 0? We need to emulate an HBlank handler running for "line -1" by getting the start of the new draw buffer and setting the scroll registers with the first data pair: 211 | 212 | ``` 213 | ldh a,[hDrawBuffer] 214 | ld h,a 215 | ld l,0 216 | 217 | ; set the scroll registers 218 | ld a,[hl+] 219 | ldh [rSCY],a 220 | ld a,[hl+] 221 | ldh [rSCX],a 222 | ``` 223 | It's convenient that the scroll register addresses are next to each other. The data in the buffer is in the same order so as you can see in the code fragment above, this makes writing simple. 224 | 225 | ### HBlank Handler 226 | 227 | In an HBlank handler, **every cycle counts**! So don't do any work in there unless it's absolutely necessary. This is a good target for hyper-optimizations -- especially if you are changing VRAM (like palettes) -- so one should design around that optimization. 228 | 229 | ``` 230 | HBlankHandler:: 231 | push af 232 | push hl 233 | 234 | ; obtain the pointer to the data pair 235 | ldh a,[rLY] 236 | inc a 237 | add a,a ; double the offset since each line uses 2 bytes 238 | ld l,a 239 | ldh a,[hDrawBuffer] 240 | adc 0 241 | ld h,a ; hl now points to somewhere in the draw buffer 242 | 243 | ; set the scroll registers 244 | ld a,[hl+] 245 | ldh [rSCY],a 246 | ld a,[hl+] 247 | ldh [rSCX],a 248 | 249 | pop hl 250 | pop af 251 | reti 252 | ``` 253 | 254 | Notice that we can take advantage of the fact that there is only 2 bytes per line. We can use `LY` directly and quickly turn it into pointer. (Thanks to rondnelson99 for pointing this out!) 255 | 256 | ### Use the fill buffer 257 | 258 | And there you have it. An automatic and stable way to take advantage of the HBlank to do whatever your imagination wants to do! 259 | 260 | All you need to do is set the fill buffer while the draw buffer is being displayed (you have an entire frame's worth of time to do this) and the system does the rest! 261 | 262 | ## Effects 263 | 264 | ### X (Horizontal) Sine 265 | 266 | This effect uses a sine table to shift each line in a pleasant way. There are 3 states to this effect: 267 | - The image is stable and a progression line moves up the screen starting each line on its way 268 | - The table cycles a few times 269 | - The image stability is restored with the progression line moving up the screen 270 | 271 | The values in the table can dramatically change the effect. For example, if the sine cycle was short enough, you could simulate a smoke effect (for example). Try it out! 272 | 273 | Also, you could create a 'glitch' effect during a cut-scene, perhaps in a sci-fi game to simulate a slightly dirty transmission. 274 | 275 | ![X Sine](./gif/xsine.gif) 276 | 277 | ### Y (Vertical) Sine 278 | 279 | This effect is structured very similar to X Sine, in that there is a table of sine values driven by 3 states. The only difference is that `SCY` is changed instead of `SCX`. 280 | 281 | This is a really good way to simulate water reflections. 282 | 283 | ![Y Sine](./gif/ysine.gif) 284 | 285 | ### X and Y Sine 286 | 287 | This is simply a combination of the X Sine and Y Sine effects so you can see how different it looks compared to just the X or Y changing. 288 | 289 | Instead of a full-screen image like this tutorial uses, imagine if you had a repeating image in VRAM (bigger than the screen) that looked like water ripples. This would move just like water! 290 | 291 | ![XY Sine](./gif/xysine.gif) 292 | 293 | ### Smear On 294 | 295 | This is like a flood fill effect used as an appearance transition. It's quite simple in that it repeats the lines to achieve the 'smear' effect and is perhaps more interesting than a fade in. 296 | 297 | The specific image used in the tutorial is light along the bottom so it looks better if the screen was already light before the effect starts. You would change this to suit your image. 298 | 299 | ![Smear On](./gif/smearon.gif) 300 | 301 | ### Smear Off 302 | 303 | This is a disappearance transition and the reverse of Smear On. Due to the specific image that was used (i.e. it is light along the bottom), it looks better in this tutorial to have the effect reveal a light screen instead of dark. Again, you would change this to suit your image. 304 | 305 | ![Smear Off](./gif/smearoff.gif) 306 | 307 | ### Roll On 308 | 309 | This effect simulates an image unrolling onto the screen. This might be useful for fantasy RPGs to transition to a map screen or perhaps a message written on a scroll. The image unrolls over a dark screen because the top of the image is mostly dark so it looks better to keep it dark than the contrast of using a light screen. 310 | 311 | ![Roll On](./gif/rollon.gif) 312 | 313 | ### Roll Off 314 | 315 | This effect simulates an image rolling off screen. This might be useful for fantasy RPGs to transition away from a map or scroll screen. This reveals a dark screen because the first thing you see in the roll is dark (because that's what's in VRAM below the screen). Keeping it dark made the transition more seamless. 316 | 317 | ![Roll Off](./gif/rolloff.gif) 318 | 319 | The roll effects look complicated but the implementation is probably one of the simpler ones. The key to make this look good is the values in the table. The roll size is 32 pixels, but you can change this to whatever size you want, provided the table values support it. This [SpecBas demo](https://www.youtube.com/watch?v=j04TKI9WKfo) was used as a reference to obtain those values. 320 | 321 | ## How to build 322 | 323 | A GNU makefile is included. You will have to tailor it for your development environment but it builds cleanly with [RGBDS](https://github.com/gbdev/rgbds) 0.4.2. The only dependency is [`hardware.inc`](https://github.com/gbdev/hardware.inc). All of the effects are shown [here](https://github.com/BlitterObjectBob/ScrollexY#effects) so you don't have to build first to see them. 324 | 325 | ## Notes about the code 326 | 327 | To reduce dependencies, everything is in one .asm file. It's structured in a logical way and there are comments where applicable. 328 | 329 | The effects are called "parts" by the code and each part has an `Init` and `Process` routine. The sequence is controlled by a table of `Init` pointers and driven by the `ProcessPartTransition` routine. Each `Init` is responsible for setting up the data for the effect (part) and to set the `Process` function pointer via the `SetProcessFunc` macro. When the effect is done, the `Process` routine calls the `SetChangePartFlag` to tell the tutorial driver to move to the next part. 330 | 331 | There are non-effect parts present to get the effect sequence looking good when the parts are played one after the other. These are "delay" parts of various flavors: 332 | - `ShowDelay`: this shows the screen normally for a few seconds 333 | - `LightDelay`: this shows a light-colored blank screen for a few seconds 334 | - `DarkDelay`: this shows a dark-colored blank screen for a few seconds 335 | 336 | The `Delay` parts share code because they're only present to make the ROM look nice, but the effects parts were developed in a way to be isolated from one another. This was done to make extraction easier. Because of this, you will see similar code present across several parts, for example, the various Sine effects. 337 | 338 | One quirk you might notice when looking at VRAM is that the tile map is placed at 0,4 instead of 0,0. This was done to get the roll/unroll to handle the top of the screen correctly. The effects look best when they smoothly (dis)appear off-screen and if the image was placed at 0,0, the code to handle that would be distracting to how to implement the core of the effect. 339 | 340 | Another topic worth mentioning is the row of light tiles that are under the image in VRAM. This was necessary to allow `LightDelay` to exist. Those light tiles don't *have* to be right under the image, that's just where it was placed for this tutorial. It could be moved well out of the way so it doesn't affect the effects that show that part of VRAM (Y Sine, Roll On, Roll Off). 341 | 342 | If you run the ROM in [BGB](https://bgb.bircd.org/) and have the Debug Messages window open, you will see the various parts announce themselves when they are initialized. 343 | 344 | ## Exercises for the reader 345 | 346 | You can do more things than just change the scroll registers. For example, you can change the palette. Can you do this to make the roll/unroll effect look better? Here's an [example of scroll register and palette changes](https://www.youtube.com/watch?v=_-GTCao5cxs). 347 | 348 | This [appearance effect](https://www.youtube.com/watch?v=leTk0uRnE_g&t=91s) from Sword of Sodan (Amiga) is really cool! (And you might recognize one of the opening effects if you scrub to the beginning.) 349 | 350 | Another raster effect you could do is a 'twist' like the one in the [Wired demo](https://www.youtube.com/watch?v=WlMl8XKCb1Y&t=63s). 351 | 352 | You can use this system to make a racing game similar to [F-1 World Grand Prix II](https://www.youtube.com/watch?v=yvbQD2pbJes) or [Wacky Races](https://www.youtube.com/watch?v=1kXiU_odMMM&t=110s). How might you achieve this? 353 | 354 | ## PRs are welcome! 355 | 356 | Other effects can be done, such as flipping the entire image about the X axis to look like its tumbling. What other effects can you create? 357 | 358 | ## Acknowledgements 359 | 360 | Thanks go to Baŝto for use of the [Dead Boy](https://opengameart.org/content/dead-boy) image and [ISSOtm](https://github.com/ISSOtm) for peer review! 361 | 362 | ## License 363 | 364 | This was released for educational purposes and so is placed in the Public Domain. See [LICENSE](./LICENSE) for more details. 365 | -------------------------------------------------------------------------------- /DeadCScroll.asm: -------------------------------------------------------------------------------- 1 | 2 | INCLUDE "hardware.inc" 3 | 4 | ;============================================================== 5 | ; rst handlers as routines 6 | ;============================================================== 7 | CALL_HL EQU $30 8 | RST_38 EQU $38 9 | 10 | 11 | ;============================================================== 12 | ; structs 13 | ;============================================================== 14 | ; each scanline gets some data, structured like so: 15 | RSRESET 16 | RASTER_SCRY RB 1 ; data for rSCY 17 | RASTER_SCRX RB 1 ; data for rSCX 18 | sizeof_RASTER RB 0 19 | 20 | sizeof_RASTER_TABLE EQU ((SCRN_Y+1)*sizeof_RASTER) 21 | ; the +1 is because data is needed to display raster 0 22 | ; (think of it as an HBlank handler happening on raster '-1' 23 | 24 | ROLL_SIZE EQU 32 25 | 26 | 27 | ;============================================================== 28 | ; macros 29 | ;============================================================== 30 | ; breakpoint (halt the debugger) 31 | BREAKPOINT: MACRO 32 | ld b,b 33 | ENDM 34 | 35 | ;-------------------------------------------------------------- 36 | ; display a log message 37 | LOGMESSAGE: MACRO 38 | ld d,d 39 | jr .end\@ 40 | DW $6464 41 | DW $0000 42 | DB \1 43 | .end\@: 44 | ENDM 45 | 46 | ;-------------------------------------------------------------- 47 | ; wait for the start of the next vblank 48 | WaitVBlankStart: MACRO 49 | .waitvbl\@ 50 | ldh a,[rLY] 51 | cp SCRN_Y 52 | jr nz,.waitvbl\@ 53 | ENDM 54 | 55 | ;-------------------------------------------------------------- 56 | SwapBuffers: MACRO 57 | ASSERT(LOW(wRasterTableA) == LOW(wRasterTableB)) 58 | ; the README uses hardcoded addresses, but any two aligned addresses work 59 | ldh a,[hFillBuffer] 60 | ldh [hDrawBuffer],a 61 | xor HIGH(wRasterTableA) ^ HIGH(wRasterTableB) 62 | ldh [hFillBuffer],a 63 | ENDM 64 | 65 | ;-------------------------------------------------------------- 66 | ; set the change tutorial part flag 67 | SetChangePartFlag: MACRO 68 | ld a,1 69 | ld [wChangePart],a 70 | ENDM 71 | 72 | ;-------------------------------------------------------------- 73 | ; SetProcessFunc 74 | SetProcessFunc: MACRO 75 | ld hl,wProcessFunc 76 | ld bc,\1 77 | ld a,c 78 | ld [hl+],a 79 | ld [hl],b 80 | ENDM 81 | 82 | 83 | ;============================================================== 84 | ; RST handlers 85 | ;============================================================== 86 | SECTION "RST $30",ROM0[CALL_HL] 87 | ; call the function pointed to by hl 88 | CallHL:: 89 | jp hl 90 | 91 | ; ------------------------------------------------------------- 92 | SECTION "RST $38",ROM0[RST_38] 93 | Rst_38:: 94 | BREAKPOINT 95 | ret 96 | 97 | 98 | ;============================================================== 99 | ; interrupt handlers 100 | ;============================================================== 101 | SECTION "VBlank Handler",ROM0[$40] 102 | VBlankHandler:: 103 | push af 104 | ld a,1 105 | ld [wVBlankDone],a 106 | jr VBlankContinued 107 | 108 | ; ------------------------------------------------------------- 109 | SECTION "HBlank Handler",ROM0[$48] 110 | HBlankHandler:: ; 40 cycles 111 | push af ; 4 112 | push hl ; 4 113 | 114 | ;------------------------------- 115 | ; obtain the pointer to the data pair 116 | ldh a,[rLY] ; 3 117 | inc a ; 1 118 | add a,a ; 1 ; double the offset since each line uses 2 bytes 119 | ld l,a ; 1 120 | ldh a,[hDrawBuffer] ; 3 121 | adc 0 ; 2 122 | ld h,a ; 1 ; hl now points to somewhere in the draw buffer 123 | 124 | ; set the scroll registers 125 | ld a,[hl+] ; 2 126 | ldh [rSCY],a ; 3 127 | ld a,[hl+] ; 2 128 | ldh [rSCX],a ; 3 129 | 130 | pop hl ; 3 131 | pop af ; 3 132 | reti ; 4 133 | 134 | ; ------------------------------------------------------------- 135 | VBlankContinued:: 136 | pop af 137 | pop af ; remove WaitForVBlankInt's ret from the stack to avoid a race condition 138 | reti 139 | 140 | 141 | ;============================================================== 142 | ; cartridge header 143 | ;============================================================== 144 | SECTION "ROM Header",ROM0[$100] 145 | ROMHeader:: 146 | nop 147 | jp Start 148 | 149 | NINTENDO_LOGO 150 | DB "DeadCScroll" ; game title 151 | DB "BObj" ; product code 152 | DB CART_COMPATIBLE_DMG 153 | DW $00 ; license code 154 | DB CART_INDICATOR_GB 155 | DB CART_ROM_MBC5 156 | DB CART_ROM_32K 157 | DB CART_SRAM_NONE 158 | DB CART_DEST_NON_JAPANESE 159 | DB $33 ; licensee code 160 | DB $00 ; mask ROM version 161 | DB $00 ; complement check 162 | DW $00 ; cartridge checksum 163 | 164 | 165 | ;============================================================== 166 | ; starting point 167 | ;============================================================== 168 | SECTION "Start",ROM0[$150] 169 | Start:: 170 | call Initialize 171 | 172 | mainloop: 173 | ; call the process function and handle any part transition 174 | ld hl,wProcessFunc 175 | ld a,[hl+] 176 | ld h,[hl] 177 | ld l,a 178 | rst CALL_HL 179 | call ProcessPartTransition ; change parts (if necessary) 180 | 181 | ;-------------------------------------- 182 | call WaitForVBlankDone 183 | ; clear the vblank done flag 184 | xor a 185 | ld [wVBlankDone],a 186 | 187 | ;-------------------------------------- 188 | SwapBuffers 189 | call PrepRaster0 190 | 191 | ;-------------------------------------- 192 | ; update the frame counter 193 | ld hl,wFrameCounter 194 | inc [hl] 195 | 196 | jr mainloop 197 | 198 | 199 | ;============================================================== 200 | ; support routines (bank 0) 201 | ;============================================================== 202 | SECTION "WaitForVBlank",ROM0 203 | ; wait for the vblank handler to set the flag 204 | ; done as a routine instead of a macro to avoid a halt race condition 205 | WaitForVBlankDone:: 206 | .waitloop: 207 | halt 208 | ld a,[wVBlankDone] 209 | and a 210 | jr z,.waitloop 211 | ret 212 | 213 | ; ------------------------------------------------------------- 214 | SECTION "PrepRaster0",ROM0 215 | ; emulate the HBlank handler as if LY=-1 (to render the 0th scanline's worth of pixels correctly) 216 | PrepRaster0:: 217 | ldh a,[hDrawBuffer] 218 | ld h,a 219 | ld l,0 220 | 221 | ; set the scroll registers 222 | ld a,[hl+] 223 | ldh [rSCY],a 224 | ld a,[hl+] 225 | ldh [rSCX],a 226 | ret 227 | 228 | ; ------------------------------------------------------------- 229 | SECTION "Tutorial Driver",ROM0 230 | ProcessPartTransition:: 231 | ; see if the transition flag is set 232 | ld a,[wChangePart] 233 | and a 234 | ret z ; not set, exit early 235 | ; clear the flag 236 | xor a 237 | ld [wChangePart],a 238 | 239 | ; put the actual pointer in hl 240 | ld hl,wTutePartPtr 241 | ld a,[hl+] 242 | ld h,[hl] 243 | ld l,a 244 | 245 | ; put the init function pointer in de 246 | ld a,[hl+] 247 | ld e,a 248 | ld a,[hl+] 249 | ld d,a 250 | push de ; 'jp de' prep (see the ret below) 251 | 252 | ; update the ptr for the next transition 253 | ld d,h 254 | ld e,l 255 | ld hl,wTutePartPtr 256 | ld a,e 257 | ld [hl+],a 258 | ld [hl],d 259 | 260 | ; reset the frame counter so each part starts at 0 261 | xor a 262 | ld hl,wFrameCounter 263 | ld [hl],a 264 | 265 | ; the ret here actually calls the init function because of the push earlier 266 | ; i.e. simulate 'jp de' 267 | ret 268 | 269 | TutePartInitFuncTable: 270 | DW InitShowDelay 271 | DW InitXSine 272 | 273 | DW InitShowDelay 274 | DW InitYSine 275 | 276 | DW InitShowDelay 277 | DW InitXYSine 278 | 279 | DW InitShowDelay 280 | DW InitSmearOff 281 | 282 | DW InitLightDelay 283 | DW InitSmearOn 284 | 285 | DW InitShowDelay 286 | DW InitRollOff 287 | 288 | DW InitDarkDelay 289 | DW InitRollOn 290 | 291 | DW InitRestart 292 | 293 | ; ------------------------------------------------------------- 294 | SECTION "Part - X Sine",ROM0 295 | InitXSine: 296 | LOGMESSAGE "InitXSine" 297 | 298 | ; set the progression line to the first raster 299 | ld hl,wProgressLine 300 | ld a,SCRN_Y 301 | ld [hl],a 302 | 303 | ; set the data pointer (technically an offset) to the end of a raster buffer 304 | ; the pointer will be resolved later 305 | ld hl,sizeof_RASTER_TABLE-sizeof_RASTER 306 | ld a,l 307 | ld [wDataPtr],a 308 | ld a,h 309 | ld [wDataPtr+1],a 310 | 311 | ; start with subpart 0 312 | ld hl,wFlags 313 | xor a 314 | ld [hl+],a ; hl = wCounter 315 | ; set the counter to 0 316 | ld [hl],a 317 | 318 | SetProcessFunc ProcessXSine 319 | ret 320 | 321 | ProcessXSine: 322 | ; check the flags 323 | ld hl,wFlags 324 | ld a,[hl] 325 | and a 326 | jr z,.subpart0 327 | dec a 328 | jr z,.subpart1 329 | 330 | ; ending (diminish the sine up the screen) 331 | .subpart2 332 | call UpdateXSine2 333 | 334 | ; update the table index 335 | ld hl,wTableIndex 336 | inc [hl] 337 | 338 | ld hl,wProgressLine 339 | ld a,[hl] 340 | dec a 341 | jr z,.subpart2done 342 | ld [hl],a 343 | ret 344 | .subpart2done 345 | SetChangePartFlag 346 | ret 347 | 348 | ; middle (watch the sine for a bit) 349 | .subpart1 350 | call UpdateXSine1 351 | 352 | ; update the table index 353 | ld hl,wTableIndex 354 | inc [hl] 355 | ret 356 | 357 | ; beginning (progress the sine up the screen) 358 | .subpart0 359 | call UpdateXSine0 360 | 361 | ld hl,wProgressLine 362 | ld a,[hl] 363 | dec a 364 | cp $FF 365 | jr z,.subpart0done 366 | ld [hl],a 367 | ret 368 | .subpart0done 369 | ; move to the next subpart 370 | ld hl,wFlags 371 | inc [hl] 372 | 373 | ; reset the timer 374 | ld hl,wFrameCounter 375 | xor a 376 | ld [hl],a 377 | ; start the sine from 0 378 | ld hl,wTableIndex 379 | ld [hl],a 380 | ret 381 | 382 | UpdateXSine0: 383 | ld hl,wProgressLine 384 | ld a,[hl] 385 | cpl 386 | add SCRN_Y+2 387 | ld e,a ; e=num iterations 388 | 389 | ; obtain a pointer into the fill buffer 390 | ld hl,wDataPtr 391 | ld a,[hl+] 392 | ld c,a 393 | ld b,[hl] 394 | ldh a,[hFillBuffer] 395 | ld h,a 396 | ld l,0 397 | add hl,bc 398 | 399 | ; set up loop constants 400 | ld bc,XSineTable 401 | .loop 402 | ; store y value 403 | ld a,ROLL_SIZE 404 | ld [hl+],a 405 | 406 | ; store x value 407 | ld a,[bc] 408 | inc c 409 | ld [hl+],a 410 | 411 | ; loop delimiter (stop at the bottom of the screen) 412 | dec e 413 | jr nz,.loop 414 | 415 | ; update the data pointer for next time 416 | ld hl,wDataPtr 417 | ld a,[hl+] 418 | ld h,[hl] 419 | ld l,a 420 | ; assume that sizeof_RASTER is 2, alas 421 | dec hl 422 | dec hl 423 | ; store the new pointer 424 | ld a,l 425 | ld [wDataPtr],a 426 | ld a,h 427 | ld [wDataPtr+1],a 428 | ret 429 | 430 | ; just a straight sine table lookup and fill the entire buffer 431 | ; (use this one if you don't need a progression effect like sub-part 0/2) 432 | UpdateXSine1: 433 | ld hl,wTableIndex 434 | ld a,[hl] 435 | ld c,a 436 | ld b,HIGH(XSineTable) 437 | 438 | ldh a,[hFillBuffer] 439 | ld h,a 440 | ld l,0 441 | 442 | ld e,SCRN_Y+1 443 | .loop 444 | ; store y value 445 | ld a,ROLL_SIZE 446 | ld [hl+],a 447 | 448 | ; store x value 449 | ld a,[bc] 450 | inc c 451 | ld [hl+],a 452 | 453 | ; loop delimiter (stop at the bottom of the screen) 454 | dec e 455 | jr nz,.loop 456 | 457 | ; see if the last raster was 0 458 | ; if it was update a counter 459 | ; when the counter reaches 3, move to the next subpart 460 | ld a,c 461 | and a 462 | ret nz 463 | ld hl,wCounter 464 | ld a,[hl] 465 | inc a 466 | cp 3 467 | jr z,.subpart1done 468 | ld [hl],a 469 | ret 470 | .subpart1done 471 | ; move to the next subpart 472 | ld hl,wFlags 473 | inc [hl] 474 | 475 | ; reset the progress line to the bottom of the screen 476 | ld hl,wProgressLine 477 | ld a,SCRN_Y 478 | ld [hl],a 479 | ret 480 | 481 | UpdateXSine2: 482 | ld hl,wProgressLine 483 | ld a,[hl] 484 | ld e,a 485 | 486 | ld hl,wTableIndex 487 | ld a,[hl] 488 | ld c,a 489 | ld b,HIGH(XSineTable) 490 | 491 | ldh a,[hFillBuffer] 492 | ld h,a 493 | ld l,0 494 | 495 | .loop 496 | ; store y value 497 | ld a,ROLL_SIZE 498 | ld [hl+],a 499 | 500 | ; store x value 501 | ld a,[bc] 502 | inc c 503 | ld [hl+],a 504 | 505 | ; loop delimiter (stop at the bottom of the screen) 506 | dec e 507 | jr nz,.loop 508 | 509 | ; below (or equal) the line 510 | ; only two lines need to be cleared 511 | ld bc,(ROLL_SIZE<<8) 512 | ld a,b 513 | ld [hl+],a 514 | ld a,c 515 | ld [hl+],a 516 | ld a,b 517 | ld [hl+],a 518 | ld a,c 519 | ld [hl+],a 520 | ret 521 | 522 | ; ------------------------------------------------------------- 523 | SECTION "Part - Y Sine",ROM0 524 | InitYSine: 525 | LOGMESSAGE "InitYSine" 526 | 527 | ; set the progression line to the last raster 528 | ld hl,wProgressLine 529 | ld a,SCRN_Y 530 | ld [hl],a 531 | 532 | ; set the data pointer (technically an offset) to the end of a raster buffer 533 | ; the pointer will be resolved later 534 | ld hl,sizeof_RASTER_TABLE-sizeof_RASTER 535 | ld a,l 536 | ld [wDataPtr],a 537 | ld a,h 538 | ld [wDataPtr+1],a 539 | 540 | ; start with subpart 0 541 | ld hl,wFlags 542 | xor a 543 | ld [hl+],a ; hl = wCounter 544 | ; set the counter to 0 545 | ld [hl],a 546 | 547 | SetProcessFunc ProcessYSine 548 | ret 549 | 550 | ProcessYSine: 551 | ; check the flags 552 | ld hl,wFlags 553 | ld a,[hl] 554 | and a 555 | jr z,.subpart0 556 | dec a 557 | jr z,.subpart1 558 | 559 | ; ending (diminish the sine up the screen) 560 | .subpart2 561 | call UpdateYSine2 562 | 563 | ; update the table index 564 | ld hl,wTableIndex 565 | inc [hl] 566 | 567 | ld hl,wProgressLine 568 | ld a,[hl] 569 | dec a 570 | jr z,.subpart2done 571 | ld [hl],a 572 | ret 573 | .subpart2done 574 | SetChangePartFlag 575 | ret 576 | 577 | ; middle (watch the sine for a bit) 578 | .subpart1 579 | call UpdateYSine1 580 | 581 | ; update the table index 582 | ld hl,wTableIndex 583 | inc [hl] 584 | ret 585 | 586 | ; beginning (progress the sine up the screen) 587 | .subpart0 588 | call UpdateYSine0 589 | 590 | ld hl,wProgressLine 591 | ld a,[hl] 592 | dec a 593 | cp $FF 594 | jr z,.subpart0done 595 | ld [hl],a 596 | ret 597 | .subpart0done 598 | ; move to the next subpart 599 | ld hl,wFlags 600 | inc [hl] 601 | 602 | ; reset the timer 603 | ld hl,wFrameCounter 604 | xor a 605 | ld [hl],a 606 | ; start the sine from 0 607 | ld hl,wTableIndex 608 | ld [hl],a 609 | ret 610 | 611 | UpdateYSine0: 612 | ld hl,wProgressLine 613 | ld a,[hl] 614 | cpl 615 | add SCRN_Y+2 616 | ld e,a ; e=num iterations 617 | 618 | ; obtain a pointer into the fill buffer 619 | ld hl,wDataPtr 620 | ld a,[hl+] 621 | ld c,a 622 | ld b,[hl] 623 | ldh a,[hFillBuffer] 624 | ld h,a 625 | ld l,0 626 | add hl,bc 627 | 628 | ; set up loop constants 629 | ld bc,YSineTable 630 | .loop 631 | ; store y value 632 | ld a,[bc] 633 | inc c 634 | add ROLL_SIZE 635 | ld [hl+],a 636 | 637 | ; store x value 638 | xor a 639 | ld [hl+],a 640 | 641 | ; loop delimiter (stop at the bottom of the screen) 642 | dec e 643 | jr nz,.loop 644 | 645 | ; update the data pointer for next time 646 | ld hl,wDataPtr 647 | ld a,[hl+] 648 | ld h,[hl] 649 | ld l,a 650 | ; assume that sizeof_RASTER is 2, alas 651 | dec hl 652 | dec hl 653 | ; store the new pointer 654 | ld a,l 655 | ld [wDataPtr],a 656 | ld a,h 657 | ld [wDataPtr+1],a 658 | ret 659 | 660 | ; just a straight sine table lookup and fill the entire buffer 661 | ; (use this one if you don't need a progression effect like sub-part 0/2) 662 | UpdateYSine1: 663 | ld hl,wTableIndex 664 | ld a,[hl] 665 | ld c,a 666 | ld b,HIGH(YSineTable) 667 | 668 | ldh a,[hFillBuffer] 669 | ld h,a 670 | ld l,0 671 | 672 | ld e,SCRN_Y+1 673 | .loop 674 | ; store y value 675 | ld a,[bc] 676 | inc c 677 | add ROLL_SIZE 678 | ld [hl+],a 679 | 680 | ; store x value 681 | xor a 682 | ld [hl+],a 683 | 684 | ; loop delimiter (stop at the bottom of the screen) 685 | dec e 686 | jr nz,.loop 687 | 688 | ; see if the last raster was 0 689 | ; if it was update a counter 690 | ; when the counter reaches 2, move to the next subpart 691 | ld a,c 692 | and a 693 | ret nz 694 | ld hl,wCounter 695 | ld a,[hl] 696 | inc a 697 | cp 2 698 | jr z,.subpart1done 699 | ld [hl],a 700 | ret 701 | .subpart1done 702 | ; move to the next subpart 703 | ld hl,wFlags 704 | inc [hl] 705 | 706 | ; reset the progress line to the bottom of the screen 707 | ld hl,wProgressLine 708 | ld a,SCRN_Y 709 | ld [hl],a 710 | ret 711 | 712 | UpdateYSine2: 713 | ld hl,wProgressLine 714 | ld a,[hl] 715 | ld e,a 716 | 717 | ld hl,wTableIndex 718 | ld a,[hl] 719 | ld c,a 720 | ld b,HIGH(YSineTable) 721 | 722 | ldh a,[hFillBuffer] 723 | ld h,a 724 | ld l,0 725 | 726 | .loop 727 | ; store y value 728 | ld a,[bc] 729 | inc c 730 | add ROLL_SIZE 731 | ld [hl+],a 732 | 733 | ; store x value 734 | xor a 735 | ld [hl+],a 736 | 737 | ; loop delimiter (stop at the bottom of the screen) 738 | dec e 739 | jr nz,.loop 740 | 741 | ; below (or equal) the line 742 | ; only two lines need to be cleared 743 | ld bc,(ROLL_SIZE<<8) 744 | ld a,b 745 | ld [hl+],a 746 | ld a,c 747 | ld [hl+],a 748 | ld a,b 749 | ld [hl+],a 750 | ld a,c 751 | ld [hl+],a 752 | ret 753 | 754 | ; ------------------------------------------------------------- 755 | SECTION "Part - XY Sine",ROM0 756 | InitXYSine: 757 | LOGMESSAGE "InitXYSine" 758 | 759 | ; set the progression line to the first raster 760 | ld hl,wProgressLine 761 | ld a,SCRN_Y 762 | ld [hl],a 763 | 764 | ; set the data pointer (technically an offset) to the end of a raster buffer 765 | ; the pointer will be resolved later 766 | ld hl,sizeof_RASTER_TABLE-sizeof_RASTER 767 | ld a,l 768 | ld [wDataPtr],a 769 | ld a,h 770 | ld [wDataPtr+1],a 771 | 772 | ; start with subpart 0 773 | ld hl,wFlags 774 | xor a 775 | ld [hl+],a ; hl = wCounter 776 | ; set the counter to 0 777 | ld [hl],a 778 | 779 | SetProcessFunc ProcessXYSine 780 | ret 781 | 782 | ProcessXYSine: 783 | ; check the flags 784 | ld hl,wFlags 785 | ld a,[hl] 786 | and a 787 | jr z,.subpart0 788 | dec a 789 | jr z,.subpart1 790 | 791 | ; ending (diminish the sine up the screen) 792 | .subpart2 793 | call UpdateXYSine2 794 | 795 | ; update the table index 796 | ld hl,wTableIndex 797 | inc [hl] 798 | 799 | ld hl,wProgressLine 800 | ld a,[hl] 801 | dec a 802 | jr z,.subpart2done 803 | ld [hl],a 804 | ret 805 | .subpart2done 806 | SetChangePartFlag 807 | ret 808 | 809 | ; middle (watch the sine for a bit) 810 | .subpart1 811 | call UpdateXYSine1 812 | 813 | ; update the table index 814 | ld hl,wTableIndex 815 | inc [hl] 816 | ret 817 | 818 | ; beginning (progress the sine up the screen) 819 | .subpart0 820 | call UpdateXYSine0 821 | 822 | ld hl,wProgressLine 823 | ld a,[hl] 824 | dec a 825 | cp $FF 826 | jr z,.subpart0done 827 | ld [hl],a 828 | ret 829 | .subpart0done 830 | ; move to the next subpart 831 | ld hl,wFlags 832 | inc [hl] 833 | 834 | ; reset the timer 835 | ld hl,wFrameCounter 836 | xor a 837 | ld [hl],a 838 | ; start the sine from 0 839 | ld hl,wTableIndex 840 | ld [hl],a 841 | ret 842 | 843 | UpdateXYSine0: 844 | ld hl,wProgressLine 845 | ld a,[hl] 846 | cpl 847 | add SCRN_Y+2 848 | ld e,a ; e=num iterations 849 | 850 | ; obtain a pointer into the fill buffer 851 | ld hl,wDataPtr 852 | ld a,[hl+] 853 | ld c,a 854 | ld b,[hl] 855 | ldh a,[hFillBuffer] 856 | ld h,a 857 | ld l,0 858 | add hl,bc 859 | 860 | ; set up loop constants 861 | ld c,0 862 | .loop 863 | ; store y value 864 | ld b,HIGH(YSineTable) 865 | ld a,[bc] 866 | add ROLL_SIZE 867 | ld [hl+],a 868 | 869 | ; store x value 870 | ld b,HIGH(XSineTable) 871 | ld a,[bc] 872 | ld [hl+],a 873 | inc c 874 | 875 | ; loop delimiter (stop at the bottom of the screen) 876 | dec e 877 | jr nz,.loop 878 | 879 | ; update the data pointer for next time 880 | ld hl,wDataPtr 881 | ld a,[hl+] 882 | ld h,[hl] 883 | ld l,a 884 | ; assume that sizeof_RASTER is 2, alas 885 | dec hl 886 | dec hl 887 | ; store the new pointer 888 | ld a,l 889 | ld [wDataPtr],a 890 | ld a,h 891 | ld [wDataPtr+1],a 892 | ret 893 | 894 | ; just a straight sine table lookup and fill the entire buffer 895 | ; (use this one if you don't need a progression effect like sub-part 0/2) 896 | UpdateXYSine1: 897 | ld hl,wTableIndex 898 | ld a,[hl] 899 | ld c,a 900 | 901 | ldh a,[hFillBuffer] 902 | ld h,a 903 | ld l,0 904 | 905 | ld e,SCRN_Y+1 906 | .loop 907 | ; store y value 908 | ld b,HIGH(YSineTable) 909 | ld a,[bc] 910 | add ROLL_SIZE 911 | ld [hl+],a 912 | 913 | ; store x value 914 | ld b,HIGH(XSineTable) 915 | ld a,[bc] 916 | ld [hl+],a 917 | inc c 918 | 919 | ; loop delimiter (stop at the bottom of the screen) 920 | dec e 921 | jr nz,.loop 922 | 923 | ; see if the last raster was 0 924 | ; if it was update a counter 925 | ; when the counter reaches 3, move to the next subpart 926 | ld a,c 927 | and a 928 | ret nz 929 | ld hl,wCounter 930 | ld a,[hl] 931 | inc a 932 | cp 3 933 | jr z,.subpart1done 934 | ld [hl],a 935 | ret 936 | .subpart1done 937 | ; move to the next subpart 938 | ld hl,wFlags 939 | inc [hl] 940 | 941 | ; reset the progress line to the bottom of the screen 942 | ld hl,wProgressLine 943 | ld a,SCRN_Y 944 | ld [hl],a 945 | ret 946 | 947 | UpdateXYSine2: 948 | ld hl,wProgressLine 949 | ld a,[hl] 950 | ld e,a 951 | 952 | ld hl,wTableIndex 953 | ld a,[hl] 954 | ld c,a 955 | 956 | ldh a,[hFillBuffer] 957 | ld h,a 958 | ld l,0 959 | 960 | .loop 961 | ; store y value 962 | ld b,HIGH(YSineTable) 963 | ld a,[bc] 964 | add ROLL_SIZE 965 | ld [hl+],a 966 | 967 | ; store x value 968 | ld b,HIGH(XSineTable) 969 | ld a,[bc] 970 | ld [hl+],a 971 | inc c 972 | 973 | ; loop delimiter (stop at the bottom of the screen) 974 | dec e 975 | jr nz,.loop 976 | 977 | ; below (or equal) the line 978 | ; only two lines need to be cleared 979 | ld bc,(ROLL_SIZE<<8) 980 | ld a,b 981 | ld [hl+],a 982 | ld a,c 983 | ld [hl+],a 984 | ld a,b 985 | ld [hl+],a 986 | ld a,c 987 | ld [hl+],a 988 | ret 989 | 990 | ; ------------------------------------------------------------- 991 | SECTION "Part - Smear On",ROM0 992 | ; smear on (bottom to top) 993 | InitSmearOn: 994 | LOGMESSAGE "InitSmearOn" 995 | 996 | ; set the progression line to the last raster 997 | ld hl,wProgressLine 998 | ld a,SCRN_Y 999 | ld [hl],a 1000 | 1001 | SetProcessFunc ProcessSmearOn 1002 | ret 1003 | 1004 | ProcessSmearOn: 1005 | ld hl,wProgressLine 1006 | ld a,[hl] 1007 | dec a 1008 | jr z,.done 1009 | ld [hl],a 1010 | call UpdateSmear 1011 | ret 1012 | .done 1013 | SetChangePartFlag 1014 | ret 1015 | 1016 | ; ------------------------------------------------------------- 1017 | SECTION "Part - Smear Off",ROM0 1018 | ; smear off (top to bottom) 1019 | InitSmearOff: 1020 | LOGMESSAGE "InitSmearOff" 1021 | 1022 | ; set the progression line to the first raster 1023 | ld hl,wProgressLine 1024 | xor a 1025 | ld [hl],a 1026 | 1027 | SetProcessFunc ProcessSmearOff 1028 | ret 1029 | 1030 | ProcessSmearOff: 1031 | ld hl,wProgressLine 1032 | ld a,[hl] 1033 | inc a 1034 | cp SCRN_Y 1035 | jr z,.done 1036 | ld [hl],a 1037 | call UpdateSmear 1038 | ret 1039 | .done 1040 | SetChangePartFlag 1041 | ret 1042 | 1043 | ; a = wProgressLine 1044 | UpdateSmear: 1045 | ; only y data is updated here 1046 | ; from the top of the screen to wProgressLine, set the value to wProgressLine 1047 | ; below wProgressLine is 0 1048 | ld e,a ; copy wProgressLine 1049 | 1050 | ldh a,[hFillBuffer] 1051 | ld h,a 1052 | ld l,0 1053 | 1054 | ; above the line 1055 | ld c,l 1056 | ld b,e ; b = wProgressLine, c = scroll x 1057 | ld d,c 1058 | .loop 1059 | ld a,b 1060 | add ROLL_SIZE 1061 | ld [hl+],a ; store scroll y value 1062 | ld a,c 1063 | ld [hl+],a ; store scroll x value 1064 | 1065 | dec b 1066 | inc d 1067 | ld a,d 1068 | cp e 1069 | jr nz,.loop 1070 | 1071 | ; below (or equal) the line 1072 | ; only two lines need to be cleared 1073 | ld bc,(ROLL_SIZE<<8) 1074 | ld a,b 1075 | ld [hl+],a 1076 | ld a,c 1077 | ld [hl+],a 1078 | ld a,b 1079 | ld [hl+],a 1080 | ld a,c 1081 | ld [hl+],a 1082 | ret 1083 | 1084 | ; ------------------------------------------------------------- 1085 | SECTION "Part - Roll Off",ROM0 1086 | ; roll off (bottom to top) 1087 | InitRollOff: 1088 | LOGMESSAGE "InitRollOff" 1089 | 1090 | ; set the progression line to the last raster 1091 | ld hl,wProgressLine 1092 | ld a,SCRN_Y+ROLL_SIZE 1093 | ld [hl],a 1094 | 1095 | ; set the data pointer (technically an offset) to the end of a raster buffer 1096 | ; the pointer will be resolved later 1097 | ld hl,sizeof_RASTER_TABLE-sizeof_RASTER 1098 | ld a,l 1099 | ld [wDataPtr],a 1100 | ld a,h 1101 | ld [wDataPtr+1],a 1102 | 1103 | SetProcessFunc ProcessRollOff 1104 | ret 1105 | 1106 | ProcessRollOff: 1107 | ld hl,wProgressLine 1108 | ld a,[hl] 1109 | dec a 1110 | jr z,.done 1111 | ld [hl],a 1112 | call UpdateRollOff 1113 | ret 1114 | .done 1115 | SetChangePartFlag 1116 | ret 1117 | 1118 | ; a=wProgressLine 1119 | UpdateRollOff: 1120 | ld b,a ; b=progress line 1121 | 1122 | ; obtain a pointer into the fill buffer 1123 | ld hl,wDataPtr 1124 | ld a,[hl+] 1125 | ld e,a 1126 | ld d,[hl] 1127 | ldh a,[hFillBuffer] 1128 | ld h,a 1129 | ld l,0 1130 | add hl,de 1131 | 1132 | ; for the height of the roll, use the table as a displacement from the current raster 1133 | ld c,ROLL_SIZE 1134 | ld de,RollTable 1135 | .rollloop 1136 | ; don't update if the current progress line is off-screen (above raster 0) 1137 | ld a,b 1138 | cp ROLL_SIZE 1139 | jr c,.skipstore 1140 | 1141 | ; store y value 1142 | ld a,[de] 1143 | add ROLL_SIZE 1144 | ld [hl+],a 1145 | 1146 | ; store x value (always 0) 1147 | xor a 1148 | ld [hl+],a 1149 | 1150 | .skipstore 1151 | inc de 1152 | 1153 | ; prevent going off the end of the buffer 1154 | inc b 1155 | ld a,b 1156 | cp SCRN_Y+ROLL_SIZE 1157 | jr z,.updatedataptr 1158 | 1159 | ; loop delimiter 1160 | dec c 1161 | jr nz,.rollloop 1162 | 1163 | ; below (or equal) the line 1164 | ld c,b 1165 | ld a,b 1166 | sub ROLL_SIZE 1167 | ld b,a 1168 | ld de,SCRN_Y+12+ROLL_SIZE 1169 | ld a,e 1170 | sub b 1171 | ld e,a 1172 | 1173 | .clearloop 1174 | ld a,e 1175 | ld [hl+],a 1176 | ld a,d 1177 | ld [hl+],a 1178 | dec e 1179 | inc c 1180 | ld a,c 1181 | cp SCRN_Y+ROLL_SIZE 1182 | jr nz,.clearloop 1183 | 1184 | .updatedataptr 1185 | ; update the data pointer for next time 1186 | ld hl,wDataPtr 1187 | ld a,[hl+] 1188 | ld h,[hl] 1189 | ld l,a 1190 | ; prevent a buffer underrun 1191 | or h 1192 | ret z 1193 | ; assume that sizeof_RASTER is 2, alas 1194 | dec hl 1195 | dec hl 1196 | ; store the new pointer 1197 | ld a,l 1198 | ld [wDataPtr],a 1199 | ld a,h 1200 | ld [wDataPtr+1],a 1201 | ret 1202 | 1203 | ; ------------------------------------------------------------- 1204 | SECTION "Part - Roll On",ROM0 1205 | ; roll on (top to bottom) 1206 | InitRollOn: 1207 | LOGMESSAGE "InitRollOn" 1208 | 1209 | ; set the progression line to the first raster 1210 | ld hl,wProgressLine 1211 | xor a 1212 | ld [hl],a 1213 | 1214 | SetProcessFunc ProcessRollOn 1215 | ret 1216 | 1217 | ProcessRollOn: 1218 | ld hl,wProgressLine 1219 | ld a,[hl] 1220 | inc a 1221 | cp SCRN_Y+ROLL_SIZE 1222 | jr z,.done 1223 | ld [hl],a 1224 | call UpdateRollOn 1225 | ret 1226 | .done 1227 | SetChangePartFlag 1228 | ret 1229 | 1230 | ; a=wProgressLine 1231 | UpdateRollOn: 1232 | ld b,a ; b=progress line 1233 | 1234 | ldh a,[hFillBuffer] 1235 | ld h,a 1236 | xor a 1237 | ld l,a 1238 | 1239 | ld a,b 1240 | cp ROLL_SIZE 1241 | jr z,.doroll 1242 | jr nc,.dofill 1243 | jr .doroll 1244 | ; fill the buffer with $3200 up to the progress line 1245 | .dofill 1246 | ld a,b 1247 | sub ROLL_SIZE 1248 | ld c,a 1249 | ld de,(ROLL_SIZE<<8) ; y=32, x=0 1250 | .zeroloop 1251 | ld a,d 1252 | ld [hl+],a 1253 | ld a,e 1254 | ld [hl+],a 1255 | dec c 1256 | jr nz,.zeroloop 1257 | 1258 | .doroll 1259 | ; for the height of the roll, use the table as a displacement from the current raster 1260 | ld c,ROLL_SIZE 1261 | ld de,RollTable 1262 | .rollloop 1263 | cp ROLL_SIZE 1264 | jr nc,.dostore 1265 | jr z,.dostore 1266 | jr .loopend 1267 | .dostore 1268 | ; store y value 1269 | ld a,[de] 1270 | add ROLL_SIZE 1271 | ld [hl+],a 1272 | 1273 | ; store x value (always 0) 1274 | xor a 1275 | ld [hl+],a 1276 | 1277 | .loopend 1278 | inc de 1279 | 1280 | ; prevent going off the end of the buffer 1281 | inc b 1282 | ld a,b 1283 | cp SCRN_Y+ROLL_SIZE 1284 | ret z 1285 | 1286 | ; loop delimiter 1287 | dec c 1288 | jr nz,.rollloop 1289 | 1290 | ret 1291 | 1292 | ; ------------------------------------------------------------- 1293 | SECTION "Part - Show Delay",ROM0 1294 | InitShowDelay: 1295 | LOGMESSAGE "InitShowDelay" 1296 | ; clear the raster tables to 0,0 for every raster 1297 | ld hl,wRasterTableA 1298 | ld b,SCRN_Y+1 1299 | call BlankScreenMem 1300 | ld hl,wRasterTableB 1301 | ld b,SCRN_Y+1 1302 | call BlankScreenMem 1303 | 1304 | SetProcessFunc ProcessDelay 1305 | ret 1306 | 1307 | ; 'clear' memory so the normal screen is positioned correctly 1308 | ; (it starts 4 tiles down instead of at 0,0) 1309 | BlankScreenMem: 1310 | ld de,(ROLL_SIZE<<8) ; y=32, x=0 1311 | .loop 1312 | ld a,d 1313 | ld [hl+],a 1314 | ld a,e 1315 | ld [hl+],a 1316 | dec b 1317 | jr nz,.loop 1318 | ret 1319 | 1320 | 1321 | ; ------------------------------------------------------------- 1322 | SECTION "Part - Light Delay",ROM0 1323 | InitLightDelay: 1324 | LOGMESSAGE "InitLightDelay" 1325 | ; clear the raster tables to offscreen for every raster 1326 | ld hl,wRasterTableA 1327 | ld b,SCRN_Y+1 1328 | ld de,SCRN_Y+4+ROLL_SIZE 1329 | call InitBlankRasterBuffer 1330 | ld hl,wRasterTableB 1331 | ld b,SCRN_Y+1 1332 | ld de,SCRN_Y+4+ROLL_SIZE 1333 | call InitBlankRasterBuffer 1334 | 1335 | SetProcessFunc ProcessDelay 1336 | ret 1337 | 1338 | ; ------------------------------------------------------------- 1339 | SECTION "Part - Dark Delay",ROM0 1340 | InitDarkDelay: 1341 | LOGMESSAGE "InitDarkDelay" 1342 | ; clear the raster tables to offscreen for every raster 1343 | ld hl,wRasterTableA 1344 | ld b,SCRN_Y+1 1345 | ld de,SCRN_Y+12+ROLL_SIZE 1346 | call InitBlankRasterBuffer 1347 | ld hl,wRasterTableB 1348 | ld b,SCRN_Y+1 1349 | ld de,SCRN_Y+12+ROLL_SIZE 1350 | call InitBlankRasterBuffer 1351 | 1352 | SetProcessFunc ProcessDelay 1353 | ret 1354 | 1355 | ProcessDelay: 1356 | ld hl,wFrameCounter 1357 | ld a,[hl] 1358 | cp 150 ; ~2.5 seconds 1359 | ret nz 1360 | SetChangePartFlag 1361 | ret 1362 | 1363 | ; b = num buffer entry (pairs) to set 1364 | ; d = x value (always 0) 1365 | ; e = y value 1366 | InitBlankRasterBuffer: 1367 | .loop 1368 | ld a,e 1369 | ld [hl+],a 1370 | ld a,d 1371 | ld [hl+],a 1372 | dec e ; offset by LY 1373 | dec b 1374 | jr nz,.loop 1375 | ret 1376 | 1377 | ; ------------------------------------------------------------- 1378 | SECTION "Part - Restart",ROM0 1379 | InitRestart: 1380 | LOGMESSAGE "InitRestart" 1381 | SetProcessFunc ProcessRestart 1382 | ret 1383 | 1384 | ProcessRestart: 1385 | call InitFirstPart 1386 | ret 1387 | 1388 | 1389 | ;============================================================== 1390 | ; support routines (bank 2) 1391 | ;============================================================== 1392 | SECTION "Bank 1 Routines",ROMX,BANK[1] 1393 | Initialize: 1394 | di 1395 | 1396 | ;-------------------------------------- 1397 | ; turn off the screen after entering a vblank 1398 | WaitVBlankStart 1399 | 1400 | ; clear LCD control registers and disable audio 1401 | xor a 1402 | ldh [rLCDC],a 1403 | ldh [rIE],a 1404 | ldh [rIF],a 1405 | ldh [rSTAT],a 1406 | ldh [rAUDENA],a ; disable the audio 1407 | 1408 | ;-------------------------------------- 1409 | ; initialize the window position to 255,255 1410 | dec a 1411 | ldh [rWY],a 1412 | ldh [rWX],a 1413 | 1414 | ;-------------------------------------- 1415 | ; set the bg palette 1416 | ld a,$E4 1417 | ldh [rBGP],a 1418 | 1419 | ;-------------------------------------- 1420 | ; copy the tile map to vram 1421 | ld de,BGTileMap ; source 1422 | ld hl,_SCRN0+(ROLL_SIZE*4) ; dest 1423 | ld bc,(BGTileMapEnd - BGTileMap) ; num bytes 1424 | call CopyMem 1425 | 1426 | ;-------------------------------------- 1427 | ; copy the bg tiles to vram 1428 | ld de,BGTiles ; source 1429 | ld hl,_VRAM8000 ; dest 1430 | ld bc,(BGTilesEnd - BGTiles) ; num bytes 1431 | call CopyMem 1432 | 1433 | ;-------------------------------------- 1434 | ; set up the initial state 1435 | call InitVariables 1436 | call InitFirstPart 1437 | 1438 | ;-------------------------------------- 1439 | ; turn on the display 1440 | ld a,LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_OBJOFF|LCDCF_BGON 1441 | ldh [rLCDC],a 1442 | 1443 | WaitVBlankStart 1444 | 1445 | ;------------------------------- 1446 | ; set up the lcdc int 1447 | ld a,STATF_MODE00 1448 | ldh [rSTAT],a 1449 | 1450 | ;-------------------------------------- 1451 | ; enable the interrupts 1452 | ld a,IEF_VBLANK|IEF_LCDC 1453 | ldh [rIE],a 1454 | xor a 1455 | ei 1456 | ldh [rIF],a 1457 | ret 1458 | 1459 | ;-------------------------------------------------------------- 1460 | ; copy bc bytes from de to hl 1461 | CopyMem: 1462 | .loop 1463 | ld a,[de] 1464 | ld [hl+],a 1465 | inc de 1466 | dec bc 1467 | ld a,b 1468 | or c 1469 | jr nz,.loop 1470 | ret 1471 | 1472 | ;-------------------------------------------------------------- 1473 | InitVariables: 1474 | ld hl,wVBlankDone 1475 | xor a 1476 | ld [hl+],a ; wVBlankDone 1477 | ld [hl+],a ; wFrameCounter 1478 | ld [hl+],a ; wChangePart 1479 | 1480 | ; set up the double-buffering system 1481 | ld a,HIGH(wRasterTableA) 1482 | ldh [hDrawBuffer],a 1483 | xor $02 1484 | ldh [hFillBuffer],a 1485 | ; hDrawBuffer=wRasterTableA 1486 | ; hFillBuffer=wRasterTableB 1487 | ret 1488 | 1489 | ;-------------------------------------------------------------- 1490 | InitFirstPart: 1491 | ; init the part pointer to the start of the table 1492 | ld de,TutePartInitFuncTable 1493 | ld hl,wTutePartPtr 1494 | ld a,e 1495 | ld [hl+],a 1496 | ld [hl],d 1497 | 1498 | ; prep the first part 1499 | SetChangePartFlag 1500 | call ProcessPartTransition 1501 | 1502 | call PrepRaster0 1503 | ret 1504 | 1505 | 1506 | ;============================================================== 1507 | ; work ram 1508 | ;============================================================== 1509 | SECTION "Raster Table A",WRAM0[$C000] 1510 | wRasterTableA:: DS sizeof_RASTER_TABLE 1511 | 1512 | SECTION "Raster Table B",WRAM0[$C200] 1513 | wRasterTableB:: DS sizeof_RASTER_TABLE 1514 | 1515 | SECTION "Tutorial Part Variables",WRAM0,ALIGN[3] 1516 | wProcessFunc:: DS 2 ; pointer to a function to call towards the start of a frame 1517 | wTutePartPtr:: DS 2 ; pointer in TutorialPartInitFuncTable for the next part 1518 | 1519 | SECTION "Variables",WRAM0 1520 | wVBlankDone: DS 1 1521 | wFrameCounter: DS 1 ; a simple frame counter 1522 | wChangePart: DS 1 1523 | wTableIndex: DS 1 ; general-purpose index into a table 1524 | wProgressLine: DS 1 ; current raster used as a progressive scan 1525 | wFlags: DS 1 ; a holder of misc flags/data for part's use 1526 | wCounter: DS 1 ; general-purpose counter for part's use 1527 | wDataPtr: DS 2 ; (part use) pointer to somewhere in wRasterTableA/wRasterTableB 1528 | 1529 | 1530 | ;============================================================== 1531 | ; high ram 1532 | ;============================================================== 1533 | SECTION "HRAM Variables",HRAM 1534 | ; buffer offsets (put in h, l=00) 1535 | ; $C0 = Table A / $C2 = Table B 1536 | hDrawBuffer:: DS 1 ; the buffer currently being drawn (the inverse of hFillBuffer) 1537 | hFillBuffer:: DS 1 ; the buffer currently being filled (the inverse of hDrawBuffer) 1538 | 1539 | 1540 | ;============================================================== 1541 | ; bank 1 data (put down here so it's out of the way) 1542 | ;============================================================== 1543 | SECTION "Bank 1 Data",ROMX,BANK[1] 1544 | BGTileMap: 1545 | DB $00,$00,$00,$00,$00,$01,$00,$00,$00,$00,$00,$00,$00,$02,$03,$00,$00,$00,$00,$04,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1546 | DB $05,$06,$07,$07,$08,$09,$0a,$07,$07,$07,$0b,$0c,$0d,$09,$0a,$07,$07,$08,$0e,$0f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1547 | DB $00,$10,$11,$11,$12,$13,$14,$15,$16,$11,$17,$18,$19,$13,$14,$11,$11,$12,$13,$1a,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1548 | DB $00,$1b,$1c,$00,$00,$1d,$00,$1e,$1f,$00,$20,$1d,$21,$1d,$00,$1c,$22,$23,$1d,$1a,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1549 | DB $02,$24,$1d,$00,$00,$1d,$00,$25,$26,$27,$28,$1d,$29,$1d,$2a,$1d,$2b,$2c,$1d,$1a,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1550 | DB $00,$1b,$1d,$2d,$2e,$2f,$30,$31,$32,$33,$34,$1d,$35,$1d,$29,$1d,$2d,$2e,$2f,$1a,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1551 | DB $00,$1b,$1d,$36,$37,$38,$39,$3a,$3a,$3a,$3b,$3c,$3d,$3e,$3f,$1d,$36,$37,$38,$40,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1552 | DB $00,$1b,$37,$38,$41,$00,$00,$42,$43,$44,$45,$46,$47,$48,$49,$37,$4a,$4b,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1553 | DB $00,$4c,$4d,$30,$4e,$00,$4f,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$02,$03,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1554 | DB $00,$00,$00,$5b,$5c,$00,$5d,$1d,$5e,$31,$5f,$60,$61,$1d,$1d,$62,$00,$63,$64,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1555 | DB $00,$00,$00,$65,$66,$00,$67,$5d,$68,$69,$6a,$1d,$1d,$1d,$6b,$6c,$6d,$6e,$1f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1556 | DB $00,$01,$00,$6f,$70,$00,$00,$71,$72,$73,$63,$1d,$1d,$1d,$74,$75,$73,$25,$76,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1557 | DB $00,$00,$00,$00,$77,$00,$00,$00,$78,$79,$00,$7a,$7b,$7c,$1d,$7d,$7e,$31,$76,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1558 | DB $7f,$80,$7f,$80,$77,$81,$82,$83,$84,$85,$86,$87,$88,$89,$8a,$8b,$8c,$1d,$8d,$20,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1559 | DB $8e,$8f,$8e,$8f,$90,$91,$92,$93,$94,$95,$96,$97,$98,$1d,$1d,$99,$9a,$1d,$9b,$8f,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1560 | DB $9c,$9d,$9c,$9d,$9e,$9f,$a0,$a1,$a2,$a3,$31,$a4,$1d,$1d,$1d,$1d,$1d,$a5,$a6,$9d,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1561 | DB $a7,$a8,$a7,$a8,$a7,$a8,$a9,$aa,$ab,$ac,$ad,$5d,$1d,$1d,$1d,$1d,$1d,$ae,$a7,$a8,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1562 | DB $af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$b0,$1d,$1d,$1d,$b1,$b2,$b3,$af,$af,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1563 | DB $af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$af,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 1564 | BGTileMapEnd: 1565 | 1566 | BGTiles: 1567 | DB $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1568 | DB $fb,$c7,$fd,$83,$ef,$b7,$ff,$b7,$fd,$83,$bb,$ef,$db,$e7,$ff,$ff 1569 | DB $ff,$f8,$ff,$f0,$ef,$f0,$ff,$ec,$ee,$fd,$fe,$f3,$f7,$eb,$ff,$ff 1570 | DB $ff,$7f,$ff,$3f,$ff,$3f,$bf,$7f,$7f,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1571 | DB $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$f7,$8f,$fb,$07,$df,$6f,$ff,$6f 1572 | DB $fc,$fc,$fc,$fc,$fc,$fc,$fe,$fe,$fe,$fe,$ff,$ff,$ff,$ff,$ff,$ff 1573 | DB $00,$00,$00,$00,$7f,$7f,$7f,$7f,$3f,$30,$3f,$30,$18,$1f,$98,$9f 1574 | DB $00,$00,$00,$00,$ff,$ff,$ff,$ff,$ff,$00,$ff,$00,$00,$ff,$00,$ff 1575 | DB $01,$01,$00,$00,$fc,$fc,$ff,$ff,$ff,$03,$ff,$00,$07,$f8,$01,$fe 1576 | DB $fc,$fc,$7c,$7c,$1c,$1c,$06,$06,$c0,$c0,$f0,$f0,$fc,$3c,$ff,$0f 1577 | DB $00,$00,$00,$00,$7f,$7f,$7f,$7f,$3f,$30,$3f,$30,$18,$1f,$18,$1f 1578 | DB $00,$00,$00,$00,$fe,$fe,$fe,$fe,$fc,$0c,$fc,$0c,$18,$f8,$18,$f8 1579 | DB $3f,$3f,$3e,$3e,$38,$38,$60,$60,$03,$03,$0f,$0f,$3f,$3c,$ff,$f0 1580 | DB $81,$81,$00,$00,$3c,$3c,$ff,$ff,$ff,$c3,$ff,$00,$e7,$18,$81,$7e 1581 | DB $ff,$ff,$7f,$7f,$1f,$1f,$07,$07,$c1,$c1,$f0,$f0,$fc,$3c,$ff,$0f 1582 | DB $fb,$07,$77,$df,$b7,$cf,$ff,$ff,$ff,$ff,$7f,$7f,$1f,$1f,$0f,$0f 1583 | DB $8c,$8f,$cc,$cf,$c6,$c7,$e6,$e7,$e3,$e3,$f3,$f3,$f3,$f3,$f3,$f3 1584 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1585 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$f0,$ff,$fc,$ff,$ff,$ff,$ff,$ff 1586 | DB $7f,$83,$1f,$e0,$07,$f8,$01,$fe,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1587 | DB $cc,$cf,$fc,$ff,$fe,$ff,$fe,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1588 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$80,$ff,$80,$ff 1589 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$7f,$ff,$7f,$ff,$3f,$ff,$3f,$ff 1590 | DB $33,$f3,$3f,$ff,$7f,$ff,$7f,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1591 | DB $fe,$c1,$f8,$07,$e0,$1f,$80,$7f,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1592 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$3c,$ff,$ff,$ff,$ff,$ff 1593 | DB $cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf,$cf 1594 | DB $f3,$f3,$f3,$f3,$f3,$f3,$f3,$f3,$f3,$f3,$f3,$f3,$f3,$f3,$f3,$f3 1595 | DB $ff,$3f,$ff,$0f,$7f,$83,$1f,$e0,$07,$f8,$01,$fe,$00,$ff,$00,$ff 1596 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1597 | DB $c0,$ff,$c0,$ff,$e0,$ff,$e0,$ff,$f0,$ff,$f0,$ff,$f8,$ff,$f8,$ff 1598 | DB $1f,$ff,$1f,$ff,$0f,$ff,$0f,$ff,$07,$ff,$07,$ff,$03,$ff,$03,$ff 1599 | DB $ff,$ff,$ff,$ff,$ff,$ff,$fb,$ff,$f7,$ff,$f3,$ff,$f3,$ff,$27,$ff 1600 | DB $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$df,$ff,$c3,$ff 1601 | DB $ff,$ff,$ff,$ff,$fc,$ff,$c0,$ff,$c1,$ff,$c0,$ff,$e0,$ff,$f8,$ff 1602 | DB $ff,$ff,$ff,$ff,$7f,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$7f,$ff,$1f,$ff 1603 | DB $f3,$73,$f3,$33,$f3,$33,$b3,$73,$73,$f3,$f3,$f3,$f3,$f3,$f3,$f3 1604 | DB $f8,$ff,$f8,$ff,$f0,$ff,$f0,$ff,$e0,$ff,$e0,$ff,$c0,$ff,$c0,$ff 1605 | DB $03,$ff,$03,$ff,$07,$ff,$07,$ff,$0f,$ff,$0f,$ff,$1f,$ff,$1f,$ff 1606 | DB $80,$ff,$c0,$ff,$e1,$ff,$fb,$ff,$ff,$ff,$fc,$ff,$f8,$ff,$f8,$ff 1607 | DB $7f,$ff,$ff,$ff,$ff,$ff,$e3,$ff,$03,$ff,$03,$ff,$03,$ff,$03,$ff 1608 | DB $c3,$ff,$c3,$ff,$c3,$ff,$c3,$ff,$c3,$ff,$c3,$ff,$c3,$ff,$c3,$ff 1609 | DB $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$f3,$ff,$c3,$ff,$c3,$ff 1610 | DB $fe,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$c7,$ff,$c0,$ff,$fc,$ff 1611 | DB $0f,$ff,$87,$ff,$c3,$ff,$c3,$ff,$c3,$ff,$c3,$ff,$03,$ff,$03,$ff 1612 | DB $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$fc,$ff,$f0,$ff 1613 | DB $ff,$ff,$ff,$ff,$fc,$ff,$f0,$ff,$c0,$ff,$00,$ff,$00,$ff,$00,$ff 1614 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$03,$ff 1615 | DB $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$fe,$ff,$fe,$ff,$fc,$ff,$fc,$ff 1616 | DB $80,$ff,$80,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1617 | DB $3f,$ff,$3f,$ff,$7f,$ff,$7f,$ff,$7f,$80,$7f,$80,$00,$ff,$00,$ff 1618 | DB $f8,$ff,$fe,$ff,$ff,$ff,$ff,$ff,$ff,$00,$ff,$00,$00,$ff,$00,$ff 1619 | DB $03,$f3,$03,$c3,$c3,$c3,$c3,$c3,$e3,$63,$e3,$63,$33,$f3,$33,$f3 1620 | DB $c3,$ff,$fb,$c7,$ff,$c3,$ff,$c3,$ff,$c3,$cf,$c3,$c7,$c3,$c7,$c3 1621 | DB $c0,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$03,$ff 1622 | DB $00,$ff,$00,$ff,$00,$ff,$03,$ff,$0f,$ff,$3c,$fc,$f0,$f0,$c1,$c1 1623 | DB $0f,$ff,$3c,$fc,$f0,$f0,$c1,$c1,$07,$07,$1f,$1f,$7f,$7f,$ff,$ff 1624 | DB $f8,$ff,$38,$3f,$30,$3f,$30,$3f,$3f,$3f,$3f,$3f,$00,$00,$00,$00 1625 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$ff,$ff,$ff,$ff,$00,$00,$00,$00 1626 | DB $1f,$ff,$1f,$ff,$0c,$fc,$0c,$fc,$fc,$fc,$fc,$fc,$00,$00,$00,$00 1627 | DB $00,$ff,$c0,$ff,$f0,$ff,$3c,$3f,$0f,$0f,$83,$83,$e0,$e0,$f8,$f8 1628 | DB $c7,$c3,$c7,$c3,$ff,$ff,$ff,$ff,$c3,$c3,$c3,$c3,$00,$00,$00,$00 1629 | DB $00,$ff,$03,$ff,$0f,$ff,$3c,$fc,$f0,$f0,$c1,$c1,$07,$07,$1e,$1f 1630 | DB $ff,$ff,$ff,$ff,$03,$03,$03,$03,$73,$73,$f3,$f3,$b3,$f3,$33,$f3 1631 | DB $0f,$0f,$1f,$1f,$7f,$7f,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1632 | DB $07,$07,$1f,$1f,$7f,$7f,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1633 | DB $ff,$ff,$ff,$ff,$c0,$ff,$e0,$ff,$e0,$ff,$e0,$ff,$e0,$ff,$e0,$ff 1634 | DB $ff,$ff,$ff,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1635 | DB $ff,$ff,$ff,$ff,$1f,$fe,$0f,$fe,$0f,$fe,$0f,$ff,$0f,$ff,$07,$ff 1636 | DB $ff,$ff,$ff,$ff,$32,$c3,$02,$e3,$22,$e3,$03,$c3,$01,$c0,$60,$60 1637 | DB $9f,$ff,$8f,$ff,$1c,$f0,$18,$f0,$38,$e0,$e0,$c0,$c0,$00,$01,$00 1638 | DB $ff,$ff,$ff,$ff,$0f,$00,$0f,$00,$1e,$01,$1c,$03,$30,$0f,$f0,$0f 1639 | DB $f8,$ff,$f0,$ff,$18,$ff,$18,$ff,$18,$ff,$18,$ff,$38,$ff,$38,$ff 1640 | DB $33,$f3,$33,$f3,$33,$f3,$33,$f3,$33,$f3,$33,$f3,$33,$f3,$33,$f3 1641 | DB $0f,$ff,$3c,$fc,$f0,$f0,$c1,$c1,$07,$07,$1e,$1f,$78,$7f,$e0,$ff 1642 | DB $07,$07,$1e,$1f,$78,$7f,$e1,$ff,$83,$ff,$07,$ff,$07,$ff,$0f,$ff 1643 | DB $f0,$f0,$f0,$f0,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1644 | DB $07,$07,$1f,$1f,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1645 | DB $7f,$ff,$7f,$ff,$7f,$ff,$7f,$ff,$7f,$ff,$3f,$ff,$bf,$7f,$d7,$3f 1646 | DB $ff,$ff,$ff,$ff,$ff,$ff,$fe,$ff,$f8,$ff,$f0,$ff,$c0,$ff,$80,$ff 1647 | DB $c0,$ff,$80,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1648 | DB $00,$ff,$00,$ff,$00,$ff,$20,$ff,$10,$ff,$1c,$ff,$0f,$ff,$0f,$ff 1649 | DB $02,$fe,$00,$fe,$00,$fe,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$c0,$ff 1650 | DB $00,$18,$01,$0e,$0f,$00,$0e,$80,$06,$f1,$00,$f0,$00,$f0,$00,$f2 1651 | DB $0f,$08,$8d,$0e,$85,$06,$05,$46,$47,$06,$5c,$3f,$5e,$bc,$04,$f0 1652 | DB $70,$0f,$30,$0f,$b3,$0f,$9f,$0f,$9f,$0f,$9f,$0f,$0f,$1f,$2f,$1f 1653 | DB $f8,$ff,$f0,$ff,$f0,$ff,$f0,$ff,$e0,$ff,$c0,$ff,$80,$ff,$00,$ff 1654 | DB $30,$f0,$30,$f0,$3f,$ff,$3f,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1655 | DB $07,$07,$1e,$1f,$f8,$ff,$e0,$ff,$00,$ff,$07,$ff,$0f,$ff,$1f,$ff 1656 | DB $80,$ff,$00,$ff,$00,$ff,$00,$ff,$1f,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1657 | DB $07,$ff,$07,$ff,$03,$ff,$03,$ff,$c1,$ff,$e0,$ff,$e0,$ff,$f0,$ff 1658 | DB $ff,$fc,$fb,$fc,$fe,$f8,$fe,$f0,$fc,$f0,$fc,$f0,$fc,$f0,$fc,$f8 1659 | DB $d7,$3f,$ff,$1f,$7f,$0f,$3f,$0f,$1f,$07,$1f,$07,$1f,$07,$3f,$0f 1660 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$80,$ff 1661 | DB $07,$ff,$07,$ff,$0f,$f3,$19,$e7,$1d,$e3,$1c,$e3,$3c,$c3,$3e,$c1 1662 | DB $0d,$f2,$04,$fb,$00,$f9,$00,$f8,$00,$fc,$00,$fc,$00,$ff,$00,$ff 1663 | DB $20,$c0,$80,$60,$00,$21,$2c,$83,$84,$03,$00,$07,$80,$07,$00,$8f 1664 | DB $06,$3f,$0c,$7f,$0c,$ff,$0c,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1665 | DB $1f,$ff,$3f,$ff,$3f,$fb,$3f,$f9,$40,$c0,$40,$c0,$40,$c0,$00,$80 1666 | DB $f0,$ff,$f8,$ff,$f8,$ff,$f8,$ff,$fc,$ff,$fc,$ff,$fc,$ff,$fe,$ff 1667 | DB $7f,$ff,$7f,$ff,$3f,$ff,$3f,$ff,$3f,$ff,$1f,$ff,$1f,$ff,$1f,$ff 1668 | DB $fc,$fc,$fb,$fb,$fb,$fb,$fc,$f8,$ff,$fc,$fd,$fc,$fe,$fc,$fe,$ff 1669 | DB $1f,$1f,$6f,$6f,$ef,$6f,$9f,$8f,$3f,$0f,$7f,$1f,$3f,$1f,$1f,$ff 1670 | DB $80,$ff,$e0,$ff,$e0,$ff,$f0,$ff,$f8,$ff,$fc,$ff,$ff,$ff,$ff,$ff 1671 | DB $3e,$c1,$3f,$c0,$3f,$c0,$3f,$c0,$3f,$c0,$3f,$c0,$3f,$c0,$3f,$c0 1672 | DB $00,$ff,$01,$ff,$07,$ff,$1f,$ff,$ff,$7f,$ff,$7f,$ff,$3f,$ff,$3f 1673 | DB $80,$ff,$c0,$ff,$c0,$ff,$e0,$ff,$e0,$ff,$e0,$ff,$e0,$ff,$f0,$ff 1674 | DB $00,$ff,$00,$ff,$00,$ff,$01,$fe,$03,$fc,$07,$f8,$07,$f8,$07,$f8 1675 | DB $00,$80,$80,$00,$e1,$00,$e1,$00,$e1,$00,$e1,$00,$e0,$00,$f0,$00 1676 | DB $ff,$ff,$ff,$ff,$ff,$7f,$ff,$7f,$ff,$7f,$ff,$7f,$ff,$3f,$ff,$3f 1677 | DB $fe,$ff,$fe,$ff,$fe,$ff,$fe,$ff,$fc,$ff,$fc,$ff,$fc,$ff,$fc,$ff 1678 | DB $fe,$fc,$ff,$fe,$fe,$ff,$fe,$fe,$ff,$fe,$ff,$ff,$ff,$ff,$ff,$ff 1679 | DB $1f,$0f,$3f,$1f,$df,$3f,$1f,$1f,$3f,$1f,$ff,$3f,$3f,$3f,$3f,$3f 1680 | DB $e0,$ff,$f0,$ff,$fe,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff 1681 | DB $1f,$e0,$1f,$e0,$1f,$e0,$1f,$e0,$1f,$e0,$1f,$e0,$3f,$c0,$3f,$c0 1682 | DB $ff,$3f,$ff,$1f,$ff,$1f,$ff,$1f,$ff,$1f,$ff,$0f,$ff,$0f,$ff,$0f 1683 | DB $07,$f8,$03,$fc,$01,$fe,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1684 | DB $f0,$00,$f0,$00,$f8,$00,$f8,$00,$fc,$00,$7c,$80,$3c,$c0,$1e,$e0 1685 | DB $03,$ff,$03,$ff,$03,$ff,$03,$ff,$03,$ff,$03,$ff,$03,$ff,$03,$ff 1686 | DB $ff,$3f,$3f,$ff,$3f,$ff,$3f,$ff,$3f,$ff,$3f,$ff,$3f,$ff,$3f,$ff 1687 | DB $ff,$c0,$ff,$c0,$ff,$c0,$ff,$c0,$ff,$c0,$ff,$c0,$ff,$c0,$ff,$e0 1688 | DB $ff,$0f,$ff,$0f,$f7,$0f,$f3,$0f,$f1,$0f,$f0,$0f,$f1,$0f,$e1,$1f 1689 | DB $c0,$ff,$fe,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$fc,$fc,$f8,$f8 1690 | DB $00,$ff,$00,$ff,$80,$ff,$c0,$ff,$e0,$ff,$a0,$b0,$30,$00,$30,$00 1691 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$1f,$00,$01 1692 | DB $1e,$e0,$0e,$f0,$07,$f8,$07,$f8,$03,$fc,$01,$fe,$01,$fe,$00,$ff 1693 | DB $7f,$0f,$7f,$0f,$7f,$0f,$7c,$0f,$78,$07,$f8,$07,$f8,$07,$f8,$07 1694 | DB $fd,$ff,$fc,$ff,$fc,$ff,$fe,$ff,$ef,$ff,$9f,$ff,$3e,$ff,$3f,$ff 1695 | DB $ff,$ff,$ff,$ff,$3f,$ff,$1d,$ff,$1b,$ff,$19,$ff,$39,$ff,$f3,$ff 1696 | DB $ff,$ff,$ff,$f3,$ff,$e1,$ff,$c0,$ff,$c0,$ff,$80,$ff,$80,$ff,$00 1697 | DB $fd,$ff,$fc,$ff,$fc,$ff,$fe,$7f,$ff,$7f,$ff,$1f,$ff,$0f,$ff,$06 1698 | DB $ff,$ff,$ff,$ff,$7f,$f8,$7f,$e0,$ff,$c0,$ff,$c1,$ff,$83,$ff,$03 1699 | DB $ff,$c0,$ff,$00,$ff,$00,$ff,$00,$ff,$78,$f7,$f8,$fd,$f2,$e8,$f7 1700 | DB $c1,$3f,$81,$7f,$81,$7f,$01,$ff,$01,$ff,$01,$ff,$01,$ff,$01,$ff 1701 | DB $ff,$bf,$ff,$8f,$e3,$02,$c0,$00,$88,$00,$bf,$00,$fe,$30,$fc,$b8 1702 | DB $f0,$f0,$e0,$c0,$e0,$00,$41,$00,$00,$09,$1b,$00,$72,$00,$46,$00 1703 | DB $2c,$00,$67,$00,$c7,$00,$cc,$0b,$1c,$83,$9c,$03,$1c,$03,$00,$1f 1704 | DB $00,$00,$f8,$00,$f7,$08,$f0,$0f,$0f,$f0,$03,$fc,$00,$ff,$00,$ff 1705 | DB $00,$1f,$00,$03,$00,$00,$00,$c0,$c0,$38,$86,$01,$70,$80,$04,$f8 1706 | DB $00,$ff,$00,$ff,$00,$7f,$00,$0f,$00,$00,$00,$00,$80,$60,$18,$07 1707 | DB $f8,$07,$cc,$03,$cc,$03,$c4,$03,$44,$03,$00,$03,$80,$03,$c0,$01 1708 | DB $03,$ff,$03,$ff,$03,$ff,$03,$ff,$41,$ff,$31,$ff,$18,$ff,$18,$ff 1709 | DB $9f,$ff,$ff,$ff,$ff,$ff,$ef,$ff,$cf,$ff,$8f,$ff,$87,$ff,$83,$ff 1710 | DB $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ef,$ff,$e7,$ff,$c3,$ff,$c7,$ff 1711 | DB $ff,$fc,$ff,$f8,$ff,$f0,$ff,$e1,$ff,$c1,$ff,$c1,$ff,$c1,$ff,$e1 1712 | DB $ff,$38,$ff,$7c,$ff,$ff,$ff,$e0,$7f,$e0,$ff,$e0,$bf,$e0,$bf,$f1 1713 | DB $ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$c0 1714 | DB $ff,$07,$ff,$0f,$ff,$1f,$ff,$3e,$ff,$3c,$ff,$0d,$ff,$01,$ff,$03 1715 | DB $d8,$e7,$e0,$9f,$c0,$3f,$80,$7f,$80,$ff,$80,$ff,$80,$ff,$80,$ff 1716 | DB $01,$ff,$03,$ff,$03,$ff,$03,$ff,$03,$ff,$06,$ff,$06,$ff,$04,$ff 1717 | DB $f0,$f8,$f0,$f0,$e0,$f0,$e0,$f8,$40,$f8,$c0,$fc,$c0,$fe,$80,$fe 1718 | DB $4a,$04,$40,$86,$40,$86,$00,$c6,$80,$47,$80,$47,$00,$47,$00,$67 1719 | DB $00,$1f,$00,$0f,$00,$0f,$00,$0f,$00,$0f,$00,$87,$00,$c3,$00,$e3 1720 | DB $81,$00,$1c,$e0,$03,$fc,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1721 | DB $e0,$01,$20,$01,$e1,$00,$0f,$f0,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1722 | DB $0f,$ff,$0f,$ff,$07,$ff,$07,$ff,$07,$ff,$0f,$ff,$0f,$ff,$0b,$ff 1723 | DB $00,$ff,$00,$ff,$03,$fc,$47,$b8,$27,$d8,$67,$98,$63,$9c,$30,$cf 1724 | DB $4f,$bf,$ce,$3f,$c6,$3f,$82,$7f,$08,$f7,$06,$f9,$83,$7c,$03,$fc 1725 | DB $3f,$e1,$3f,$e1,$3f,$f1,$5f,$b1,$3f,$dd,$67,$9f,$63,$9c,$30,$cf 1726 | DB $5f,$b0,$df,$38,$cf,$38,$8f,$7c,$0f,$f4,$07,$fe,$83,$7f,$03,$fc 1727 | DB $ff,$c0,$ff,$80,$ff,$80,$ff,$80,$ff,$80,$ff,$c0,$ff,$c0,$7f,$fc 1728 | DB $ff,$73,$ff,$e3,$be,$ef,$ba,$ff,$88,$f7,$c6,$f9,$e3,$7c,$f3,$3c 1729 | DB $80,$ff,$80,$ff,$c0,$ff,$40,$ff,$60,$ff,$60,$bf,$70,$bf,$30,$df 1730 | DB $0c,$ff,$0f,$ff,$0f,$ff,$1b,$ff,$19,$f7,$17,$f9,$13,$fd,$13,$fd 1731 | DB $04,$63,$0c,$73,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff 1732 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$03,$ff,$02,$ff,$01,$ff,$01,$ff 1733 | DB $0c,$ff,$06,$ff,$03,$fe,$03,$fe,$f3,$fe,$7f,$9e,$63,$9c,$30,$cf 1734 | DB $00,$ff,$18,$e7,$0c,$f3,$1c,$e3,$3c,$c3,$fe,$01,$ff,$00,$ff,$00 1735 | DB $26,$d9,$10,$ef,$1c,$e3,$1e,$e1,$3f,$c0,$7f,$80,$ff,$00,$ff,$00 1736 | DB $07,$ff,$19,$e7,$0c,$f3,$1c,$e3,$3c,$c3,$fe,$01,$ff,$00,$ff,$00 1737 | DB $f6,$19,$f0,$df,$7c,$f3,$1e,$e1,$3f,$c0,$7f,$80,$ff,$00,$ff,$00 1738 | DB $18,$ff,$18,$ef,$0c,$ff,$1c,$e7,$3c,$c7,$fe,$07,$ff,$03,$ff,$00 1739 | DB $36,$f9,$30,$ef,$3c,$e3,$3e,$e1,$3f,$e0,$3f,$e0,$bf,$e0,$ff,$e0 1740 | DB $80,$ff,$c0,$ff,$60,$ff,$20,$ff,$30,$ff,$fc,$1f,$fe,$07,$ff,$03 1741 | DB $01,$ff,$01,$ff,$01,$ff,$01,$ff,$01,$ff,$01,$ff,$01,$ff,$03,$ff 1742 | DB $ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$00 1743 | DB $c0,$ff,$e0,$7f,$f0,$3f,$f8,$1f,$fc,$0f,$fc,$07,$f8,$0f,$f8,$0f 1744 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$00,$ff,$02,$ff,$03,$ff,$07,$fd 1745 | DB $00,$ff,$00,$ff,$00,$ff,$00,$ff,$01,$ff,$07,$ff,$0f,$fc,$bf,$f8 1746 | DB $03,$fe,$07,$fe,$1f,$fc,$3f,$f0,$ff,$e0,$ff,$00,$ff,$00,$ff,$00 1747 | BGTilesEnd: 1748 | 1749 | ; ------------------------------------------------------------- 1750 | SECTION "X Sine Table",ROMX,BANK[1],ALIGN[8] 1751 | XSineTable: 1752 | DB $00,$00,$FF,$FE,$FD,$FD,$FC,$FB,$FA,$F9,$F9,$F8,$F7,$F6,$F6,$F5 1753 | DB $F4,$F4,$F3,$F2,$F1,$F1,$F0,$EF,$EF,$EE,$ED,$ED,$EC,$EC,$EB,$EA 1754 | DB $EA,$E9,$E9,$E8,$E8,$E7,$E7,$E6,$E6,$E5,$E5,$E5,$E4,$E4,$E4,$E3 1755 | DB $E3,$E3,$E2,$E2,$E2,$E2,$E1,$E1,$E1,$E1,$E1,$E1,$E1,$E1,$E1,$E1 1756 | DB $E0,$E1,$E1,$E1,$E1,$E1,$E1,$E1,$E1,$E1,$E1,$E2,$E2,$E2,$E2,$E3 1757 | DB $E3,$E3,$E4,$E4,$E4,$E5,$E5,$E5,$E6,$E6,$E7,$E7,$E8,$E8,$E9,$E9 1758 | DB $EA,$EA,$EB,$EC,$EC,$ED,$ED,$EE,$EF,$EF,$F0,$F1,$F1,$F2,$F3,$F4 1759 | DB $F4,$F5,$F6,$F6,$F7,$F8,$F9,$F9,$FA,$FB,$FC,$FD,$FD,$FE,$FF,$00 1760 | DB $00,$00,$01,$02,$03,$03,$04,$05,$06,$07,$07,$08,$09,$0A,$0A,$0B 1761 | DB $0C,$0C,$0D,$0E,$0F,$0F,$10,$11,$11,$12,$13,$13,$14,$14,$15,$16 1762 | DB $16,$17,$17,$18,$18,$19,$19,$1A,$1A,$1B,$1B,$1B,$1C,$1C,$1C,$1D 1763 | DB $1D,$1D,$1E,$1E,$1E,$1E,$1F,$1F,$1F,$1F,$1F,$1F,$1F,$1F,$1F,$1F 1764 | DB $20,$1F,$1F,$1F,$1F,$1F,$1F,$1F,$1F,$1F,$1F,$1E,$1E,$1E,$1E,$1D 1765 | DB $1D,$1D,$1C,$1C,$1C,$1B,$1B,$1B,$1A,$1A,$19,$19,$18,$18,$17,$17 1766 | DB $16,$16,$15,$14,$14,$13,$13,$12,$11,$11,$10,$0F,$0F,$0E,$0D,$0C 1767 | DB $0C,$0B,$0A,$0A,$09,$08,$07,$07,$06,$05,$04,$03,$03,$02,$01,$00 1768 | 1769 | SECTION "Y Sine Table",ROMX,BANK[1],ALIGN[8] 1770 | YSineTable: 1771 | DB $00,$00,$01,$01,$02,$02,$03,$03,$04,$04,$05,$05,$06,$06,$06,$07 1772 | DB $07,$07,$07,$07,$07,$08,$07,$07,$07,$07,$07,$07,$06,$06,$06,$05 1773 | DB $05,$04,$04,$03,$03,$02,$02,$01,$01,$00,$00,$00,$FF,$FF,$FE,$FE 1774 | DB $FD,$FC,$FC,$FC,$FB,$FB,$FA,$FA,$FA,$F9,$F9,$F9,$F9,$F9,$F9,$F8 1775 | DB $F9,$F9,$F9,$F9,$F9,$F9,$FA,$FA,$FA,$FB,$FB,$FC,$FC,$FD,$FD,$FE 1776 | DB $FE,$FF,$FF,$00 1777 | DB $00,$00,$01,$01,$02,$02,$03,$03,$04,$04,$05,$05,$06,$06,$06,$07 1778 | DB $07,$07,$07,$07,$07,$08,$07,$07,$07,$07,$07,$07,$06,$06,$06,$05 1779 | DB $05,$04,$04,$03,$03,$02,$02,$01,$01,$00,$00,$00,$FF,$FF,$FE,$FE 1780 | DB $FD,$FC,$FC,$FC,$FB,$FB,$FA,$FA,$FA,$F9,$F9,$F9,$F9,$F9,$F9,$F8 1781 | DB $F9,$F9,$F9,$F9,$F9,$F9,$FA,$FA,$FA,$FB,$FB,$FC,$FC,$FD,$FD,$FE 1782 | DB $FE,$FF,$FF,$00 1783 | DB $00,$00,$01,$01,$02,$02,$03,$03,$04,$04,$05,$05,$06,$06,$06,$07 1784 | DB $07,$07,$07,$07,$07,$08,$07,$07,$07,$07,$07,$07,$06,$06,$06,$05 1785 | DB $05,$04,$04,$03,$03,$02,$02,$01,$01,$00,$00,$00,$FF,$FF,$FE,$FE 1786 | DB $FD,$FC,$FC,$FC,$FB,$FB,$FA,$FA,$FA,$F9,$F9,$F9,$F9,$F9,$F9,$F8 1787 | DB $F9,$F9,$F9,$F9,$F9,$F9,$FA,$FA,$FA,$FB,$FB,$FC,$FC,$FD,$FD,$FE 1788 | DB $FE,$FF,$FF,$00 1789 | DB $00 1790 | 1791 | SECTION "Roll Table",ROMX,BANK[1],ALIGN[8] 1792 | RollTable: 1793 | DB 64,60,58,56,54,52,50,48,46,44,43,41,39,38,36,34 1794 | DB 33,31,29,27,26,24,22,21,19,17,15,13,11, 9, 7, 5 1795 | --------------------------------------------------------------------------------