├── .gitignore ├── .gitmodules ├── .vscode └── tasks.json ├── LICENSE.md ├── README.md ├── docs ├── init.md ├── input.md ├── interrupts.md ├── mappers.md ├── palette.md ├── patterns.md ├── pause.md ├── scroll │ ├── metatiles.md │ └── tiles.md ├── sprites.md ├── tilemap.md ├── utils │ ├── ram.md │ └── registerPreservation.md └── vdp.md ├── examples ├── 01-helloWorld │ └── main.asm ├── 02-staticSprites │ └── main.asm ├── 03-vblank │ └── main.asm ├── 04-input │ ├── main.asm │ └── table.asm ├── 05-movingSprites │ ├── bubble.asm │ └── main.asm ├── 06-paging │ └── main.asm ├── 07-scrolling-tilemap │ └── main.asm ├── 08-scrolling-metatiles │ └── main.asm ├── assets │ ├── README.md │ ├── bubble │ │ ├── bubble.png │ │ ├── bubble.pyxel │ │ ├── palette.bin │ │ └── patterns.bin │ ├── font.bin │ ├── metatiles │ │ ├── metatileDefs.asm │ │ ├── metatileMap.asm │ │ ├── palette.bin │ │ └── patterns.bin │ └── tilemap │ │ ├── palette.bin │ │ ├── patterns.bin │ │ └── tilemap.bin └── build.sh ├── init.asm ├── input.asm ├── interrupts.asm ├── mapper ├── 48k.asm ├── _mapper.asm ├── sega.asm └── waimanu.asm ├── palette.asm ├── patterns.asm ├── pause.asm ├── scroll ├── metatiles.asm └── tiles.asm ├── smslib.asm ├── sprites.asm ├── tests ├── build.sh ├── input │ ├── _helpers.asm │ ├── if.test.asm │ ├── ifHeld.test.asm │ ├── ifPressed.test.asm │ ├── ifReleased.test.asm │ ├── ifXDir.test.asm │ ├── ifXDirHeld.test.asm │ ├── ifXDirPressed.test.asm │ ├── ifXDirReleased.test.asm │ ├── ifYDir.test.asm │ ├── ifYDirHeld.test.asm │ ├── ifYDirPressed.test.asm │ ├── ifYDirReleased.test.asm │ ├── init.test.asm │ ├── loadADirX.test.asm │ ├── loadADirY.test.asm │ ├── readPort1.test.asm │ └── readPort2.test.asm ├── interrupts │ ├── enable.test.asm │ ├── init.test.asm │ ├── setLineInterval.test.asm │ └── waitForVBlank.test.asm ├── palette │ ├── setIndex.test.asm │ ├── writeBytes.test.asm │ ├── writeRgb.test.asm │ └── writeSlice.test.asm ├── patterns │ ├── setIndex.test.asm │ ├── writeBytes.test.asm │ └── writeSlice.test.asm ├── pause │ ├── callIfPaused.test.asm │ ├── init.test.asm │ ├── jpIfPaused.test.asm │ └── waitIfPaused.test.asm ├── scroll │ ├── metatiles │ │ ├── _helpers.asm │ │ ├── init.test.asm │ │ ├── render.test.asm │ │ └── update.test.asm │ └── tiles │ │ ├── init.test.asm │ │ ├── render.test.asm │ │ └── update.test.asm ├── smslib-zest.asm ├── sprites │ ├── add.test.asm │ ├── addGroup.test.asm │ ├── copyToVram.test.asm │ └── init.test.asm ├── suite.asm ├── testing.md ├── tilemap │ ├── adjustXPixels.test.asm │ ├── adjustYPixels.test.asm │ ├── calculateScroll.test.asm │ ├── ifColScroll.test.asm │ ├── ifColScrollElseRet.test.asm │ ├── ifRowScroll.test.asm │ ├── ifRowScrollElseRet.test.asm │ ├── loadHLWriteAddress.test.asm │ ├── reset.test.asm │ ├── setColRow.test.asm │ ├── setIndex.test.asm │ ├── stopDownRowScroll.test.asm │ ├── stopLeftColScroll.test.asm │ ├── stopRightColScroll.test.asm │ ├── stopUpRowScroll.test.asm │ ├── writeBytes.test.asm │ ├── writeBytesUntil.test.asm │ ├── writeRow.test.asm │ ├── writeRows.test.asm │ ├── writeScrollBuffers.test.asm │ ├── writeScrollRegisters.test.asm │ ├── writeTile.test.asm │ └── writeTiles.test.asm └── utils │ ├── clobbers │ ├── clobbers.end.jpc.test.asm │ ├── clobbers.end.jpm.test.asm │ ├── clobbers.end.jpnc.test.asm │ ├── clobbers.end.jpnz.test.asm │ ├── clobbers.end.jpp.test.asm │ ├── clobbers.end.jppe.test.asm │ ├── clobbers.end.jppo.test.asm │ ├── clobbers.end.jpz.test.asm │ ├── clobbers.end.jrc.test.asm │ ├── clobbers.end.jrnc.test.asm │ ├── clobbers.end.jrnz.test.asm │ ├── clobbers.end.jrz.test.asm │ ├── clobbers.end.retc.test.asm │ ├── clobbers.end.retm.test.asm │ ├── clobbers.end.retnc.test.asm │ ├── clobbers.end.retnz.test.asm │ ├── clobbers.end.retp.test.asm │ ├── clobbers.end.retpe.test.asm │ ├── clobbers.end.retpo.test.asm │ ├── clobbers.end.retz.test.asm │ ├── clobbers.endBranch.test.asm │ ├── clobbers.withBranching.test.asm │ └── sequentialClobberScopes.test.asm │ └── registers │ ├── _helpers.asm │ ├── autoPreserve.test.asm │ ├── iRegister.test.asm │ ├── nestedPreserveScopes.test.asm │ └── registers.test.asm ├── tilemap.asm ├── utils ├── assert.asm ├── clobbers.asm ├── math.asm ├── outiBlock.asm ├── port.asm ├── preserve.asm ├── ram.asm ├── registers.asm ├── restore.asm └── vdp.asm └── vdp.asm /.gitignore: -------------------------------------------------------------------------------- 1 | examples/dist 2 | tests/dist 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/zest"] 2 | path = tests/zest 3 | url = https://github.com/lajohnston/zest 4 | branch = main 5 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build examples", 8 | "type": "shell", 9 | "command": "./examples/build.sh", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 lajohnston 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/init.md: -------------------------------------------------------------------------------- 1 | # Initialise (init.asm) 2 | 3 | Adds a code section at address 0 to initialise the system and any smslib modules you are using. When it is done it will jump to an `init` label that you must define in your code. 4 | 5 | If you're using the individual libraries (rather than the full smslib.asm suite), `init.asm` should be included last to ensure if knows which libraries to initialise. 6 | 7 | ## Settings 8 | 9 | The following constants can be set before including this module or smslib.asm: 10 | 11 | ```asm 12 | ; Disables the boot handler at address 0 13 | .define init.DISABLE_HANDLER 14 | ``` 15 | 16 | ## init 17 | 18 | Defines the boot handler code at the current location. 19 | 20 | ```asm 21 | init 22 | ``` 23 | 24 | ## init.smslibModules 25 | 26 | Initialise all active smslib modules, which happens automatically at boot (unless `init.DISABLE_HANDLER` is defined). Note: the modules must have been `.included` before this is called. 27 | 28 | ```asm 29 | init.smslibModules 30 | ``` -------------------------------------------------------------------------------- /docs/interrupts.md: -------------------------------------------------------------------------------- 1 | # Interrupts (interrupts.asm) 2 | 3 | Handles VBlank and/or HBlank interrupts. When enabled, these interrupts allow the VDP to send a signal to the Z80 processor when it has finished drawing certain lines on the screen (HBlanks) and each time it's finished drawing the whole screen/frame (VBlanks). You can use these to time your game logic or add screen effects, such as changing the color palette for a portion of the screen. 4 | 5 | ## Enabling interrupts 6 | 7 | You will need to enable interrupts in both the VDP and Z80. After you initialise your game you can enable VBlanks and HBlanks in the VDP using registers 0 and 1: 8 | 9 | - Enable HBlank - VDP register 0, bit 4 10 | - Enable VBlank - VDP register 1, bit 5 11 | 12 | You can use [vdp.asm](./vdp.md) for this, taking care not to overwrite any other flags that are also stored within these registers (see `vdp.asm` file for documentation): 13 | 14 | ```asm 15 | vdp.enableHBlank 16 | vdp.enableVBlank 17 | ``` 18 | 19 | You also need to enable interrupts within the Z80 CPU: 20 | 21 | ```asm 22 | interrupts.enable 23 | ``` 24 | 25 | ## VBlanks (frame interrupts) 26 | 27 | VBlanks occur each time the VDP has finished drawing a frame (50 times a second in PAL, 60 times a second in NTSC). It's a small window of opportunity to blast data to the VDP before it starts drawing the next frame. Sending data to the VDP outside this window can result in missed writes and graphical corruption. The only other safe time to write to the VDP is when the display is off. 28 | 29 | Enable the VBlank handler by defining `interrupts.HANDLE_VBLANK` setting before including `interrupts.asm`: 30 | 31 | ```asm 32 | .define interrupts.HANDLE_VBLANK 1 33 | .include "interrupts.asm" 34 | ``` 35 | 36 | You will also need to define an `interrupts.onVBlank` label that the handler will jump to when a VBlank occurs. This handler must end with a macro call to `interrupts.endVBlank`: 37 | 38 | ```asm 39 | interrupts.onVBlank: 40 | ... ; write data to VRAM 41 | interrupts.endVBlank ; return from VBlank 42 | ``` 43 | 44 | VBlanks can also be used to regulate the speed of your game logic. Place `interrupts.waitForVBlank` in your game loop to ensure the logic doesn't update too quickly. 45 | 46 | ```asm 47 | gameLoop: 48 | interrupts.waitForVBlank 49 | ... update logic 50 | jp gameLoop ; run loop again 51 | ``` 52 | 53 | ## HBlanks (line interrupts) 54 | 55 | HBlanks occur when the line counter in the VDP falls below zero. This counter is set to the value stored in VDP Register 10 before the frame is drawn and each time a line is drawn (from top to bottom) it is decremented. Further documentation on this can be found in Richard Talbot-Watkins documentation on [VDP Register 10](https://www.smspower.org/uploads/Development/richard.txt). 56 | 57 | Enable the HBlank handler by defining `interrupts.HANDLE_HBLANK` setting before including `interrupts.asm`: 58 | 59 | ```asm 60 | ; Note: you can also enable interrupts.HANDLE_VBLANK alongside this if you wish 61 | .define interrupts.HANDLE_HBLANK 1 62 | .include "interrupts.asm" 63 | ``` 64 | 65 | You will also need to define an `interrupts.onHBlank` label that the handler will jump to when an HBlank occurs. This handler must end with a macro call to `interrupts.endHBlank`: 66 | 67 | ```asm 68 | interrupts.onHBlank: 69 | ... 70 | interrupts.endHBlank ; return from HBlank 71 | ``` 72 | 73 | The HBlank won't trigger unless the line interval has been set. This takes a zero-based value: 74 | 75 | ```asm 76 | interrupts.setLineInterval 1 ; trigger HBlank every line (lines 0, 1, 2...) 77 | interrupts.setLineInterval 10 ; trigger every 10th line (lines 9, 19, 29...) 78 | ``` 79 | 80 | This can also be set dynamically from `a` by omitting the argument. When using this method the value in `a` must be 0-based: 81 | 82 | ```asm 83 | ; Trigger every 20th line 84 | ld a, 19 85 | interrupts.setLineInterval 86 | ``` 87 | 88 | Please note that if you change the interval during active screen time, the new interval won't take effect until the next HBlank has occurred. This means that each interval you specify will trigger a minimum of 2 times, i.e. an interval every 10 lines will trigger for lines 9 (0-based) and line 19 even if you change it after line 9. 89 | 90 | You can read the current line being drawn. The value will be loaded into `a` 91 | 92 | ```asm 93 | interrupts.getLine 94 | ``` 95 | -------------------------------------------------------------------------------- /docs/mappers.md: -------------------------------------------------------------------------------- 1 | # Memory Mappers 2 | 3 | The Master System can only view 48KB of ROM memory at a time. Mappers control which portions of ROM are visible within this 48KB window and can dynamically switch portions at runtime to allow for much larger cartridge sizes. The included smslib mappers can abstract this complexity from you or can be used as examples to create your own. 4 | 5 | Mappers define one or more fixed-sized 'slots' that can provide access to a small portion of the larger ROM at any given time. The portion of ROM they provide access to (called a 'bank') can be changed at runtime. 6 | 7 | ## Selecting a Mapper 8 | 9 | SMSLib will default to using a basic 48KB mapper which is small enough to not require any paging. If you require a larger ROM than this then you can choose another one by including it before `smslib.asm`: 10 | 11 | ```asm 12 | .incdir "lib/smslib" ; point to smslib directory 13 | .include "mapper/sega.asm" ; use sega mapper 14 | .include "smslib.asm" ; include rest of smslib 15 | ``` 16 | 17 | ## Slots 18 | 19 | Only one mapper can be used per project. All mappers expose `FIXED_SLOT`, `PAGE_SLOT_A`, `PAGE_SLOT_B` and `RAM_SLOT` constants. Using these constants should make it easier for you to swap out a mapper at a later stage of development if you decide to do so. 20 | 21 | ```asm 22 | ; Pageable slots are good for asset data that is only needed at certain times 23 | .slot mapper.PAGE_SLOT_A 24 | .include "assets.asm" ; contents can now be paged into PAGE_SLOT_A 25 | 26 | ; Fixed slot is good for code to ensure it's always accessible 27 | .slot mapper.FIXED_SLOT 28 | .include "game.asm" ; contents will always be accessible 29 | 30 | ; RAM slot should be used for RAM variables 31 | .ramsection "foo" slot mapper.RAM_SLOT 32 | bar DB 33 | .ends 34 | ``` 35 | 36 | ## Paging Data 37 | 38 | Before accessing data from the page slots (e.g. when loading an asset) remember to first tell the mapper to 'page' to the bank you want to access. You can use WLA-DX's colon prefix for a label to retrieve the bank number it has been placed in. 39 | 40 | ```asm 41 | mapper.pageBank :paletteData ; ensure the bank containing paletteData is accessible 42 | palette.writeSlice paletteData, 1 ; you can now use paletteData 43 | ``` 44 | 45 | ### Page Slot B 46 | 47 | Some mappers also provide a second pageable slot, `PAGE_SLOT_B`. It's generally simpler to stick to the one (`PAGE_SLOT_A`) but if you happened to have an asset over 16KB in size then both page slots could be used together to map the whole asset at once. 48 | 49 | ```asm 50 | ; Asset intended to be mapped into mapper.PAGE_SLOT_B 51 | .slot mapper.PAGE_SLOT_B 52 | paletteData: 53 | ... 54 | 55 | ; you must provide PAGE_SLOT_B as the second parameter 56 | mapper.pageBank :paletteData, mapper.PAGE_SLOT_B 57 | palette.writeSlice paletteData, 1 ; palette data now accessible 58 | 59 | ``` 60 | 61 | ## Settings 62 | 63 | You can customise some mappers with additional parameters. Check the relevant mapper asm file to see which settings are supported. 64 | 65 | ```asm 66 | .define mapper.PAGEABLE_BANKS 4 67 | .define mapper.ENABLE_CARTRIDGE_RAM 1 68 | .include "smslib/mapper/sega.asm" 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/palette.md: -------------------------------------------------------------------------------- 1 | # Color Palette (palette.asm) 2 | 3 | Handles the VDP color palettes. There are 2 palettes of 16 colors: 4 | 5 | - Each background pattern (tile) can use either the first 16 indices (0-15) or 6 | the last 16 (16-31) 7 | - Sprites can only use the last 16 indices (16-31) 8 | 9 | The first color index of each palette (index 0 or index 16) is used as the transparent color. This color will be omitted for sprites. Background tiles aren't affected by it unless they are marked as a 'priority' in the tilemap. Priority background patterns are rendered in front of sprites, except for the first color which is rendered behind. 10 | 11 | The color in each index is a byte containing 2-bit RGB color values (--BBGGRR). 12 | 13 | The pixel values defined within tiles/patterns don't define their colors directly but rather reference the color index you set in the palette. 14 | 15 | ## Setting palette colors 16 | 17 | To get you started you can call `palette.rgb` with some RGB values to generate a color byte with an approximate RGB value. In reality you'll probably use a tool to generate this data for you from image data (such as [BMP2Tile](https://www.smspower.org/maxim/Software/BMP2Tile)). 18 | 19 | Each color component can have the value of 0, 85, 170 or 255. Values inbetween these will be rounded to the closest factor. 20 | 21 | ```asm 22 | paletteData: 23 | palette.rgb 255, 0, 0 ; red 24 | palette.rgb 255, 170, 0 ; orange 25 | palette.rgb 255, 255, 0 ; yellow 26 | palette.rgb 0, 255, 0 ; green 27 | palette.rgb 0, 0, 255 ; blue 28 | palette.rgb 85, 0, 85 ; indigo 29 | palette.rgb 170, 0, 255 ; violet 30 | ``` 31 | 32 | You can then write these into the VDP VRAM the following macros. 33 | 34 | ### palette.setIndex 35 | 36 | Set the color palette index ready to write to: 37 | 38 | ```asm 39 | palette.setIndex 0 ; set the first color index 40 | palette.setIndex palette.SPRITE_PALETTE ; first color in 'sprite' palette 41 | palette.setIndex palette.SPRITE_PALETTE + 1 ; second color in 'sprite' palette 42 | ``` 43 | ### palette.writeBytes 44 | 45 | Write bytes of raw data to the color RAM: 46 | 47 | ```asm 48 | myPaletteData: 49 | .incbin "myPalette.inc" fsize myPaletteDataSize 50 | 51 | palette.setIndex 0 52 | palette.writeBytes myPaletteData, myPaletteDataSize 53 | ``` 54 | 55 | ### palette.writeSlice 56 | 57 | Write specific colors from the data. 58 | 59 | ```asm 60 | palette.setIndex 0 ; point to first palette index 61 | palette.writeSlice paletteData, 3 ; write 3 colors from current index onwards (indices 0, 1, 2) 62 | palette.writeSlice otherPaletteData, 2; write 2 more colors (indices 3 and 4) 63 | ``` 64 | 65 | An optional third parameter lets you skip some colors in the data: 66 | 67 | ```asm 68 | ; Load 5 colors but skip the first 2 69 | palette.writeSlice paletteData, 5, 2 70 | ``` 71 | 72 | ### palette.writeRgb 73 | 74 | Loads an approximate RGB value into the current palette index. Each component is rounded to the nearest of the following values: 0, 85, 170, 255. 75 | 76 | ```asm 77 | palette.setIndex 0 78 | palette.writeRgb 255, 0, 0 ; a bright red 79 | ``` -------------------------------------------------------------------------------- /docs/patterns.md: -------------------------------------------------------------------------------- 1 | # Patterns (patterns.asm) 2 | 3 | Loads patterns (tiles) into the VRAM, which can be used for background images or sprites. 4 | 5 | This only deals with uncompressed tile data and is provided for example purposes to get you started. For an actual game you would want to compress pattern data using an algorithm such as zx7 or aPLib and use the appropriate lib to decompress the data and write it to VRAM. 6 | 7 | ## patterns.writeBytes 8 | 9 | Writes uncompressed pattern data into VRAM. 10 | 11 | Due to WLA-DX limitations the size parameter must be an `immediate` value, so cannot be calculated using something like `endAddr - startAddr`. It can therefore either be a constant, or if using `fsize` to calculate the size of an included binary you just have to ensure this label is defined before this macro is called. 12 | 13 | ```asm 14 | uncompressedPatternData: 15 | .incbin "tiles.bin" fsize patternDataSize 16 | 17 | ; Write pattern data from index 16 onwards 18 | patterns.setIndex 16 19 | patterns.writeBytes uncompressedPatternData, patternDataSize 20 | ``` 21 | 22 | ## patterns.writeSlice 23 | 24 | Lets you pick out certain tiles from the binary data and write them individually. 25 | 26 | ```asm 27 | myUncompressedPatternData: 28 | .incbin 'tiles.bin' 29 | 30 | ; Write 4 patterns into pattern index 0 onwards (indices 0-3) 31 | patterns.setIndex 0 32 | patterns.writeSlice myUncompressedPatternData, 4 33 | 34 | ; ...then write another pattern into the next index (index 4) 35 | patterns.writeSlice myUncompressedPatternData, 1 36 | ``` 37 | 38 | An optional third parameter lets you skip a certain number of patterns in the data: 39 | 40 | ```asm 41 | ; Write another pattern, skipping the first 9 42 | patterns.writeSlice otherPatternData, 1, 9 43 | ``` 44 | 45 | ## Data format 46 | 47 | Patterns are an image of 8x8 pixels. Each pixel is a 4-bit color palette index reference, making a total of 32-bytes. 48 | 49 | The 4-bit color palette index value (0-15) can reference either index 0-15 or index 16-31 of the palette, depending on the context where the pattern is used; sprites use indices 16-31 whereas background tiles in the tilemap contain a bit that determines which to use. If the pattern is used for a sprite then color index 0 is used as the transparent color (i.e. not drawn) 50 | 51 | Pixels are encoded in bitplanes so the bits of each are strewn across 2-bytes. This is hard to explain, but given 4 pixels (A, B, C, D), the ordering of each bit will be bitplane encoded as: 52 | 53 | - Byte 1: A1 B1 C1 D1, A2 B2 C2 D2 54 | - Byte 2: A3 B3 C3 D3, A4 B4 C4 D4 55 | 56 | If A was 1111 and the rest were 0000, the encoding would be: 57 | 58 | - Byte 1: **1**000 **1**000 59 | - Byte 2: **1**000 **1**000 60 | 61 | If B was 1101 and the rest were 0000, the encoding would be: 62 | 63 | - Byte 1: 0**1**00 0**1**00 (first two bits- 1, 1) 64 | - Byte 2: 0**0**00 0**1**00 (second two bits - 0, 1) 65 | 66 | ## Settings 67 | 68 | If you wish to change these defaults, use `.define` to define these value at some point before you import the library. 69 | 70 | ### patterns.VRAM_ADDRESS 71 | 72 | The pattern address in VRAM. Defaults to $0000 73 | -------------------------------------------------------------------------------- /docs/pause.md: -------------------------------------------------------------------------------- 1 | # Pause (pause.asm) 2 | 3 | Provides a pause handler that toggles a flag in RAM whenever the pause button is pressed. This flag can be detected at a safe position in your code such as at the start of the game loop. 4 | 5 | Basic pause functionality can be provided by simply waiting until the pause button is pressed again: 6 | 7 | ```asm 8 | pause.waitIfPaused 9 | ``` 10 | 11 | If you wish to jp or call a label based on the pause state, you can use the following: 12 | 13 | ```asm 14 | pause.jpIfPaused myPauseState 15 | pause.callIfPaused myPauseState 16 | 17 | myPauseState: 18 | ... 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/scroll/tiles.md: -------------------------------------------------------------------------------- 1 | # scroll/tiles.asm 2 | 3 | Manages a scrollable tilemap consisting of raw uncompressed tiles (see [tilemap.asm](../tilemap.md) for more information about the tile data format). This builds on the API provided by [tilemap.asm](../tilemap.md). 4 | 5 | ## Importing the handler and setting the metatile size 6 | 7 | The module isn't included by default so will need to be included: 8 | 9 | ```asm 10 | .include "scroll/tiles.asm" 11 | ``` 12 | 13 | ## 1. Initialise the map 14 | 15 | Initialise the map and draw the initial view using `scroll.tiles.init`. This should be performed when the display is off. 16 | 17 | ```asm 18 | .define tilemapData ... ; the tilemap data (pointing to the top left of the map) 19 | .define mapCols 64 ; the number of columns in the map (width in tiles); Max 127 20 | .define mapRows 64 ; the number of rows in the map (height in tiles); Max 255 21 | .define colOffset 0 ; columns to offset the initial view by 22 | .define rowOffset 0 ; rows to offset the initial view by 23 | 24 | scroll.tiles.init tilemapData mapCols mapRows colOffset rowOffset 25 | ``` 26 | 27 | If needed you can use registers for this, though you'll need to handle the initial view offsetting yourself in the top left pointer. 28 | 29 | ```asm 30 | ld a, mapCols * 2 ; number of bytes per row (columns * 2) 31 | ld b, mapRows ; the number of rows in the tilemap 32 | ld d, 1 ; column offset (1-based; 1 = none) 33 | ld e, 1 ; row offset (1-based; 1 = none) 34 | ld hl, tilemapData ; pointer to our tilemap (top-left corner) 35 | 36 | scroll.tiles.init ; initialise and draw 37 | ``` 38 | 39 | ## 2. Adjust once per frame 40 | 41 | Adjust the x and y pixels by up to 8 pixels each per frame (-8 to +8 inclusive). This limit isn't enforced so ensure you stay within it for correct results. 42 | 43 | ```asm 44 | ; Negative x values move left, positive move right 45 | ld a, 1 ; move right 1 pixel 46 | scroll.tiles.adjustXPixels 47 | ``` 48 | 49 | ```asm 50 | ; Negative y values move up, positive move down 51 | ld a, -2 ; move up 2 pixels 52 | scroll.tiles.adjustYPixels 53 | ``` 54 | 55 | After adjusting these, use `scroll.tiles.update` to apply the changes to the RAM buffers. 56 | 57 | ## 3. Transfer changes to VRAM (during VBlank) 58 | 59 | `scroll.tiles.render` will apply the scroll register changes to the VDP, and write the new row and/or column to the tilemap if required. 60 | -------------------------------------------------------------------------------- /docs/sprites.md: -------------------------------------------------------------------------------- 1 | # Sprites (sprites.asm) 2 | 3 | The VDP holds a sprite table in VRAM containing the tile patterns and x and y positions of up to 64 on-screen sprites. It's generally advisable to work with a copy of this table in normal RAM so you can edit it efficiently. You can then transfer it to VRAM in bulk when it's safe to do so without causing graphical corruption i.e when the display is off or during VBlank (see [interrupts](./interrupts.md)) 4 | 5 | `sprites.asm` manages an efficient sprite buffer in RAM and lets you write it to VRAM when required. 6 | 7 | ## Init 8 | 9 | Reset the sprite buffer at the start of each game loop. This will set it back to the first index so you can start adding sprites to the beginning of the table. 10 | 11 | ```asm 12 | sprites.reset 13 | ``` 14 | 15 | ## Adding Sprites 16 | 17 | ```asm 18 | ld a, 100 ; a = yPos 19 | ld b, 80 ; b = xPos 20 | ld c, 5 ; c = pattern number 21 | sprites.add 22 | ``` 23 | 24 | ## Sprite Groups 25 | 26 | Sprite groups allow you to conveniently add multiple sprites with positions relative to a shared anchor point. The offsets must be positive numbers. If any sub-sprites fall off screen they will not be added: 27 | 28 | ```asm 29 | ; Define a sprite group of 2x2 sprites 30 | spriteGroup: 31 | sprites.startGroup 32 | ; pattern number, relX, relY 33 | sprites.sprite 1, 0, 0 ; top left 34 | sprites.sprite 2, 8, 0 ; top right (x + 8) 35 | sprites.sprite 3, 0, 8 ; bottom left (y + 8) 36 | sprites.sprite 4, 8, 8 ; bottom right (x + 8, y + 8) 37 | sprites.endGroup ; end of group 38 | 39 | ; Add the group to the buffer 40 | code: 41 | ld hl, spriteGroup ; hl = pointer to our sprite group 42 | ld b, 150 ; b = anchor x pos 43 | ld c, 50 ; c = anchor y pos 44 | sprites.addGroup ; add the sprites to the sprite table 45 | ``` 46 | 47 | ## Batching 48 | 49 | It is more efficient to add multiple sprites and/or sprite groups within a 'batch'. This allows smslib to avoid having to store and retrieve the next index from RAM for each sprite, and instead can do it once at the beginning of the batch and once at the end. 50 | 51 | During a batch the next sprite index will be kept in `de` and incremented each time so be careful not to clobber this: 52 | 53 | ```asm 54 | sprites.startBatch 55 | ... ; add multiple sprites or sprite groups 56 | sprites.endBatch 57 | ``` 58 | 59 | ## Transfer to VRAM 60 | 61 | Transfer buffer to VRAM when safe to do so, either when the display is off or during VBlank: 62 | 63 | ```asm 64 | sprites.copyToVram 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/utils/ram.md: -------------------------------------------------------------------------------- 1 | # utils.ram 2 | 3 | General Z80 routines for manipulating RAM values. 4 | 5 | ## Importing 6 | 7 | If you're including the whole smslib.asm library then this will probably already have been imported, otherwise you can safely import it as follows: 8 | 9 | ```asm 10 | .ifndef utils.ram 11 | .include "utils/ram.asm" 12 | .endif 13 | ``` 14 | 15 | ## utils.ram.fill 16 | 17 | Fills a portion of RAM with a value. 18 | 19 | ```asm 20 | ; Fill 20-bytes of RAM with the value of 0, starting from someAddress 21 | utils.ram.fill 20 someAddress 0 22 | 23 | ; Fill 20-bytes of RAM with the value stored in A, starting from someAddress 24 | ld a, 123 25 | utils.ram.fill 20 someAddress 26 | 27 | ; Fill 20-bytes of RAM with the value stored in A, starting from HL 28 | ld hl, someAddress 29 | ld a, 123 30 | utils.ram.fill 20 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/vdp.md: -------------------------------------------------------------------------------- 1 | # Visual Display Processor (vdp.asm) 2 | 3 | Handles the general VDP (graphics chip) registers and settings. More specific aspects controlled by the VDP have been separated into their own modules, such as `sprites.asm`, `palette.asm`, `patterns.asm` and `tilemap.asm`. 4 | 5 | Change register values using the provided macros. See vdp.asm for more details about each setting. 6 | 7 | ```asm 8 | vdp.setBorderColorIndex 16 9 | 10 | vdp.enableDisplay 11 | vdp.disableDisplay 12 | 13 | vdp.enableVBlank 14 | vdp.disableVBlank 15 | 16 | vdp.enableTallSprites 17 | vdp.disableTallSprites 18 | 19 | vdp.enableSpriteZoom 20 | vdp.disableSpriteZoom 21 | 22 | vdp.enableHBlank 23 | vdp.disableHBlank 24 | 25 | vdp.enableSpriteShift 26 | vdp.disableSpriteShift 27 | 28 | vdp.showLeftColumn 29 | vdp.hideLeftColumn 30 | 31 | ; If parameter omitted, the value in register A is used 32 | vdp.setScrollX 100 33 | vdp.setScrollY 255 34 | 35 | vdp.lockHScroll 36 | vdp.unlockHScroll 37 | 38 | vdp.lockVScroll 39 | vdp.unlockVScroll 40 | 41 | vdp.setLineInterrupt 5 42 | ``` 43 | 44 | ## Batches 45 | 46 | Many of the VDP's settings are stored within the same VDP registers, so if you are changing multiple settings then it's much more efficient to batch them together by wrapping them in calls to `vdp.startBatch` and `vdp.endBatch`. `vdp.asm` knows which ones belong within the same register and so only updates that register once. 47 | 48 | ```asm 49 | vdp.startBatch 50 | vdp.enableDisplay 51 | vdp.enableVBlank 52 | vdp.endBatch 53 | ``` 54 | -------------------------------------------------------------------------------- /examples/01-helloWorld/main.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; SMSLib Hello World example 3 | ;==== 4 | .sdsctag 1.10, "smslib Hello World", "smslib Hello World example based on Maxim's tutorial", "lajohnston" 5 | 6 | ;==== 7 | ; Import smslib 8 | ;==== 9 | .incdir "../../" ; point to smslib directory 10 | .include "smslib.asm" 11 | .incdir "." ; point back to current working directory 12 | 13 | ;==== 14 | ; Define asset data 15 | ;==== 16 | 17 | ; Map ASCII data to byte values so we can use .asc later (see wla-dx docs) 18 | .asciitable 19 | map " " to "~" = 0 20 | .enda 21 | 22 | .section "assets" free 23 | paletteData: 24 | palette.rgb 0, 0, 0 ; black 25 | palette.rgb 255, 255, 255 ; white 26 | 27 | fontData: 28 | ; font.bin contains uncompressed graphics representing the letters of the 29 | ; alphabet. Here we include it and set fontDataSize to its total size 30 | .incbin "../assets/font.bin" fsize fontDataSize 31 | 32 | message: 33 | .asc "Hello, world!" 34 | .db $ff ; terminator byte 35 | .ends 36 | 37 | ;==== 38 | ; Initialise program 39 | ; 40 | ; SMSLib will jump to 'init' label after initialising the system 41 | ;==== 42 | .section "init" free 43 | init: 44 | ; Load palette 45 | palette.setIndex 0 ; point to first color index 46 | palette.writeSlice paletteData, 2 ; write 2 colors from paletteData 47 | 48 | ; Load font tiles 49 | patterns.setIndex 0 ; point to first pattern index 50 | patterns.writeBytes fontData, fontDataSize ; write uncompressed font data into pattern VRAM 51 | 52 | ; Display font tiles on screen 53 | tilemap.setColRow 0, 0 ; set tilemap index x0, y0 (top left) 54 | tilemap.writeBytesUntil $ff message ; write data from 'message' until reaching terminator ($ff) byte 55 | 56 | ; Enable the display 57 | vdp.enableDisplay 58 | 59 | ; End program with infinite loop 60 | -: jr - 61 | .ends 62 | -------------------------------------------------------------------------------- /examples/02-staticSprites/main.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; SMSLib Static sprites example 3 | ; 4 | ; Renders static sprites on the screen 5 | ;==== 6 | .sdsctag 1.10, "smslib sprites", "smslib static sprite tutorial", "lajohnston" 7 | 8 | ;==== 9 | ; Import smslib 10 | ;==== 11 | .incdir "../../" ; back to smslib directory 12 | .include "smslib.asm" ; base library 13 | .incdir "." ; back to current directory 14 | 15 | ;==== 16 | ; Assets we'll refer to in the code 17 | ;==== 18 | .section "assets" free 19 | bubblePatterns: 20 | .incbin "../assets/bubble/patterns.bin" fsize bubblePatternsSize 21 | 22 | bubblePalette: 23 | .incbin "../assets/bubble/palette.bin" fsize bubblePaletteSize 24 | 25 | spriteGroup: 26 | sprites.startGroup 27 | ; pattern, relativeX, relativeY 28 | sprites.sprite 1, 0, 0 ; top left (x0, y0) 29 | sprites.sprite 2, 8, 0 ; top right (x+8, y0) 30 | sprites.sprite 3, 0, 8 ; bottom left (x0, y+8) 31 | sprites.sprite 4, 8, 8 ; bottom right (x+8, y+8) 32 | sprites.endGroup 33 | .ends 34 | 35 | ;==== 36 | ; Initialise program 37 | ; 38 | ; SMSLib will jump to 'init' label after initialising the system 39 | ;==== 40 | .section "init" free 41 | init: 42 | ; Set background colour 43 | palette.setIndex 0 44 | palette.writeRgb 0, 0, 0 ; black 45 | 46 | ; Load sprite palette 47 | palette.setIndex palette.SPRITE_PALETTE 48 | palette.writeSlice bubblePalette, 6 49 | 50 | ; Load pattern data into indices 256+ (used for sprites, by default) 51 | patterns.setIndex 256 52 | patterns.writeBytes bubblePatterns, bubblePatternsSize 53 | 54 | ; It's more efficient (but optional) to add multiple sprites within a 55 | ; batch, so start a new batch 56 | sprites.startBatch 57 | 58 | ; Add a sprite to the buffer. See sprites.add documentation in sprite.asm 59 | ; for details about which parameters it expects in which registers 60 | ld a, 100 ; y position 61 | ld b, 80 ; x position 62 | ld c, 5 ; pattern number 63 | sprites.add ; add sprite 64 | 65 | ; Add a sprite group - multiple sprites relative to a position 66 | ld hl, spriteGroup ; point to group (see assets below) 67 | ld b, 140 ; base x pos 68 | ld c, 50 ; base y pos 69 | sprites.addGroup ; add group to buffer 70 | 71 | ; Another group - same group, different position 72 | ld hl, spriteGroup ; point to group 73 | ld b, 170 ; base x pos 74 | ld c, 120 ; base y pos 75 | sprites.addGroup ; add group to buffer 76 | 77 | ; End the sprite batch 78 | sprites.endBatch 79 | 80 | ; Copy buffer to VRAM 81 | sprites.copyToVram 82 | 83 | ; Enable the display then stop 84 | vdp.enableDisplay 85 | -: jr - 86 | .ends 87 | -------------------------------------------------------------------------------- /examples/04-input/table.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; A table that will indicate which button conditions are true for the 3 | ; current frame 4 | ;==== 5 | 6 | ; The starting row the render the table from 7 | .define TABLE_ROW_OFFSET = 5 8 | 9 | ; The indicator tile columns for each condition (Pressed, Current, Held) 10 | .define INDICATOR_COLUMN_START = 9 11 | .define PRESSED_INDICATOR_COLUMN = INDICATOR_COLUMN_START + 1 12 | .define CURRENT_INDICATOR_COLUMN = PRESSED_INDICATOR_COLUMN + 6 13 | .define HELD_INDICATOR_COLUMN = CURRENT_INDICATOR_COLUMN + 6 14 | .define RELEASED_INDICATOR_COLUMN = HELD_INDICATOR_COLUMN + 6 15 | 16 | ; The row number for each button 17 | .define TABLE_BODY_ROW = TABLE_ROW_OFFSET + 4 18 | .define UP_ROW = TABLE_BODY_ROW 19 | .define DOWN_ROW = TABLE_BODY_ROW + 1 20 | .define LEFT_ROW = TABLE_BODY_ROW + 2 21 | .define RIGHT_ROW = TABLE_BODY_ROW + 3 22 | .define BUTTON_1_ROW = TABLE_BODY_ROW + 5 23 | .define BUTTON_2_ROW = TABLE_BODY_ROW + 6 24 | .define COMBO_ROW = TABLE_BODY_ROW + 8 25 | 26 | ; Map ASCII data to byte values so we can use .asc later (see wla-dx docs) 27 | .asciitable 28 | map " " to "~" = 0 29 | .enda 30 | 31 | .section "assets" free 32 | table.fontPalette: 33 | palette.rgb 0, 0, 0 34 | palette.rgb 170, 85, 170 35 | 36 | table.fontPatterns: 37 | .incbin "../assets/font.bin" fsize table.fontPatternsSize 38 | 39 | ; Table template 40 | table.template: 41 | .asc " Pressed " 42 | .asc " Current " 43 | .asc " Held " 44 | .asc " Released" 45 | .asc "Up ( ) ( ) ( ) ( ) " 46 | .asc "Down ( ) ( ) ( ) ( ) " 47 | .asc "Left ( ) ( ) ( ) ( ) " 48 | .asc "Right ( ) ( ) ( ) ( ) " 49 | .asc " " 50 | .asc "Button 1 ( ) ( ) ( ) ( ) " 51 | .asc "Button 2 ( ) ( ) ( ) ( ) " 52 | .asc " " 53 | .asc "Up and 1 ( ) ( ) ( ) ( ) " 54 | .db $ff ; terminator 55 | 56 | table.blankRow: 57 | .asc "( ) ( ) ( ) ( )" 58 | .db $ff ; terminator 59 | 60 | ; We'll add an asterisk in between the brackets in the template string, 61 | ; indicating which condition has been met 62 | table.asciiAsterisk: 63 | .asc '*' 64 | .ends 65 | 66 | ;==== 67 | ; Draws the blank table with none of the indicators populated 68 | ;==== 69 | .section "table.draw" free 70 | table.draw: 71 | palette.setIndex 0 72 | palette.writeBytes table.fontPalette 2 73 | 74 | patterns.setIndex 0 75 | patterns.writeBytes table.fontPatterns, table.fontPatternsSize 76 | 77 | tilemap.setColRow 0, TABLE_ROW_OFFSET 78 | tilemap.writeBytesUntil $ff, table.template 79 | ret 80 | .ends 81 | 82 | ;==== 83 | ; Resets the indicators in the table 84 | ;==== 85 | .section "table.reset" free 86 | table.reset: 87 | tilemap.setColRow INDICATOR_COLUMN_START, UP_ROW 88 | tilemap.writeBytesUntil $ff table.blankRow 89 | 90 | tilemap.setColRow INDICATOR_COLUMN_START, DOWN_ROW 91 | tilemap.writeBytesUntil $ff table.blankRow 92 | 93 | tilemap.setColRow INDICATOR_COLUMN_START, LEFT_ROW 94 | tilemap.writeBytesUntil $ff table.blankRow 95 | 96 | tilemap.setColRow INDICATOR_COLUMN_START, RIGHT_ROW 97 | tilemap.writeBytesUntil $ff table.blankRow 98 | 99 | tilemap.setColRow INDICATOR_COLUMN_START, BUTTON_1_ROW 100 | tilemap.writeBytesUntil $ff table.blankRow 101 | 102 | tilemap.setColRow INDICATOR_COLUMN_START, BUTTON_2_ROW 103 | tilemap.writeBytesUntil $ff table.blankRow 104 | 105 | tilemap.setColRow INDICATOR_COLUMN_START, COMBO_ROW 106 | tilemap.writeBytesUntil $ff table.blankRow 107 | 108 | ret 109 | .ends 110 | 111 | ;==== 112 | ; Draws an ascii asterisk in the given column and row 113 | ;==== 114 | .macro "table.drawIndicator" args column row 115 | tilemap.setColRow column, row 116 | tilemap.writeBytes table.asciiAsterisk 1 117 | .endm 118 | -------------------------------------------------------------------------------- /examples/05-movingSprites/bubble.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; A 'bubble' entity that can be controlled with the joypad direction buttons 3 | ;==== 4 | 5 | ;==== 6 | ; Define Bubble struct 7 | ;==== 8 | .struct "Bubble" 9 | xPos: db ; the current x position 10 | yPos: db ; the current y position 11 | xVec: db ; the number of x pixels to move per frame 12 | yVec: db ; the number of y pixels to move per frame 13 | .endst 14 | 15 | ;==== 16 | ; Initialises a bubble instance in RAM 17 | ; 18 | ; @in ix pointer to bubble 19 | ;==== 20 | .macro "bubble.init" 21 | ld (ix + Bubble.xVec), 0 22 | ld (ix + Bubble.yVec), 0 23 | ld (ix + Bubble.xPos), 100 24 | ld (ix + Bubble.yPos), 80 25 | .endm 26 | 27 | ;==== 28 | ; Updates the bubble's movement vector based on the joypad input 29 | ; 30 | ; @in ix pointer to bubble 31 | ;==== 32 | .section "bubble.updateInput" free 33 | bubble.updateInput: 34 | ; Read the input from port 1 35 | input.readPort1 36 | 37 | ; Set movement vectors based on input 38 | input.loadADirX 2 ; left = -2; right = 2; none = 0 39 | ld (ix + Bubble.xVec), a ; update x movement based on input 40 | 41 | input.loadADirY 2 ; up = -2; down = 2; none = 0 42 | ld (ix + Bubble.yVec), a ; update y movement based on input 43 | 44 | ret 45 | .ends 46 | 47 | ;==== 48 | ; Updates the bubble's position based on its movement vector 49 | ; 50 | ; @in ix pointer to bubble 51 | ;==== 52 | .section "bubble.updateMovement" free 53 | bubble.updateMovement: 54 | ; xPos = xPos + yVec 55 | ld a, (ix + Bubble.xPos) ; load xPos into a 56 | add a, (ix + Bubble.xVec) ; add xVec to xPos 57 | ld (ix + Bubble.xPos), a ; store updated xPos 58 | 59 | ; yPos = yPos + yVec 60 | ld a, (ix + Bubble.yPos) ; load yPos into a 61 | add a, (ix + Bubble.yVec) ; add yVec to yPos 62 | ld (ix + Bubble.yPos), a ; store updated yPos 63 | ret 64 | .ends 65 | 66 | ;==== 67 | ; Adds the bubble's sprites to the sprite buffer 68 | ; 69 | ; @in ix pointer to bubble 70 | ;==== 71 | .section "bubble.updateSprite" 72 | bubble.updateSprite: 73 | ; Add sprite group to sprite buffer 74 | ld hl, bubble.spriteGroup 75 | ld b, (ix + Bubble.xPos) ; base x pos 76 | ld c, (ix + Bubble.yPos) ; base y pos 77 | sprites.addGroup ; add group 78 | ret 79 | .ends 80 | 81 | ;==== 82 | ; Assets 83 | ;==== 84 | .section "bubble assets" free 85 | bubble.palette: 86 | .incbin "../assets/bubble/palette.bin" fsize bubble.paletteSize 87 | 88 | bubble.patterns: 89 | .incbin "../assets/bubble/patterns.bin" fsize bubble.patternsSize 90 | 91 | bubble.spriteGroup: 92 | sprites.startGroup 93 | ; pattern, relativeX, relativeY 94 | sprites.sprite 1, 0, 0 ; top left (x0, y0) 95 | sprites.sprite 2, 8, 0 ; top right (x+8, y0) 96 | sprites.sprite 3, 0, 8 ; bottom left (x0, y+8) 97 | sprites.sprite 4, 8, 8 ; bottom right (x+8, y+8) 98 | sprites.endGroup 99 | .ends -------------------------------------------------------------------------------- /examples/05-movingSprites/main.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; SMSLib moving sprites example 3 | ; 4 | ; Create a sprite that can be controlled with the directional input of joypad 1. 5 | ; Demonstrates VBlank handling using interrupts.asm 6 | ;==== 7 | .sdsctag 1.10, "smslib sprites", "smslib moving sprites tutorial", "lajohnston" 8 | 9 | ;==== 10 | ; Tell smslib interrupts module to handle frame (VBlank) interrupts. This will 11 | ; call the interrupts.onVBlank label each time a frame has finished being drawn. 12 | ; This occurs 50 times a second for PAL and 60 times per second for NTSC and can 13 | ; be used to regulate the logic speed 14 | ;==== 15 | .define interrupts.HANDLE_VBLANK 1 16 | 17 | ; Import smslib 18 | .incdir "../../" ; back to smslib directory 19 | .include "smslib.asm" ; base library 20 | .incdir "." ; return to current directory 21 | 22 | ; Import bubble entity 23 | .include "bubble.asm" 24 | 25 | ;==== 26 | ; Reserve a place in RAM for a Bubble instance 27 | ;==== 28 | .ramsection "bubble" slot mapper.RAM_SLOT 29 | bubbleInstance: instanceof Bubble 30 | .ends 31 | 32 | ;==== 33 | ; Initialise the example 34 | ;==== 35 | .section "init" free 36 | init: 37 | ; Set background colour 38 | palette.setIndex 0 39 | palette.writeRgb 0, 0, 0 ; black 40 | 41 | ; Load sprite palette 42 | palette.setIndex palette.SPRITE_PALETTE 43 | palette.writeBytes bubble.palette, bubble.paletteSize 44 | 45 | ; Load pattern data to draw sprites. Sprites use indices 256+ by default 46 | patterns.setIndex 256 47 | patterns.writeBytes bubble.patterns, bubble.patternsSize 48 | 49 | ; Initialise bubble with default values 50 | ld ix, bubbleInstance 51 | bubble.init 52 | 53 | ;==== 54 | ; Enable the display and interrupts 55 | ; When changing multiple vdp settings it's more efficient (but optional) 56 | ; to specify changes within a 'batch' 57 | ;==== 58 | vdp.startBatch 59 | vdp.enableDisplay 60 | vdp.enableVBlank 61 | 62 | ; hide the left-most column - allows sprites to scroll more smoothly 63 | ; off the left side of the screen 64 | vdp.hideLeftColumn 65 | vdp.endBatch 66 | 67 | ; Now we've finished initialising, enable interrupts 68 | interrupts.enable 69 | 70 | ; Begin 71 | jp mainLoop 72 | .ends 73 | 74 | ;==== 75 | ; Update bubble each frame 76 | ;==== 77 | .section "mainLoop" free 78 | mainLoop: 79 | ; Wait for frame interrupt handler to finish before continuing 80 | interrupts.waitForVBlank 81 | 82 | ; Update bubble instance 83 | ld ix, bubbleInstance 84 | call bubble.updateInput 85 | call bubble.updateMovement 86 | 87 | ; Add sprites 88 | sprites.reset ; reset buffer 89 | call bubble.updateSprite ; add sprites 90 | 91 | ; Next loop 92 | jp mainLoop 93 | .ends 94 | 95 | ;==== 96 | ; VBlank routine called by interrupts.asm after each frame is drawn. 97 | ; 98 | ; This is a good time to write data to VRAM before it starts drawing the 99 | ; next frame. 100 | ; 101 | ; When this has finished, interrupts.waitForVBlank in the main loop will be 102 | ; satisfied and the rest of the loop will continue 103 | ;==== 104 | .section "vBlankHandler" free 105 | ; This is called by interrupts.asm 106 | interrupts.onVBlank: 107 | ; Copy buffer to VRAM 108 | sprites.copyToVram 109 | 110 | ; End vBlank handler 111 | interrupts.endVBlank 112 | .ends 113 | -------------------------------------------------------------------------------- /examples/07-scrolling-tilemap/main.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; SMSLib scrolling tilemap example 3 | ; 4 | ; We will utilise the scroll.tiles module to maintain a tilemap that is larger 5 | ; than the screen. Each frame we will adjust its scroll position based on the 6 | ; directional input from the controller. 7 | ;==== 8 | .sdsctag 1.10, "smslib tilemap scrolling", "smslib tilemap scrolling example", "lajohnston" 9 | 10 | ;==== 11 | ; Import smslib 12 | ;==== 13 | .define interrupts.HANDLE_VBLANK 1 ; enable VBlanks (see VBlank example) 14 | .incdir "../../" ; point to smslib directory 15 | .include "smslib.asm" 16 | 17 | ; Include a scroll handler that uses raw tile data 18 | .include "scroll/tiles.asm" 19 | 20 | .incdir "." ; point back to current working directory 21 | 22 | ;==== 23 | ; Define asset data 24 | ;==== 25 | 26 | ; The number of tile rows and columns in our full tilemap 27 | .define MAP_COLS 64 28 | .define MAP_ROWS 64 29 | 30 | ; Scroll speed in pixels per axis (should be between 0 and 8 inclusive) 31 | .define SCROLL_SPEED 1 32 | 33 | .section "assets" free 34 | paletteData: 35 | .incbin "../assets/tilemap/palette.bin" fsize paletteDataSize 36 | 37 | patternData: 38 | .incbin "../assets/tilemap/patterns.bin" fsize patternDataSize 39 | 40 | tilemapData: 41 | .incbin "../assets/tilemap/tilemap.bin" fsize tilemapDataSize 42 | .ends 43 | 44 | ;==== 45 | ; Initialise program 46 | ;==== 47 | .section "init" free 48 | ; SMSLib will jump to the init label after booting the system 49 | init: 50 | ; Load palette and patterns 51 | palette.setIndex 0 52 | palette.writeBytes paletteData, paletteDataSize 53 | patterns.setIndex 0 54 | patterns.writeBytes patternData, patternDataSize 55 | 56 | ; Initialise map (offset x0, y0) 57 | scroll.tiles.init tilemapData MAP_COLS MAP_ROWS 0 0 58 | 59 | ; Enable the display 60 | vdp.startBatch 61 | vdp.enableDisplay 62 | vdp.enableVBlank 63 | 64 | ; Left column gets filled with junk when scrolling, so best to hide it 65 | vdp.hideLeftColumn 66 | vdp.endBatch 67 | 68 | ; Enable interrupts then start the update loop 69 | interrupts.enable 70 | jp update 71 | .ends 72 | 73 | ;==== 74 | ; The update loop that runs each frame during active display 75 | ;==== 76 | .section "update" free 77 | update: 78 | ; Wait for the next VBlank to finish 79 | interrupts.waitForVBlank 80 | 81 | ; Adjust tilemap based on joypad input direction 82 | input.readPort1 ; read the state of joypad 1 83 | 84 | input.loadADirX SCROLL_SPEED; load A with X direction * scroll speed 85 | scroll.tiles.adjustXPixels ; adjust tilemap X by that many pixels 86 | 87 | input.loadADirY SCROLL_SPEED; load A with Y direction * scroll speed 88 | scroll.tiles.adjustYPixels ; adjust tilemap Y by that many pixels 89 | 90 | ; Buffer changes in RAM (doesn't update VDP/VRAM yet) 91 | scroll.tiles.update 92 | 93 | jp update ; start loop again 94 | .ends 95 | 96 | ;==== 97 | ; The VBlank routine where we'll apply changes to the VDP 98 | ;==== 99 | .section "render" free 100 | interrupts.onVBlank: 101 | ; Apply scroll changes to the VDP/VRAM 102 | scroll.tiles.render 103 | 104 | ; Mark the end of the VBlank handler 105 | interrupts.endVBlank 106 | .ends 107 | -------------------------------------------------------------------------------- /examples/assets/README.md: -------------------------------------------------------------------------------- 1 | # Assets 2 | 3 | ## Bubble 4 | 5 | The bubble asset was created using the following (clunky) procedure: 6 | 7 | 1. I created the bubble sprites in PyxelEdit (`bubble.pyxel`) 8 | 2. I exported `bubble.pyxel` as a tileset to `bubble.png` (File > Export > Tileset) 9 | 3. I converted `bubble.png` to an indexed-color png using AseSprite (Sprite > Color Mode > Indexed) and saved 10 | 4. I imported `bubble.png` into bmp2tile and exported the palette and pattern data 11 | 12 | - The tiles had `Remove duplicates` and `Planar tile output` selected. Planar is required for SMS patterns. Note that `Use tile mirroring` can't be used for sprites on the SMS but should be used for background tiles 13 | 14 | ### bmp2tile 15 | 16 | - Program: https://github.com/maxim-zhao/bmp2tile/releases 17 | - Compression codecs: https://github.com/maxim-zhao/bmp2tilecompressors/releases 18 | 19 | Place compressor .dlls into the same directory as bmp2Tile. For this example I just used the `raw` compressor which allows us to export the data as an uncompressed binary file. 20 | 21 | ## font.bin 22 | 23 | The font file was taken from Maxim's Hello World SMS programming tutorial: https://www.smspower.org/maxim/HowToProgram/Lesson1AllOnOnePage 24 | 25 | ## Tilemap 26 | 27 | Rough workflow that needs improvement and automation: 28 | 29 | 1. Created new Aseprite document with indexed colour mode and imported the Master System palette 30 | 2. Drew tilemap using the tilesheet features in v1.3 31 | 3. Set Sprite > Color Mode to RGB 32 | 4. Selected unused palette entries and deleted them 33 | 5. Created a new color at the end of the palette, then moved it to the beginning 34 | 6. Set Sprite > Color Mode to Indexed 35 | 7. Exported bmp using File > Export 36 | 8. Imported into bmp2tile and exported the bin files (palette, patterns, tilemap) 37 | -------------------------------------------------------------------------------- /examples/assets/bubble/bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lajohnston/smslib/67e62f51d8548257038d4dc0a0e82d10ff9d23ff/examples/assets/bubble/bubble.png -------------------------------------------------------------------------------- /examples/assets/bubble/bubble.pyxel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lajohnston/smslib/67e62f51d8548257038d4dc0a0e82d10ff9d23ff/examples/assets/bubble/bubble.pyxel -------------------------------------------------------------------------------- /examples/assets/bubble/palette.bin: -------------------------------------------------------------------------------- 1 | "62? -------------------------------------------------------------------------------- /examples/assets/bubble/patterns.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lajohnston/smslib/67e62f51d8548257038d4dc0a0e82d10ff9d23ff/examples/assets/bubble/patterns.bin -------------------------------------------------------------------------------- /examples/assets/font.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lajohnston/smslib/67e62f51d8548257038d4dc0a0e82d10ff9d23ff/examples/assets/font.bin -------------------------------------------------------------------------------- /examples/assets/metatiles/metatileDefs.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Programmatically creates some metatiles definitions according to the size, 3 | ; to demonstrate the flexible scroll.metatiles.ROWS_PER_METATILE and 4 | ; scroll.metatiles.COLS_PER_METATILE values you can set. Set these in the 5 | ; metatile scrolling example before including the scroll/metatiles.asm file. 6 | ; 7 | ; Each metatile definition is square or rectangle with its own color. 8 | ;==== 9 | 10 | ; The number of metatiles definitions/colors 11 | .define colors = 6 12 | 13 | .if scroll.metatiles.ROWS_PER_METATILE == 2 && scroll.metatiles.COLS_PER_METATILE == 2 14 | ; 2x2 metatiles just require four corners 15 | .repeat colors index color 16 | .redefine cornerPattern color * 4 17 | 18 | tilemap.tile cornerPattern 19 | tilemap.tile cornerPattern tilemap.FLIP_X 20 | tilemap.tile cornerPattern tilemap.FLIP_Y 21 | tilemap.tile cornerPattern tilemap.FLIP_XY 22 | .endr 23 | .else 24 | ; Larger metatiles require corners, side, and middle 25 | .repeat colors index color 26 | ; Pattern numbers that define the corners, sides and middle 27 | .redefine basePattern color * 4 ; the first pattern for this color 28 | .redefine cornerPattern basePattern ; corner outline 29 | .redefine sideEdgePattern basePattern + 1 ; side outline (vertical line) 30 | .redefine topEdgePattern basePattern + 2 ; top outline (horizontal line) 31 | .redefine middlePattern basePattern + 3 ; plain tile without an outline 32 | 33 | ;=== 34 | ; Top row 35 | ;=== 36 | tilemap.tile cornerPattern ; top left corner 37 | 38 | .repeat scroll.metatiles.COLS_PER_METATILE - 2 39 | tilemap.tile topEdgePattern ; top edge 40 | .endr 41 | 42 | tilemap.tile cornerPattern tilemap.FLIP_X ; top right corner 43 | 44 | ;=== 45 | ; Middle rows 46 | ;=== 47 | .repeat scroll.metatiles.ROWS_PER_METATILE - 2 48 | tilemap.tile sideEdgePattern ; left edge 49 | 50 | .repeat scroll.metatiles.COLS_PER_METATILE - 2 51 | tilemap.tile middlePattern ; middle 52 | .endr 53 | 54 | tilemap.tile sideEdgePattern tilemap.FLIP_X ; right edge 55 | .endr 56 | 57 | ;=== 58 | ; Last row 59 | ;=== 60 | tilemap.tile cornerPattern tilemap.FLIP_Y ; bottom left corner 61 | 62 | .repeat scroll.metatiles.COLS_PER_METATILE - 2 63 | tilemap.tile topEdgePattern tilemap.FLIP_Y ; bottom middle edge 64 | .endr 65 | 66 | tilemap.tile cornerPattern tilemap.FLIP_XY ; bottom right corner 67 | .endr 68 | .endif 69 | -------------------------------------------------------------------------------- /examples/assets/metatiles/metatileMap.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Generates a map of metatile references. Each ref is a byte and refers to one 3 | ; of the metatile definitions (0-255). 4 | ; 5 | ; Here we programmatically generate the map but a real game may have a designed 6 | ; map that's compressed on the ROM. 7 | ;==== 8 | .repeat MAP_HEIGHT_METATILES index row 9 | .repeat MAP_WIDTH_METATILES index col 10 | .if row == 0 || col == 0 || row == MAP_HEIGHT_METATILES - 1 || col == MAP_WIDTH_METATILES - 1 11 | ; Border around the edges 12 | scroll.metatiles.ref 5 13 | .else 14 | ; Diagonal stripes in middle 15 | scroll.metatiles.ref ((row + col) # 4) 16 | .endif 17 | .endr 18 | .endr 19 | -------------------------------------------------------------------------------- /examples/assets/metatiles/palette.bin: -------------------------------------------------------------------------------- 1 | 60#! -------------------------------------------------------------------------------- /examples/assets/metatiles/patterns.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lajohnston/smslib/67e62f51d8548257038d4dc0a0e82d10ff9d23ff/examples/assets/metatiles/patterns.bin -------------------------------------------------------------------------------- /examples/assets/tilemap/palette.bin: -------------------------------------------------------------------------------- 1 | 8)9=&*:>7? -------------------------------------------------------------------------------- /examples/assets/tilemap/patterns.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lajohnston/smslib/67e62f51d8548257038d4dc0a0e82d10ff9d23ff/examples/assets/tilemap/patterns.bin -------------------------------------------------------------------------------- /examples/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### 4 | # Build examples on Linux 5 | ### 6 | 7 | set -e # exit on errors 8 | 9 | # cd to script directory 10 | EXAMPLES_DIRECTORY=$(cd `dirname $0` && pwd) 11 | cd $EXAMPLES_DIRECTORY 12 | 13 | # Prep build directory 14 | BUILD_DIR=$(realpath dist) 15 | TEMP_DIR=$BUILD_DIR/tmp 16 | 17 | rm -rf $BUILD_DIR 18 | mkdir $BUILD_DIR 19 | mkdir $TEMP_DIR 20 | 21 | # Build examples 22 | 23 | EXAMPLES=( $(ls -d [0-9][0-9]-*) ) 24 | 25 | for EXAMPLE in "${EXAMPLES[@]}" 26 | do 27 | cd $EXAMPLES_DIRECTORY/$EXAMPLE 28 | 29 | echo "Building example ${EXAMPLE}" 30 | 31 | # Create simple linkfile 32 | LINKFILE=$TEMP_DIR/linkfile 33 | echo [objects] > $LINKFILE 34 | echo $EXAMPLE.o >> $LINKFILE 35 | 36 | # Assemble objects 37 | wla-z80 -o $TEMP_DIR/$EXAMPLE.o main.asm 38 | 39 | # Link objects 40 | cd $TEMP_DIR 41 | wlalink -d -S -A linkfile $EXAMPLE.sms 42 | cd - > /dev/null # return to former directory 43 | 44 | # Place output in build directory 45 | mv $TEMP_DIR/$EXAMPLE.sms $BUILD_DIR 46 | mv $TEMP_DIR/$EXAMPLE.sym $BUILD_DIR 47 | 48 | cd $EXAMPLES_DIRECTORY 49 | done 50 | -------------------------------------------------------------------------------- /init.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Defines a section at address 0 to initialise the system as well as any 3 | ; SMSLib modules that have been included. Once complete it will jump to an 4 | ; 'init' label that you must define in your code. 5 | ; 6 | ; This file should be included after including the other modules to ensure it 7 | ; knows which modules are active. 8 | ;==== 9 | 10 | ;==== 11 | ; Settings 12 | ;==== 13 | 14 | ;=== 15 | ; init.DISABLE_HANDLER 16 | ; If defined, disables the boot handler 17 | ;=== 18 | 19 | ;==== 20 | ; Dependencies 21 | ;==== 22 | .ifndef utils.vdp 23 | .include "utils/vdp.asm" 24 | .endif 25 | 26 | ;==== 27 | ; Code 28 | ;==== 29 | 30 | ;==== 31 | ; Boot sequence 32 | ;==== 33 | .macro "init" 34 | di ; disable interrupts 35 | im 1 ; interrupt mode 1 36 | ld sp, $dff0 ; set stack pointer 37 | 38 | ; Initialise the system 39 | call init.smslibModules 40 | 41 | ; Jump to init label, defined by user 42 | jp init 43 | .endm 44 | 45 | ;==== 46 | ; Boot sequence at ROM address 0 47 | ;==== 48 | .ifndef init.DISABLE_HANDLER 49 | .bank 0 slot 0 50 | .orga 0 51 | .section "init.bootHandler" force 52 | init 53 | .ends 54 | .endif 55 | 56 | ;==== 57 | ; Initialise any SMSLib modules that are activated 58 | ;==== 59 | .macro "init.smslibModules" 60 | ; initialise paging registers 61 | .ifdef mapper.ENABLED 62 | .ifeq mapper.ENABLED 1 63 | mapper.init 64 | .endif 65 | .endif 66 | 67 | ; initialise vdp registers 68 | .ifdef vdp.ENABLED 69 | .ifeq vdp.ENABLED 1 70 | vdp.init 71 | .endif 72 | .endif 73 | 74 | ; initialise pause handler 75 | .ifdef pause.ENABLED 76 | .ifeq pause.ENABLED 1 77 | pause.init 78 | .endif 79 | .endif 80 | 81 | ; initialise sprite buffer 82 | .ifdef sprites.ENABLED 83 | .ifeq sprites.ENABLED 1 84 | sprites.init 85 | .endif 86 | .endif 87 | 88 | ; initialise input handler 89 | .ifdef input.ENABLED 90 | .ifeq input.ENABLED 1 91 | input.init 92 | .endif 93 | .endif 94 | 95 | ; initialise interrupt handler 96 | .ifdef interrupts.ENABLED 97 | .ifeq interrupts.ENABLED 1 98 | interrupts.init 99 | .endif 100 | .endif 101 | .endm 102 | 103 | .section "init.smslibModules" free 104 | init.smslibModules: 105 | ; Initialise modules 106 | init.smslibModules 107 | 108 | ; Zeroes VRAM, then returns 109 | jp utils.vdp.clearVram 110 | .ends 111 | -------------------------------------------------------------------------------- /mapper/48k.asm: -------------------------------------------------------------------------------- 1 | ;=== 2 | ; Simple 48KB ROM with no paging 3 | ;=== 4 | .define mapper.ENABLED 1 5 | 6 | .define mapper.FIXED_SLOT = 0 7 | .define mapper.PAGE_SLOT_A = 0 ; no paging 8 | .define mapper.PAGE_SLOT_B = 0 ; no paging 9 | .define mapper.RAM_SLOT = 1 10 | 11 | .memorymap 12 | defaultslot mapper.FIXED_SLOT 13 | 14 | ; 48KB ROM 15 | slotsize $C000 16 | slot mapper.FIXED_SLOT $0000 17 | 18 | ; 8KB RAM 19 | slotsize $2000 20 | slot mapper.RAM_SLOT $c000 21 | .endme 22 | 23 | .rombankmap 24 | ; Single 48KB bank 25 | bankstotal 1 26 | banksize $C000 27 | banks 1 28 | .endro 29 | 30 | .macro "mapper.init" 31 | ; nothing to set up 32 | .endm 33 | 34 | .macro "mapper.pageBank" 35 | ; no paging 36 | .endm -------------------------------------------------------------------------------- /mapper/_mapper.asm: -------------------------------------------------------------------------------- 1 | ; Common functions used by mappers. Not intended to be used directly 2 | 3 | ;==== 4 | ; Paging registers 5 | ; 6 | ; Writes to these addresses are intercepted by the cartridge's mapper chip and 7 | ; inform it which banks of ROM to make available and which cartridge features 8 | ; to enable 9 | ; 10 | ; Further documentation can be found at: 11 | ; https://www.smspower.org/Development/Mappers 12 | ;==== 13 | 14 | ;==== 15 | ; Cartridge features 16 | ; 00000000 17 | ; ||||||**- Bank shift (no known software uses this) 18 | ; |||||*--- RAM bank select 19 | ; ||||*---- Enable cartridge RAM ($8000- $bfff, slot 2, overrides ROM bank) 20 | ; |||*----- Enable cartridge RAM ($4000- $7fff) - no known software uses 21 | ; |**------ unused 22 | ; *-------- Enable ROM-write (no known software uses this) 23 | ;==== 24 | .define _mapper.FEATURES_REGISTER = $fffc 25 | 26 | ; Values written to these determine which bank is paged into which slot 27 | .define _mapper.SLOT_0_REGISTER = $fffd 28 | .define _mapper.SLOT_1_REGISTER = $fffe 29 | .define _mapper.SLOT_2_REGISTER = $ffff 30 | 31 | ;==== 32 | ; Enables the on-cartridge RAM in slot 2, accessible with addresses 33 | ; $8000- $bfff 34 | ;==== 35 | .macro "_mapper.enableCartridgeRam" 36 | ld a, %00001000 37 | ld (_mapper.FEATURES_REGISTER), a 38 | .endm 39 | 40 | ;=== 41 | ; Page a bank into the given slot 42 | ; 43 | ; @in bankNumber the bank number to page in. Use the colon prefix in 44 | ; WLA-DX to retrieve the bank number of an address, 45 | ; i.e. mapper.pageBank :myAsset 46 | ; @in [slotNumber] the slot number to page the bank into. Defaults to 47 | ; slot 1 48 | ;=== 49 | .macro "_mapper.pageBank" args bankNumber slotNumber 50 | ld a, bankNumber 51 | 52 | .ifeq slotNumber 2 53 | ld (_mapper.SLOT_2_REGISTER), a 54 | .else 55 | ; Default 56 | ld (_mapper.SLOT_1_REGISTER), a 57 | .endif 58 | .endm 59 | -------------------------------------------------------------------------------- /mapper/sega.asm: -------------------------------------------------------------------------------- 1 | ;=== 2 | ; Standard SEGA mapper 3 | ; 4 | ; Features: 5 | ; 1 x 16KB fixed slot (for code) 6 | ; 2 x 16KB pageable slots (for assets) 7 | ; 8 | ; It's generally simpler to only use one of the 16KB pageable slots but if you 9 | ; happen to have an asset larger than 16KB you could use both slots together to 10 | ; page a contiguous 32KB block of memory 11 | ; 12 | ; More info: https://www.smspower.org/forums/17445-MemoryMappingBasicTutorial 13 | ;=== 14 | 15 | .define mapper.ENABLED 1 16 | .include "mapper/_mapper.asm" 17 | 18 | ;==== 19 | ; Settings 20 | ; 21 | ; Define these before including this file if you wish to override 22 | ; the defaults 23 | ;==== 24 | 25 | ; Define the number of pageable 16KB banks (default 6 = 128KB) 26 | .ifndef mapper.PAGEABLE_BANKS 27 | .define mapper.PAGEABLE_BANKS 6 28 | .endif 29 | 30 | ; Some cartridges have an additional 8KB of RAM. If enabled this will be 31 | ; accessible in slot 2 32 | .ifndef mapper.ENABLE_CARTRIDGE_RAM 33 | .define mapper.ENABLE_CARTRIDGE_RAM 0 34 | .endif 35 | 36 | ;==== 37 | ; Constants 38 | ;==== 39 | .define mapper.FIXED_SLOT = 0 40 | .define mapper.PAGE_SLOT_A = 1 41 | .define mapper.PAGE_SLOT_B = 2 42 | .define mapper.RAM_SLOT = 3 43 | 44 | ; Slots 45 | .memorymap 46 | defaultslot mapper.FIXED_SLOT 47 | slotsize $4000 ; each slot is 16KB 48 | 49 | slot mapper.FIXED_SLOT $0000 ; ROM (non-pageable) 50 | slot mapper.PAGE_SLOT_A $4000 ; ROM (pageable slot 1) 51 | slot mapper.PAGE_SLOT_B $8000 ; ROM (pageable slot 2) 52 | slot mapper.RAM_SLOT $c000 ; RAM (8KB + 8KB mirror) 53 | .endme 54 | 55 | ; ROM Banks 56 | ; These can be loaded into the slots at runtime 57 | .rombankmap 58 | bankstotal mapper.PAGEABLE_BANKS 59 | banksize $4000 ; 16KB 60 | banks mapper.PAGEABLE_BANKS 61 | .endro 62 | 63 | ;=== 64 | ; Initialise the paging registers 65 | ;=== 66 | .macro "mapper.init" 67 | .ifeq mapper.ENABLE_CARTRIDGE_RAM, 1 68 | _mapper.ENABLE_CARTRIDGE_RAM 69 | .endif 70 | .endm 71 | 72 | ;=== 73 | ; Page a bank into the given slot 74 | ; 75 | ; @in bankNumber the bank number to page in. Use the colon prefix in 76 | ; WLA-DX to retrieve the bank number of an address, 77 | ; i.e. mapper.pageBank :myAsset 78 | ; @in [slotNumber] the slot number to page into. Defaults to PAGE_SLOT_A 79 | ;=== 80 | .macro "mapper.pageBank" args bankNumber slotNumber 81 | .ifndef slotNumber 82 | .define slotNumber mapper.PAGE_SLOT_A 83 | .endif 84 | 85 | _mapper.pageBank bankNumber slotNumber 86 | .endm 87 | -------------------------------------------------------------------------------- /mapper/waimanu.asm: -------------------------------------------------------------------------------- 1 | ;=== 2 | ; Taken from Waimanu source 3 | ; 4 | ; @author Disjointed Studio 5 | ; 6 | ; Documented in http://www.smspower.org/forums/15794-AFewHintsOnCodingAMediumLargeSizedGameUsingWLADX 7 | ; 8 | ; Features: 9 | ; * Main 32KB bank in non-pageable slot 0 10 | ; * Multiple (default 6) additional 16KB banks in pageable slot 2 11 | ;=== 12 | 13 | .define mapper.ENABLED 1 14 | .include "mapper/_mapper.asm" 15 | 16 | ;==== 17 | ; Settings 18 | ; 19 | ; Define these before including this file if you wish to override the defaults 20 | ;==== 21 | 22 | ; Define the number of pageable 16KB banks (default 6 = 128KB) 23 | .ifndef mapper.PAGEABLE_BANKS 24 | .define mapper.PAGEABLE_BANKS 6 25 | .endif 26 | 27 | ; Ensure ENABLE_CARTRIDGE_RAM hasn't been enabled 28 | .ifdef mapper.ENABLE_CARTRIDGE_RAM 29 | .ifeq mapper.ENABLE_CARTRIDGE_RAM 1 30 | ; Waimanu uses slot 2 for paging which is needed for on-cartridge RAM 31 | .print "waimanu mapper doesn't support on-cartridge RAM. mapper.ENABLE_CARTRIDGE_RAM must not be set to 1\n" 32 | .fail 33 | .endif 34 | .endif 35 | 36 | ;==== 37 | ; Constants 38 | ;==== 39 | .define mapper.FIXED_SLOT = 0 40 | .define mapper.PAGE_SLOT_A = 2 41 | .define mapper.PAGE_SLOT_B = -1 ; PAGE_SLOT_B not supported 42 | .define mapper.RAM_SLOT = 3 43 | 44 | ; Slots 45 | .memorymap 46 | defaultslot mapper.FIXED_SLOT 47 | 48 | ; ROM (non-pageable) 49 | slotsize $7ff0 ; 32KB minus 16-byte header 50 | slot mapper.FIXED_SLOT $0000 51 | 52 | ; SEGA ROM header (non-pageable) 53 | slotsize $0010 ; 16-byte SEGA ROM header 54 | slot 1 $7ff0 55 | 56 | ; ROM (pageable) 57 | slotsize $4000 ; 16KB 58 | slot mapper.PAGE_SLOT_A $8000 59 | 60 | ; RAM 61 | slotsize $2000 ; 8KB 62 | slot mapper.RAM_SLOT $c000 63 | .endme 64 | 65 | ; Banks 66 | ; These can be loaded into the slots at runtime 67 | .rombankmap 68 | bankstotal mapper.PAGEABLE_BANKS + 2 69 | 70 | banksize $7ff0 ; 32KB minus 16 bytes 71 | banks 1 72 | 73 | banksize $0010 ; 16-bytes, for SEGA ROM header 74 | banks 1 75 | 76 | banksize $4000 ; 16KB 77 | banks mapper.PAGEABLE_BANKS 78 | .endro 79 | 80 | ;=== 81 | ; Initialise the paging registers 82 | ;=== 83 | .macro "mapper.init" 84 | ; nothing to initialise 85 | .endm 86 | 87 | ;=== 88 | ; Page a bank into the given slot 89 | ; 90 | ; @in bankNumber the bank number to page in. Use the colon prefix in 91 | ; WLA-DX to retrieve the bank number of an address, 92 | ; i.e. mapper.pageBank :myAsset 93 | ; @in [slotNumber] the slot number to page into. Defaults to PAGE_SLOT_A 94 | ;=== 95 | .macro "mapper.pageBank" args bankNumber slotNumber 96 | .ifndef slotNumber 97 | .define slotNumber mapper.PAGE_SLOT_A 98 | .endif 99 | 100 | .ifneq slotNumber mapper.PAGE_SLOT_A 101 | .print "Error: waimanu mapper.pageBank only supports mapper.PAGE_SLOT_A as the slot number" 102 | .endif 103 | 104 | _mapper.pageBank bankNumber slotNumber 105 | .endm 106 | -------------------------------------------------------------------------------- /patterns.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Manages patterns (graphic tiles) in the VDP 3 | ; 4 | ; Provided for example purposes to get you started. For an actual game you 5 | ; would want to compress pattern data using an algorithm such as zx7 or aPLib 6 | ; and use the appropriate lib to decompress and write to VRAM 7 | ;==== 8 | 9 | ;==== 10 | ; Settings 11 | ; 12 | ; Define these before including this file if you wish to override the defaults 13 | ;==== 14 | 15 | ; Pattern address in VRAM 16 | .ifndef patterns.VRAM_ADDRESS 17 | .define patterns.VRAM_ADDRESS $0000 18 | .endif 19 | 20 | ;==== 21 | ; Constants 22 | ;==== 23 | .define patterns.ELEMENT_SIZE_BYTES 32 24 | .define patterns.MAX_PATTERN_INDEX 511 25 | 26 | ;==== 27 | ; Dependencies 28 | ;==== 29 | .ifndef utils.assert 30 | .include "utils/assert.asm" 31 | .endif 32 | 33 | .ifndef utils.clobbers 34 | .include "utils/clobbers.asm" 35 | .endif 36 | 37 | .ifndef utils.outiBlock 38 | .include "utils/outiBlock.asm" 39 | .endif 40 | 41 | .ifndef utils.vdp 42 | .include "utils/vdp.asm" 43 | .endif 44 | 45 | ;==== 46 | ; Write patterns (tile graphics) into VRAM 47 | ; 48 | ; @in dataAddress the address of the first byte of data 49 | ; @in count the number of patterns to write (1-based) 50 | ; @in [offset=0] the number of patterns to skip at the beginning of the data 51 | ;==== 52 | .macro "patterns.writeSlice" args dataAddress count offset 53 | utils.assert.label dataAddress, "patterns.asm \.: Invalid dataAddress argument" 54 | utils.assert.range count, 1, patterns.MAX_PATTERN_INDEX + 1, "patterns.asm \.: Invalid count argument" 55 | 56 | .ifndef offset 57 | utils.assert.equals NARGS, 2, "patterns.asm \. received the wrong number of arguments" 58 | utils.outiBlock.writeSlice dataAddress, patterns.ELEMENT_SIZE_BYTES, count, 0 59 | .else 60 | utils.assert.equals NARGS, 3, "patterns.asm \. received the wrong number of arguments" 61 | utils.assert.number offset, "patterns.asm \.: Invalid offset argument" 62 | 63 | utils.outiBlock.writeSlice dataAddress, patterns.ELEMENT_SIZE_BYTES, count, offset 64 | .endif 65 | .endm 66 | 67 | ;==== 68 | ; Write uncompressed patterns into VRAM 69 | ; 70 | ; @in dataAddress start address of the data 71 | ; @in size data size in bytes. Due to WLA-DX limitations this must be an 72 | ; immediate value, i.e. it can't be calculated from a size 73 | ; calculation like end - start. It can be a fsize label (such as 74 | ; using .incbin "file.bin" fsize size) so long as this label is 75 | ; defined before this macro is called. 76 | ;==== 77 | .macro "patterns.writeBytes" args dataAddress size 78 | utils.assert.equals NARGS, 2, "patterns.asm \. received the wrong number of arguments" 79 | utils.assert.label dataAddress, "patterns.asm \.: Invalid dataAddress argument" 80 | utils.assert.number size, "patterns.asm \.: Invalid size argument" 81 | 82 | utils.clobbers "hl" 83 | ld hl, dataAddress 84 | utils.outiBlock.write size 85 | utils.clobbers.end 86 | .endm 87 | 88 | ;==== 89 | ; Set the current pattern index ready to write data into 90 | ; 91 | ; @in index the index number (0-512) 92 | ;==== 93 | .macro "patterns.setIndex" args index 94 | utils.assert.equals NARGS, 1, "patterns.asm \. received the wrong number of arguments" 95 | utils.assert.range index, 0, patterns.MAX_PATTERN_INDEX, "patterns.asm \.: Invalid size argument" 96 | 97 | utils.vdp.prepWrite (patterns.VRAM_ADDRESS + (index * patterns.ELEMENT_SIZE_BYTES)) 98 | .endm 99 | -------------------------------------------------------------------------------- /pause.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Pause handler 3 | ;==== 4 | 5 | .define pause.ENABLED 1 6 | 7 | ;==== 8 | ; Dependencies 9 | ;==== 10 | .ifndef utils.assert 11 | .include "utils/assert.asm" 12 | .endif 13 | 14 | .ifndef utils.ram 15 | .include "utils/ram.asm" 16 | utils.ram.assertRamSlot 17 | .endif 18 | 19 | .ifndef utils.clobbers 20 | .include "utils/clobbers.asm" 21 | .endif 22 | 23 | ;==== 24 | ; RAM 25 | ; 26 | ; Flag is set when the pause button has been pressed 27 | ;==== 28 | .ramsection "pause.ram.pauseFlag" slot utils.ram.SLOT 29 | pause.ram.pauseFlag: db 30 | .ends 31 | 32 | ;==== 33 | ; Code 34 | ;==== 35 | 36 | ;==== 37 | ; Pause handler 38 | ; 39 | ; Toggles a flag in RAM which can be detected when appropriate 40 | ;==== 41 | .macro "pause.handler" 42 | push af 43 | ld a, (pause.ram.pauseFlag) ; read flag 44 | xor 1 ; toggle flag 45 | ld (pause.ram.pauseFlag), a ; store 46 | pop af 47 | 48 | retn 49 | .endm 50 | 51 | ;==== 52 | ; Pause handler sequence at ROM address $66. The SMS will jump to this location 53 | ; when the pause button is pressed 54 | ;==== 55 | .ifndef pause.DISABLE_HANDLER 56 | .bank 0 slot 0 57 | .orga $66 58 | .section "pause.handler" force 59 | pause.handler 60 | .ends 61 | .endif 62 | 63 | ;==== 64 | ; Initialises the pause handler in RAM 65 | ;==== 66 | .macro "pause.init" 67 | utils.clobbers "af" 68 | xor a 69 | ld (pause.ram.pauseFlag), a 70 | utils.clobbers.end 71 | .endm 72 | 73 | ;==== 74 | ; If pause activated, waits until pause button is pressed again before 75 | ; continuing 76 | ;==== 77 | .macro "pause.waitIfPaused" isolated 78 | utils.clobbers "af" 79 | -: 80 | ld a, (pause.ram.pauseFlag) ; read pauseFlag 81 | or a ; analyse flag 82 | jr z, + ; jp if not paused 83 | halt ; wait for an interrupt (pause, vBlank, hBlank) 84 | jr - ; check again 85 | +: 86 | utils.clobbers.end 87 | .endm 88 | 89 | ;==== 90 | ; Check if the pause button has been pressed. 91 | ; 92 | ; @out f z flag will be reset if pause has been pressed, otherwise it will 93 | ; be set 94 | ;==== 95 | .macro "pause.checkPause" 96 | ld a, (pause.ram.pauseFlag) 97 | or a ; analyse a 98 | .endm 99 | 100 | ;==== 101 | ; Jumps to the given address if the pause button has been pressed 102 | ; 103 | ; @in address address to jump to 104 | ;==== 105 | .macro "pause.jpIfPaused" args address 106 | utils.assert.label address "pause.asm \.: Invalid address argument" 107 | 108 | pause.checkPause 109 | jp nz, address 110 | .endm 111 | 112 | ;==== 113 | ; Calls the given address if the pause button has been pressed 114 | ; 115 | ; @in address address to call 116 | ;==== 117 | .macro "pause.callIfPaused" args address 118 | utils.assert.label address "pause.asm \.: Invalid address argument" 119 | 120 | pause.checkPause 121 | call nz, address 122 | .endm 123 | -------------------------------------------------------------------------------- /smslib.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; SMSLib 3 | ; 4 | ; Includes the full suite of SMSLib modules. Each module can be included 5 | ; individually if desired, but ensure you call their respective init macros 6 | ; (if they exist). You can also use init.asm to do this for you. 7 | ; 8 | ; Usage: 9 | ; 10 | ; .incdir "./lib/smslib" ; path to SMSLib directory 11 | ; .include "smslib.asm" ; import this file 12 | ; 13 | ; See README.md for documentation regarding each module 14 | ;==== 15 | 16 | ; Use basic 48k mapper if none defined 17 | .ifndef mapper.ENABLED 18 | .include "mapper/48k.asm" 19 | .endif 20 | 21 | ; Intelligent register preservation 22 | .include "utils/clobbers.asm" 23 | .include "utils/preserve.asm" 24 | .include "utils/restore.asm" 25 | 26 | ; Modules 27 | .include "input.asm" ; handles input 28 | .include "interrupts.asm" ; handles line and frame interrupts 29 | .include "palette.asm" ; handles colors 30 | .include "patterns.asm" ; handles patterns (tile images) 31 | .include "pause.asm" ; handles pause button 32 | .include "sprites.asm" ; handles sprites 33 | .include "tilemap.asm" ; handles tilemap 34 | .include "vdp.asm" ; handles vdp settings 35 | 36 | .include "init.asm" ; initialises system and smslib modules 37 | -------------------------------------------------------------------------------- /tests/build.sh: -------------------------------------------------------------------------------- 1 | project_dir="$(cd "$(dirname "$0")" && pwd -P)" 2 | build_dir=$project_dir/dist 3 | 4 | mkdir -p $build_dir 5 | 6 | # Assemble ROM 7 | assemble() { 8 | name=$1 9 | entry_dir=$project_dir/$(dirname "$2") 10 | entry_file=$(basename "$2") 11 | 12 | # Create simple link_file 13 | link_file="${build_dir}/${name}_link_file" 14 | echo [objects] > $link_file 15 | echo $name.o >> $link_file 16 | 17 | # Assemble object files 18 | cd $entry_dir 19 | wla-z80 -o $build_dir/$name.o $entry_file 20 | 21 | # Create ROM from object files 22 | cd $build_dir 23 | wlalink -d -S -A $link_file $name.sms 24 | 25 | # Delete temp files 26 | rm -f $build_dir/$name.o $link_file 27 | } 28 | 29 | # Assemble ROMs 30 | assemble smslib-tests suite.asm -------------------------------------------------------------------------------- /tests/input/_helpers.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Helper macros for the input tests 3 | ;==== 4 | 5 | ; Unrolled macros, to save some ROM space 6 | .section "test.input.unrolledMacros" free 7 | test.input.readPort1: 8 | input.readPort1 9 | ret 10 | .ends 11 | 12 | ;==== 13 | ; Defines button variables for use in the input tests 14 | ; 15 | ; @in buttonNumber 0-6 (UP, DOWN, LEFT, RIGHT, 1, 2 respectively) 16 | ; 17 | ; @out the following defines: 18 | ; BUTTON_NAME = the name of the button 19 | ; FAKE_INPUT = the fake controller input, to pass to zest.mockController1/2 20 | ; ALL_BUTTONS_EXCEPT_CURRENT = FAKE_INPUT inverted 21 | ; TEST_INPUT = the fake value to past to the input.if* macros 22 | ;==== 23 | .macro "test.input.defineButtonData" args buttonNumber 24 | .if buttonNumber == 0 25 | .redefine BUTTON_NAME "U" 26 | .redefine FAKE_INPUT zest.UP 27 | .redefine TEST_INPUT input.UP 28 | .elif buttonNumber == 1 29 | .redefine BUTTON_NAME "D" 30 | .redefine FAKE_INPUT zest.DOWN 31 | .redefine TEST_INPUT input.DOWN 32 | .elif buttonNumber == 2 33 | .redefine BUTTON_NAME "L" 34 | .redefine FAKE_INPUT zest.LEFT 35 | .redefine TEST_INPUT input.LEFT 36 | .elif buttonNumber == 3 37 | .redefine BUTTON_NAME "R" 38 | .redefine FAKE_INPUT zest.RIGHT 39 | .redefine TEST_INPUT input.RIGHT 40 | .elif buttonNumber == 4 41 | .redefine BUTTON_NAME "1" 42 | .redefine FAKE_INPUT zest.BUTTON_1 43 | .redefine TEST_INPUT input.BUTTON_1 44 | .elif buttonNumber == 5 45 | .redefine BUTTON_NAME "2" 46 | .redefine FAKE_INPUT zest.BUTTON_2 47 | .redefine TEST_INPUT input.BUTTON_2 48 | .endif 49 | 50 | .redefine ALL_BUTTONS_EXCEPT_CURRENT FAKE_INPUT ~ $ff 51 | .redefine ALL_BUTTONS %00111111 52 | .redefine NO_BUTTONS 0 53 | .endm 54 | 55 | ;==== 56 | ; Mocks the input for the given controller 57 | ; 58 | ; @in controller 0 = controller 1, 1 = controller 2 59 | ; @in input the input data to pass to zest.mockController1/2 60 | ; @in [frames] the number of input frames to mock (defaults to 1) 61 | ;==== 62 | .macro "test.input.mockController" args controller input frames 63 | .if NARGS == 2 64 | .define frames 1 65 | .endif 66 | 67 | .if controller == 0 68 | zest.mockController1 input 69 | 70 | .repeat frames 71 | call test.input.readPort1 72 | .endr 73 | .else 74 | zest.mockController2 input 75 | 76 | .repeat frames 77 | input.readPort2 78 | .endr 79 | .endif 80 | .endm 81 | -------------------------------------------------------------------------------- /tests/input/if.test.asm: -------------------------------------------------------------------------------- 1 | .redefine utils.registers.AUTO_PRESERVE 1 2 | 3 | .repeat 2 index controller 4 | describe { "input.if should run the code block if the given button is pressed (controller {controller + 1})" } 5 | .repeat 6 index buttonNumber 6 | test.input.defineButtonData buttonNumber ; set constants (see helpers) 7 | 8 | test { "{BUTTON_NAME} button" } 9 | test.input.mockController controller, FAKE_INPUT 10 | 11 | zest.initRegisters 12 | 13 | input.if TEST_INPUT, + 14 | expect.all.toBeUnclobbered 15 | jp ++ ; pass test 16 | +: 17 | 18 | ; Otherwise, fail 19 | zest.fail 20 | 21 | ++: 22 | .endr 23 | 24 | describe { "input.if should jump over the code block if the given button is not pressed (controller {controller + 1})" } 25 | .repeat 6 index buttonNumber 26 | test.input.defineButtonData buttonNumber ; set constants (see helpers) 27 | 28 | test { "{BUTTON_NAME} button" } 29 | test.input.mockController controller, ALL_BUTTONS_EXCEPT_CURRENT 30 | 31 | zest.initRegisters 32 | 33 | input.if TEST_INPUT, + 34 | ; Fail test 35 | zest.fail 36 | +: 37 | 38 | expect.all.toBeUnclobbered 39 | .endr 40 | 41 | describe { "input.if when multiple buttons are given (controller {controller + 1})" } 42 | it { "should run the code block when all buttons are pressed" } 43 | test.input.mockController controller, $ff ; all buttons pressed 44 | 45 | zest.initRegisters 46 | 47 | ; Check if all buttons are pressed 48 | input.if input.UP, input.DOWN, input.LEFT, input.RIGHT, input.BUTTON_1, input.BUTTON_2, + 49 | expect.all.toBeUnclobbered 50 | jp ++ ; pass test 51 | +: 52 | 53 | ; Otherwise, fail 54 | zest.fail 55 | 56 | ++: 57 | 58 | describe { "input.if should jump over the code block if multiple buttons are given but not all are pressed (controller {controller + 1})" } 59 | .repeat 6 index buttonNumber 60 | test { "{BUTTON_NAME} button" } 61 | test.input.defineButtonData buttonNumber ; set constants (see helpers) 62 | test.input.mockController controller, ALL_BUTTONS_EXCEPT_CURRENT 63 | 64 | zest.initRegisters 65 | 66 | ; Check if all buttons are pressed 67 | input.if input.UP, input.DOWN, input.LEFT, input.RIGHT, input.BUTTON_1, input.BUTTON_2, + 68 | zest.fail ; fail test 69 | +: 70 | 71 | expect.all.toBeUnclobbered 72 | .endr 73 | .endr 74 | 75 | .redefine utils.registers.AUTO_PRESERVE 0 -------------------------------------------------------------------------------- /tests/input/ifReleased.test.asm: -------------------------------------------------------------------------------- 1 | .redefine utils.registers.AUTO_PRESERVE 1 2 | 3 | .repeat 2 index controller 4 | describe { "input.ifReleased should run the code block if the given button was pressed last frame but isn't now (controller {controller + 1})" } 5 | .repeat 6 index buttonNumber 6 | test.input.defineButtonData buttonNumber ; set constants (see helpers) 7 | 8 | test { "{BUTTON_NAME} button" } 9 | test.input.mockController controller, FAKE_INPUT ; was pressed 10 | test.input.mockController controller, ALL_BUTTONS_EXCEPT_CURRENT ; isn't now 11 | 12 | zest.initRegisters 13 | 14 | input.ifReleased TEST_INPUT, + 15 | expect.all.toBeUnclobbered 16 | jp ++ ; pass test 17 | +: 18 | 19 | ; Otherwise, fail 20 | zest.fail 21 | 22 | ++: 23 | .endr 24 | 25 | describe { "input.ifReleased should jump over the code block if the given button is still pressed (controller {controller + 1})" } 26 | .repeat 6 index buttonNumber 27 | test.input.defineButtonData buttonNumber ; set constants (see helpers) 28 | 29 | test { "{BUTTON_NAME} button" } 30 | test.input.mockController controller, FAKE_INPUT 31 | 32 | zest.initRegisters 33 | 34 | input.ifReleased TEST_INPUT, + 35 | zest.fail 36 | +: 37 | 38 | expect.all.toBeUnclobbered 39 | .endr 40 | 41 | describe { "input.ifReleased should jump over the code block if the given button wasn't pressed last frame (controller {controller + 1})" } 42 | .repeat 6 index buttonNumber 43 | test.input.defineButtonData buttonNumber ; set constants (see helpers) 44 | 45 | test { "{BUTTON_NAME} button" } 46 | test.input.mockController controller, ALL_BUTTONS_EXCEPT_CURRENT, 2 47 | 48 | zest.initRegisters 49 | 50 | input.ifReleased TEST_INPUT, + 51 | zest.fail 52 | +: 53 | 54 | expect.all.toBeUnclobbered 55 | .endr 56 | 57 | ;==== 58 | ; Multiple buttons 59 | ;==== 60 | 61 | describe { "input.ifReleased should run the code block when all given buttons were pressed but now aren't (controller {controller + 1})" } 62 | .repeat 6 index buttonNumber 63 | test.input.defineButtonData buttonNumber ; set constants (see helpers) 64 | 65 | test { "{BUTTON_NAME} button" } 66 | test.input.mockController controller, ALL_BUTTONS ; this frame 67 | test.input.mockController controller, ALL_BUTTONS_EXCEPT_CURRENT ; last frame 68 | 69 | zest.initRegisters 70 | 71 | input.ifReleased input.UP, input.DOWN, input.LEFT, input.RIGHT, input.BUTTON_1, input.BUTTON_2, + 72 | expect.all.toBeUnclobbered 73 | jp ++ ; pass test 74 | +: 75 | 76 | ; Otherwise, fail 77 | zest.fail 78 | 79 | ++: 80 | .endr 81 | 82 | describe { "input.ifReleased should jp over the code block if not all given buttons were pressed last frame (controller {controller + 1})" } 83 | .repeat 6 index buttonNumber 84 | test.input.defineButtonData buttonNumber ; set constants (see helpers) 85 | 86 | test { "{BUTTON_NAME} button" } 87 | test.input.mockController controller, ALL_BUTTONS_EXCEPT_CURRENT ; last frame 88 | test.input.mockController controller, NO_BUTTONS ; this frame 89 | 90 | zest.initRegisters 91 | 92 | ; Check if all buttons are pressed 93 | input.ifReleased input.UP, input.DOWN, input.LEFT, input.RIGHT, input.BUTTON_1, input.BUTTON_2, + 94 | zest.fail ; fail test 95 | +: 96 | 97 | expect.all.toBeUnclobbered 98 | .endr 99 | .endr 100 | 101 | .redefine utils.registers.AUTO_PRESERVE 0 102 | -------------------------------------------------------------------------------- /tests/input/ifXDir.test.asm: -------------------------------------------------------------------------------- 1 | .repeat 2 index controller 2 | describe { "input.ifXDir (pad {controller + 1})" } 3 | test "jps when neither left nor right are pressed" 4 | test.input.mockController controller, zest.NO_INPUT 5 | 6 | zest.initRegisters 7 | 8 | utils.preserve 9 | input.ifXDir +, ++, +++ 10 | utils.restore 11 | +: 12 | zest.fail "Left called" 13 | ++: 14 | zest.fail "Right called" 15 | +++: 16 | expect.all.toBeUnclobbered 17 | ++++: 18 | 19 | test "jps to left when left is pressed" 20 | test.input.mockController controller, zest.LEFT 21 | 22 | zest.initRegisters 23 | 24 | utils.preserve 25 | input.ifXDir +, ++, +++ 26 | utils.restore 27 | +: 28 | expect.all.toBeUnclobbered 29 | jp ++++ ; pass 30 | ++: 31 | zest.fail "Right called" 32 | +++: 33 | zest.fail "Else called" 34 | ++++: 35 | 36 | test "jps to right when right is pressed" 37 | test.input.mockController controller, zest.RIGHT 38 | 39 | zest.initRegisters 40 | 41 | utils.preserve 42 | input.ifXDir +, ++, +++ 43 | utils.restore 44 | +: 45 | zest.fail "Left called" 46 | ++: 47 | expect.all.toBeUnclobbered 48 | jp ++++ ; pass 49 | +++: 50 | zest.fail "Else called" 51 | ++++: 52 | .endr 53 | -------------------------------------------------------------------------------- /tests/input/ifXDirHeld.test.asm: -------------------------------------------------------------------------------- 1 | .repeat 2 index controller 2 | describe { "input.ifXDirHeld (pad {controller + 1})" } 3 | test "jps to else when neither left nor right are pressed" 4 | test.input.mockController controller, zest.NO_INPUT 2 5 | 6 | zest.initRegisters 7 | 8 | utils.preserve 9 | input.ifXDirHeld +, ++, +++ 10 | utils.restore 11 | +: 12 | zest.fail "Left called" 13 | ++: 14 | zest.fail "Right called" 15 | +++: 16 | expect.all.toBeUnclobbered 17 | ++++: 18 | 19 | test "jps to else when left was pressed last frame but not this frame" 20 | test.input.mockController controller, zest.LEFT 21 | test.input.mockController controller, zest.NO_INPUT 22 | 23 | zest.initRegisters 24 | 25 | utils.preserve 26 | input.ifXDirHeld +, ++, +++ 27 | utils.restore 28 | +: 29 | zest.fail "Left called" 30 | ++: 31 | zest.fail "Right called" 32 | +++: 33 | expect.all.toBeUnclobbered 34 | ++++: 35 | 36 | test "jps to else when right was pressed last frame but not this frame" 37 | test.input.mockController controller, zest.RIGHT 38 | test.input.mockController controller, zest.NO_INPUT 39 | 40 | zest.initRegisters 41 | 42 | utils.preserve 43 | input.ifXDirHeld +, ++, +++ 44 | utils.restore 45 | +: 46 | zest.fail "Left called" 47 | ++: 48 | zest.fail "Right called" 49 | +++: 50 | expect.all.toBeUnclobbered 51 | ++++: 52 | 53 | test "jps to else when left was pressed this frame but not last frame" 54 | test.input.mockController controller, zest.NO_INPUT 55 | test.input.mockController controller, zest.LEFT 56 | 57 | zest.initRegisters 58 | 59 | utils.preserve 60 | input.ifXDirHeld +, ++, +++ 61 | utils.restore 62 | +: 63 | zest.fail "Left called" 64 | ++: 65 | zest.fail "Right called" 66 | +++: 67 | expect.all.toBeUnclobbered 68 | ++++: 69 | 70 | test "jps to else when right was pressed this frame but not last frame" 71 | test.input.mockController controller, zest.NO_INPUT 72 | test.input.mockController controller, zest.RIGHT 73 | 74 | zest.initRegisters 75 | 76 | utils.preserve 77 | input.ifXDirHeld +, ++, +++ 78 | utils.restore 79 | +: 80 | zest.fail "Left called" 81 | ++: 82 | zest.fail "Right called" 83 | +++: 84 | expect.all.toBeUnclobbered 85 | ++++: 86 | 87 | test "jps to left when left is held" 88 | test.input.mockController controller, zest.LEFT 2 89 | 90 | zest.initRegisters 91 | 92 | utils.preserve 93 | input.ifXDirHeld +, ++, +++ 94 | utils.restore 95 | +: 96 | expect.all.toBeUnclobbered 97 | jp ++++ ; pass 98 | ++: 99 | zest.fail "Right called" 100 | +++: 101 | zest.fail "Else called" 102 | ++++: 103 | 104 | test "jps to right when right is pressed" 105 | test.input.mockController controller, zest.RIGHT 2 106 | 107 | zest.initRegisters 108 | 109 | utils.preserve 110 | input.ifXDirHeld +, ++, +++ 111 | utils.restore 112 | +: 113 | zest.fail "Left called" 114 | ++: 115 | expect.all.toBeUnclobbered 116 | jp ++++ ; pass 117 | +++: 118 | zest.fail "Else called" 119 | ++++: 120 | .endr 121 | -------------------------------------------------------------------------------- /tests/input/ifYDir.test.asm: -------------------------------------------------------------------------------- 1 | .repeat 2 index controller 2 | describe { "input.ifYDir (pad {controller + 1})" } 3 | test "jps to else when neither up nor down are pressed" 4 | test.input.mockController controller, zest.NO_INPUT 5 | 6 | zest.initRegisters 7 | 8 | utils.preserve 9 | input.ifYDir +, ++, +++ 10 | utils.restore 11 | +: 12 | zest.fail "Up called" 13 | ++: 14 | zest.fail "Down called" 15 | +++: 16 | expect.all.toBeUnclobbered 17 | ++++: 18 | 19 | test "jps to up when up is pressed" 20 | test.input.mockController controller, zest.UP 21 | 22 | zest.initRegisters 23 | 24 | utils.preserve 25 | input.ifYDir +, ++, +++ 26 | utils.restore 27 | +: 28 | expect.all.toBeUnclobbered 29 | jp ++++ ; pass 30 | ++: 31 | zest.fail "Down called" 32 | +++: 33 | zest.fail "Else called" 34 | ++++: 35 | 36 | test "jps to down when down is pressed" 37 | test.input.mockController controller, zest.DOWN 38 | 39 | zest.initRegisters 40 | 41 | utils.preserve 42 | input.ifYDir +, ++, +++ 43 | utils.restore 44 | +: 45 | zest.fail "Up called" 46 | ++: 47 | expect.all.toBeUnclobbered 48 | jp ++++ ; pass 49 | +++: 50 | zest.fail "Else called" 51 | ++++: 52 | .endr 53 | -------------------------------------------------------------------------------- /tests/input/ifYDirHeld.test.asm: -------------------------------------------------------------------------------- 1 | .repeat 2 index controller 2 | describe { "input.ifYDirHeld (pad {controller + 1})" } 3 | test "jps to else when neither up nor down are pressed" 4 | test.input.mockController controller, zest.NO_INPUT 2 5 | 6 | zest.initRegisters 7 | 8 | utils.preserve 9 | input.ifYDirHeld +, ++, +++ 10 | utils.restore 11 | +: 12 | zest.fail "Up called" 13 | ++: 14 | zest.fail "Down called" 15 | +++: 16 | expect.all.toBeUnclobbered 17 | ++++: 18 | 19 | test "jps to else when up was pressed last frame but not this frame" 20 | test.input.mockController controller, zest.UP 21 | test.input.mockController controller, zest.NO_INPUT 22 | 23 | zest.initRegisters 24 | 25 | utils.preserve 26 | input.ifYDirHeld +, ++, +++ 27 | utils.restore 28 | +: 29 | zest.fail "Up called" 30 | ++: 31 | zest.fail "Down called" 32 | +++: 33 | expect.all.toBeUnclobbered 34 | ++++: 35 | 36 | test "jps to else when down was pressed last frame but not this frame" 37 | test.input.mockController controller, zest.DOWN 38 | test.input.mockController controller, zest.NO_INPUT 39 | 40 | zest.initRegisters 41 | 42 | utils.preserve 43 | input.ifYDirHeld +, ++, +++ 44 | utils.restore 45 | +: 46 | zest.fail "Up called" 47 | ++: 48 | zest.fail "Down called" 49 | +++: 50 | expect.all.toBeUnclobbered 51 | ++++: 52 | 53 | test "jps to else when up was pressed this frame but not last frame" 54 | test.input.mockController controller, zest.NO_INPUT 55 | test.input.mockController controller, zest.UP 56 | 57 | zest.initRegisters 58 | 59 | utils.preserve 60 | input.ifYDirHeld +, ++, +++ 61 | utils.restore 62 | +: 63 | zest.fail "Up called" 64 | ++: 65 | zest.fail "Down called" 66 | +++: 67 | expect.all.toBeUnclobbered 68 | ++++: 69 | 70 | test "jps to else when down was pressed this frame but not last frame" 71 | test.input.mockController controller, zest.NO_INPUT 72 | test.input.mockController controller, zest.DOWN 73 | 74 | zest.initRegisters 75 | 76 | utils.preserve 77 | input.ifYDirHeld +, ++, +++ 78 | utils.restore 79 | +: 80 | zest.fail "Up called" 81 | ++: 82 | zest.fail "Down called" 83 | +++: 84 | expect.all.toBeUnclobbered 85 | ++++: 86 | 87 | test "jps to up when up is held" 88 | test.input.mockController controller, zest.UP 2 89 | 90 | zest.initRegisters 91 | 92 | utils.preserve 93 | input.ifYDirHeld +, ++, +++ 94 | utils.restore 95 | +: 96 | expect.all.toBeUnclobbered 97 | jp ++++ ; pass 98 | ++: 99 | zest.fail "Down called" 100 | +++: 101 | zest.fail "Else called" 102 | ++++: 103 | 104 | test "jps to down when down is pressed" 105 | test.input.mockController controller, zest.DOWN 2 106 | 107 | zest.initRegisters 108 | 109 | utils.preserve 110 | input.ifYDirHeld +, ++, +++ 111 | utils.restore 112 | +: 113 | zest.fail "Up called" 114 | ++: 115 | expect.all.toBeUnclobbered 116 | jp ++++ ; pass 117 | +++: 118 | zest.fail "Else called" 119 | ++++: 120 | .endr 121 | -------------------------------------------------------------------------------- /tests/input/init.test.asm: -------------------------------------------------------------------------------- 1 | describe "input.init (with port 2 disabled)" 2 | .undefine input.ENABLE_PORT_2 3 | 4 | test "preserves registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | input.init 9 | utils.restore 10 | 11 | expect.all.toBeUnclobbered 12 | 13 | describe "input.init (with port 2 enabled)" 14 | .define input.ENABLE_PORT_2 15 | 16 | test "preserves registers" 17 | zest.initRegisters 18 | 19 | utils.preserve 20 | input.init 21 | utils.restore 22 | 23 | expect.all.toBeUnclobbered 24 | -------------------------------------------------------------------------------- /tests/input/loadADirX.test.asm: -------------------------------------------------------------------------------- 1 | .redefine utils.registers.AUTO_PRESERVE 1 2 | 3 | .repeat 2 index controller 4 | describe { "input.loadADirX (pad {controller + 1})" } 5 | test "returns 0 if left or right aren't pressed" 6 | test.input.mockController controller, zest.NO_INPUT 7 | zest.initRegisters 8 | 9 | input.loadADirX 10 | 11 | expect.all.toBeUnclobberedExcept "af" 12 | expect.a.toBe 0 13 | 14 | test "returns -1 if left is pressed" 15 | test.input.mockController controller, zest.LEFT 16 | zest.initRegisters 17 | 18 | input.loadADirX 19 | 20 | expect.all.toBeUnclobberedExcept "af" 21 | expect.a.toBe -1 22 | 23 | test "returns 1 if right is pressed" 24 | test.input.mockController controller, zest.RIGHT 25 | zest.initRegisters 26 | 27 | input.loadADirX 28 | 29 | expect.all.toBeUnclobberedExcept "af" 30 | expect.a.toBe 1 31 | 32 | .redefine utils.registers.AUTO_PRESERVE 0 33 | -------------------------------------------------------------------------------- /tests/input/loadADirY.test.asm: -------------------------------------------------------------------------------- 1 | .redefine utils.registers.AUTO_PRESERVE 1 2 | 3 | .repeat 2 index controller 4 | describe { "input.loadADirY (pad {controller + 1})" } 5 | test "returns 0 if up or down aren't pressed" 6 | test.input.mockController controller, zest.NO_INPUT 7 | zest.initRegisters 8 | 9 | input.loadADirY 10 | 11 | expect.all.toBeUnclobberedExcept "af" 12 | expect.a.toBe 0 13 | 14 | test "returns -1 if up is pressed" 15 | test.input.mockController controller, zest.UP 16 | zest.initRegisters 17 | 18 | input.loadADirY 19 | 20 | expect.all.toBeUnclobberedExcept "af" 21 | expect.a.toBe -1 22 | 23 | test "returns 1 if down is pressed" 24 | test.input.mockController controller, zest.DOWN 25 | zest.initRegisters 26 | 27 | input.loadADirY 28 | 29 | expect.all.toBeUnclobberedExcept "af" 30 | expect.a.toBe 1 31 | 32 | .redefine utils.registers.AUTO_PRESERVE 0 33 | -------------------------------------------------------------------------------- /tests/input/readPort1.test.asm: -------------------------------------------------------------------------------- 1 | describe "input.readPort1 (with port 2 disabled)" 2 | .undefine input.ENABLE_PORT_2 3 | 4 | test "preserves registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | input.readPort1 9 | utils.restore 10 | 11 | expect.all.toBeUnclobbered 12 | 13 | describe "input.readPort1 (with port 2 enabled)" 14 | .define input.ENABLE_PORT_2 15 | 16 | test "preserves registers" 17 | zest.initRegisters 18 | 19 | utils.preserve 20 | input.readPort1 21 | utils.restore 22 | 23 | expect.all.toBeUnclobbered 24 | -------------------------------------------------------------------------------- /tests/input/readPort2.test.asm: -------------------------------------------------------------------------------- 1 | describe "input.readPort2" 2 | test "preserves registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | input.readPort2 7 | utils.restore 8 | 9 | expect.all.toBeUnclobbered 10 | -------------------------------------------------------------------------------- /tests/interrupts/enable.test.asm: -------------------------------------------------------------------------------- 1 | describe "interrupts.enable" 2 | test "does not clobber any registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | interrupts.enable 7 | utils.restore 8 | 9 | expect.all.toBeUnclobbered -------------------------------------------------------------------------------- /tests/interrupts/init.test.asm: -------------------------------------------------------------------------------- 1 | describe "interrupts.init" 2 | test "does not clobber any registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | interrupts.init 7 | utils.restore 8 | 9 | expect.all.toBeUnclobbered -------------------------------------------------------------------------------- /tests/interrupts/setLineInterval.test.asm: -------------------------------------------------------------------------------- 1 | describe "interrupts.setLineInterval" 2 | test "does not clobber any registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | interrupts.setLineInterval 7 | utils.restore 8 | 9 | expect.all.toBeUnclobbered -------------------------------------------------------------------------------- /tests/interrupts/waitForVBlank.test.asm: -------------------------------------------------------------------------------- 1 | describe "interrupts.waitForVBlank" 2 | test "does not clobber any registers" 3 | ; Set VBlank flag 4 | ld a, 1 5 | ld (interrupts.ram.vBlankFlag), a 6 | 7 | zest.initRegisters 8 | 9 | utils.preserve 10 | interrupts.waitForVBlank 11 | utils.restore 12 | 13 | expect.all.toBeUnclobbered -------------------------------------------------------------------------------- /tests/palette/setIndex.test.asm: -------------------------------------------------------------------------------- 1 | describe "palette.setIndex" 2 | test "sets C to the port but does not clobber other registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | palette.setIndex 1 7 | utils.restore 8 | 9 | expect.all.toBeUnclobberedExcept "c" 10 | expect.c.toBe $be ; vdp data port 11 | 12 | test "allows the index to be set from 0 to 31" 13 | palette.setIndex 0 14 | palette.setIndex 31 15 | -------------------------------------------------------------------------------- /tests/palette/writeBytes.test.asm: -------------------------------------------------------------------------------- 1 | describe "palette.writeBytes" 2 | jr + 3 | _palette.writeBytes.data: 4 | .db 0 5 | .db 1 6 | .db 3 7 | +: 8 | 9 | test "does not clobber registers" 10 | zest.initRegisters 11 | 12 | utils.preserve 13 | palette.setIndex 0 14 | palette.writeBytes _palette.writeBytes.data 1 15 | utils.restore 16 | 17 | expect.all.toBeUnclobberedExcept "c" 18 | expect.c.toBe $be ; vdp data port 19 | -------------------------------------------------------------------------------- /tests/palette/writeRgb.test.asm: -------------------------------------------------------------------------------- 1 | describe "palette.writeRgb" 2 | test "does not clobber the registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | palette.setIndex 0 7 | palette.writeRgb 100 150 200 8 | utils.restore 9 | 10 | expect.all.toBeUnclobberedExcept "c" 11 | expect.c.toBe $be ; vdp data port 12 | -------------------------------------------------------------------------------- /tests/palette/writeSlice.test.asm: -------------------------------------------------------------------------------- 1 | describe "palette.writeSlice" 2 | jr + 3 | _palette.writeSlice.data: 4 | .db 0 5 | .db 1 6 | .db 3 7 | +: 8 | 9 | test "does not clobber registers" 10 | zest.initRegisters 11 | 12 | utils.preserve 13 | palette.setIndex 0 14 | palette.writeSlice _palette.writeSlice.data 1 15 | utils.restore 16 | 17 | expect.all.toBeUnclobberedExcept "c" 18 | expect.c.toBe $be ; vdp data port 19 | 20 | describe "palette.writeSlice with offset" 21 | test "does not clobber registers" 22 | zest.initRegisters 23 | 24 | utils.preserve 25 | palette.setIndex 0 26 | palette.writeSlice _palette.writeSlice.data 1 1 27 | utils.restore 28 | 29 | expect.all.toBeUnclobberedExcept "c" 30 | expect.c.toBe $be ; vdp data port 31 | -------------------------------------------------------------------------------- /tests/patterns/setIndex.test.asm: -------------------------------------------------------------------------------- 1 | describe "patterns.setIndex" 2 | test "sets C to the VDP data port but does not clobber other registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | patterns.setIndex 1 7 | utils.restore 8 | 9 | expect.all.toBeUnclobberedExcept "c" 10 | expect.c.toBe $be ; vdp data port 11 | 12 | test "allows the index to be set from 0 to 511" 13 | patterns.setIndex 0 14 | patterns.setIndex 511 -------------------------------------------------------------------------------- /tests/patterns/writeBytes.test.asm: -------------------------------------------------------------------------------- 1 | describe "patterns.writeBytes" 2 | jr + 3 | _patterns.writeBytes.data: 4 | .db 0 5 | .db 1 6 | .db 3 7 | +: 8 | 9 | test "does not clobber registers" 10 | zest.initRegisters 11 | 12 | utils.preserve 13 | patterns.setIndex 0 14 | patterns.writeBytes _patterns.writeBytes.data 1 15 | utils.restore 16 | 17 | expect.all.toBeUnclobberedExcept "c" 18 | expect.c.toBe $be ; vdp data port 19 | -------------------------------------------------------------------------------- /tests/patterns/writeSlice.test.asm: -------------------------------------------------------------------------------- 1 | describe "patterns.writeSlice" 2 | jr + 3 | _patterns.writeSlice.data: 4 | .db 0 5 | .db 1 6 | .db 3 7 | +: 8 | 9 | test "does not clobber registers" 10 | zest.initRegisters 11 | 12 | utils.preserve 13 | patterns.setIndex 0 14 | patterns.writeSlice _patterns.writeSlice.data 1 15 | utils.restore 16 | 17 | expect.all.toBeUnclobberedExcept "c" 18 | expect.c.toBe $be ; vdp data port 19 | 20 | describe "patterns.writeSlice with offset" 21 | test "does not clobber registers" 22 | zest.initRegisters 23 | 24 | utils.preserve 25 | patterns.setIndex 0 26 | patterns.writeSlice _patterns.writeSlice.data 1 1 27 | utils.restore 28 | 29 | expect.all.toBeUnclobberedExcept "c" 30 | expect.c.toBe $be ; vdp data port 31 | -------------------------------------------------------------------------------- /tests/pause/callIfPaused.test.asm: -------------------------------------------------------------------------------- 1 | describe "pause.callIfPaused" 2 | test "calls the given routine if the pause flag is set" 3 | ; Set pause flag 4 | ld a, 1 5 | ld (pause.ram.pauseFlag), a 6 | 7 | jr + 8 | -: 9 | jp ++ ; routine called; jp to pass 10 | +: 11 | 12 | pause.callIfPaused - 13 | zest.fail "Did not call" 14 | ++: 15 | 16 | test "does not call the routine if the pause flag is reset" 17 | ; Reset pause flag 18 | xor a 19 | ld (pause.ram.pauseFlag), a 20 | 21 | jr + 22 | -: 23 | zest.fail "Unexpected call" 24 | +: 25 | 26 | pause.callIfPaused - 27 | -------------------------------------------------------------------------------- /tests/pause/init.test.asm: -------------------------------------------------------------------------------- 1 | describe "pause.init" 2 | test "does not clobber any registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | pause.init 7 | utils.restore 8 | 9 | expect.all.toBeUnclobbered -------------------------------------------------------------------------------- /tests/pause/jpIfPaused.test.asm: -------------------------------------------------------------------------------- 1 | describe "pause.jpIfPaused" 2 | test "jumps if the pause flag is set" 3 | ; Set pause flag 4 | ld a, 1 5 | ld (pause.ram.pauseFlag), a 6 | 7 | pause.jpIfPaused + 8 | zest.fail "Did not jump" 9 | +: 10 | 11 | test "does not jump if the pause flag is reset" 12 | ; Reset pause flag 13 | xor a 14 | ld (pause.ram.pauseFlag), a 15 | 16 | pause.jpIfPaused + 17 | jp ++ ; did not jump; jump to pass 18 | 19 | +: 20 | zest.fail "Unexpected jump" 21 | 22 | ++: ; pass 23 | -------------------------------------------------------------------------------- /tests/pause/waitIfPaused.test.asm: -------------------------------------------------------------------------------- 1 | describe "pause.waitIfPaused" 2 | test "does not clobber any registers" 3 | ; Reset pause flag 4 | xor a 5 | ld (pause.ram.pauseFlag), a 6 | 7 | zest.initRegisters 8 | 9 | utils.preserve 10 | pause.waitIfPaused 11 | utils.restore 12 | 13 | expect.all.toBeUnclobbered -------------------------------------------------------------------------------- /tests/scroll/metatiles/_helpers.asm: -------------------------------------------------------------------------------- 1 | .section "tests.scroll.metatiles.init" free 2 | tests.scroll.metatiles.init: 3 | ld a, 64 ; the map's width in metatiles 4 | ld b, 0 ; the column offset in metatiles 5 | ld c, 0 ; the row offset in metatiles 6 | ld d, 64 ; the map's height in metatiles 7 | scroll.metatiles.init 8 | 9 | ret 10 | .ends 11 | -------------------------------------------------------------------------------- /tests/scroll/metatiles/init.test.asm: -------------------------------------------------------------------------------- 1 | describe "scroll/metatiles init" 2 | 3 | test "does not clobber registers" 4 | zest.initRegisters 5 | 6 | utils.preserve 7 | ld a, 64 ; the map's width in metatiles 8 | ld b, 0 ; the column offset in metatiles 9 | ld c, 0 ; the row offset in metatiles 10 | ld d, 64 ; the map's height in metatiles 11 | scroll.metatiles.init 12 | utils.restore 13 | 14 | expect.all.toBeUnclobberedExcept "a", "b", "c", "d" 15 | expect.a.toBe 64 16 | expect.b.toBe 0 17 | expect.c.toBe 0 18 | expect.d.toBe 64 19 | -------------------------------------------------------------------------------- /tests/scroll/metatiles/render.test.asm: -------------------------------------------------------------------------------- 1 | describe "scroll.metatiles.render does not clobber registers when:" 2 | 3 | test "no row or col scroll needed" 4 | ; Setup 5 | call tests.scroll.metatiles.init 6 | xor a 7 | scroll.metatiles.adjustXPixels 8 | xor a 9 | scroll.metatiles.adjustYPixels 10 | scroll.metatiles.update 11 | 12 | zest.initRegisters 13 | 14 | utils.preserve 15 | scroll.metatiles.render 16 | utils.restore 17 | 18 | expect.all.toBeUnclobbered 19 | 20 | test "row scroll needed" 21 | ; Setup 22 | call tests.scroll.metatiles.init 23 | xor a 24 | scroll.metatiles.adjustXPixels 25 | ld a, 8 26 | scroll.metatiles.adjustYPixels 27 | scroll.metatiles.update 28 | 29 | zest.initRegisters 30 | 31 | utils.preserve 32 | scroll.metatiles.render 33 | utils.restore 34 | 35 | expect.all.toBeUnclobbered 36 | 37 | test "col scroll needed" 38 | ; Setup 39 | call tests.scroll.metatiles.init 40 | ld a, 8 41 | scroll.metatiles.adjustXPixels 42 | xor a 43 | scroll.metatiles.adjustYPixels 44 | scroll.metatiles.update 45 | 46 | zest.initRegisters 47 | 48 | utils.preserve 49 | scroll.metatiles.render 50 | utils.restore 51 | 52 | expect.all.toBeUnclobbered 53 | -------------------------------------------------------------------------------- /tests/scroll/metatiles/update.test.asm: -------------------------------------------------------------------------------- 1 | describe "scroll.metatiles.update does not clobber registers when:" 2 | 3 | test "no row or col scroll needed" 4 | ; Setup 5 | call tests.scroll.metatiles.init 6 | xor a 7 | scroll.metatiles.adjustXPixels 8 | xor a 9 | scroll.metatiles.adjustYPixels 10 | 11 | zest.initRegisters 12 | 13 | utils.preserve 14 | scroll.metatiles.update 15 | utils.restore 16 | 17 | expect.all.toBeUnclobbered 18 | 19 | test "row scroll needed" 20 | ; Setup 21 | call tests.scroll.metatiles.init 22 | xor a 23 | scroll.metatiles.adjustXPixels 24 | ld a, 8 25 | scroll.metatiles.adjustYPixels 26 | 27 | zest.initRegisters 28 | 29 | utils.preserve 30 | scroll.metatiles.update 31 | utils.restore 32 | 33 | expect.all.toBeUnclobbered 34 | 35 | test "col scroll needed" 36 | ; Setup 37 | call tests.scroll.metatiles.init 38 | ld a, 8 39 | scroll.metatiles.adjustXPixels 40 | xor a 41 | scroll.metatiles.adjustYPixels 42 | 43 | zest.initRegisters 44 | 45 | utils.preserve 46 | scroll.metatiles.update 47 | utils.restore 48 | 49 | expect.all.toBeUnclobbered 50 | -------------------------------------------------------------------------------- /tests/scroll/tiles/init.test.asm: -------------------------------------------------------------------------------- 1 | describe "scroll/tiles init" 2 | 3 | test "does not clobber registers when no macro args given" 4 | zest.initRegisters 5 | 6 | utils.preserve 7 | ld a, 0 ; mapCols 8 | ld b, 0 ; mapRows 9 | ld d, 0 ; colOffset 10 | ld e, 0 ; rowOffset 11 | ld hl, 0 ; topLeftPointer 12 | scroll.tiles.init 13 | utils.restore 14 | 15 | expect.all.toBeUnclobberedExcept "a", "b", "d", "e", "h", "l" 16 | expect.a.toBe 0 17 | expect.b.toBe 0 18 | expect.de.toBe 0 19 | expect.hl.toBe 0 20 | 21 | 22 | test "does not clobber registers when macro args are given" 23 | zest.initRegisters 24 | 25 | utils.preserve 26 | scroll.tiles.init 0 64 64 0 0 27 | utils.restore 28 | 29 | expect.all.toBeUnclobbered 30 | -------------------------------------------------------------------------------- /tests/scroll/tiles/render.test.asm: -------------------------------------------------------------------------------- 1 | describe "scroll.tiles.render does not clobber registers when:" 2 | 3 | test "no row or col scroll needed" 4 | ; Setup 5 | scroll.tiles.init 0 64 64 0 0 6 | xor a 7 | scroll.tiles.adjustXPixels 8 | xor a 9 | scroll.tiles.adjustYPixels 10 | scroll.tiles.update 11 | 12 | zest.initRegisters 13 | 14 | utils.preserve 15 | scroll.tiles.render 16 | utils.restore 17 | 18 | expect.all.toBeUnclobbered 19 | 20 | test "row scroll needed" 21 | ; Setup 22 | scroll.tiles.init 0 64 64 0 0 23 | xor a 24 | scroll.tiles.adjustXPixels 25 | ld a, 8 26 | scroll.tiles.adjustYPixels 27 | scroll.tiles.update 28 | 29 | zest.initRegisters 30 | 31 | utils.preserve 32 | scroll.tiles.render 33 | utils.restore 34 | 35 | expect.all.toBeUnclobbered 36 | 37 | test "col scroll needed" 38 | ; Setup 39 | scroll.tiles.init 0 64 64 0 0 40 | ld a, 8 41 | scroll.tiles.adjustXPixels 42 | xor a 43 | scroll.tiles.adjustYPixels 44 | scroll.tiles.update 45 | 46 | zest.initRegisters 47 | 48 | utils.preserve 49 | scroll.tiles.render 50 | utils.restore 51 | 52 | expect.all.toBeUnclobbered 53 | -------------------------------------------------------------------------------- /tests/scroll/tiles/update.test.asm: -------------------------------------------------------------------------------- 1 | describe "scroll.tiles.update does not clobber registers when:" 2 | 3 | test "no row or col scroll needed" 4 | ; Setup 5 | scroll.tiles.init 0 64 64 0 0 6 | xor a 7 | scroll.tiles.adjustXPixels 8 | xor a 9 | scroll.tiles.adjustYPixels 10 | 11 | zest.initRegisters 12 | 13 | utils.preserve 14 | scroll.tiles.update 15 | utils.restore 16 | 17 | expect.all.toBeUnclobbered 18 | 19 | test "row scroll needed" 20 | ; Setup 21 | scroll.tiles.init 0 64 64 0 0 22 | xor a 23 | scroll.tiles.adjustXPixels 24 | ld a, 8 25 | scroll.tiles.adjustYPixels 26 | 27 | zest.initRegisters 28 | 29 | utils.preserve 30 | scroll.tiles.update 31 | utils.restore 32 | 33 | expect.all.toBeUnclobbered 34 | 35 | test "col scroll needed" 36 | ; Setup 37 | scroll.tiles.init 0 64 64 0 0 38 | ld a, 8 39 | scroll.tiles.adjustXPixels 40 | xor a 41 | scroll.tiles.adjustYPixels 42 | 43 | zest.initRegisters 44 | 45 | utils.preserve 46 | scroll.tiles.update 47 | utils.restore 48 | 49 | expect.all.toBeUnclobbered 50 | -------------------------------------------------------------------------------- /tests/smslib-zest.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Integration for use with Zest (https://github.com/lajohnston/zest). 3 | ;==== 4 | 5 | ; Disable the various handlers (boot, pause, interrupts), as Zest has its own 6 | .define init.DISABLE_HANDLER 7 | .define interrupts.DISABLE_HANDLER 8 | .define pause.DISABLE_HANDLER 9 | 10 | ; Disable mapper, as Zest uses its own 11 | .define mapper.ENABLED 0 12 | .define mapper.RAM_SLOT zest.RAM_SLOT 13 | 14 | ; Use Zest's mapper's RAM SLOT 15 | .define smslib.RAM_SLOT zest.RAM_SLOT 16 | 17 | ; Define a fake version of utils.port.read 18 | .ifndef utils.port 19 | .define utils.port 1 20 | 21 | .macro "utils.port.read" args portNumber 22 | .if portNumber == $dc 23 | zest.loadFakePortDC 24 | .elif portNumber == $dd 25 | zest.loadFakePortDD 26 | .endif 27 | .endm 28 | .endif 29 | 30 | ; Include the rest of the library 31 | .include "smslib.asm" 32 | 33 | ;==== 34 | ; Zest hooks 35 | ;==== 36 | .section "smslib-zest.preSuite" appendto zest.preSuite 37 | smslib-zest.preSuite: 38 | init.smslibModules 39 | .ends 40 | -------------------------------------------------------------------------------- /tests/sprites/add.test.asm: -------------------------------------------------------------------------------- 1 | describe "sprites.add" 2 | sprites.init 3 | 4 | test "should not clobber any registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | sprites.add 9 | utils.restore 10 | 11 | expect.all.toBeUnclobbered 12 | 13 | test "when in a batch should not clobber any registers apart from DE" 14 | zest.initRegisters 15 | 16 | utils.preserve 17 | sprites.startBatch 18 | sprites.add 19 | sprites.endBatch 20 | utils.restore 21 | 22 | expect.all.toBeUnclobberedExcept "de" 23 | -------------------------------------------------------------------------------- /tests/sprites/addGroup.test.asm: -------------------------------------------------------------------------------- 1 | describe "sprites.addGroup" 2 | sprites.init 3 | 4 | jr + 5 | testSpriteGroup: 6 | sprites.startGroup 7 | sprites.sprite 1, 0, 0 8 | sprites.sprite 2, 8, 0 9 | sprites.endGroup 10 | +: 11 | 12 | test "should not clobber any registers" 13 | sprites.reset 14 | 15 | zest.initRegisters 16 | 17 | utils.preserve 18 | ld hl, testSpriteGroup 19 | sprites.addGroup 20 | utils.restore 21 | 22 | expect.all.toBeUnclobberedExcept "hl" 23 | expect.hl.toBe testSpriteGroup 24 | 25 | test "when in a batch should not clobber any registers apart from DE" 26 | zest.initRegisters 27 | 28 | utils.preserve 29 | sprites.startBatch 30 | ld hl, testSpriteGroup 31 | sprites.addGroup 32 | sprites.endBatch 33 | utils.restore 34 | 35 | expect.all.toBeUnclobberedExcept "hl" "de" 36 | expect.hl.toBe testSpriteGroup 37 | -------------------------------------------------------------------------------- /tests/sprites/copyToVram.test.asm: -------------------------------------------------------------------------------- 1 | describe "sprites.copyToVram" 2 | sprites.init 3 | 4 | test "should not clobber any registers when there are no sprites" 5 | sprites.reset 6 | zest.initRegisters 7 | 8 | utils.preserve 9 | sprites.copyToVram 10 | utils.restore 11 | 12 | expect.all.toBeUnclobbered 13 | 14 | test "should not clobber any registers when there are sprites" 15 | sprites.reset 16 | sprites.add 17 | 18 | zest.initRegisters 19 | 20 | utils.preserve 21 | sprites.copyToVram 22 | utils.restore 23 | 24 | expect.all.toBeUnclobbered 25 | -------------------------------------------------------------------------------- /tests/sprites/init.test.asm: -------------------------------------------------------------------------------- 1 | describe "sprites.init" 2 | test "sets the nextIndex to $40" 3 | ld a, $ff 4 | ld (sprites.ram.buffer.nextIndex), a 5 | 6 | sprites.init 7 | 8 | ld a, (sprites.ram.buffer.nextIndex) 9 | expect.a.toBe $40 10 | 11 | test "does not clobber any registers" 12 | zest.initRegisters 13 | 14 | utils.preserve 15 | sprites.init 16 | utils.restore 17 | 18 | expect.all.toBeUnclobbered -------------------------------------------------------------------------------- /tests/testing.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | This directory contains the library's internal tests, which use the [Zest](https://github.com/lajohnston/zest) testing framework. The `smslib-zest.asm` file below may help you if you wish to write tests for code that utilises SMSLib. 4 | 5 | ## smslib-zest.asm 6 | 7 | The `tests/smslib-zest.asm` file imports the full smslib library with a set of options to aid with testing your code using the [Zest](https://github.com/lajohnston/zest) testing framework. The aim is to only stub out/disable the minimal amount of functionality needed to provide an accurate simulation. 8 | 9 | ```asm 10 | ; Import Zest 11 | .incdir "../zest" ; point to Zest directory 12 | .include "zest.asm" ; import Zest 13 | 14 | ; Import smslib-zest 15 | .incdir "../smslib" ; point to the root of the smslib directory 16 | .include "tests/smslib-zest.asm" ; import tests/smslib-zest.asm 17 | ``` 18 | 19 | The file performs the following: 20 | 21 | - Disables the smslib mapper and integrates the library with Zest's mapper 22 | - Disables the boot, interrupt and pause handlers, as Zest has its own 23 | - Creates a fake `utils.port.read` macro that uses Zest's input mocking 24 | - Uses Zest's `preSuite` hook to initialise the smslib variables before the start of the suite 25 | - Imports all the modules 26 | 27 | An alternative approach if you wish to test your code in complete isolation would be to not import smslib at all and instead create fake macros and mocks for the routines you use. This may simplify some tests as you would just assert that the `smslib` routine were called with the correct parameters and trust the library to handle the hardware layer. It would however require lots of manual setup, so is perhaps better suited if you are only using a few routines. 28 | 29 | ## Testing controller input 30 | 31 | You can utilise Zest's input mocking functionality to fake input values at the port level. SMSLib's `input` module itself isn't stubbed out so you can continue to use its API in your code. 32 | -------------------------------------------------------------------------------- /tests/tilemap/adjustXPixels.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.adjustXPixels" 2 | tilemap.reset 3 | 4 | test "does not clobber registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | ld a, 1 9 | tilemap.adjustXPixels 10 | utils.restore 11 | 12 | expect.all.toBeUnclobberedExcept "a" 13 | expect.a.toBe 1 14 | -------------------------------------------------------------------------------- /tests/tilemap/adjustYPixels.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.adjustYPixels" 2 | tilemap.reset 3 | 4 | test "does not clobber registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | ld a, 1 9 | tilemap.adjustYPixels 10 | utils.restore 11 | 12 | expect.all.toBeUnclobberedExcept "a" 13 | expect.a.toBe 1 14 | -------------------------------------------------------------------------------- /tests/tilemap/calculateScroll.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.calculateScroll" 2 | tilemap.reset 3 | 4 | test "does not clobber registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | tilemap.calculateScroll 9 | utils.restore 10 | 11 | expect.all.toBeUnclobbered 12 | -------------------------------------------------------------------------------- /tests/tilemap/ifColScroll.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.ifColScroll with 1 arg (else label)" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "jumps to the label if no scroll is needed" 5 | tilemap.reset 6 | 7 | zest.initRegisters 8 | 9 | tilemap.ifColScroll + 10 | zest.fail "ifColScroll was true" 11 | +: 12 | 13 | expect.all.toBeUnclobbered 14 | 15 | test "does not jump to the given label if a left scroll is needed" 16 | tilemap.reset 17 | ld a, -8 18 | tilemap.adjustXPixels 19 | tilemap.calculateScroll 20 | 21 | zest.initRegisters 22 | 23 | tilemap.ifColScroll + 24 | expect.all.toBeUnclobbered 25 | jr ++ ; pass 26 | +: 27 | 28 | zest.fail "ifColScroll was false" 29 | 30 | ++: 31 | 32 | test "does not jump to the given label if a right scroll is needed" 33 | tilemap.reset 34 | ld a, 8 35 | tilemap.adjustXPixels 36 | tilemap.calculateScroll 37 | 38 | zest.initRegisters 39 | 40 | tilemap.ifColScroll + 41 | expect.all.toBeUnclobbered 42 | jr ++ ; pass 43 | +: 44 | 45 | zest.fail "ifColScroll was false" 46 | 47 | ++: 48 | 49 | describe "tilemap.ifColScroll with left, right, else args" 50 | test "jumps to the left label when left col scroll needed" 51 | tilemap.reset 52 | ld a, -8 53 | tilemap.adjustXPixels 54 | tilemap.calculateScroll 55 | 56 | zest.initRegisters 57 | 58 | tilemap.ifColScroll, +, ++, +++ 59 | +: ; Left 60 | expect.all.toBeUnclobbered 61 | jp ++++ ; pass 62 | ++: 63 | zest.fail "Right called" 64 | +++: 65 | zest.fail "Else called" 66 | ++++: ; pass 67 | 68 | test "jumps to the right label when right scroll needed" 69 | tilemap.reset 70 | ld a, 8 71 | tilemap.adjustXPixels 72 | tilemap.calculateScroll 73 | 74 | zest.initRegisters 75 | 76 | tilemap.ifColScroll, +, ++, +++ 77 | +: 78 | zest.fail "Left called" 79 | ++: ; Right 80 | expect.all.toBeUnclobbered 81 | jp ++++ ; pass 82 | +++: 83 | zest.fail "Else called" 84 | ++++: ; pass 85 | 86 | test "jumps to else label when no col scroll needed" 87 | tilemap.reset 88 | zest.initRegisters 89 | 90 | tilemap.ifColScroll, +, ++, +++ 91 | +: 92 | zest.fail "Left called" 93 | ++: 94 | zest.fail "Right called" 95 | +++: 96 | 97 | expect.all.toBeUnclobbered 98 | 99 | .redefine utils.registers.AUTO_PRESERVE 0 100 | -------------------------------------------------------------------------------- /tests/tilemap/ifColScrollElseRet.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.ifColScrollElseRet" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "jumps to the left label when left col scroll needed" 5 | tilemap.reset 6 | ld a, -8 7 | tilemap.adjustXPixels 8 | tilemap.calculateScroll 9 | 10 | zest.initRegisters 11 | 12 | call + 13 | zest.fail "Unexpected return" 14 | 15 | +: 16 | 17 | tilemap.ifColScrollElseRet, ++, +++ 18 | ++: 19 | ; Left 20 | expect.all.toBeUnclobbered 21 | jp ++++ ; pass 22 | +++: 23 | zest.fail "Right called" 24 | ++++: ; pass 25 | 26 | test "jumps to the right label when right col scroll needed" 27 | tilemap.reset 28 | ld a, 8 29 | tilemap.adjustXPixels 30 | tilemap.calculateScroll 31 | 32 | zest.initRegisters 33 | 34 | call + 35 | zest.fail "Unexpected return" 36 | 37 | +: 38 | 39 | tilemap.ifColScrollElseRet, ++, +++ 40 | ++: 41 | ; Left 42 | zest.fail "Left called" 43 | +++: 44 | ; Right 45 | expect.all.toBeUnclobbered 46 | 47 | test "returns when no col scroll needed" 48 | tilemap.reset 49 | zest.initRegisters 50 | 51 | call + 52 | expect.all.toBeUnclobbered 53 | jp +++ ; pass 54 | 55 | +: 56 | tilemap.ifColScrollElseRet, +, ++ 57 | +: 58 | ; Left 59 | zest.fail "Left called" 60 | ++: 61 | ; Right 62 | zest.fail "Right called" 63 | +++: ; pass 64 | 65 | .redefine utils.registers.AUTO_PRESERVE 0 66 | -------------------------------------------------------------------------------- /tests/tilemap/ifRowScroll.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.ifRowScroll with 1 arg (else label)" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "jumps to the label if no scroll is needed" 5 | tilemap.reset 6 | 7 | zest.initRegisters 8 | 9 | tilemap.ifRowScroll + 10 | zest.fail "ifRowScroll was true" 11 | +: 12 | 13 | expect.all.toBeUnclobbered 14 | 15 | test "does not jump to the given label if an up row scroll is needed" 16 | tilemap.reset 17 | ld a, -8 18 | tilemap.adjustYPixels 19 | tilemap.calculateScroll 20 | 21 | zest.initRegisters 22 | 23 | tilemap.ifRowScroll + 24 | expect.all.toBeUnclobbered 25 | jr ++ ; pass 26 | +: 27 | 28 | zest.fail "ifRowScroll was false" 29 | 30 | ++: 31 | 32 | test "does not jump to the given label if a down scroll is needed" 33 | tilemap.reset 34 | ld a, 8 35 | tilemap.adjustYPixels 36 | tilemap.calculateScroll 37 | 38 | zest.initRegisters 39 | 40 | tilemap.ifRowScroll + 41 | expect.all.toBeUnclobbered 42 | jr ++ ; pass 43 | +: 44 | 45 | zest.fail "ifRowScroll was false" 46 | 47 | ++: 48 | 49 | describe "tilemap.ifRowScroll with up, down, else args" 50 | test "jumps to the up label when up col scroll needed" 51 | tilemap.reset 52 | ld a, -8 53 | tilemap.adjustYPixels 54 | tilemap.calculateScroll 55 | 56 | zest.initRegisters 57 | 58 | tilemap.ifRowScroll, +, ++, +++ 59 | +: ; up 60 | expect.all.toBeUnclobbered 61 | jp ++++ ; pass 62 | ++: 63 | zest.fail "Down called" 64 | +++: 65 | zest.fail "Else called" 66 | ++++: ; pass 67 | 68 | test "jumps to the down label when down scroll needed" 69 | tilemap.reset 70 | ld a, 8 71 | tilemap.adjustYPixels 72 | tilemap.calculateScroll 73 | 74 | zest.initRegisters 75 | 76 | tilemap.ifRowScroll, +, ++, +++ 77 | +: 78 | zest.fail "Up called" 79 | ++: ; down 80 | expect.all.toBeUnclobbered 81 | jp ++++ ; pass 82 | +++: 83 | zest.fail "Else called" 84 | ++++: ; pass 85 | 86 | test "jumps to else label when no col scroll needed" 87 | tilemap.reset 88 | zest.initRegisters 89 | 90 | tilemap.ifRowScroll, +, ++, +++ 91 | +: 92 | zest.fail "Up called" 93 | ++: 94 | zest.fail "Down called" 95 | +++: 96 | 97 | expect.all.toBeUnclobbered 98 | 99 | .redefine utils.registers.AUTO_PRESERVE 0 100 | -------------------------------------------------------------------------------- /tests/tilemap/ifRowScrollElseRet.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.ifRowScrollElseRet" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "jumps to the up label when up row scroll needed" 5 | tilemap.reset 6 | ld a, -8 7 | tilemap.adjustYPixels 8 | tilemap.calculateScroll 9 | 10 | zest.initRegisters 11 | 12 | call + 13 | zest.fail "Unexpected return" 14 | 15 | +: 16 | 17 | tilemap.ifRowScrollElseRet, ++, +++ 18 | ++: 19 | ; Up 20 | expect.all.toBeUnclobbered 21 | jp ++++ ; pass 22 | +++: 23 | zest.fail "Down called" 24 | ++++: 25 | 26 | test "jumps to the down label when down row scroll needed" 27 | tilemap.reset 28 | ld a, 8 29 | tilemap.adjustYPixels 30 | tilemap.calculateScroll 31 | 32 | zest.initRegisters 33 | 34 | call + 35 | zest.fail "Unexpected return" 36 | 37 | +: 38 | 39 | tilemap.ifRowScrollElseRet, ++, +++ 40 | ++: 41 | zest.fail "Up called" 42 | +++: 43 | ; Down 44 | expect.all.toBeUnclobbered 45 | 46 | test "returns when no row scroll needed" 47 | tilemap.reset 48 | zest.initRegisters 49 | 50 | call + 51 | expect.all.toBeUnclobbered 52 | jp +++ ; pass 53 | 54 | +: 55 | tilemap.ifRowScrollElseRet, +, ++ 56 | +: 57 | ; Up 58 | zest.fail "Up called" 59 | ++: 60 | ; Down 61 | zest.fail "Down called" 62 | +++: 63 | 64 | .redefine utils.registers.AUTO_PRESERVE 0 65 | -------------------------------------------------------------------------------- /tests/tilemap/loadHLWriteAddress.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.loadHLWriteAddress" 2 | test "returns HL but doesn't clobber other registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | tilemap.loadHLWriteAddress 7 | utils.restore 8 | 9 | expect.all.toBeUnclobberedExcept "hl" 10 | 11 | test "sets the high bits to %01 for the VDP write command" 12 | ld hl, 12 + (27 * 32) ; col 12, row 27 13 | tilemap.loadHLWriteAddress 14 | ld a, h 15 | and %11000000 16 | expect.a.toBe %01000000 17 | -------------------------------------------------------------------------------- /tests/tilemap/reset.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.reset" 2 | test "does not clobber registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | tilemap.reset 7 | utils.restore 8 | 9 | expect.all.toBeUnclobbered 10 | -------------------------------------------------------------------------------- /tests/tilemap/setColRow.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.setColRow" 2 | test "sets C to the VDP data port but does not clobber other registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | tilemap.setColRow 0 0 7 | utils.restore 8 | 9 | expect.all.toBeUnclobberedExcept "c" 10 | expect.c.toBe $be ; vdp data port 11 | 12 | test "allows the column to be set from 0-31" 13 | tilemap.setColRow 0 0 14 | tilemap.setColRow 31 0 15 | 16 | test "allows the row to be set from 0-27" 17 | tilemap.setColRow 0 0 18 | tilemap.setColRow 0 27 -------------------------------------------------------------------------------- /tests/tilemap/setIndex.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.setIndex" 2 | test "sets C to the VDP data port but does not clobber other registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | tilemap.setIndex 1 7 | utils.restore 8 | 9 | expect.all.toBeUnclobberedExcept "c" 10 | expect.c.toBe $be ; vdp data port 11 | 12 | test "allows the index to be set from 0 to 895" 13 | tilemap.setIndex 0 14 | tilemap.setIndex 895 -------------------------------------------------------------------------------- /tests/tilemap/stopDownRowScroll.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.stopDownRowScroll" 2 | tilemap.reset 3 | 4 | test "does not clobber registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | tilemap.stopDownRowScroll 9 | utils.restore 10 | 11 | expect.all.toBeUnclobbered 12 | -------------------------------------------------------------------------------- /tests/tilemap/stopLeftColScroll.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.stopLeftColScroll" 2 | tilemap.reset 3 | 4 | test "does not clobber registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | tilemap.stopLeftColScroll 9 | utils.restore 10 | 11 | expect.all.toBeUnclobbered 12 | -------------------------------------------------------------------------------- /tests/tilemap/stopRightColScroll.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.stopRightColScroll" 2 | tilemap.reset 3 | 4 | test "does not clobber registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | tilemap.stopRightColScroll 9 | utils.restore 10 | 11 | expect.all.toBeUnclobbered 12 | -------------------------------------------------------------------------------- /tests/tilemap/stopUpRowScroll.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.stopUpRowScroll" 2 | tilemap.reset 3 | 4 | test "does not clobber registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | tilemap.stopUpRowScroll 9 | utils.restore 10 | 11 | expect.all.toBeUnclobbered 12 | -------------------------------------------------------------------------------- /tests/tilemap/writeBytes.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.writeBytes" 2 | jr + 3 | _writeBytesData: 4 | .db 0 5 | .dw 1 6 | +: 7 | 8 | test "does not clobber registers" 9 | zest.initRegisters 10 | 11 | utils.preserve 12 | tilemap.setColRow 0 0 13 | tilemap.writeBytes _writeBytesData 2 0 14 | utils.restore 15 | 16 | expect.all.toBeUnclobberedExcept "c" 17 | expect.c.toBe $be 18 | -------------------------------------------------------------------------------- /tests/tilemap/writeBytesUntil.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.writeBytesUntil" 2 | jr + 3 | _writeBytesUntilData: 4 | .db 0 5 | .dw 1 6 | .db $ff 7 | +: 8 | 9 | test "does not clobber registers" 10 | zest.initRegisters 11 | 12 | utils.preserve 13 | tilemap.setColRow 0 0 14 | tilemap.writeBytesUntil $ff _writeBytesUntilData tilemap.FLIP_XY 15 | utils.restore 16 | 17 | expect.all.toBeUnclobberedExcept "c" 18 | expect.c.toBe $be ; vdp data port 19 | -------------------------------------------------------------------------------- /tests/tilemap/writeRow.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.writeRow" 2 | test "does not clobber registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | tilemap.setColRow 0, 0 7 | ld hl, 0 8 | tilemap.writeRow 9 | utils.restore 10 | 11 | expect.all.toBeUnclobberedExcept "c" "hl" 12 | expect.c.toBe $be ; vdp data port 13 | expect.hl.toBe 0 14 | -------------------------------------------------------------------------------- /tests/tilemap/writeRows.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.writeRows" 2 | test "does not clobber registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | tilemap.setColRow 0, 0 7 | ld d, 1 8 | ld e, 2 9 | ld hl, 0 10 | tilemap.writeRows 11 | utils.restore 12 | 13 | expect.all.toBeUnclobberedExcept "c" "de", "hl" 14 | expect.c.toBe $be ; vdp data port 15 | expect.d.toBe 1 16 | expect.e.toBe 2 17 | expect.hl.toBe 0 18 | -------------------------------------------------------------------------------- /tests/tilemap/writeScrollBuffers.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.writeScrollBuffers" 2 | tilemap.reset 3 | 4 | ; Dummy tile data 5 | jp + 6 | -: 7 | .dsb 31 * 2, $00 8 | +: 9 | 10 | ; Initialise column buffer 11 | tilemap.loadDEColBuffer 12 | tilemap.loadBCColBytes 13 | ld hl, - ; point to data 14 | ldir ; write data 15 | 16 | ; Initialise row buffer 17 | tilemap.loadDERowBuffer 18 | tilemap.loadBCRowBytes 19 | ld hl, - ; point to data 20 | ldir ; write data 21 | 22 | it "preserves the registers when no scroll needed" 23 | zest.initRegisters 24 | 25 | utils.preserve 26 | tilemap.writeScrollBuffers 27 | utils.restore 28 | 29 | expect.all.toBeUnclobbered 30 | 31 | it "preserves the registers when left scroll needed" 32 | tilemap.reset 33 | ld a, -8 34 | tilemap.adjustXPixels 35 | tilemap.calculateScroll 36 | 37 | zest.initRegisters 38 | 39 | utils.preserve 40 | tilemap.writeScrollBuffers 41 | utils.restore 42 | 43 | expect.all.toBeUnclobbered 44 | 45 | it "preserves the registers when right scroll needed" 46 | tilemap.reset 47 | ld a, 8 48 | tilemap.adjustXPixels 49 | tilemap.calculateScroll 50 | 51 | zest.initRegisters 52 | 53 | utils.preserve 54 | tilemap.writeScrollBuffers 55 | utils.restore 56 | 57 | expect.all.toBeUnclobbered 58 | 59 | it "preserves the registers when up scroll needed" 60 | tilemap.reset 61 | ld a, -8 62 | tilemap.adjustYPixels 63 | tilemap.calculateScroll 64 | 65 | zest.initRegisters 66 | 67 | utils.preserve 68 | tilemap.writeScrollBuffers 69 | utils.restore 70 | 71 | expect.all.toBeUnclobbered 72 | 73 | it "preserves the registers when down scroll needed" 74 | tilemap.reset 75 | ld a, 8 76 | tilemap.adjustYPixels 77 | tilemap.calculateScroll 78 | 79 | zest.initRegisters 80 | 81 | utils.preserve 82 | tilemap.writeScrollBuffers 83 | utils.restore 84 | 85 | expect.all.toBeUnclobbered 86 | -------------------------------------------------------------------------------- /tests/tilemap/writeScrollRegisters.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.writeScrollRegisters" 2 | tilemap.reset 3 | 4 | it "preserves the registers" 5 | zest.initRegisters 6 | 7 | utils.preserve 8 | tilemap.writeScrollRegisters 9 | utils.restore 10 | 11 | expect.all.toBeUnclobbered -------------------------------------------------------------------------------- /tests/tilemap/writeTile.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.writeTile" 2 | test "does not clobber registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | tilemap.setColRow 0, 0 7 | tilemap.writeTile 1 8 | tilemap.writeTile 2 tilemap.FLIP_X 9 | utils.restore 10 | 11 | expect.all.toBeUnclobberedExcept "c" 12 | expect.c.toBe $be ; vdp data port 13 | 14 | test "allows the pattern to be 0 to 511" 15 | tilemap.setColRow 0, 0 16 | tilemap.writeTile 0 17 | tilemap.writeTile 511 18 | -------------------------------------------------------------------------------- /tests/tilemap/writeTiles.test.asm: -------------------------------------------------------------------------------- 1 | describe "tilemap.writeTiles" 2 | test "does not clobber registers" 3 | zest.initRegisters 4 | 5 | utils.preserve 6 | tilemap.setColRow 0, 0 7 | ld hl, 0 8 | tilemap.writeTiles 2 9 | utils.restore 10 | 11 | expect.all.toBeUnclobberedExcept "c", "hl" 12 | expect.hl.toBe 0 13 | expect.c.toBe $be ; vdp data port 14 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jpc.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jpc" 2 | test "does not jump or restore when carry is reset" 3 | ; Set all registers to $FF 4 | ld a, $ff 5 | call suite.registers.setAllToA 6 | 7 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 8 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 9 | ; Set all registers to zero 10 | call suite.registers.setAllToZero 11 | call suite.registers.resetAllFlags ; reset C 12 | 13 | ; This shouldn't jump 14 | utils.clobbers.end.jpc suite.registers.unexpectedJump 15 | 16 | ; Expect everything to still be zero 17 | call suite.registers.expectAllToBeZero 18 | utils.clobbers.end 19 | utils.restore 20 | 21 | test "restores and jumps when carry is set" 22 | zest.initRegisters 23 | 24 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 25 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 26 | ; Zero registers but set C flag 27 | call suite.registers.setAllToZero 28 | call suite.registers.setAllFlags 29 | 30 | ; Expect this to jump 31 | utils.clobbers.end.jpc + 32 | zest.fail "Did not jump" 33 | utils.clobbers.end 34 | 35 | +: 36 | expect.all.toBeUnclobbered 37 | utils.restore 38 | 39 | describe "utils.clobbers.end.jpc with nothing to restore" 40 | test "does not jump or restore when carry is reset" 41 | zest.initRegisters 42 | 43 | utils.preserve "hl" 44 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 45 | utils.clobbers.withBranching "af" 46 | ; Set all registers to zero 47 | call suite.registers.setAllToZero 48 | call suite.registers.resetAllFlags 49 | 50 | ; This shouldn't jump 51 | utils.clobbers.end.jpc suite.registers.unexpectedJump 52 | 53 | ; Expect everything to still be zero 54 | call suite.registers.expectAllToBeZero 55 | utils.clobbers.end 56 | utils.restore 57 | 58 | test "jumps but doesn't restore when carry is set" 59 | zest.initRegisters 60 | 61 | utils.preserve "hl" 62 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 63 | utils.clobbers.withBranching "af" 64 | ; Set all registers to zero, but set Z flag 65 | call suite.registers.setAllToZero 66 | call suite.registers.setAllFlags 67 | 68 | ; Expect this to jump 69 | utils.clobbers.end.jpc + 70 | zest.fail "Did not jump" 71 | utils.clobbers.end 72 | 73 | +: 74 | ; Expect values to still be zero 75 | call suite.registers.expectAllToBeZero 76 | utils.restore 77 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jpm.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jpm" 2 | test "does not jump or restore when sign is reset" 3 | ; Set all registers to $FF 4 | ld a, $ff 5 | call suite.registers.setAllToA 6 | 7 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 8 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 9 | ; Set all registers to zero 10 | call suite.registers.setAllToZero 11 | call suite.registers.resetAllFlags ; reset sign 12 | 13 | ; This shouldn't jump 14 | utils.clobbers.end.jpm suite.registers.unexpectedJump 15 | 16 | ; Expect everything to still be zero 17 | call suite.registers.expectAllToBeZero 18 | utils.clobbers.end 19 | utils.restore 20 | 21 | test "restores and jumps when sign is set" 22 | zest.initRegisters 23 | 24 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 25 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 26 | ; Zero registers 27 | call suite.registers.setAllToZero 28 | call suite.registers.setAllFlags ; set sign flag 29 | 30 | ; Expect this to jump 31 | utils.clobbers.end.jpm + 32 | zest.fail "Did not jump" 33 | utils.clobbers.end 34 | 35 | +: 36 | expect.all.toBeUnclobbered 37 | expect.stack.size.toBe 0 38 | utils.restore 39 | 40 | describe "utils.clobbers.end.jpm with nothing to restore" 41 | test "does not jump or restore when sign is reset" 42 | zest.initRegisters 43 | 44 | utils.preserve "hl" 45 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 46 | utils.clobbers.withBranching "af" 47 | ; Set all registers to zero 48 | call suite.registers.setAllToZero 49 | call suite.registers.resetAllFlags ; set sign 50 | 51 | ; This shouldn't jump 52 | utils.clobbers.end.jpm suite.registers.unexpectedJump 53 | 54 | ; Expect everything to still be zero 55 | call suite.registers.expectAllToBeZero 56 | utils.clobbers.end 57 | 58 | expect.stack.size.toBe 0 59 | utils.restore 60 | 61 | test "jumps but doesn't restore when sign is set" 62 | zest.initRegisters 63 | 64 | utils.preserve "hl" 65 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 66 | utils.clobbers.withBranching "af" 67 | ; Set all registers to zero 68 | call suite.registers.setAllToZero 69 | call suite.registers.setAllFlags 70 | 71 | ; Expect this to jump 72 | utils.clobbers.end.jpm + 73 | zest.fail "Did not jump" 74 | utils.clobbers.end 75 | 76 | +: 77 | ; Expect values to still be zero 78 | call suite.registers.expectAllToBeZero 79 | expect.stack.size.toBe 0 80 | utils.restore 81 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jpnc.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jpnc" 2 | test "does not jump or restore when carry is set" 3 | ; Set all registers to $FF 4 | ld a, $ff 5 | call suite.registers.setAllToA 6 | 7 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 8 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 9 | ; Set all registers to zero 10 | call suite.registers.setAllToZero 11 | scf ; set carry 12 | 13 | ; This shouldn't jump 14 | utils.clobbers.end.jpnc suite.registers.unexpectedJump 15 | 16 | ; Expect everything to still be zero 17 | call suite.registers.expectAllToBeZero 18 | utils.clobbers.end 19 | utils.restore 20 | 21 | test "restores and jumps when carry is reset" 22 | zest.initRegisters 23 | 24 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 25 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 26 | ; Zero registers 27 | call suite.registers.setAllToZero 28 | call suite.registers.resetAllFlags ; reset carry flag 29 | 30 | ; Expect this to jump 31 | utils.clobbers.end.jpnc + 32 | zest.fail "Did not jump" 33 | utils.clobbers.end 34 | 35 | +: 36 | expect.all.toBeUnclobbered 37 | expect.stack.size.toBe 0 38 | utils.restore 39 | 40 | describe "utils.clobbers.end.jpnc with nothing to restore" 41 | test "does not jump or restore when carry is set" 42 | zest.initRegisters 43 | 44 | utils.preserve "hl" 45 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 46 | utils.clobbers.withBranching "af" 47 | ; Set all registers to zero 48 | call suite.registers.setAllToZero 49 | scf ; set carry 50 | 51 | ; This shouldn't jump 52 | utils.clobbers.end.jpnc suite.registers.unexpectedJump 53 | 54 | ; Expect everything to still be zero 55 | call suite.registers.expectAllToBeZero 56 | utils.clobbers.end 57 | 58 | expect.stack.size.toBe 0 59 | utils.restore 60 | 61 | test "jumps but doesn't restore when carry is reset" 62 | zest.initRegisters 63 | 64 | utils.preserve "hl" 65 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 66 | utils.clobbers.withBranching "af" 67 | ; Set all registers to zero, but set Z flag 68 | call suite.registers.setAllToZero 69 | call suite.registers.resetAllFlags 70 | 71 | ; Expect this to jump 72 | utils.clobbers.end.jpnc + 73 | zest.fail "Did not jump" 74 | utils.clobbers.end 75 | 76 | +: 77 | ; Expect values to still be zero 78 | call suite.registers.expectAllToBeZero 79 | expect.stack.size.toBe 0 80 | utils.restore 81 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jpnz.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jpnz" 2 | test "when Z - does not jump or restore" 3 | ; Set all registers to $FF 4 | ld a, $ff 5 | call suite.registers.setAllToA 6 | 7 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 8 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 9 | ; Set all registers to zero 10 | call suite.registers.setAllToZero 11 | call suite.registers.setAllFlags ; set Z 12 | 13 | ; This shouldn't jump 14 | utils.clobbers.end.jpnz suite.registers.unexpectedJump 15 | 16 | ; Expect everything to still be zero 17 | call suite.registers.expectAllToBeZero 18 | utils.clobbers.end 19 | utils.restore 20 | 21 | test "when NZ - restores and jumps" 22 | zest.initRegisters 23 | 24 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 25 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 26 | ; Zero registers but reset Z flag 27 | call suite.registers.setAllToZero 28 | call suite.registers.resetAllFlags 29 | 30 | ; Expect this to jump 31 | utils.clobbers.end.jpnz + 32 | zest.fail "Did not jump" 33 | utils.clobbers.end 34 | 35 | +: 36 | expect.all.toBeUnclobbered 37 | utils.restore 38 | 39 | describe "utils.clobbers.end.jpnz with nothing to restore" 40 | test "when Z - does not jump or restore" 41 | zest.initRegisters 42 | 43 | utils.preserve "hl" 44 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 45 | utils.clobbers.withBranching "af" 46 | ; Set all registers to zero 47 | call suite.registers.setAllToZero 48 | call suite.registers.setAllFlags 49 | 50 | ; This shouldn't jump 51 | utils.clobbers.end.jpnz suite.registers.unexpectedJump 52 | 53 | ; Expect everything to still be zero 54 | call suite.registers.expectAllToBeZero 55 | utils.clobbers.end 56 | utils.restore 57 | 58 | test "when NZ - jumps but doesn't restore" 59 | zest.initRegisters 60 | 61 | utils.preserve "hl" 62 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 63 | utils.clobbers.withBranching "af" 64 | ; Set all registers to zero, but set Z flag 65 | call suite.registers.setAllToZero 66 | call suite.registers.resetAllFlags 67 | 68 | ; Expect this to jump 69 | utils.clobbers.end.jpnz + 70 | zest.fail "Did not jump" 71 | utils.clobbers.end 72 | 73 | +: 74 | ; Expect values to still be zero 75 | call suite.registers.expectAllToBeZero 76 | utils.restore 77 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jpp.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jpp" 2 | test "does not jump or restore when sign is set" 3 | ; Set all registers to $FF 4 | ld a, $ff 5 | call suite.registers.setAllToA 6 | 7 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 8 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 9 | ; Set all registers to zero 10 | call suite.registers.setAllToZero 11 | call suite.registers.setAllFlags ; set sign 12 | 13 | ; This shouldn't jump 14 | utils.clobbers.end.jpp suite.registers.unexpectedJump 15 | 16 | ; Expect everything to still be zero 17 | call suite.registers.expectAllToBeZero 18 | utils.clobbers.end 19 | utils.restore 20 | 21 | test "restores and jumps when sign is reset" 22 | zest.initRegisters 23 | 24 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 25 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 26 | ; Zero registers 27 | call suite.registers.setAllToZero 28 | call suite.registers.resetAllFlags ; reset parity 29 | 30 | ; Expect this to jump 31 | utils.clobbers.end.jpp + 32 | zest.fail "Did not jump" 33 | utils.clobbers.end 34 | 35 | +: 36 | expect.all.toBeUnclobbered 37 | utils.restore 38 | 39 | describe "utils.clobbers.end.jpp with nothing to restore" 40 | test "does not jump or restore when sign is set" 41 | zest.initRegisters 42 | 43 | utils.preserve "hl" 44 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 45 | utils.clobbers.withBranching "af" 46 | ; Set all registers to zero 47 | call suite.registers.setAllToZero 48 | call suite.registers.setAllFlags 49 | 50 | ; This shouldn't jump 51 | utils.clobbers.end.jpp suite.registers.unexpectedJump 52 | 53 | ; Expect everything to still be zero 54 | call suite.registers.expectAllToBeZero 55 | utils.clobbers.end 56 | utils.restore 57 | 58 | test "jumps but doesn't restore when sign is reset" 59 | zest.initRegisters 60 | 61 | utils.preserve "hl" 62 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 63 | utils.clobbers.withBranching "af" 64 | ; Set all registers to zero 65 | call suite.registers.setAllToZero 66 | call suite.registers.resetAllFlags ; reset parity 67 | 68 | ; Expect this to jump 69 | utils.clobbers.end.jpp + 70 | zest.fail "Did not jump" 71 | utils.clobbers.end 72 | 73 | +: 74 | ; Expect values to still be zero 75 | call suite.registers.expectAllToBeZero 76 | utils.restore 77 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jppe.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jppe" 2 | test "does not jump or restore when parity/overflow is reset" 3 | ; Set all registers to $FF 4 | ld a, $ff 5 | call suite.registers.setAllToA 6 | 7 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 8 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 9 | ; Set all registers to zero 10 | call suite.registers.setAllToZero 11 | call suite.registers.resetAllFlags ; reset parity/overflow 12 | 13 | ; This shouldn't jump 14 | utils.clobbers.end.jppe suite.registers.unexpectedJump 15 | 16 | ; Expect everything to still be zero 17 | call suite.registers.expectAllToBeZero 18 | utils.clobbers.end 19 | utils.restore 20 | 21 | test "restores and jumps when parity/overflow is set" 22 | zest.initRegisters 23 | 24 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 25 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 26 | ; Zero registers 27 | call suite.registers.setAllToZero 28 | call suite.registers.setAllFlags ; set parity/overflow flag 29 | 30 | ; Expect this to jump 31 | utils.clobbers.end.jppe + 32 | zest.fail "Did not jump" 33 | utils.clobbers.end 34 | 35 | +: 36 | expect.all.toBeUnclobbered 37 | expect.stack.size.toBe 0 38 | utils.restore 39 | 40 | describe "utils.clobbers.end.jppe with nothing to restore" 41 | test "does not jump or restore when parity/overflow is reset" 42 | zest.initRegisters 43 | 44 | utils.preserve "hl" 45 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 46 | utils.clobbers.withBranching "af" 47 | ; Set all registers to zero 48 | call suite.registers.setAllToZero 49 | call suite.registers.resetAllFlags ; set parity/overflow 50 | 51 | ; This shouldn't jump 52 | utils.clobbers.end.jppe suite.registers.unexpectedJump 53 | 54 | ; Expect everything to still be zero 55 | call suite.registers.expectAllToBeZero 56 | utils.clobbers.end 57 | 58 | expect.stack.size.toBe 0 59 | utils.restore 60 | 61 | test "jumps but doesn't restore when parity/overflow is set" 62 | zest.initRegisters 63 | 64 | utils.preserve "hl" 65 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 66 | utils.clobbers.withBranching "af" 67 | ; Set all registers to zero, but set parity flag 68 | call suite.registers.setAllToZero 69 | call suite.registers.setAllFlags 70 | 71 | ; Expect this to jump 72 | utils.clobbers.end.jppe + 73 | zest.fail "Did not jump" 74 | utils.clobbers.end 75 | 76 | +: 77 | ; Expect values to still be zero 78 | call suite.registers.expectAllToBeZero 79 | expect.stack.size.toBe 0 80 | utils.restore 81 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jppo.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jppo" 2 | test "does not jump or restore when parity/overflow is set" 3 | ; Set all registers to $FF 4 | ld a, $ff 5 | call suite.registers.setAllToA 6 | 7 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 8 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 9 | ; Set all registers to zero 10 | call suite.registers.setAllToZero 11 | call suite.registers.setAllFlags ; set parity/overflow 12 | 13 | ; This shouldn't jump 14 | utils.clobbers.end.jppo suite.registers.unexpectedJump 15 | 16 | ; Expect everything to still be zero 17 | call suite.registers.expectAllToBeZero 18 | utils.clobbers.end 19 | utils.restore 20 | 21 | test "restores and jumps when parity/overflow is reset" 22 | zest.initRegisters 23 | 24 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 25 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 26 | ; Zero registers 27 | call suite.registers.setAllToZero 28 | call suite.registers.resetAllFlags ; reset parity 29 | 30 | ; Expect this to jump 31 | utils.clobbers.end.jppo + 32 | zest.fail "Did not jump" 33 | utils.clobbers.end 34 | 35 | +: 36 | expect.all.toBeUnclobbered 37 | utils.restore 38 | 39 | describe "utils.clobbers.end.jppo with nothing to restore" 40 | test "does not jump or restore when parity/overflow is set" 41 | zest.initRegisters 42 | 43 | utils.preserve "hl" 44 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 45 | utils.clobbers.withBranching "af" 46 | ; Set all registers to zero 47 | call suite.registers.setAllToZero 48 | call suite.registers.setAllFlags 49 | 50 | ; This shouldn't jump 51 | utils.clobbers.end.jppo suite.registers.unexpectedJump 52 | 53 | ; Expect everything to still be zero 54 | call suite.registers.expectAllToBeZero 55 | utils.clobbers.end 56 | utils.restore 57 | 58 | test "jumps but doesn't restore when parity/overflow is reset" 59 | zest.initRegisters 60 | 61 | utils.preserve "hl" 62 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 63 | utils.clobbers.withBranching "af" 64 | ; Set all registers to zero 65 | call suite.registers.setAllToZero 66 | call suite.registers.resetAllFlags ; reset parity 67 | 68 | ; Expect this to jump 69 | utils.clobbers.end.jppo + 70 | zest.fail "Did not jump" 71 | utils.clobbers.end 72 | 73 | +: 74 | ; Expect values to still be zero 75 | call suite.registers.expectAllToBeZero 76 | utils.restore 77 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jpz.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jpz" 2 | test "when NZ - does not jump or restore" 3 | ; Set all registers to $FF 4 | ld a, $ff 5 | call suite.registers.setAllToA 6 | 7 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 8 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 9 | ; Set all registers to zero 10 | call suite.registers.setAllToZero 11 | call suite.registers.resetAllFlags ; set NZ 12 | 13 | ; This shouldn't jump 14 | utils.clobbers.end.jpz suite.registers.unexpectedJump 15 | 16 | ; Expect everything to still be zero 17 | call suite.registers.expectAllToBeZero 18 | utils.clobbers.end 19 | utils.restore 20 | 21 | test "when Z - restores and jumps" 22 | zest.initRegisters 23 | 24 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 25 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 26 | ; Zero registers but set Z flag 27 | call suite.registers.setAllToZero 28 | call suite.registers.setAllFlags 29 | 30 | ; Expect this to jump 31 | utils.clobbers.end.jpz + 32 | zest.fail "Did not jump" 33 | utils.clobbers.end 34 | 35 | +: 36 | expect.all.toBeUnclobbered 37 | utils.restore 38 | 39 | describe "utils.clobbers.end.jpz with nothing to restore" 40 | test "when NZ - does not jump or restore" 41 | zest.initRegisters 42 | 43 | utils.preserve "hl" 44 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 45 | utils.clobbers.withBranching "af" 46 | ; Set all registers to zero 47 | call suite.registers.setAllToZero 48 | call suite.registers.resetAllFlags 49 | 50 | ; This shouldn't jump 51 | utils.clobbers.end.jpz suite.registers.unexpectedJump 52 | 53 | ; Expect everything to still be zero 54 | call suite.registers.expectAllToBeZero 55 | utils.clobbers.end 56 | utils.restore 57 | 58 | test "when Z - jumps but doesn't restore" 59 | zest.initRegisters 60 | 61 | utils.preserve "hl" 62 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 63 | utils.clobbers.withBranching "af" 64 | ; Set all registers to zero, but set Z flag 65 | call suite.registers.setAllToZero 66 | call suite.registers.setAllFlags 67 | 68 | ; Expect this to jump 69 | utils.clobbers.end.jpz + 70 | zest.fail "Did not jump" 71 | utils.clobbers.end 72 | 73 | +: 74 | ; Expect values to still be zero 75 | call suite.registers.expectAllToBeZero 76 | utils.restore 77 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jrc.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jrc" 2 | test "does not jump or restore when carry is reset" 3 | jr + 4 | -: 5 | zest.fail "Unexpected jump" 6 | +: 7 | 8 | ; Set all registers to $FF 9 | ld a, $ff 10 | call suite.registers.setAllToA 11 | 12 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 13 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 14 | ; Set all registers to zero 15 | call suite.registers.setAllToZero 16 | call suite.registers.resetAllFlags ; reset C 17 | 18 | ; This shouldn't jump 19 | utils.clobbers.end.jrc - 20 | 21 | ; Expect everything to still be zero 22 | call suite.registers.expectAllToBeZero 23 | utils.clobbers.end 24 | utils.restore 25 | 26 | test "restores and jumps when carry is set" 27 | zest.initRegisters 28 | 29 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 30 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 31 | ; Zero registers but set C flag 32 | call suite.registers.setAllToZero 33 | call suite.registers.setAllFlags 34 | 35 | ; Expect this to jump 36 | utils.clobbers.end.jrc + 37 | zest.fail "Did not jump" 38 | utils.clobbers.end 39 | 40 | +: 41 | expect.all.toBeUnclobbered 42 | utils.restore 43 | 44 | describe "utils.clobbers.end.jrc with nothing to restore" 45 | test "does not jump or restore when carry is reset" 46 | jr + 47 | -: 48 | zest.fail "Unexpected jump" 49 | +: 50 | 51 | zest.initRegisters 52 | 53 | utils.preserve "hl" 54 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 55 | utils.clobbers.withBranching "af" 56 | ; Set all registers to zero 57 | call suite.registers.setAllToZero 58 | call suite.registers.resetAllFlags 59 | 60 | ; This shouldn't jump 61 | utils.clobbers.end.jrc - 62 | 63 | ; Expect everything to still be zero 64 | call suite.registers.expectAllToBeZero 65 | utils.clobbers.end 66 | utils.restore 67 | 68 | test "jumps but doesn't restore when carry is set" 69 | zest.initRegisters 70 | 71 | utils.preserve "hl" 72 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 73 | utils.clobbers.withBranching "af" 74 | ; Set all registers to zero, but set Z flag 75 | call suite.registers.setAllToZero 76 | call suite.registers.setAllFlags 77 | 78 | ; Expect this to jump 79 | utils.clobbers.end.jrc + 80 | zest.fail "Did not jump" 81 | utils.clobbers.end 82 | 83 | +: 84 | ; Expect values to still be zero 85 | call suite.registers.expectAllToBeZero 86 | utils.restore 87 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jrnc.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jrnc" 2 | test "does not jump or restore when carry is set" 3 | jr + 4 | -: 5 | zest.fail "Unexpected jump" 6 | +: 7 | 8 | ; Set all registers to $FF 9 | ld a, $ff 10 | call suite.registers.setAllToA 11 | 12 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 13 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 14 | ; Set all registers to zero 15 | call suite.registers.setAllToZero 16 | call suite.registers.setAllFlags ; set carry 17 | 18 | ; This shouldn't jump 19 | utils.clobbers.end.jrnc - 20 | 21 | ; Expect everything to still be zero 22 | call suite.registers.expectAllToBeZero 23 | utils.clobbers.end 24 | utils.restore 25 | 26 | test "restores and jumps when carry is reset" 27 | zest.initRegisters 28 | 29 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 30 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 31 | ; Zero registers 32 | call suite.registers.setAllToZero 33 | call suite.registers.resetAllFlags ; reset carry flag 34 | 35 | ; Expect this to jump 36 | utils.clobbers.end.jrnc + 37 | zest.fail "Did not jump" 38 | utils.clobbers.end 39 | 40 | +: 41 | expect.all.toBeUnclobbered 42 | utils.restore 43 | 44 | describe "utils.clobbers.end.jrnc with nothing to restore" 45 | test "does not jump or restore when carry is set" 46 | jr + 47 | -: 48 | zest.fail "Unexpected jump" 49 | +: 50 | 51 | zest.initRegisters 52 | 53 | utils.preserve "hl" 54 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 55 | utils.clobbers.withBranching "af" 56 | ; Set all registers to zero 57 | call suite.registers.setAllToZero 58 | call suite.registers.setAllFlags 59 | 60 | ; This shouldn't jump 61 | utils.clobbers.end.jrnc - 62 | 63 | ; Expect everything to still be zero 64 | call suite.registers.expectAllToBeZero 65 | utils.clobbers.end 66 | utils.restore 67 | 68 | test "jumps but doesn't restore when carry is reset" 69 | zest.initRegisters 70 | 71 | utils.preserve "hl" 72 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 73 | utils.clobbers.withBranching "af" 74 | ; Set all registers to zero, but set Z flag 75 | call suite.registers.setAllToZero 76 | call suite.registers.resetAllFlags 77 | 78 | ; Expect this to jump 79 | utils.clobbers.end.jrnc + 80 | zest.fail "Did not jump" 81 | utils.clobbers.end 82 | 83 | +: 84 | ; Expect values to still be zero 85 | call suite.registers.expectAllToBeZero 86 | utils.restore 87 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jrnz.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jrnz" 2 | test "when Z - does not jump or restore" 3 | jr + 4 | -: 5 | zest.fail "Unexpected jump" 6 | +: 7 | 8 | ; Set all registers to $FF 9 | ld a, $ff 10 | call suite.registers.setAllToA 11 | 12 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 13 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 14 | ; Set all registers to zero 15 | call suite.registers.setAllToZero 16 | call suite.registers.setAllFlags ; set Z 17 | 18 | ; This shouldn't jump 19 | utils.clobbers.end.jrnz - 20 | 21 | ; Expect everything to still be zero 22 | call suite.registers.expectAllToBeZero 23 | utils.clobbers.end 24 | utils.restore 25 | 26 | test "when NZ - restores and jumps" 27 | zest.initRegisters 28 | 29 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 30 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 31 | ; Zero registers but reset Z flag 32 | call suite.registers.setAllToZero 33 | call suite.registers.resetAllFlags 34 | 35 | ; Expect this to jump 36 | utils.clobbers.end.jrnz + 37 | zest.fail "Did not jump" 38 | utils.clobbers.end 39 | 40 | +: 41 | expect.all.toBeUnclobbered 42 | utils.restore 43 | 44 | describe "utils.clobbers.end.jrnz with nothing to restore" 45 | test "when Z - does not jump or restore" 46 | jr + 47 | -: 48 | zest.fail "Unexpected jump" 49 | +: 50 | 51 | zest.initRegisters 52 | 53 | utils.preserve "hl" 54 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 55 | utils.clobbers.withBranching "af" 56 | ; Set all registers to zero 57 | call suite.registers.setAllToZero 58 | call suite.registers.setAllFlags 59 | 60 | ; This shouldn't jump 61 | utils.clobbers.end.jrnz - 62 | 63 | ; Expect everything to still be zero 64 | call suite.registers.expectAllToBeZero 65 | utils.clobbers.end 66 | utils.restore 67 | 68 | test "when NZ - jumps but doesn't restore" 69 | zest.initRegisters 70 | 71 | utils.preserve "hl" 72 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 73 | utils.clobbers.withBranching "af" 74 | ; Set all registers to zero, but set Z flag 75 | call suite.registers.setAllToZero 76 | call suite.registers.resetAllFlags 77 | 78 | ; Expect this to jump 79 | utils.clobbers.end.jrnz + 80 | zest.fail "Did not jump" 81 | utils.clobbers.end 82 | 83 | +: 84 | ; Expect values to still be zero 85 | call suite.registers.expectAllToBeZero 86 | utils.restore 87 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.jrz.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.jrz" 2 | test "when NZ - does not jump or restore" 3 | jr + 4 | -: 5 | zest.fail "Unexpected jump" 6 | +: 7 | 8 | ; Set all registers to $FF 9 | ld a, $ff 10 | call suite.registers.setAllToA 11 | 12 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 13 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 14 | ; Set all registers to zero 15 | call suite.registers.setAllToZero 16 | call suite.registers.resetAllFlags ; set NZ 17 | 18 | ; This shouldn't jump 19 | utils.clobbers.end.jrz - 20 | 21 | ; Expect everything to still be zero 22 | call suite.registers.expectAllToBeZero 23 | utils.clobbers.end 24 | utils.restore 25 | 26 | test "when Z - restores and jumps" 27 | zest.initRegisters 28 | 29 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 30 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 31 | ; Zero registers but set Z flag 32 | call suite.registers.setAllToZero 33 | call suite.registers.setAllFlags 34 | 35 | ; Expect this to jump 36 | utils.clobbers.end.jrz + 37 | zest.fail "Did not jump" 38 | utils.clobbers.end 39 | 40 | +: 41 | expect.all.toBeUnclobbered 42 | utils.restore 43 | 44 | describe "utils.clobbers.end.jrz with nothing to restore" 45 | test "when NZ - does not jump or restore" 46 | jr + 47 | -: 48 | zest.fail "Unexpected jump" 49 | +: 50 | 51 | zest.initRegisters 52 | 53 | utils.preserve "hl" 54 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 55 | utils.clobbers.withBranching "af" 56 | ; Set all registers to zero 57 | call suite.registers.setAllToZero 58 | call suite.registers.resetAllFlags 59 | 60 | ; This shouldn't jump 61 | utils.clobbers.end.jrz - 62 | 63 | ; Expect everything to still be zero 64 | call suite.registers.expectAllToBeZero 65 | utils.clobbers.end 66 | utils.restore 67 | 68 | test "when Z - jumps but doesn't restore" 69 | zest.initRegisters 70 | 71 | utils.preserve "hl" 72 | ; Doesn't clobber scope doesn't affect HL - nothing to preserve 73 | utils.clobbers.withBranching "af" 74 | ; Set all registers to zero, but set Z flag 75 | call suite.registers.setAllToZero 76 | call suite.registers.setAllFlags 77 | 78 | ; Expect this to jump 79 | utils.clobbers.end.jrz + 80 | zest.fail "Did not jump" 81 | utils.clobbers.end 82 | 83 | +: 84 | ; Expect values to still be zero 85 | call suite.registers.expectAllToBeZero 86 | utils.restore 87 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.retc.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.retc" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "does not restore or return when carry is reset" 5 | zest.initRegisters 6 | call + 7 | zest.fail "Routine returned" 8 | 9 | +: 10 | utils.clobbers.withBranching utils.registers.ALL 11 | ; Set registers to 0 12 | call suite.registers.setAllToZero 13 | or a ; reset carry 14 | 15 | ; Call macro 16 | utils.clobbers.end.retc 17 | 18 | ; Expect everything to still be zero 19 | call suite.registers.expectAllToBeZero 20 | utils.clobbers.end 21 | 22 | test "restores and returns when carry is set" 23 | zest.initRegisters 24 | 25 | utils.preserve utils.registers.ALL 26 | jp + 27 | -: 28 | utils.clobbers.withBranching utils.registers.ALL 29 | ; Set registers to 0; Reset flags 30 | call suite.registers.setAllToZero 31 | scf ; set carry 32 | 33 | ; Call macro 34 | utils.clobbers.end.retc 35 | zest.fail "Routine did not return" 36 | utils.clobbers.end 37 | +: 38 | 39 | call - 40 | expect.all.toBeUnclobbered 41 | utils.restore 42 | 43 | describe "utils.clobbers.end.retc with nothing to restore" 44 | .redefine utils.registers.AUTO_PRESERVE 0 45 | 46 | test "does not restore or return when carry is reset" 47 | zest.initRegisters 48 | call + 49 | zest.fail "Routine returned" 50 | 51 | +: 52 | utils.clobbers.withBranching utils.registers.ALL 53 | ; Set registers to 0 54 | call suite.registers.setAllToZero 55 | or a ; reset carry 56 | 57 | ; Call macro 58 | utils.clobbers.end.retc 59 | 60 | ; Expect everything to still be zero 61 | call suite.registers.expectAllToBeZero 62 | utils.clobbers.end 63 | 64 | test "restores and returns when carry is set" 65 | zest.initRegisters 66 | 67 | utils.preserve utils.registers.ALL 68 | jp + 69 | -: 70 | utils.clobbers.withBranching utils.registers.ALL 71 | ; Set registers to 0; Reset flags 72 | call suite.registers.setAllToZero 73 | scf ; set carry 74 | 75 | ; Call macro 76 | utils.clobbers.end.retc 77 | zest.fail "Routine did not return" 78 | utils.clobbers.end 79 | +: 80 | 81 | call - 82 | expect.all.toBeUnclobbered 83 | utils.restore 84 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.retm.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.retm" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "does not restore or return when sign is reset" 5 | zest.initRegisters 6 | call + 7 | zest.fail "Routine returned" 8 | 9 | +: 10 | utils.clobbers.withBranching utils.registers.ALL 11 | ; Set registers to 0 (including flags) 12 | call suite.registers.setAllToZero 13 | 14 | ; Call macro 15 | utils.clobbers.end.retm 16 | 17 | ; Expect everything to still be zero 18 | call suite.registers.expectAllToBeZero 19 | utils.clobbers.end 20 | 21 | test "restores and returns when sign is set" 22 | zest.initRegisters 23 | 24 | utils.preserve utils.registers.ALL 25 | jp + 26 | -: 27 | utils.clobbers.withBranching utils.registers.ALL 28 | ; Set registers to 0; Reset flags 29 | call suite.registers.setAllToZero 30 | dec a 31 | or a ; set sign 32 | 33 | ; Call macro 34 | utils.clobbers.end.retm 35 | zest.fail "Routine did not return" 36 | utils.clobbers.end 37 | +: 38 | 39 | call - 40 | expect.all.toBeUnclobbered 41 | utils.restore 42 | 43 | describe "utils.clobbers.end.retm with nothing to restore" 44 | .redefine utils.registers.AUTO_PRESERVE 0 45 | 46 | test "does not restore or return when sign is reset" 47 | zest.initRegisters 48 | call + 49 | zest.fail "Routine returned" 50 | 51 | +: 52 | utils.clobbers.withBranching utils.registers.ALL 53 | ; Set registers to 0 including flags 54 | call suite.registers.setAllToZero 55 | 56 | ; Call macro 57 | utils.clobbers.end.retm 58 | 59 | ; Expect everything to still be zero 60 | call suite.registers.expectAllToBeZero 61 | utils.clobbers.end 62 | 63 | test "restores and returns when sign is set" 64 | zest.initRegisters 65 | 66 | utils.preserve utils.registers.ALL 67 | jp + 68 | -: 69 | utils.clobbers.withBranching utils.registers.ALL 70 | ; Set registers to 0; Reset flags 71 | call suite.registers.setAllToZero 72 | dec a 73 | or a ; set sign 74 | 75 | ; Call macro 76 | utils.clobbers.end.retm 77 | zest.fail "Routine did not return" 78 | utils.clobbers.end 79 | +: 80 | 81 | call - 82 | expect.all.toBeUnclobbered 83 | utils.restore 84 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.retnc.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.retnc" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "does not restore or return when carry is set" 5 | zest.initRegisters 6 | call + 7 | zest.fail "Routine returned" 8 | 9 | +: 10 | utils.clobbers.withBranching utils.registers.ALL 11 | ; Set registers to 0 12 | call suite.registers.setAllToZero 13 | scf ; set carry 14 | 15 | ; Call macro 16 | utils.clobbers.end.retnc 17 | 18 | ; Expect everything to still be zero 19 | call suite.registers.expectAllToBeZero 20 | utils.clobbers.end 21 | 22 | test "restores and returns when carry is reset" 23 | zest.initRegisters 24 | 25 | utils.preserve utils.registers.ALL 26 | jp + 27 | -: 28 | utils.clobbers.withBranching utils.registers.ALL 29 | ; Set registers to 0; Reset flags 30 | call suite.registers.setAllToZero 31 | or a ; reset carry 32 | 33 | ; Call macro 34 | utils.clobbers.end.retnc 35 | zest.fail "Routine did not return" 36 | utils.clobbers.end 37 | +: 38 | 39 | call - 40 | expect.all.toBeUnclobbered 41 | utils.restore 42 | 43 | describe "utils.clobbers.end.retnc with nothing to restore" 44 | .redefine utils.registers.AUTO_PRESERVE 0 45 | 46 | test "does not restore or return when carry is set" 47 | zest.initRegisters 48 | call + 49 | zest.fail "Routine returned" 50 | 51 | +: 52 | utils.clobbers.withBranching utils.registers.ALL 53 | ; Set registers to 0 54 | call suite.registers.setAllToZero 55 | scf ; set carry 56 | 57 | ; Call macro 58 | utils.clobbers.end.retnc 59 | 60 | ; Expect everything to still be zero 61 | call suite.registers.expectAllToBeZero 62 | utils.clobbers.end 63 | 64 | test "restores and returns when carry is reset" 65 | zest.initRegisters 66 | 67 | utils.preserve utils.registers.ALL 68 | jp + 69 | -: 70 | utils.clobbers.withBranching utils.registers.ALL 71 | ; Set registers to 0; Reset flags 72 | call suite.registers.setAllToZero 73 | or a ; reset carry 74 | 75 | ; Call macro 76 | utils.clobbers.end.retnc 77 | zest.fail "Routine did not return" 78 | utils.clobbers.end 79 | +: 80 | 81 | call - 82 | expect.all.toBeUnclobbered 83 | utils.restore 84 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.retnz.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.retnz" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "does not restore or return when zero is set" 5 | zest.initRegisters 6 | call + 7 | zest.fail "Routine returned" 8 | 9 | +: 10 | utils.clobbers.withBranching utils.registers.ALL 11 | ; Set registers to 0 12 | call suite.registers.setAllToZero 13 | or a ; set zero 14 | 15 | ; Call macro 16 | utils.clobbers.end.retnz 17 | 18 | ; Expect everything to still be zero 19 | call suite.registers.expectAllToBeZero 20 | utils.clobbers.end 21 | 22 | test "restores and returns when zero is reset" 23 | zest.initRegisters 24 | 25 | utils.preserve utils.registers.ALL 26 | jp + 27 | -: 28 | utils.clobbers.withBranching utils.registers.ALL 29 | ; Set registers to 0; Reset flags 30 | call suite.registers.setAllToZero 31 | inc a 32 | or a ; reset zero 33 | 34 | ; Call macro 35 | utils.clobbers.end.retnz 36 | zest.fail "Routine did not return" 37 | utils.clobbers.end 38 | +: 39 | 40 | call - 41 | expect.all.toBeUnclobbered 42 | utils.restore 43 | 44 | describe "utils.clobbers.end.retnz with nothing to restore" 45 | .redefine utils.registers.AUTO_PRESERVE 0 46 | 47 | test "does not restore or return when zero is set" 48 | zest.initRegisters 49 | call + 50 | zest.fail "Routine returned" 51 | 52 | +: 53 | utils.clobbers.withBranching utils.registers.ALL 54 | ; Set registers to 0 55 | call suite.registers.setAllToZero 56 | or a ; set zero 57 | 58 | ; Call macro 59 | utils.clobbers.end.retnz 60 | 61 | ; Expect everything to still be zero 62 | call suite.registers.expectAllToBeZero 63 | utils.clobbers.end 64 | 65 | test "restores and returns when zero is reset" 66 | zest.initRegisters 67 | 68 | utils.preserve utils.registers.ALL 69 | jp + 70 | -: 71 | utils.clobbers.withBranching utils.registers.ALL 72 | ; Set registers to 0; Reset flags 73 | call suite.registers.setAllToZero 74 | inc a 75 | or a ; reset zero 76 | 77 | ; Call macro 78 | utils.clobbers.end.retnz 79 | zest.fail "Routine did not return" 80 | utils.clobbers.end 81 | +: 82 | 83 | call - 84 | expect.all.toBeUnclobbered 85 | utils.restore 86 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.retp.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.retp" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "does not restore or return when sign is set" 5 | zest.initRegisters 6 | call + 7 | zest.fail "Routine returned" 8 | 9 | +: 10 | utils.clobbers.withBranching utils.registers.ALL 11 | ; Set registers to 0 12 | call suite.registers.setAllToZero 13 | dec a 14 | or a ; set sign 15 | ld a, 0 16 | 17 | ; Call macro 18 | utils.clobbers.end.retp 19 | 20 | ; Expect everything to still be zero 21 | call suite.registers.expectAllToBeZero 22 | utils.clobbers.end 23 | 24 | test "restores and returns when sign is reset" 25 | zest.initRegisters 26 | 27 | utils.preserve utils.registers.ALL 28 | jp + 29 | -: 30 | utils.clobbers.withBranching utils.registers.ALL 31 | ; Set registers to 0; Reset flags 32 | call suite.registers.setAllToZero 33 | 34 | ; Call macro 35 | utils.clobbers.end.retp 36 | zest.fail "Routine did not return" 37 | utils.clobbers.end 38 | +: 39 | 40 | call - 41 | expect.all.toBeUnclobbered 42 | utils.restore 43 | 44 | describe "utils.clobbers.end.retp with nothing to restore" 45 | .redefine utils.registers.AUTO_PRESERVE 0 46 | 47 | test "does not restore or return when sign is set" 48 | zest.initRegisters 49 | call + 50 | zest.fail "Routine returned" 51 | 52 | +: 53 | utils.clobbers.withBranching utils.registers.ALL 54 | ; Set registers to 0 55 | call suite.registers.setAllToZero 56 | dec a 57 | or a ; set sign 58 | ld a, 0 59 | 60 | ; Call macro 61 | utils.clobbers.end.retp 62 | 63 | ; Expect everything to still be zero 64 | call suite.registers.expectAllToBeZero 65 | utils.clobbers.end 66 | 67 | test "restores and returns when sign is reset" 68 | zest.initRegisters 69 | 70 | utils.preserve utils.registers.ALL 71 | jp + 72 | -: 73 | utils.clobbers.withBranching utils.registers.ALL 74 | ; Set registers to 0; Reset flags 75 | call suite.registers.setAllToZero 76 | 77 | ; Call macro 78 | utils.clobbers.end.retp 79 | zest.fail "Routine did not return" 80 | utils.clobbers.end 81 | +: 82 | 83 | call - 84 | expect.all.toBeUnclobbered 85 | utils.restore 86 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.retpe.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.retpe" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "does not restore or return when parity/overflow is reset" 5 | zest.initRegisters 6 | call + 7 | zest.fail "Routine returned" 8 | 9 | +: 10 | utils.clobbers.withBranching utils.registers.ALL 11 | ; Set registers to 0 (inc flags) 12 | call suite.registers.setAllToZero 13 | 14 | ; Call macro 15 | utils.clobbers.end.retpe 16 | 17 | ; Expect everything to still be zero 18 | call suite.registers.expectAllToBeZero 19 | utils.clobbers.end 20 | 21 | test "restores and returns when parity/overflow is set" 22 | zest.initRegisters 23 | 24 | utils.preserve utils.registers.ALL 25 | jp + 26 | -: 27 | utils.clobbers.withBranching utils.registers.ALL 28 | ; Set registers to 0; Reset flags 29 | call suite.registers.setAllToZero 30 | or a ; set parity/overflow 31 | 32 | ; Call macro 33 | utils.clobbers.end.retpe 34 | zest.fail "Routine did not return" 35 | utils.clobbers.end 36 | +: 37 | 38 | call - 39 | expect.all.toBeUnclobbered 40 | utils.restore 41 | 42 | describe "utils.clobbers.end.retpe with nothing to restore" 43 | .redefine utils.registers.AUTO_PRESERVE 0 44 | 45 | test "does not restore or return when parity/overflow is reset" 46 | zest.initRegisters 47 | call + 48 | zest.fail "Routine returned" 49 | 50 | +: 51 | utils.clobbers.withBranching utils.registers.ALL 52 | ; Set registers to 0 inc flags 53 | call suite.registers.setAllToZero 54 | 55 | ; Call macro 56 | utils.clobbers.end.retpe 57 | 58 | ; Expect everything to still be zero 59 | call suite.registers.expectAllToBeZero 60 | utils.clobbers.end 61 | 62 | test "restores and returns when parity/overflow is set" 63 | zest.initRegisters 64 | 65 | utils.preserve utils.registers.ALL 66 | jp + 67 | -: 68 | utils.clobbers.withBranching utils.registers.ALL 69 | ; Set registers to 0; Reset flags 70 | call suite.registers.setAllToZero 71 | or a ; set parity/overflow 72 | 73 | ; Call macro 74 | utils.clobbers.end.retpe 75 | zest.fail "Routine did not return" 76 | utils.clobbers.end 77 | +: 78 | 79 | call - 80 | expect.all.toBeUnclobbered 81 | utils.restore 82 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.retpo.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.retpo" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "does not restore or return when zero is set" 5 | zest.initRegisters 6 | call + 7 | zest.fail "Routine returned" 8 | 9 | +: 10 | utils.clobbers.withBranching utils.registers.ALL 11 | ; Set registers to 0 12 | call suite.registers.setAllToZero 13 | or a ; set zero 14 | 15 | ; Call macro 16 | utils.clobbers.end.retpo 17 | 18 | ; Expect everything to still be zero 19 | call suite.registers.expectAllToBeZero 20 | utils.clobbers.end 21 | 22 | test "restores and returns when zero is reset" 23 | zest.initRegisters 24 | 25 | utils.preserve utils.registers.ALL 26 | jp + 27 | -: 28 | utils.clobbers.withBranching utils.registers.ALL 29 | ; Set registers to 0; Reset flags 30 | call suite.registers.setAllToZero 31 | inc a 32 | or a ; reset zero 33 | 34 | ; Call macro 35 | utils.clobbers.end.retpo 36 | zest.fail "Routine did not return" 37 | utils.clobbers.end 38 | +: 39 | 40 | call - 41 | expect.all.toBeUnclobbered 42 | utils.restore 43 | 44 | describe "utils.clobbers.end.retpo with nothing to restore" 45 | .redefine utils.registers.AUTO_PRESERVE 0 46 | 47 | test "does not restore or return when zero is set" 48 | zest.initRegisters 49 | call + 50 | zest.fail "Routine returned" 51 | 52 | +: 53 | utils.clobbers.withBranching utils.registers.ALL 54 | ; Set registers to 0 55 | call suite.registers.setAllToZero 56 | or a ; set zero 57 | 58 | ; Call macro 59 | utils.clobbers.end.retpo 60 | 61 | ; Expect everything to still be zero 62 | call suite.registers.expectAllToBeZero 63 | utils.clobbers.end 64 | 65 | test "restores and returns when zero is reset" 66 | zest.initRegisters 67 | 68 | utils.preserve utils.registers.ALL 69 | jp + 70 | -: 71 | utils.clobbers.withBranching utils.registers.ALL 72 | ; Set registers to 0; Reset flags 73 | call suite.registers.setAllToZero 74 | inc a 75 | or a ; reset zero 76 | 77 | ; Call macro 78 | utils.clobbers.end.retpo 79 | zest.fail "Routine did not return" 80 | utils.clobbers.end 81 | +: 82 | 83 | call - 84 | expect.all.toBeUnclobbered 85 | utils.restore 86 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.end.retz.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.end.retc" 2 | .redefine utils.registers.AUTO_PRESERVE 1 3 | 4 | test "does not restore or return when carry is reset" 5 | zest.initRegisters 6 | call + 7 | zest.fail "Routine returned" 8 | 9 | +: 10 | utils.clobbers.withBranching utils.registers.ALL 11 | ; Set registers to 0 12 | call suite.registers.setAllToZero 13 | or a ; reset carry 14 | 15 | ; Call macro 16 | utils.clobbers.end.retc 17 | 18 | ; Expect everything to still be zero 19 | call suite.registers.expectAllToBeZero 20 | utils.clobbers.end 21 | 22 | test "restores and returns when carry is set" 23 | zest.initRegisters 24 | 25 | utils.preserve utils.registers.ALL 26 | jp + 27 | -: 28 | utils.clobbers.withBranching utils.registers.ALL 29 | ; Set registers to 0; Reset flags 30 | call suite.registers.setAllToZero 31 | scf ; set carry 32 | 33 | ; Call macro 34 | utils.clobbers.end.retc 35 | zest.fail "Routine did not return" 36 | utils.clobbers.end 37 | +: 38 | 39 | call - 40 | expect.all.toBeUnclobbered 41 | utils.restore 42 | 43 | describe "utils.clobbers.end.retc with nothing to restore" 44 | .redefine utils.registers.AUTO_PRESERVE 0 45 | 46 | test "does not restore or return when carry is reset" 47 | zest.initRegisters 48 | call + 49 | zest.fail "Routine returned" 50 | 51 | +: 52 | utils.clobbers.withBranching utils.registers.ALL 53 | ; Set registers to 0 54 | call suite.registers.setAllToZero 55 | or a ; reset carry 56 | 57 | ; Call macro 58 | utils.clobbers.end.retc 59 | 60 | ; Expect everything to still be zero 61 | call suite.registers.expectAllToBeZero 62 | utils.clobbers.end 63 | 64 | test "restores and returns when carry is set" 65 | zest.initRegisters 66 | 67 | utils.preserve utils.registers.ALL 68 | jp + 69 | -: 70 | utils.clobbers.withBranching utils.registers.ALL 71 | ; Set registers to 0; Reset flags 72 | call suite.registers.setAllToZero 73 | scf ; set carry 74 | 75 | ; Call macro 76 | utils.clobbers.end.retc 77 | zest.fail "Routine did not return" 78 | utils.clobbers.end 79 | +: 80 | 81 | call - 82 | expect.all.toBeUnclobbered 83 | utils.restore 84 | -------------------------------------------------------------------------------- /tests/utils/clobbers/clobbers.endBranch.test.asm: -------------------------------------------------------------------------------- 1 | describe "utils.clobbers.endBranch" 2 | test "restores protected registers that are being clobbered" 3 | zest.initRegisters 4 | 5 | utils.preserve "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 6 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 7 | call suite.registers.clobberAll 8 | 9 | utils.clobbers.endBranch 10 | 11 | expect.all.toBeUnclobbered 12 | 13 | jp + 14 | utils.clobbers.end 15 | 16 | +: 17 | utils.restore 18 | 19 | test "only restores the protected registers" 20 | ld bc, $bc01 21 | 22 | utils.preserve "bc" 23 | utils.clobbers.withBranching "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'" "hl'" 24 | ; Zero all registers 25 | call suite.registers.setAllToZero 26 | 27 | utils.clobbers.endBranch 28 | 29 | ; Expect BC to be restored 30 | expect.bc.toBe $bc01 31 | 32 | ; Expect the rest to be 0 33 | expect.a.toBe 0 34 | expect.de.toBe 0 35 | expect.hl.toBe 0 36 | expect.ix.toBe 0 37 | expect.iy.toBe 0 38 | expect.i.toBe 0 39 | 40 | ex af, af' 41 | exx 42 | 43 | expect.a.toBe 0 44 | expect.bc.toBe 0 45 | expect.de.toBe 0 46 | expect.hl.toBe 0 47 | 48 | jp + 49 | utils.clobbers.end 50 | 51 | +: 52 | utils.restore 53 | -------------------------------------------------------------------------------- /tests/utils/clobbers/sequentialClobberScopes.test.asm: -------------------------------------------------------------------------------- 1 | describe "register preservation: sequential (unnested) clobber scopes" 2 | test "the second scope shouldn't preserve the same registers" 3 | ld bc, $bc00 4 | 5 | ; Preserve scope 6 | utils.preserve "bc" 7 | ; First clobber scope clobbers BC 8 | utils.clobbers "bc" 9 | expect.stack.size.toBe 1 10 | expect.stack.toContain $bc00 11 | ld bc, $bc01 12 | utils.clobbers.end 13 | 14 | ; Second clobber scope also clobbers BC, but shouldn't push to stack again 15 | utils.clobbers "bc" 16 | expect.stack.size.toBe 1 "Expected stack size to still be 1" 17 | expect.stack.toContain $bc00 0 "Expected stack to still contain original BC value" 18 | ld bc, $bc02 19 | utils.clobbers.end 20 | utils.restore 21 | 22 | expect.bc.toBe $bc00 23 | 24 | test "the second scope should preserve the registers the first hasn't" 25 | ld bc, $bc00 26 | ld de, $de00 27 | 28 | ; Preserve scope preserves BC and DE 29 | utils.preserve "bc" "de" 30 | ; First clobber scope clobbers DE 31 | utils.clobbers "de" 32 | expect.stack.toContain $de00 33 | ld de, $de01 34 | utils.clobbers.end 35 | 36 | ; Second clobber scope clobbers BC 37 | utils.clobbers "bc" 38 | expect.stack.toContain $bc00 39 | ld bc, $bc02 40 | utils.clobbers.end 41 | utils.restore 42 | 43 | expect.bc.toBe $bc00 44 | expect.de.toBe $de00 45 | 46 | test "restores the correct registers (random order)" 47 | zest.initRegisters 48 | 49 | utils.preserve "af", "bc", "de", "hl", "ix", "iy", "i", "af'", "bc'", "de'", "hl'" 50 | utils.clobbers "hl" 51 | ld h, a 52 | ld l, a 53 | utils.clobbers.end 54 | 55 | utils.clobbers "ix", "bc" 56 | ld b, a 57 | ld c, a 58 | ld ixh, a 59 | ld ixl, a 60 | utils.clobbers.end 61 | utils.restore 62 | 63 | expect.all.toBeUnclobbered -------------------------------------------------------------------------------- /tests/utils/registers/_helpers.asm: -------------------------------------------------------------------------------- 1 | .section "suite.registers helpers" 2 | suite.registers.setAllToZero: 3 | xor a 4 | suite.registers.setAllToA: 5 | ld b, a 6 | ld c, a 7 | ld d, a 8 | ld e, a 9 | ld h, a 10 | ld l, a 11 | ld ixl, a 12 | ld ixh, a 13 | ld iyl, a 14 | ld iyh, a 15 | ld i, a 16 | 17 | push hl 18 | pop af ; set flags 19 | 20 | ex af, af' 21 | ld a, b 22 | exx 23 | ld b, a 24 | ld c, a 25 | ld d, a 26 | ld e, a 27 | ld h, a 28 | ld l, a 29 | 30 | push hl 31 | pop af ; set flags 32 | 33 | ret 34 | 35 | suite.registers.clobberAll: 36 | ex af, af' ; switch AF and AF' to clobber both 37 | exx ; switch main registers with shadow to clobber both sets 38 | 39 | ; Clobber index registers 40 | ld ixl, a 41 | ld ixh, a 42 | ld iyl, a 43 | ld iyh, a 44 | ld i, a 45 | ret 46 | 47 | suite.registers.expectAllToBeZero: 48 | expect.a.toBe 0 49 | expect.bc.toBe 0 50 | expect.de.toBe 0 51 | expect.hl.toBe 0 52 | expect.ix.toBe 0 53 | expect.iy.toBe 0 54 | expect.i.toBe 0 55 | 56 | ex af, af' 57 | expect.a.toBe 0 58 | ex af, af' 59 | exx 60 | expect.bc.toBe 0 61 | expect.de.toBe 0 62 | expect.hl.toBe 0 63 | exx 64 | ret 65 | 66 | suite.registers.setAllFlags: 67 | push hl 68 | ld h, a 69 | ld l, $ff ; flags 70 | push hl ; push to stack 71 | pop af ; restore to AF 72 | pop hl 73 | ret 74 | 75 | suite.registers.resetAllFlags: 76 | push hl 77 | ld h, a 78 | ld l, 0 ; flags 79 | push hl ; push to stack 80 | pop af ; restore to AF 81 | pop hl 82 | ret 83 | 84 | suite.registers.unexpectedJump: 85 | zest.fail "Unexpected jump" 86 | .ends -------------------------------------------------------------------------------- /tests/utils/registers/autoPreserve.test.asm: -------------------------------------------------------------------------------- 1 | describe "automatic register preservation" 2 | ; Activate AUTO_PRESERVE 3 | .redefine utils.registers.AUTO_PRESERVE 1 4 | 5 | test "preserves all clobbered registers" 6 | zest.initRegisters 7 | 8 | utils.clobbers "af" "bc" "de" "hl" "ix" "iy" "i" "af'" "bc'" "de'", "hl'" 9 | call suite.registers.clobberAll 10 | utils.clobbers.end 11 | 12 | expect.all.toBeUnclobbered 13 | 14 | test "only preserves clobbered registers" 15 | ld bc, $bc01 16 | ld de, $de01 17 | 18 | utils.clobbers "bc" 19 | expect.stack.size.toBe 1 20 | expect.stack.toContain $bc01 21 | utils.clobbers.end 22 | 23 | utils.clobbers "bc" "de" 24 | expect.stack.size.toBe 2 25 | expect.stack.toContain $de01 26 | expect.stack.toContain $bc01 1 27 | utils.clobbers.end 28 | 29 | test "only preserves registers once if there are nested preserve scopes" 30 | utils.clobbers "af" 31 | expect.stack.size.toBe 1 32 | 33 | utils.clobbers "af" 34 | expect.stack.size.toBe 1 "Expected stack size to still be 1" 35 | utils.clobbers.end 36 | utils.clobbers.end 37 | 38 | test "does not override existing preserve scopes" 39 | ld bc, $bc00 40 | 41 | ; Preserve scope already exists - this should not be overridden 42 | utils.registers.preserve "bc" 43 | ; Clobber scope clobbers all registers 44 | utils.clobbers "af", "bc", "de", "hl", "ix", "iy", "i", "af'", "bc'", "de'", "hl'" 45 | ; Only BC should have been preserved 46 | expect.stack.size.toBe 1 47 | expect.stack.toContain $bc00 48 | utils.clobbers.end 49 | utils.restore 50 | 51 | expect.bc.toBe $bc00 52 | 53 | .redefine registers.AUTO_PRESERVE 0 54 | -------------------------------------------------------------------------------- /tests/utils/registers/iRegister.test.asm: -------------------------------------------------------------------------------- 1 | describe "i-register preservation" 2 | test "preserves and restores AF and I" 3 | zest.initRegisters 4 | 5 | utils.preserve "af" "i" 6 | utils.clobbers "i" 7 | ld i, a 8 | utils.clobbers.end 9 | 10 | utils.clobbers "af" 11 | inc a 12 | utils.clobbers.end 13 | utils.restore 14 | 15 | expect.all.toBeUnclobbered 16 | 17 | test "should preserve multiple nested values" 18 | ; Set I to 0 19 | ld a, 0 20 | ld i, a 21 | 22 | ; Create multiple nested preserve and clobber scopes that inc I 23 | .repeat utils.registers.I_STACK_MAX_SIZE index index 24 | utils.preserve "i" 25 | utils.clobbers "i" 26 | inc a 27 | ld i, a 28 | .endr 29 | 30 | ; Restore each nested preserve scope 31 | .repeat utils.registers.I_STACK_MAX_SIZE index restoreIndex 32 | utils.clobbers.end 33 | utils.restore 34 | 35 | expect.i.toBe utils.registers.I_STACK_MAX_SIZE - restoreIndex - 1 36 | .endr 37 | -------------------------------------------------------------------------------- /tests/utils/registers/registers.test.asm: -------------------------------------------------------------------------------- 1 | describe "register preservation" 2 | ; Deactivate AUTO_PRESERVE 3 | .redefine utils.registers.AUTO_PRESERVE 0 4 | 5 | test "preserves all clobbered registers by default" 6 | zest.initRegisters 7 | 8 | utils.preserve ; no registers - preserve all by default 9 | utils.clobbers "af", "bc", "de", "hl", "ix", "iy", "i", "af'", "bc'", "de'", "hl'" 10 | call suite.registers.clobberAll 11 | utils.clobbers.end 12 | utils.restore 13 | 14 | expect.all.toBeUnclobbered 15 | 16 | test "should not preserve any registers if no preserve scopes are in progress" 17 | utils.clobbers "af", "bc", "de", "hl", "ix", "iy", "i", "af'", "bc'", "de'", "hl'" 18 | expect.stack.size.toBe 0 19 | utils.clobbers.end 20 | 21 | test "preserves all registers marked as clobbered" 22 | zest.initRegisters 23 | 24 | ; Preserve all registers 25 | utils.preserve "af", "bc", "de", "hl", "ix", "iy", "i", "af'", "bc'", "de'", "hl'" 26 | ; This clobber scope clobbers all registers 27 | utils.clobbers "af", "bc", "de", "hl", "ix", "iy", "i", "af'", "bc'", "de'", "hl'" 28 | call suite.registers.clobberAll 29 | utils.clobbers.end 30 | utils.restore 31 | 32 | ; Expect all registers to have been preserved 33 | expect.all.toBeUnclobbered 34 | 35 | test "only preserves the requested registers" 36 | ld bc, $bc01 37 | ld de, $de01 38 | ld hl, $ffff 39 | 40 | ; Preserve BC and DE only 41 | utils.preserve "bc", "de" 42 | ; This clobber scope clobbers all registers 43 | utils.clobbers "bc", "de", "hl" 44 | ld bc, 0 45 | ld de, 0 46 | ld hl, 0 47 | utils.clobbers.end 48 | utils.restore 49 | 50 | ; Expect BC and DE to have been preserved 51 | expect.bc.toBe $bc01 52 | expect.de.toBe $de01 53 | 54 | ; Expect HL to have been clobbered 55 | expect.hl.toBe 0 56 | 57 | test "only preserves registers marked as clobbered" 58 | ld bc, $bc01 59 | ld de, $de01 60 | ld hl, $ffff 61 | 62 | ; Preserve all registers 63 | utils.preserve "af", "bc", "de", "hl", "ix", "iy", "i", "af'", "bc'", "de'", "hl'" 64 | ; This clobber scope only clobbers DE and HL 65 | utils.clobbers "bc", "de" 66 | ld bc, 0 67 | ld de, 0 68 | ld hl, 0 69 | utils.clobbers.end 70 | utils.restore 71 | 72 | ; Expect BC and DE to have been preserved 73 | expect.bc.toBe $bc01 74 | expect.de.toBe $de01 75 | 76 | ; Expect HL to have been clobbered 77 | expect.hl.toBe 0 78 | 79 | test "only preserves registers once per preserve context" 80 | ld bc, $bc01 81 | 82 | ; Preserve BC 83 | utils.preserve "bc" 84 | ; Nothing should have been preserved yet 85 | expect.stack.size.toBe 0 86 | 87 | ; This clobber scope clobbers BC 88 | utils.clobbers "bc" 89 | ; Expect BC to have been pushed to the stack 90 | expect.stack.size.toBe 1 91 | expect.stack.toContain $bc01 92 | 93 | ld bc, $bc02 94 | 95 | ; This inner clobber scope also clobbers BC 96 | utils.clobbers "bc" 97 | ; The outer scope has already preserved BC in the stack, 98 | ; so expect this not to have pushed it again 99 | expect.stack.size.toBe 1 "Expected stack size to still be 1" 100 | expect.stack.toContain $bc01 0 "Expected stack to still contain the original value" 101 | 102 | ld bc, $bc03 103 | utils.clobbers.end 104 | utils.clobbers.end 105 | utils.restore 106 | 107 | expect.stack.size.toBe 0 "Expected stack size to be back to 0" 108 | expect.bc.toBe $bc01 109 | -------------------------------------------------------------------------------- /utils/port.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Performs a simple 'in' operation to read the given port. This serves to 3 | ; decouple the modules from this operation so it could be stubbed out with a 4 | ; fake version for testing purposes 5 | ;==== 6 | 7 | .define utils.port 8 | 9 | ;==== 10 | ; Reads the given port into register A 11 | ; 12 | ; @in portNumber the port to read 13 | ; @out a the read value 14 | ;==== 15 | .macro "utils.port.read" args portNumber 16 | in a, (portNumber) 17 | .endm 18 | -------------------------------------------------------------------------------- /utils/preserve.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Alias for utils.registers.preserve 3 | ;==== 4 | 5 | .define utils.preserve 6 | 7 | ;==== 8 | ; Dependencies 9 | ;==== 10 | .ifndef utils.registers 11 | .include "utils/registers.asm" 12 | .endif 13 | 14 | ;==== 15 | ; Alias for utils.registers.preserve 16 | ;==== 17 | .macro "utils.preserve" 18 | .if nargs == 0 19 | utils.registers.preserve 20 | .elif nargs == 1 21 | utils.registers.preserve \1 22 | .elif nargs == 2 23 | utils.registers.preserve \1 \2 24 | .elif nargs == 3 25 | utils.registers.preserve \1 \2 \3 26 | .elif nargs == 4 27 | utils.registers.preserve \1 \2 \3 \4 28 | .elif nargs == 5 29 | utils.registers.preserve \1 \2 \3 \4 \5 30 | .elif nargs == 6 31 | utils.registers.preserve \1 \2 \3 \4 \5 \6 32 | .elif nargs == 7 33 | utils.registers.preserve \1 \2 \3 \4 \5 \6 \7 34 | .elif nargs == 8 35 | utils.registers.preserve \1 \2 \3 \4 \5 \6 \7 \8 36 | .elif nargs == 9 37 | utils.registers.preserve \1 \2 \3 \4 \5 \6 \7 \8 \9 38 | .elif nargs == 10 39 | utils.registers.preserve \1 \2 \3 \4 \5 \6 \7 \8 \9 \10 40 | .elif nargs == 11 41 | utils.registers.preserve \1 \2 \3 \4 \5 \6 \7 \8 \9 \10 \11 42 | .else 43 | .print "\.: Too many arguments passed\n" 44 | .fail 45 | .endif 46 | .endm 47 | 48 | ;==== 49 | ; Closes a preserve scope without producing the restore instructions. Sometimes 50 | ; needed by branching clobber scopes which restore the registers separately 51 | ;==== 52 | .macro "utils.preserve.close" 53 | utils.registers.closePreserveScope 54 | .endm -------------------------------------------------------------------------------- /utils/ram.asm: -------------------------------------------------------------------------------- 1 | .define utils.ram 2 | 3 | ;==== 4 | ; Constants 5 | ;==== 6 | .define utils.ram.BYTES $2000 7 | 8 | ;==== 9 | ; Dependencies 10 | ;==== 11 | .ifndef utils.assert 12 | .include "utils/assert.asm" 13 | .endif 14 | 15 | ;==== 16 | ; Sets a utils.ram.SLOT value to either the user-defined smslib.RAM_SLOT value or 17 | ; the mapper-defined slot (mapper.RAM_SLOT). This allows modules to define RAM 18 | ; sections using the correct slot without being coupled to the mapper or global 19 | ; variable 20 | ;==== 21 | .ifdef smslib.RAM_SLOT 22 | .define utils.ram.SLOT smslib.RAM_SLOT 23 | .else 24 | .ifdef mapper.RAM_SLOT 25 | .define utils.ram.SLOT mapper.RAM_SLOT 26 | .endif 27 | .endif 28 | 29 | ;==== 30 | ; Asserts that the RAM slot was able to be determined and is set in 31 | ; utils.ram.SLOT, otherwise fails with instructions 32 | ;==== 33 | .macro "utils.ram.assertRamSlot" 34 | .ifndef utils.ram.SLOT 35 | .print "\.: Cannot determine which RAM slot to use:" 36 | .print " Either .define an smslib.RAM_SLOT value or include an" 37 | .print " smslib mapper before including the other modules" 38 | .print "\n\n" 39 | .fail 40 | .endif 41 | .endm 42 | 43 | ;==== 44 | ; Fills a portion of RAM with the given value 45 | ; 46 | ; @in bytes the number of bytes to fill 47 | ; @in address|hl the first byte to fill 48 | ; @in value|a the value to set the bytes to 49 | ;==== 50 | .macro "utils.ram.fill" args bytes address value 51 | utils.assert.range NARGS, 1, 3, "utils/ram.asm \.: Invalid number of arguments" 52 | utils.assert.range bytes, 1, utils.ram.BYTES, "utils/ram.asm \.: Invalid bytes argument" 53 | 54 | ; Assert address argument is valid 55 | .ifdef address 56 | utils.assert.label address, "utils/ram.asm \.: Invalid address argument" 57 | .endif 58 | 59 | ; Set A to value, if given 60 | .ifdef value 61 | utils.assert.range value, 0, 255, "utils/ram.asm \.: Invalid value argument" 62 | 63 | .if value == 0 64 | xor a ; set A to 0 65 | .else 66 | ld a, value 67 | .endif 68 | .endif 69 | 70 | ; Check number of bytes to fill 71 | .if bytes <= 8 72 | ;=== 73 | ; There aren't many bytes to fill 74 | ;=== 75 | .ifdef address 76 | ; If address is a constant, just use ld (nn), a 77 | ; Fastest method, but uses 3-bytes of code per byte 78 | .repeat bytes index index 79 | ld (address + index), a 80 | .endr 81 | .else 82 | ; If address is a variable in HL, use ld (hl), a 83 | .repeat bytes index index 84 | ld (hl), a 85 | 86 | ; Increment HL if there are more bytes to go 87 | .if index < bytes - 1 88 | inc hl 89 | .endif 90 | .endr 91 | .endif 92 | .else 93 | ;=== 94 | ; There are lots of bytes to fill 95 | ;=== 96 | 97 | .ifdef address 98 | ; Set HL to address and DE to address + 1 99 | ld hl, address 100 | ld de, address + 1 101 | .else 102 | ; Address is a variable in HL. Set DE to HL + 1 103 | ld d, h 104 | ld e, l 105 | inc de 106 | .endif 107 | 108 | ld (hl), a ; set first byte 109 | ld bc, bytes - 1 ; set BC to bytes, minus the first one we set 110 | 111 | ; Copy byte n to n + 1 until done 112 | ldir 113 | .endif 114 | .endm 115 | -------------------------------------------------------------------------------- /utils/restore.asm: -------------------------------------------------------------------------------- 1 | ;==== 2 | ; Alias for utils.registers.restore 3 | ;==== 4 | 5 | .define utils.restore 6 | 7 | ;==== 8 | ; Dependencies 9 | ;==== 10 | .ifndef utils.registers 11 | .include "utils/registers.asm" 12 | .endif 13 | 14 | ;==== 15 | ; Alias for utils.registers.restore 16 | ;==== 17 | .macro "utils.restore" 18 | utils.registers.restore 19 | .endm 20 | --------------------------------------------------------------------------------