Side-scrolling platformer with scroll streaming and SNESMOD audio. More...
Macros | |
| #define | FRAME_JUMP 1 |
| Dynamic sprite frame index: jump pose. | |
| #define | FRAME_STAND 6 |
| Dynamic sprite frame index: standing idle pose. | |
| #define | FRAME_WALK0 2 |
| Dynamic sprite frame index: walk animation frame 0. | |
| #define | FRAME_WALK1 3 |
| Dynamic sprite frame index: walk animation frame 1. | |
| #define | GRAVITY 48 |
| Gravity acceleration in 8.8 fixed-point units per frame. | |
| #define | MARIO_ACCEL 0x0038 |
| Horizontal acceleration per frame in 8.8 fixed-point (0.22 pixels/frame) | |
| #define | MARIO_ACT_FALL 3 |
| Mario is falling (downward velocity or walked off an edge) | |
| #define | MARIO_ACT_JUMP 2 |
| Mario is jumping (upward velocity, ascending phase) | |
| #define | MARIO_ACT_STAND 0 |
| Mario is standing on solid ground, idle. | |
| #define | MARIO_ACT_WALK 1 |
| Mario is walking (horizontal velocity nonzero, on ground) | |
| #define | MARIO_HIJUMPING 0x0594 |
| High jump initial upward velocity (when holding UP during jump) | |
| #define | MARIO_JUMPING 0x0394 |
| Normal jump initial upward velocity in 8.8 fixed-point. | |
| #define | MARIO_MAXACCEL 0x0140 |
| Maximum horizontal velocity in 8.8 fixed-point (1.25 pixels/frame) | |
| #define | T_EMPTY 0x0000 |
| Tile property: passable (air, background decoration) | |
| #define | T_SOLID 0xFF00 |
| Tile property: solid (ground, walls, platforms) | |
| #define | VRAM_BG_MAP 0x6800 |
| VRAM word address for BG1 tilemap (SC_64x32) | |
| #define | VRAM_BG_TILES 0x2000 |
| VRAM word address for BG1 tile character data. | |
| #define | VRAM_SPR_LARGE 0x0000 |
| VRAM word address for large (16x16) sprite tiles. | |
| #define | VRAM_SPR_SMALL 0x1000 |
| VRAM word address for small (8x8) sprite tiles. | |
Functions | |
| static s16 | asr8 (s16 val) |
| Arithmetic right shift by 8 bits (extract integer part of 8.8 fixed-point). | |
| u8 | getSpriteTilBank (void) |
| Get the ROM bank byte of mario_sprite_til. | |
| void | loadGraphics (void) |
| DMA all tile and palette data from ROM to VRAM/CGRAM. | |
| int | main (void) |
| Main entry point – platformer initialization and game loop. | |
| static void | map_flush_column (void) |
| Flush the buffered column to VRAM via DMA (flush phase). | |
| static u16 | map_get_tile_prop (s16 px, s16 py) |
| Look up the collision property of the tile at pixel coordinates. | |
| static void | map_load (void) |
| Load the map from ROM and populate the initial VRAM tilemap. | |
| static void | map_prepare_column (u16 map_col, u16 vram_col) |
| Buffer a map column for deferred VRAM write (prepare phase). | |
| static void | map_update (void) |
| Check if the camera has scrolled to a new tile column and prepare it. | |
| static void | mario_animate (void) |
| Update Mario's sprite animation frame based on current action. | |
| static void | mario_apply_physics (void) |
| Apply gravity and integrate velocity into position. | |
| static void | mario_clamp_and_transition (void) |
| Clamp Mario to world boundaries and handle action state transitions. | |
| static void | mario_collide_horizontal (void) |
| Check horizontal tile collisions (wall sliding). | |
| static void | mario_collide_vertical (void) |
| Check vertical tile collisions (ground landing and ceiling bonk). | |
| static void | mario_handle_input (void) |
| Process D-pad and button input for Mario's movement. | |
| static void | mario_init (void) |
| Initialize Mario's position, velocity, animation, and sprite configuration. | |
| static void | mario_update_camera (void) |
| Update camera to follow Mario and position the sprite on screen. | |
| static void | write_vram_column (u16 map_col, u16 vram_col) |
| Write a full tile column to VRAM via direct register writes. | |
Variables | |
| static u8 | anim_tick |
| static s16 | cam_max_x |
| static s16 | camera_x |
| static u16 | col_buffer [32] |
| Column tile buffer for deferred VRAM streaming. | |
| static u8 | col_pending |
| static u16 | col_vram_base |
| static s16 | last_tile_x |
| static u16 * | map_data |
| static u16 | map_height |
| static s16 | map_max_x |
| static u16 * | map_row_ptrs [32] |
| Precomputed row pointers into map_data for fast tile lookup. | |
| static u16 | map_width |
| u8 | mapmario [] |
| Map data (tile indices + header with width/height) | |
| static u8 | mario_action |
| static u8 | mario_anim_idx |
| u8 | mario_sprite_til [] |
| Mario sprite tile data (16x16, 4bpp) for the dynamic sprite engine. | |
| static s16 | mario_x |
| static u8 | mario_xfrac |
| static s16 | mario_xvel |
| static s16 | mario_y |
| static u8 | mario_yfrac |
| static s16 | mario_yvel |
| static u8 | sfx_jump_slot |
| SPC700 sound effect slot index for the jump sound. | |
| static u16 * | tile_props |
| u8 | tilesetatt [] |
| Tile attribute table (T_SOLID/T_EMPTY per tile index, b16 format) | |
Side-scrolling platformer with scroll streaming and SNESMOD audio.
A Mario-style platformer demonstrating the core techniques of a scrolling SNES game: tile-based map streaming, subpixel physics, tile collision, camera tracking, animated sprites, and SPC700 music/sound effects via the SNESMOD audio driver.
The map engine streams one tile column per frame using a prepare/flush pattern: during active display, the next column is read from ROM into a RAM buffer (map_prepare_column); during VBlank, a 64-byte DMA writes it to VRAM (map_flush_column). This keeps VRAM writes within the VBlank budget. The column at +32 tiles ahead is streamed to cover the 33rd partial tile at the screen edge.
Physics use 8.8 fixed-point velocities with arithmetic right shift for signed values. Collision checks test two probe points per axis against the tile property table (T_SOLID / T_EMPTY).
Port of the PVSnesLib likemario example.
| #define FRAME_JUMP 1 |
Dynamic sprite frame index: jump pose.
| #define FRAME_STAND 6 |
Dynamic sprite frame index: standing idle pose.
| #define FRAME_WALK0 2 |
Dynamic sprite frame index: walk animation frame 0.
| #define FRAME_WALK1 3 |
Dynamic sprite frame index: walk animation frame 1.
| #define GRAVITY 48 |
Gravity acceleration in 8.8 fixed-point units per frame.
Applied to mario_yvel every frame when airborne (jumping or falling). 48/256 = 0.1875 pixels/frame^2 – gives a natural parabolic arc.
| #define MARIO_ACCEL 0x0038 |
Horizontal acceleration per frame in 8.8 fixed-point (0.22 pixels/frame)
| #define MARIO_ACT_FALL 3 |
Mario is falling (downward velocity or walked off an edge)
| #define MARIO_ACT_JUMP 2 |
Mario is jumping (upward velocity, ascending phase)
| #define MARIO_ACT_STAND 0 |
Mario is standing on solid ground, idle.
| #define MARIO_ACT_WALK 1 |
Mario is walking (horizontal velocity nonzero, on ground)
| #define MARIO_HIJUMPING 0x0594 |
High jump initial upward velocity (when holding UP during jump)
| #define MARIO_JUMPING 0x0394 |
Normal jump initial upward velocity in 8.8 fixed-point.
| #define MARIO_MAXACCEL 0x0140 |
Maximum horizontal velocity in 8.8 fixed-point (1.25 pixels/frame)
| #define T_EMPTY 0x0000 |
Tile property: passable (air, background decoration)
| #define T_SOLID 0xFF00 |
Tile property: solid (ground, walls, platforms)
| #define VRAM_BG_MAP 0x6800 |
VRAM word address for BG1 tilemap (SC_64x32)
| #define VRAM_BG_TILES 0x2000 |
VRAM word address for BG1 tile character data.
| #define VRAM_SPR_LARGE 0x0000 |
VRAM word address for large (16x16) sprite tiles.
| #define VRAM_SPR_SMALL 0x1000 |
VRAM word address for small (8x8) sprite tiles.
Arithmetic right shift by 8 bits (extract integer part of 8.8 fixed-point).
The 65816 has no native arithmetic shift right instruction, and the compiler's LSR (logical shift) would zero-fill the sign bit, turning negative values positive. This function works around it:
| val | 8.8 fixed-point signed value |
|
extern |
Get the ROM bank byte of mario_sprite_til.
The dynamic sprite engine needs the bank byte to set up DMA source addresses for sprite tile uploads. Since the tiles are in a SUPERFREE section, their bank is determined at link time and only available via the :label assembly operator.
|
extern |
DMA all tile and palette data from ROM to VRAM/CGRAM.
Implemented in assembly because SUPERFREE sections may land in ROM banks $01+. The assembly uses :label to get the correct bank byte for each DMA source, which C cannot express.
| int main | ( | void | ) |
Main entry point – platformer initialization and game loop.
Initializes all subsystems during force blank: Mode 1 video, tile/palette DMA via assembly loader, dynamic sprite engine, map loading, Mario state, and SNESMOD audio (music + jump sound effect). Then enters the main loop with a strict pipeline:
Active display (CPU-only, no VRAM):
VBlank (VRAM writes allowed):
|
static |
Flush the buffered column to VRAM via DMA (flush phase).
Must be called during VBlank. Uses DMA channel 1 in word mode to transfer 64 bytes (32 tilemap entries) to VRAM with vertical increment addressing. DMA takes ~512 master cycles vs ~57,600 for a compiled C register-write loop – a 100x speedup that fits easily within the VBlank budget.
Look up the collision property of the tile at pixel coordinates.
Converts pixel coordinates to tile coordinates, reads the tile index from the map via precomputed row pointers, then looks up the tile's property (T_SOLID or T_EMPTY) in the attribute table. Out-of-bounds coordinates above or left return T_SOLID (wall); below or right return T_EMPTY (open air, allowing fall-off-screen respawn).
| px | World X position in pixels |
| py | World Y position in pixels |
|
static |
Load the map from ROM and populate the initial VRAM tilemap.
Parses the map header (width, height in pixels), builds the row pointer table for O(1) tile lookups, and writes all 64 columns to VRAM. Must be called during force blank since it performs direct VRAM writes.
Buffer a map column for deferred VRAM write (prepare phase).
Called during active display (not VBlank). Reads tile indices from ROM map data into the col_buffer[] RAM array and records the target VRAM address. No VRAM writes occur here – those happen in map_flush_column() during the next VBlank.
| map_col | Source column index in the map data |
| vram_col | Target column in the SC_64x32 tilemap (0-63) |
|
static |
Check if the camera has scrolled to a new tile column and prepare it.
Compares the current camera tile position to last_tile_x. If the camera moved right, prepares the column at +32 (one tile ahead of the visible right edge, covering the 33rd partial tile). If moved left, prepares the newly revealed left column. At most one column is prepared per frame.
|
static |
Update Mario's sprite animation frame based on current action.
Walking alternates between WALK0 and WALK1 every 4 frames. Jumping and falling use the JUMP frame. Standing uses the STAND frame. The oamrefresh flag tells the dynamic sprite engine to re-upload the new frame's tile data to VRAM on the next flush.
|
static |
Apply gravity and integrate velocity into position.
Adds GRAVITY to Y velocity when airborne (capped at terminal velocity). Then integrates both axes: adds velocity to the fractional accumulator, extracts the integer pixel displacement via asr8(), and stores the remaining fraction for sub-pixel precision next frame.
|
static |
Clamp Mario to world boundaries and handle action state transitions.
Prevents Mario from leaving the map horizontally or going above the top. If Mario falls below Y=240 (off the bottom of the screen), respawns at the top-left. Also handles state machine transitions: WALK->STAND when velocity reaches zero, JUMP->FALL at the apex.
|
static |
Check horizontal tile collisions (wall sliding).
Probes one point at Mario's vertical midpoint in the direction of movement. If a solid tile is hit, snaps X to the tile boundary and zeroes horizontal velocity. Only checks the direction Mario is actually moving to avoid false positives.
|
static |
Check vertical tile collisions (ground landing and ceiling bonk).
Ground check: probes two points at Mario's feet (left+2, right+13). If either hits a solid tile, snaps Y to the tile boundary and resets vertical velocity. Ceiling check: probes one point above Mario's head. Collision detection uses the tile property table, not per-pixel checks.
|
static |
Process D-pad and button input for Mario's movement.
LEFT/RIGHT apply acceleration (with max velocity clamping and deceleration when released). A triggers a jump if grounded; holding UP during jump gives a higher arc. The sprite's H-flip attribute is set to match the facing direction.
|
static |
Initialize Mario's position, velocity, animation, and sprite configuration.
Places Mario at a safe spawn point, sets the dynamic sprite engine's frame to the standing pose, and configures the sprite attribute byte (priority 3, H-flip for facing right). The bank byte for sprite tile data is retrieved via getSpriteTilBank() because the tile data may reside in any ROM bank due to SUPERFREE section placement.
|
static |
Update camera to follow Mario and position the sprite on screen.
Centers the camera on Mario (offset by half-screen width = 128 pixels), clamped to the map boundaries. Then converts Mario's world position to screen-relative coordinates for the sprite engine and triggers the dynamic sprite draw for OAM buffer update.
Write a full tile column to VRAM via direct register writes.
Used during force blank (initial map load) when DMA is not needed because there is no time constraint. Sets VMAIN to vertical increment mode (bit 0 set = increment address by 32 after each write), which writes one tile per row down a column.
| map_col | Source column index in the map data |
| vram_col | Destination column in the SC_64x32 tilemap (0-63) |
|
static |
Frame counter for animation timing
|
static |
Maximum camera X offset (map_width*8 - 256)
|
static |
Current camera X scroll offset in pixels
|
static |
Column tile buffer for deferred VRAM streaming.
During active display, map_prepare_column() fills this 32-entry buffer from ROM map data (RAM-only writes). During VBlank, map_flush_column() DMAs these 64 bytes to VRAM in one transfer.
|
static |
Nonzero when col_buffer contains data awaiting flush
|
static |
VRAM word address for the pending column DMA
|
static |
Last tile column for which streaming was performed
|
static |
Pointer to the tile index array within mapmario
|
static |
Map height in tiles (parsed from map header)
|
static |
Rightmost valid Mario X position (map_width*8 - 16)
|
static |
Precomputed row pointers into map_data for fast tile lookup.
Avoids an expensive multiply (row * map_width) on every tile access. map_row_ptrs[row] points directly to the start of that row's tile data. Maximum 32 rows for SC_64x32 tilemap height.
|
static |
Map width in tiles (parsed from map header)
|
extern |
Map data (tile indices + header with width/height)
|
static |
Current action state (MARIO_ACT_STAND/WALK/JUMP/FALL)
|
static |
Walk animation toggle (0 or 1, selects WALK0/WALK1)
|
extern |
Mario sprite tile data (16x16, 4bpp) for the dynamic sprite engine.
|
static |
Mario world X position in pixels
|
static |
Mario X sub-pixel fraction (8.8 fixed-point low byte)
|
static |
Mario X velocity in 8.8 fixed-point
|
static |
Mario world Y position in pixels
|
static |
Mario Y sub-pixel fraction (8.8 fixed-point low byte)
|
static |
Mario Y velocity in 8.8 fixed-point (negative = upward)
|
static |
SPC700 sound effect slot index for the jump sound.
|
static |
Pointer to the tile property table (T_SOLID/T_EMPTY)
|
extern |
Tile attribute table (T_SOLID/T_EMPTY per tile index, b16 format)