All SDK methods are accessed through the Console struct passed to your update and render callbacks. Import the prelude at the top of game.rs:

use console32_sdk::prelude::*;

Display

Screen: 320×240 pixels, RGB565. Origin (0, 0) is top-left. Coordinates are i16. The framebuffer is DMA-flushed to the panel after render returns — never flush manually.

Clearing and background

c.display.clear(colour);                  // fill entire screen

Primitives

c.display.draw_pixel(x, y, colour);

c.display.draw_line(x0, y0, x1, y1, colour);

c.display.draw_rect(x, y, w, h, colour);       // outline
c.display.fill_rect(x, y, w, h, colour);       // filled

c.display.draw_circle(cx, cy, r, colour);      // outline
c.display.fill_circle(cx, cy, r, colour);      // filled

Text

c.display.draw_text(text: &str, x, y, colour);

// Returns the pixel width of the string in the current font
let w: u16 = c.display.text_width(text: &str);

// Switch to a custom font (see Font in the ABI types)
c.display.set_font(font: &'static Font);
// Restore the built-in default font
c.display.reset_font();

Camera and clipping

// Offset all draw calls by (cam_x, cam_y) — world-space scrolling
c.display.set_camera(cam_x: i16, cam_y: i16);
c.display.reset_camera();

// Restrict all drawing to a rectangle; pixels outside are discarded
c.display.set_clip_rect(x, y, w, h);
c.display.reset_clip_rect();

Sprites

// Blit a sprite at (x, y). Transparent colour is skipped.
c.display.blit(sprite: &Sprite, x, y);

// Blit with horizontal flip
c.display.blit_flipped(sprite: &Sprite, x, y);

// Blit a sub-rectangle of a sprite sheet
c.display.blit_frame(sheet: &SpriteSheet, frame: u16, x, y);

Colours

Colours are u16 RGB565. Named constants in the prelude:

ConstantValue
BLACK0x0000
WHITE0xFFFF
RED0xF800
GREEN0x07E0
BLUE0x001F
YELLOW0xFFE0
CYAN0x07FF
MAGENTA0xF81F

Build arbitrary colours with:

let orange = rgb(255, 140, 0);   // rgb(r: u8, g: u8, b: u8) -> Colour

Input

Button state is sampled once per frame by the firmware before update is called.

Button constants

GroupConstants
Facebuttons::A, buttons::B, buttons::X, buttons::Y
D-padbuttons::UP, buttons::DOWN, buttons::LEFT, buttons::RIGHT
Shoulderbuttons::L, buttons::R

Constants can be OR'd together to test multiple buttons at once.

Methods

// True every frame the button is physically held
c.input.is_held(mask: u32) -> bool

// True only on the first frame the button goes down (rising edge)
c.input.is_pressed(mask: u32) -> bool

// True only on the frame the button comes up (falling edge)
c.input.is_released(mask: u32) -> bool

// Number of consecutive frames the button has been held
c.input.hold_duration(mask: u32) -> u32

// Raw bitmask of all currently held buttons
c.input.held() -> u32

Example

use buttons::*;

if c.input.is_held(LEFT)  { self.x -= 2; }
if c.input.is_held(RIGHT) { self.x += 2; }
if c.input.is_held(UP)    { self.y -= 2; }
if c.input.is_held(DOWN)  { self.y += 2; }

if c.input.is_pressed(A) { self.jump(); }
if c.input.is_pressed(L) { self.toggle_pause(); }

// Chord — only fires when both are held simultaneously
if c.input.is_held(L | R) { self.open_map(); }

Audio

Four independent channels. Each plays a mono AudioSample (signed 8-bit PCM, 22 050 Hz).

// Play a sample on a channel (0–3)
// volume: 0 (silent) – 255 (full)
c.audio.play_sfx(
    sample:  &'static AudioSample,
    channel: u8,
    volume:  u8,
    looping: bool,
);

// Stop a channel
c.audio.stop_sfx(channel: u8);

// Set channel volume without restarting playback
c.audio.set_channel_vol(channel: u8, volume: u8);

// Master volume (multiplied with channel volumes)
c.audio.set_master_vol(volume: u8);
c.audio.get_master_vol() -> u8;

Embedding audio

Use include_bytes! with the asset converter (cargo console32 convert) to turn WAV files into embeddable PCM data:

static JUMP_PCM: &[i8] = include_bytes_as_i8!("assets/jump.pcm");
static JUMP: AudioSample = AudioSample::from_pcm(JUMP_PCM);

Storage

Persistent key-value slots. Each slot is a fixed-size [u8; N] buffer.

// Returns true if a save exists at the given slot index
c.storage.save_exists(slot: u8) -> bool

// Read saved bytes into a buffer; returns number of bytes read
c.storage.save_read(slot: u8, buf: &mut [u8]) -> usize

// Write bytes to a slot
c.storage.save_write(slot: u8, data: &[u8])

// Erase a slot
c.storage.save_erase(slot: u8)

System

// Frames elapsed since game_init was called (wraps at u32::MAX)
c.system.frame_count() -> u32

// Microseconds elapsed since game_init (wraps at u64::MAX)
c.system.time_us() -> u64

// Seeded pseudo-random number in 0..=u32::MAX
c.system.random() -> u32

// Emit a debug message visible in `cargo console32 monitor`
c.system.log(msg: &str)

// Request the firmware return to the launcher after this frame
c.system.quit()

Maths

All trig uses u16 full-circle angles where 0 = 0°, 16384 = 90°, 32768 = 180°, 65535 ≈ 360°. Results are signed 1.15 fixed-point — shift right by 15 to get a ratio.

c.math.sin(angle: u16) -> i16       // 1.15 fixed-point
c.math.cos(angle: u16) -> i16       // 1.15 fixed-point
c.math.atan2(y: i16, x: i16) -> u16 // full-circle angle

c.math.sqrt(x: u32) -> u32
c.math.distance(x0: i16, y0: i16, x1: i16, y1: i16) -> u32

c.math.lerp(a: i32, b: i32, t_256: u8) -> i32   // t in 0–255 (256 = 1.0)
c.math.clamp(v: i32, min: i32, max: i32) -> i32

Fixed-point types

Fixed is a 16.16 fixed-point scalar (i32 × 65536). Vec2i is a pair of Fixed values.

let speed = Fixed::from(3);             // integer → Fixed
let half  = Fixed::from_raw(32768);     // raw 16.16 value

let a = Fixed::from(10);
let b = Fixed::from(3);
let c = a * b;                          // Fixed × Fixed → Fixed
let i = a.to_i32();                     // Fixed → i32 (truncated)

// Vec2i
let pos = Vec2i::new(Fixed::from(160), Fixed::from(120));
let vel = Vec2i::new(Fixed::from(2), Fixed::from(-1));
let new_pos = pos + vel;

let dist_sq = pos.length_sq_px();      // distance² in pixel² (no sqrt)

Pool

Pool<T, N> is a fixed-capacity object collection with no heap allocation.

let mut enemies: Pool<Enemy, 32> = Pool::new();

// Spawn a new object (returns None if the pool is full)
enemies.spawn_with(Enemy { x: 100, y: 50, hp: 3 });

// Iterate
for e in enemies.iter() { /* ... */ }
for e in enemies.iter_mut() { e.x += e.vx; }

// Remove items matching a predicate
enemies.retain(|e| e.hp > 0);

// Current count and capacity
let count = enemies.len();     // items alive
let cap   = enemies.capacity(); // always N