Loading...
Searching...
No Matches
main.c File Reference

Side-scrolling platformer with scroll streaming and SNESMOD audio. More...

#include <snes.h>
#include <snes/snesmod.h>
#include "soundbank.h"

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 u16map_data
 
static u16 map_height
 
static s16 map_max_x
 
static u16map_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 u16tile_props
 
u8 tilesetatt []
 Tile attribute table (T_SOLID/T_EMPTY per tile index, b16 format)
 

Detailed Description

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.

SNES Concepts
  • Scroll streaming: 1 column/frame prepare-during-display, DMA-in-VBlank
  • SC_64x32 tilemap with 64-column wraparound for seamless scrolling
  • Subpixel 8.8 fixed-point physics (acceleration, gravity, max velocity)
  • Tile collision via property lookup table (tilesetatt)
  • Dynamic sprite engine with frame-based animation
  • SNESMOD: SPC700 module playback and sound effects
  • Assembly DMA loader with bank bytes for SUPERFREE ROM data
What to Observe
  • Mario walks left/right with acceleration and deceleration
  • Press A to jump (hold UP for a higher jump)
  • Camera follows Mario; background scrolls smoothly
  • Music plays continuously; a jump sound effect triggers on A press
  • Falling off the bottom respawns Mario at the top
Modules Used
console, sprite, sprite_dynamic, sprite_lut, dma, input, background
See also
background.h, sprite.h, input.h, dma.h, snesmod.h

Macro Definition Documentation

◆ FRAME_JUMP

#define FRAME_JUMP   1

Dynamic sprite frame index: jump pose.

◆ FRAME_STAND

#define FRAME_STAND   6

Dynamic sprite frame index: standing idle pose.

◆ FRAME_WALK0

#define FRAME_WALK0   2

Dynamic sprite frame index: walk animation frame 0.

◆ FRAME_WALK1

#define FRAME_WALK1   3

Dynamic sprite frame index: walk animation frame 1.

◆ GRAVITY

#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.

◆ MARIO_ACCEL

#define MARIO_ACCEL   0x0038

Horizontal acceleration per frame in 8.8 fixed-point (0.22 pixels/frame)

◆ MARIO_ACT_FALL

#define MARIO_ACT_FALL   3

Mario is falling (downward velocity or walked off an edge)

◆ MARIO_ACT_JUMP

#define MARIO_ACT_JUMP   2

Mario is jumping (upward velocity, ascending phase)

◆ MARIO_ACT_STAND

#define MARIO_ACT_STAND   0

Mario is standing on solid ground, idle.

◆ MARIO_ACT_WALK

#define MARIO_ACT_WALK   1

Mario is walking (horizontal velocity nonzero, on ground)

◆ MARIO_HIJUMPING

#define MARIO_HIJUMPING   0x0594

High jump initial upward velocity (when holding UP during jump)

◆ MARIO_JUMPING

#define MARIO_JUMPING   0x0394

Normal jump initial upward velocity in 8.8 fixed-point.

◆ MARIO_MAXACCEL

#define MARIO_MAXACCEL   0x0140

Maximum horizontal velocity in 8.8 fixed-point (1.25 pixels/frame)

◆ T_EMPTY

#define T_EMPTY   0x0000

Tile property: passable (air, background decoration)

◆ T_SOLID

#define T_SOLID   0xFF00

Tile property: solid (ground, walls, platforms)

◆ VRAM_BG_MAP

#define VRAM_BG_MAP   0x6800

VRAM word address for BG1 tilemap (SC_64x32)

◆ VRAM_BG_TILES

#define VRAM_BG_TILES   0x2000

VRAM word address for BG1 tile character data.

◆ VRAM_SPR_LARGE

#define VRAM_SPR_LARGE   0x0000

VRAM word address for large (16x16) sprite tiles.

◆ VRAM_SPR_SMALL

#define VRAM_SPR_SMALL   0x1000

VRAM word address for small (8x8) sprite tiles.

Function Documentation

◆ asr8()

static s16 asr8 ( s16  val)
static

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:

  • Positive values: simple unsigned shift (LSR is correct)
  • Negative values: bitwise NOT, unsigned shift, NOT back – preserves the sign bit through the inversion trick.
Parameters
val8.8 fixed-point signed value
Returns
Integer part (effectively val / 256 with sign preservation)

◆ getSpriteTilBank()

u8 getSpriteTilBank ( void  )
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.

Returns
Bank byte (e.g., 0x00, 0x01) of the mario_sprite_til data

◆ loadGraphics()

void loadGraphics ( void  )
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.

◆ main()

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):

  1. mario_handle_input() - read pad, apply acceleration
  2. mario_apply_physics() - gravity, velocity integration
  3. mario_collide_vertical/horizontal() - tile collision response
  4. mario_clamp_and_transition() - boundary checks, state transitions
  5. mario_animate() - frame selection
  6. mario_update_camera() - camera + sprite positioning
  7. map_update() - prepare next column in RAM buffer

VBlank (VRAM writes allowed):

  1. map_flush_column() - DMA one column (64 bytes)
  2. bgSetScroll() - update hardware scroll registers
  3. oamVramQueueUpdate() - upload sprite tiles
  4. snesmodProcess() - SPC700 communication (no VBlank restriction)
Returns
0 (never reached – infinite game loop)

◆ map_flush_column()

static void map_flush_column ( void  )
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.

◆ map_get_tile_prop()

static u16 map_get_tile_prop ( s16  px,
s16  py 
)
static

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).

Parameters
pxWorld X position in pixels
pyWorld Y position in pixels
Returns
T_SOLID (0xFF00) or T_EMPTY (0x0000)

◆ map_load()

static void map_load ( void  )
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.

◆ map_prepare_column()

static void map_prepare_column ( u16  map_col,
u16  vram_col 
)
static

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.

Parameters
map_colSource column index in the map data
vram_colTarget column in the SC_64x32 tilemap (0-63)

◆ map_update()

static void map_update ( void  )
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.

◆ mario_animate()

static void mario_animate ( void  )
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.

◆ mario_apply_physics()

static void mario_apply_physics ( void  )
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.

◆ mario_clamp_and_transition()

static void mario_clamp_and_transition ( void  )
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.

◆ mario_collide_horizontal()

static void mario_collide_horizontal ( void  )
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.

◆ mario_collide_vertical()

static void mario_collide_vertical ( void  )
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.

◆ mario_handle_input()

static void mario_handle_input ( void  )
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.

◆ mario_init()

static void mario_init ( void  )
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.

◆ mario_update_camera()

static void mario_update_camera ( void  )
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_vram_column()

static void write_vram_column ( u16  map_col,
u16  vram_col 
)
static

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.

Parameters
map_colSource column index in the map data
vram_colDestination column in the SC_64x32 tilemap (0-63)

Variable Documentation

◆ anim_tick

u8 anim_tick
static

Frame counter for animation timing

◆ cam_max_x

s16 cam_max_x
static

Maximum camera X offset (map_width*8 - 256)

◆ camera_x

s16 camera_x
static

Current camera X scroll offset in pixels

◆ col_buffer

u16 col_buffer[32]
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.

◆ col_pending

u8 col_pending
static

Nonzero when col_buffer contains data awaiting flush

◆ col_vram_base

u16 col_vram_base
static

VRAM word address for the pending column DMA

◆ last_tile_x

s16 last_tile_x
static

Last tile column for which streaming was performed

◆ map_data

u16* map_data
static

Pointer to the tile index array within mapmario

◆ map_height

u16 map_height
static

Map height in tiles (parsed from map header)

◆ map_max_x

s16 map_max_x
static

Rightmost valid Mario X position (map_width*8 - 16)

◆ map_row_ptrs

u16* map_row_ptrs[32]
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.

◆ map_width

u16 map_width
static

Map width in tiles (parsed from map header)

◆ mapmario

u8 mapmario[]
extern

Map data (tile indices + header with width/height)

◆ mario_action

u8 mario_action
static

Current action state (MARIO_ACT_STAND/WALK/JUMP/FALL)

◆ mario_anim_idx

u8 mario_anim_idx
static

Walk animation toggle (0 or 1, selects WALK0/WALK1)

◆ mario_sprite_til

u8 mario_sprite_til[]
extern

Mario sprite tile data (16x16, 4bpp) for the dynamic sprite engine.

◆ mario_x

s16 mario_x
static

Mario world X position in pixels

◆ mario_xfrac

u8 mario_xfrac
static

Mario X sub-pixel fraction (8.8 fixed-point low byte)

◆ mario_xvel

s16 mario_xvel
static

Mario X velocity in 8.8 fixed-point

◆ mario_y

s16 mario_y
static

Mario world Y position in pixels

◆ mario_yfrac

u8 mario_yfrac
static

Mario Y sub-pixel fraction (8.8 fixed-point low byte)

◆ mario_yvel

s16 mario_yvel
static

Mario Y velocity in 8.8 fixed-point (negative = upward)

◆ sfx_jump_slot

u8 sfx_jump_slot
static

SPC700 sound effect slot index for the jump sound.

◆ tile_props

u16* tile_props
static

Pointer to the tile property table (T_SOLID/T_EMPTY)

◆ tilesetatt

u8 tilesetatt[]
extern

Tile attribute table (T_SOLID/T_EMPTY per tile index, b16 format)