MaxTile System or just MaxTile is a new feature introduced on SA-1 Pack v1.40 that fixes all known OAM issues involving the game, allowing custom sprites using both $0200 & $0300 area and implements a basic sprite priority system for custom resources interested in using the MaxTile API directly, while still keeping compatibility with regular and custom sprites that weren't designed for MaxTile.
How it works:
- At the beginning of the frame, $7F:8000 is called and invalidates all OAM data.
- $0200 - $049F can be used normally on the game.
- When a sprite is executed, its OAM index is set to $0328.
- When the next sprite is executed, the previous OAM tiles are copied into a new buffer and the $0328+ is freed so the next sprite can use it.
- At the end of the frame, the entire OAM is scanned for other tiles and unused tiles are removed.
- During the process, any tile outside screen is automatically dropped as well, maximizing free OAM slots.
- The sprite buffer then is copied back to the OAM and all sprites are rendered to the screen.
In practice however the process is a bit more complex:
- At the beginning of the frame:
- $7F:8000 is called and clears all SMW OAM's table.
- Reset SA-1's MaxTile internal pointer to $40:C000.
- During the frame:
- $0200 - $049F can be used normally on the game.
- When a new regular sprite asks for a OAM slot, SA-1 MaxTile will transfer all used tiles between $0328-$03FC to its internal buffer and give slot #$28 ($0328).
- Sprites made for MaxTile will be able to assign a priority number on its attribute and MaxTile will acknowledge the information at the end of the frame.
- At the end of the frame:
- MaxTile will iterate though $0200-$02FC and copy all tiles to its internal buffer and mark as "highest priority".
- Copy $0300-$0324 to its internal buffer and mark as "high priority".
- Copy $0328-$03FC to its internal buffer and mark as "standard priority".
- MaxTile will iterate its internal buffer and will copy all tiles marked as "highest priority" starting at $0200, then will repeat the process of "high", "standard" and "low" priority until it's over, gradually filling SMW's buffer with the sorted tiles.
- While iterating, MaxTile will analyze if each tile is inside or outside screen, discarding the ones outside screen for saving more slots.
- If it runs out of OAM slots, MaxTile will discard the lowest priority tiles and abort.
To be questioned:
- What will happen if one of the Mode 7 bosses change the OAM low address?
- Is it really needed to analyze if each tile is inside or outside screen?
- What if a sprite stores to $0400-$041F directly?
When working with MaxTile, you can continue storing to $0200-$02FC directly for setting your own priority (but with static tile management), you can use $0328+ for using as standard sprite priority or you can use its built-in priority system that will automatically ensure that your tiles are placed on the requested order.
The available priorities are:
- Maximum priority: store all tiles before the ones statically stored at $0200-$02FC. This is useful for sprite status bar or other status text.
- High priority: store all tiles before the ones statically stored at $0300-$0324. This is useful if you wanna make sprite appears in front of the player and yoshi like custom powerups or even additional players. It can also be useful for platforms and clouds.
- Standard priority: store all tiles in the same order as the sprites, so between $0328-$03FC. Useful for standard sprites that you don't really have to worry about the priority between them.
- Low priority: store all tiles after the ones normally written at $0328-$03FC. Useful for custom sprite backgrounds.
Some SMW sprites has hardcoded priorities and because of that they were adjusted to work with MaxTile's priority system. The following sprites are affected:
- Lakitu's cloud
Although SA-1 Pack for Super Mario World has a considerable amount of free memory on SA-1 side, it's always on concern about memory use when you are talking about SMW hacking.
The initial MaxTile idea would require only 384-400 free RAM bytes, but with the so-requested priority system, we ran into the following issue: although the OAM has only 128 sprite slots, we don't know how many slots will each priority take. It might sound impossible, but priority #2 or priority #4 can take almost all slots. Or vice-versa, priority #3 can take most of the slots or priority #1 can end up completely empty.
The simplest approach would be letting each priority have all slots available: 512 bytes main table + 128 bytes attribute table. With four priorities available, you would end up spending 2560 bytes of RAM, plus the pointers of each one. Overkill considering there are only 128 free slots and in the end a good chunk of the RAM will end up unused.
An alternative approach is making priority #2 and priority #4 reversed, so they will decrement the pointer and fill the previous priority buffer, but that would not allow custom sprites change priority by just swapping the pointer, requiring different graphics routines for each one.
Another thought approach was instead of using 2560 bytes of RAM, is using 2048 bytes at $40:C000-$40:C7FF so it could be mapped to $6000-$7FFF using the $2224 and $2225 SA-1 registers. However, each there would be only enough free space for three OAM buffers for four different priorities, which we would be forced to have two or three profiles with less OAM slots available and different performance profiles would be needed to implement (one that gives more free slots to low priority, another that gives more free slots to high priority, etc.).
The chosen approach was actually using the unused bits of properties table to define sprite priority (#$00 - normal, #$40 - low, #$80 - highest, #$C0 - high), reducing the amount of required memory to 512 + 128 (main draw buffer and properties) + pointers, keeping the cycle complexity somewhat similar (four table iterations).
- $6200-$63FF: SMW's OAM table. An Y value of #$F0 means the slot is empty.
- $6400-$641F: SMW's OAM attribute table. No sprite should ever modify this value directly, unless if the finish OAM write is not called (and therefore MaxTile won't run), like on the Nintendo presents screen.
- $6420-$649F: SMW's OAM expanded attribute table. Format: --OO --sH, where OO is priority (0-standard,1-low,2-maximum,3-high), s is the OAM tile size (8x8 or 16x16) and H is the X position most significant bit (9th).
- $40:C000-$40:C1FF: MaxTile's OAM table buffer.
- $40:C200-$40:C27F: MaxTile's OAM properties buffer. Format: ---- --sH.
- $40:C280-$40:C2FF: MaxTile's OAM priority buffer. Format: F-OO ---, where OO is priority and F is "force empty" flag.
- $40:C300-$40:C301: MaxTile's OAM pointer. Every frame it's reset to #$0000 and gradually incremented though the OAM tiles are copied to the buffer.