picogame — quick reference
A one-page cheat sheet of the engine’s everyday API: the native picogame C module
and the pure-Python picogame_* helper libraries in lib/. Signatures show parameter
names and defaults; * marks keyword-only arguments. Colours are wire-order RGB565 ints
(build them with rgb565). For longer explanations see the engine guide.
See also: Fit it in RAM · Drawing paths · Performance · Run on hardware · Coming from another engine.
Native module: picogame (import picogame as pg)
Section titled “Native module: picogame (import picogame as pg)”Constants & colour
Section titled “Constants & colour”RGB565,PAL8— bitmap pixel formats.API_LEVEL—int; engine API generation, bumped when the Python-visible surface grows. Libraries checkgetattr(pg, "API_LEVEL", 0) >= Nto diagnose a too-old firmware up front instead of failing later on a missing attribute.RGB444_SUPPORTED—bool; whether this board’s panel can drive 12-bit RGB444 (lets one game opt intoDisplay(rgb444=True)only where it works).FPU—bool;Truewhen the 3D math primitives (pg.project) run the hardware-float path (RP2350, ESP32-S3),Falseon the RP2040 (16.16 fixed-point). Packprojectbuffers to match:array("f")whenpg.FPUelsearray("i")with valuesint(v * 65536).rgb565(r, g, b) -> int— wire-order colour from 8-bit channels.collide(x1, y1, x2, y2, ax1, ay1, ax2, ay2) -> bool— AABB overlap (8 args = box vs box) or point-in-box (6 args:collide(x1, y1, x2, y2, px, py)). Inclusive AABB, so boxes collide when they touch (pass sprite boxes as(x, y, x+w, y+h); fires on contact).
Bitmap(data, width, height, *, format=RGB565, palette=None, frames=1, stride=0, transparent=None)
Section titled “Bitmap(data, width, height, *, format=RGB565, palette=None, frames=1, stride=0, transparent=None)”An image atlas of equal-size frames (any size). data is a buffer; palette (array of wire colours) is required for PAL8. transparent = the index/colour skipped when blitting.
- Read-only props:
width,height,frames,format,stride(pixels per source row; leave0for tightly-packed data, set it only for a sub-window of a larger image),palette(the PAL8 palette buffer orNone),transparent(the transparent value orNone).
Sprite(bitmap, x=0, y=0, *, frame=0, visible=True, flip_x=False, flip_y=False)
Section titled “Sprite(bitmap, x=0, y=0, *, frame=0, visible=True, flip_x=False, flip_y=False)”A positioned, animatable instance of a Bitmap.
- Position/anim props:
x,y(int px) ·fx,fy(float sub-pixel) ·frame·visible·flip_x,flip_y·bitmap(swap) ·data(your payload). - Transform props (nearest-neighbour, about the anchor):
scale— float draw scale;1.0= native (fast path),2.0= double size, fractional allowed (e.g. a pulse).angle— rotation in degrees;0= none (fast path). Combines withscale.transpose— bool; swaps X/Y axes. On its own that is a diagonal mirror, not a rotation; combine with a flip for a crisp, shimmer-free quarter-turn. Withflip_x/flip_yit reaches all 8 orientations. Fast path only (scale 1, angle 0); footprint swaps w/h. Recipes (screen y-down): 90° CW =transpose+flip_y· 180° =flip_x+flip_y· 270° CW =transpose+flip_x.anchor=(fx, fy)— pivot as fractions of the bitmap (0..1):(0.5, 0.5)= centre,(0.5, 1.0)= bottom-centre.x/yand rotation are about this point.
- Blit-effect props (one at a time; setting one clears the others; cheap, no extra bitmaps):
shadow— bool; opaque pixels darken the destination (drop-shadow / dim overlay).flash— wire-RGB565 colour (or0/None= off); opaque pixels drawn as that flat colour (hit-flash). Pulse 1–3 frames.tint— wire-RGB565 colour (or0= off); opaque pixels multiplied by it, colouring the sprite while keeping its shading (damage-red, freeze-blue, glow).dither—0(opaque) ..16(invisible); Bayer-stipple translucency, no alpha (ghosts, fog, fade-in/out).
move(x, y)— set position. ·touch()— mark dirty after an in-place bitmap/palette edit.overlaps(other, inset=0) -> bool·near(other, r) -> bool— native collision tests (see Sprite collision below).
Display(busdisplay, *, rgb444=False)
Section titled “Display(busdisplay, *, rgb444=False)”Fast DMA backend wrapping a board’s busdisplay (FourWire SPI). Pass to Scene. rgb444=True drives the panel in 12-bit RGB444 (~25% less SPI traffic) on panels that support it; gate with RGB444_SUPPORTED.
Scene(display, buffer_a, buffer_b, *, background=0, top=0, bottom=0, left=0, right=0)
Section titled “Scene(display, buffer_a, buffer_b, *, background=0, top=0, bottom=0, left=0, right=0)”Retained-mode scene with dirty-rectangle rendering; buffer_a/b are strip buffers.
add(item, *, fixed=False) -> item— add a Sprite/Tilemap/Particles/Canvas/StripDraw (insertion order = bottom→top) and return it (sospr = scene.add(Sprite(...))works).fixed=True(keyword-only) pins it to the screen (ignores the camera) for HUD/dialog.add_all(items)— add several (bottom→top).remove(item)— unlink a previously added item (no ghost — next refresh repaints over it, likeinvalidate()); the item survives and can beadd()ed again.ValueErrorif not in the scene.set_view(ox, oy)— camera offset (screen position of the scene origin); changing it repaints all.view— read-only(ox, oy)camera offset.invalidate()— force a full repaint next refresh.refresh() -> list | None— diff & repaint changed regions; returns the dirty rect[x1,y1,x2,y2](reused) or None.
Tilemap(tileset, cols, rows)
Section titled “Tilemap(tileset, cols, rows)”A grid of tile indices into a tileset Bitmap (each frame = one tile); a Scene layer.
tile(tx, ty, value=None, *, flip_x=False, flip_y=False, transpose=False) -> int— get tile, or set it (with optional keyword-only per-cell orientation:flip_x/flip_y/transposegive all 8 orientations of a tile; pair with a deduplicated tileset, seepng2picogame.py --dedup). Out-of-range ignored. The orientation plane is allocated lazily (RAM only if a map uses it).fill(value)— set every tile (clears orientation).move(x, y)— position the map.- Read-only props:
x,y,cols,rows.
Particles(capacity, *, size=1, gravity=0.0, fade=False)
Section titled “Particles(capacity, *, size=1, gravity=0.0, fade=False)”A pooled particle layer (small moving dots) drawn as one Scene layer.
emit(x, y, count, speed=1, life=30, color=0xFFFF)— burstcountdots, random velocity ≤speedpx/tick, livinglifeticks.tick()— advance one step (move, gravity, ageing). Call each frame.clear()— remove all.
Canvas(width, height, *, transparent=None, buffer=None)
Section titled “Canvas(width, height, *, transparent=None, buffer=None)”A RAM RGB565 drawing surface composited as a Scene layer (width*height*2 bytes). transparent makes it a shaped overlay; buffer backs it with external memory (e.g. an arena slice). For animated full-frame surfaces prefer StripDraw (no buffer).
clear(color)·pixel(x, y, color)·fill_rect(x, y, w, h, color)·rect(x, y, w, h, color)line(x0, y0, x1, y1, color)·circle(cx, cy, r, color)·fill_circle(cx, cy, r, color)·ring(cx, cy, r, thickness, color)triangle(x0,y0, x1,y1, x2,y2, color)·fill_triangle(...)·ellipse(cx, cy, rx, ry, color)·fill_ellipse(...)fill_round_rect(x, y, w, h, r, color)·frame3d(x, y, w, h, light, dark)(beveled box) ·move(x, y)blit(bitmap, x, y, frame=0, flip_x=False, flip_y=False)— stamp a bitmap frame into the surface (honours its transparent key; the retained way to bake an icon/portrait/rendered text into a panel).text(x, y, s, fg, font, bg=None)— composite a string in C, rasterizing each glyph offont(afontio.BuiltinFont) on the fly: no Python glyph cache and no per-call Bitmap/Sprite.bg=None→ transparent glyph background. ASCII/built-in font only. Works on a Canvas or aStripDrawview; the latter does not retain a separate text surface.mode7(texture, horizon, y_off, z, rx0, ry0, rsx, rsy, cam_x, cam_y)— fill the rows belowhorizonwith a Mode-7 perspective floor oftexture(power-of-2 dims; one world unit = one tile). 10 fixed-point (16.16) args — you normally letpicogame_mode7.Cameracompute them from a camera pose. Draws into a Canvas or a 0-RAMStripDrawview (passy_off= the strip top).fill_triangles(verts, colors, n, x_off=0, y_off=0)— fillntriangles in one call:verts= int16x0,y0,x1,y1,x2,y2per triangle,colors= wire-RGB565 uint16 per triangle. Same rasteriser asfill_triangle, but the whole batch crosses the Python/C boundary once — the win for many small triangles (blocky 3D, low-poly, isometric), where the ~10 µs per-call overhead otherwise dominates.x_off/y_offtranslate every vertex before clipping: passy_off=-vyin aStripDrawcallback to replay one screen-space batch into each render strip (off-band triangles are rejected with three compares) — full-res 3D with no retained canvas at all, the preferred path on framebuffer boards. Companion ofpg.projectandpicogame_iso.emit_blocks.road(ri0, tab, rl, rr, d05_q8, d07_q8, colors)— draw one OutRun-style racing-road strip from precomputed tables: the whole per-scanline loop (sky/road/rumble/dash colour picks) in one call.ri0= road-table row at this surface’s row 0 (negative = sky rows);tab= int16 rows of{edge_w, dash_hw, wb05_q8, wb07_q8, flags};rl/rr= int16 per-row edges frompg.road_edges;d05/d07= Q8 scroll phases;colors= 6× uint16{sky, road_a, road_b, rumble_a, rumble_b, dash}. Designed as aStripDrawcallback body (0-RAM road).- Read-only props:
x,y,width,height.
StripDraw(callback, x=0, y=0, width=0, height=0, *, always_dirty=True)
Section titled “StripDraw(callback, x=0, y=0, width=0, height=0, *, always_dirty=True)”Immediate-mode layer with no pixel buffer: each refresh it calls callback(view, vx, vy, vw, vh) once per render strip inside its rect. view is a Canvas pointing at the live strip (use Canvas primitives, incl. view.text); view-local (0,0) = screen (vx, vy). In a scrolling scene add it fixed.
always_dirty=True(default) repaints every frame → for animated/scanline content (pseudo-3D, gradients).always_dirty=Falserepaints only when invalidated or overlapped by another change → for static/on-change panels (it still renders once on first refresh).invalidate()— mark it dirty so the next refresh repaints it (the way to update analways_dirty=Falsepanel when its content changes).
Triangles(verts, colors)
Section titled “Triangles(verts, colors)”A retained screen-space triangle batch the compositor rasterises entirely in C per render strip (cheap band reject + the Canvas rasteriser) — no pixel buffer AND no Python per strip. verts = int16 array (x0,y0,x1,y1,x2,y2 per triangle), colors = uint16 wire-RGB565 per triangle — both caller-owned (fill them in place each frame). This is the 3D-scene layer: pg.project into the arrays, painter’s-order the faces, set count, scene.refresh(). Because no Python runs during compose, it stays composable by the core1 band split — unlike a StripDraw callback.
count— how many triangles draw next refresh (clamped to the buffer capacity); assigning marks the layer dirty for a full repaint (set it every frame in a live 3D scene).- Measured (roadhop lab): replaces the
fill_triangles-in-StripDraw replay with ~30 % less refresh time at 320×240 and unlocks the dual-core compose (640×480 at a locked 20 fps on an RP2350 with a free second core). - Read/write props:
x,y,width,height— move or resize the layer (after shrinking, callscene.invalidate()) ·always_dirty.
Low-level draw functions
Section titled “Low-level draw functions”Most games never call these (picogame_game.setup + Scene use them internally), but they are exposed for hand-built render loops.
render(display, sprites, buffer, x0, y0, x1, y1, *, background=0)— render a sprite list into the region[x0,x1) × [y0,y1)and push it todisplay.bufferis a reusable strip buffer (≥ region-width × 2 bytes). Mixing with a retained scene: the scene doesn’t knowrender()changed the pixels — if the region overlaps the scene’s play rect, callscene.invalidate()after (or usepicogame_game.overlay, which does both); HUD bands outside the play rect don’t need it.invert(display, on)— toggle the panel’s hardware colour inversion. Changes the panel’s inversion state without sending pixel data, so a brief invert makes a full-screen negative flash (a 1-bit “hit” look) with no redraw. Seepicogame_fx.InvertFlash.project(cam, pts, n, out_sx, out_sy)— batch perspective projection ofn3D points to screen in C.cam= 15 camera params(ex,ey,ez, rx,rz, ux,uy,uz, fx,fy,fz, focal, cx0, cy0, near),pts=n×3world coords,out_sx/out_sy= int16 screen coords (a point behind the near plane gets the sentinel-32768— skip its faces). Buffer format followspg.FPU(float32 on FPU boards, 16.16 int32 on the RP2040 — a format mismatch culls everything = black screen). One call per frame +Canvas.fill_triangles= real flat-shaded polygon 3D (Elite-class): project your vertices, painter’s-sort faces, fill. ~0.7 ms/480 pts on an RP2350, ~2.2 ms on an RP2040.road_edges(rl, rr, hw, n, cx0, dist, cfg)— one racing-road frame’s curve accumulator + integer edge tables in a single call (the OutRun-genrecompute_roadloop).rl/rr= int16 outputs forCanvas.road,hw= int32 Q16 per-row half-widths,cx0= Q16 screen centre (incl. lateral offset),dist= integer world distance,cfg= int32[7] curve/hill config. Pairs withCanvas.roadfor a 0-RAM 30 fps road on the RP2040.vblank()— (DVI boards, RP2350) block until the scanout’s next vertical blanking (≤ ~16.7 ms). Starting a full-frame compose right after vblank keeps the publish front consistently behind the beam, so each sweep shows one whole frame — removes single-buffer tearing while the compose fits within two sweeps. Costs the wait: budget it against your FPS cap.core1(on) -> bool— (RP2 boards) route splittable engine kernels (Canvas.mode7rows, the framebuffer compose bands) through the second core. Returns the resulting state:Falsewhen core1 is unavailable — e.g. a USB-host board (Fruit Jam) runs its USB service on core1 permanently, so the engine refuses rather than stomping it. Dual-core compose measured ~1.75× on an RP2350 with a free core1.
Procedural noise (coherent value noise, 0..1)
Section titled “Procedural noise (coherent value noise, 0..1)”value2d(x, y, *, seed=0) -> float·value1d(x, *, seed=0) -> floatfbm2d(x, y, *, octaves=4, seed=0, lacunarity=2.0, gain=0.5) -> float·fbm1d(x, *, octaves=4, seed=0, lacunarity=2.0, gain=0.5) -> float— fractal (summed octaves).
Helper libraries (lib/picogame_*.py, pure Python)
Section titled “Helper libraries (lib/picogame_*.py, pure Python)”picogame_game — one-call boot
Section titled “picogame_game — one-call boot”setup(display=None, strip_h=None, background=0, fast=True, top=0, bottom=0, left=0, right=0, rgb444=False) -> (scene, buffer_a, buffer_b)— take over the display, build a Scene + two strip buffers.top/bottom/left/rightreserve fixed HUD margins;rgb444=Trueopts into 12-bit colour on a supporting SPI panel, andrgb444="auto"enables it only where the board reportspicogame.RGB444_SUPPORTED.overlay(scene, display, items, buffer, x0, y0, x1, y1, *, background=0)— immediate-drawitemsover a live scene (pause / menu / cutscene / banner) =pg.render+scene.invalidate(), so the nextrefresh()repaints the full frame instead of leaving overlay fragments.open_framebuffer(width, height, color_depth=None) -> display— set the resolution from inside a game on a framebuffer board (Fruit Jam DVI), e.g.open_framebuffer(640, 480); a no-op that returnsboard.DISPLAYon a fixed SPI panel. Pass the result tosetup(display=…).resolve_display(display=None) -> (display, is_framebuffer)— normalise a display/framebuffer handle (used by the HUD / immediate-render helpers).
picogame_clock — frame pacing
Section titled “picogame_clock — frame pacing”Clock(fps=30, max_dt=0.1)·.set_fps(fps)·.tick() -> dt(sleep to frame, return seconds) ·.tick_async()(the same, forasyncioloops).FixedStep(step_fps=60, max_steps=5)·.steps()— generator yielding a constant dt per fixed step ·.step_count().
picogame_input — buttons
Section titled “picogame_input — buttons”- Masks:
UP DOWN LEFT RIGHT A B X Y L1 L2 R1 R2 START SELECT ALL(a superset; each board maps the subset it has); profilePICOPAD. Buttons(profile=None, pull=None, prefer_keypad=True, debounce_s=0.02, matrix=None, usb=None, sources=None)·.poll() -> mask·.is_pressed(mask=ALL)·.just_pressed(mask=ALL)·.just_released(mask=ALL)·.has(mask=ALL)(is the mask present in the profile) ·.repeat(button, delay=15, interval=4)— PICO-8btnpauto-repeat (menus / grid move) ·.clear()(drop held state).matrix=— a scanned key-matrix source (also configured board-wide via thePICOGAME_MATRIX_*settings keys);usb=— one or more extra button sources (USB pad/keyboard, below).ButtonsORs every source together, so a game reads them all with no code change.
Timer(frames)— input-leniency window (coyote time / jump buffering):.feed(condition)(recharge while true, else decay) ·.charge()·.is_active·.consume()(true once, then clears).
picogame_usbpad — USB HID gamepad source (USB-host boards, e.g. Fruit Jam)
Section titled “picogame_usbpad — USB HID gamepad source (USB-host boards, e.g. Fruit Jam)”UsbPad(buttons=None)— a button source forButtons(usb=…)(auto-attached by default on a USB-host build). Reads a USB HID gamepad and ORs it into the button mask, so a plugged-in pad works with zero game code changes. Needs a USB-host CircuitPython build (usb.core); a no-op on boards without it.- Default map = the ubiquitous DragonRise
081f:e401SNES-style pad; remap per pad fromsettings.toml(PICOGAME_USBPAD, no reflash — see Custom board). Discover a new pad’s report bytes withtools/usbpad_probe.py. .mapped— mask of buttons this pad can report;VERSION,MAPPEDmodule constants.
picogame_usbkbd — USB HID keyboard source (USB-host boards)
Section titled “picogame_usbkbd — USB HID keyboard source (USB-host boards)”UsbKbd(keys=None)— the keyboard twin ofUsbPad, aButtons(usb=…)source. Found by its boot-keyboard HID interface (no fixed VID/PID); works with wired and 2.4 GHz-dongle keyboards (not Bluetooth).- Default map: arrows + WASD → D-pad, Z/Space → A, X → B, C → X, V → Y, Q → L1, E → R1, Enter → START, Esc → SELECT. Remap from
settings.toml(PICOGAME_USBKBD,NAME=HID-keycode). For a combo dongle whose real keystrokes flow on a sibling interface, point it at the right channel withPICOGAME_USBKBD_EP = "iface:endpoint"(find it withtools/usbkbd_probe.py).
picogame_font — text bitmaps (external font module)
Section titled “picogame_font — text bitmaps (external font module)”Which text path to use (Canvas.text vs a rendered Bitmap vs a StripDraw view — and what each costs): see the decision matrix in Drawing paths.
render_text(pg, font, text, fg, bg=None) -> (bitmap, w, h)— render a string to a PAL8 Bitmap (bg=None→ transparent).render_text_pal(pg, font, text, fg, bg=None) -> (bitmap, w, h, palette)— same, plus the palette array; mutatepalette[1]to recolour the text in place (no rebuild).Label(pg, font, x, y, fg, bg)·.move(x, y)·.set(text) -> changed·.draw(display, buffer).
picogame_bitfont — built-in font (no font module needed)
Section titled “picogame_bitfont — built-in font (no font module needed)”render_text(pg, text, fg=None, outline=None, mid=None, bg=None) -> (bitmap, w, h)— render with the bundled bitmap font; optionaloutline/midgive a cheap 2-tone outlined look.
picogame_ui — HUD & menu widgets (LINE_H = 12)
Section titled “picogame_ui — HUD & menu widgets (LINE_H = 12)”SceneLabel(scene, pg, font, x, y, fg, bg)·.set(text)·.destroy()— camera-independent text label (a fixed Scene layer); destroy() detaches a ONE-SHOT label so GC reclaims it (recurring HUD: build once + set/hide instead).SceneBox(scene, pg, font, x, y, w, h, fg, bg, nlines=3, key=None, border=None)·.show(lines)·.hide()·.set_line(i, text)·.destroy()— a multi-line in-scene panel (dialog/log); destroy() = one-shot teardown (needs firmware withScene.remove).HudBar(pg, display, buffer, x, y, w, h, bg)·.add(sprite)(an icon Sprite) ·.label(font, x, y, fg, text=" ")→ a text handle; update it withhandle.set(text)·.draw()(repaint the bar, call on HUD changes) — a fixed bar that composites sprites + labels (0 retained RAM).TextBox(pg, font, x, y, w, h, fg, bg, maxlines=6)·.draw(display, buffer, lines, force=False).Menu(pg, font, x, y, items, fg, bg, *, title=None, rows=None, width=None, paged=True)·.tick(btn)→ index ≥0 on A,CANCEL(= -2) on B,Nonewhile navigating ·.draw(display, buffer, force=False).SceneMenu(scene, pg, font, x, y, items, fg, bg, title=None, rows=None, width=None, border=None, paged=True)·.show(sel=0)·.hide()·.tick(btn)→ index ≥0 on A,CANCEL(= -2) on B,Nonewhile navigating — the same menu as an in-scene layer.GridCursor(cols, rows, tx=0, ty=0, wrap=False)·.index·.tick(btn)— D-pad cursor over a grid (inventory / board).
picogame_options — settings menu
Section titled “picogame_options — settings menu”OptionsMenu(scene, pg, font, x, y, w, rows, fg, bg, title=None, border=None)·.value(key)·.show(sel=0)·.hide()·.tick(btn)— an in-scene options screen of toggles/choices.
picogame_shapes — single-colour bitmap generators
Section titled “picogame_shapes — single-colour bitmap generators”rect(w, h, color)·circle(d, color)·ring(d, color, thickness=2)from_mask(mask, color)— Bitmap from a string mask ('#'= set).atlas(frames_data, w, h, color)— pack w×h buffers into a multi-frame Bitmap.color_frames(w, h, colors)— frame i = solidcolors[i].tileset_colors(w, h, colors)— tileset: frame 0 empty, frames 1..N coloured.poly_frames(size, points, nframes, color, fill=True)— bakenframesrotations of a polygon.
picogame_pool — reusable sprite pool
Section titled “picogame_pool — reusable sprite pool”Pool(scene, bitmap, capacity, anchor=None, fixed=False)·.spawn() -> sprite | None·.free(s)·.free_all()·.count() -> int. (.items= all sprites.)
Sprite collision (native methods)
Section titled “Sprite collision (native methods)”Collision lives on the Sprite itself: zero-alloc, anchor/scale/rotation aware (no separate module).
Sprite.overlaps(other, inset=0) -> bool— inclusive AABB box overlap (touch = hit).other= anotherSprite, a point(x, y), or a rect(x1, y1, x2, y2)(trigger zone / screen-cull).insetshrinks THIS sprite’s box by N px per side for a fair hitbox.Sprite.near(other, r) -> bool— circular: this sprite’s centre withinrpx ofother’s centre (squared distance, no sqrt).other= aSpriteor a point(x, y).- Raw primitive (any coords, no sprite):
pg.collide(x1, y1, x2, y2, ax1, ay1[, ax2, ay2])— 8 args box-vs-box, 6 args box-vs-point. - Tile-grid collision (walls/terrain): probe
picogame_tilesflags (at_px(tm, x, y, SOLID)), not AABB.
picogame_math — numeric helpers, vectors & turn-based trig
Section titled “picogame_math — numeric helpers, vectors & turn-based trig”clamp(v, lo, hi)·mid(a, b, c)·lerp(a, b, t)·inv_lerp(a, b, v)·remap(v, a, b, c, d)·sgn(x)·approach(v, target, step)·wrap(v, lo, hi).sin_t(turns)·cos_t(turns)·atan2_t(dy, dx) -> turns— angles as 0..1 turns (standard, not PICO-8’s inverted sin).length(dx, dy)·distance(x1, y1, x2, y2)·normalize(dx, dy)·angle_rad(dx, dy)(radians) ·from_angle_rad(a, mag=1.0)— vector helpers.
picogame_tiles — per-tile metadata flags (PICO-8 fget/fset)
Section titled “picogame_tiles — per-tile metadata flags (PICO-8 fget/fset)”- Bits/masks:
B_SOLID B_HAZARD B_LADDER …(indices) andSOLID HAZARD LADDER …(masks). TileFlags(flags=None, tile_px=8)—flags={tile_index: bitfield}or a list..get(tile, bit=None)·.set(tile, bit, value=True)·.at(tilemap, tx, ty, bit)·.at_px(tilemap, px, py, bit)(collision one-liner). Keyed by tile index (shared by all cells using it).
picogame_seq — generator-driven sequences (coroutine pattern)
Section titled “picogame_seq — generator-driven sequences (coroutine pattern)”wait(frames)·over(frames, fn)(fn(t), t 0..1) ·move_over(sprite, x, y, frames)— all are generators; compose withyield from.Seq(gen=None)·.start(gen)·.tick() -> done— advance one step per frame (cutscenes, “do X over N frames”).
picogame_anim — frame animation over time
Section titled “picogame_anim — frame animation over time”FrameAnim(sprite, frames, *, fps=8, loop=True)·.configure(frames, fps=8, loop=True)·.reset()·.tick(dt).AnimatedSprite(sprite, anims)·.play(name)·.tick(dt).
picogame_fx — juice & raster effects
Section titled “picogame_fx — juice & raster effects”Shake(scene, max_offset=6, decay=0.03, seed=0x9E37)·.add(amount)(0.6 hit, 0.15 bump) ·.tick(cam_x=0, cam_y=0)— trauma screen shake composed on top of the camera.Fade(scene, width, height, x=0, y=0, color=0, cell=8)·.to(target, speed=2.0)·.out()/.into()/.set(level)/.dim(level=8)/.clear()/.pulse(level=12, speed=2.0)·.is_done·.tick() -> done— dither fade / dim / flash, full-screen or a region. UsesStripDrawwithout a retained pixel surface.Tween(value=0.0, speed=0.2)·.to(target, speed=None)·.set(value)·.tick() -> value·.is_done— ease a scalar (UI/pop-ups).Camera(scene, w, h, lerp=0.18, world_w=0, world_h=0)·.follow(tx, ty, snap=False)·.offset() -> (ox,oy)·.apply()— smoothed follow + world clamp (compose withShakeviashake.tick(*cam.offset())).Sky(scene, x, y, w, h, top, bottom)— vertical gradient with a2*h-byte colour table. ·Scanlines(scene, x, y, w, h, step=2, dark=0)— CRT overlay retaining onew-byte PAL8 row and its palette.InvertFlash(display, frames=3, normal=None)·.pulse(frames=None)·.tick()— hardware-invert hit flash for a supported SPI panel. It does not redraw the scene and is not a framebuffer effect.
picogame_palette — Game-Boy palette tricks on PAL8 art (call sprite.touch() after)
Section titled “picogame_palette — Game-Boy palette tricks on PAL8 art (call sprite.touch() after)”cycle(palette, lo, hi, step=1)— rotate entries (animated water/lava/portals; ~0 extra art).swap(dst_palette, src_palette)— recolour a shared bitmap (GBC-style; cheaper than a 2nd bitmap).fade(palette, base, t, target=0, skip=None)— lerp toward a colour (smooth brightness fade;base=snapshot()of the original).snapshot(palette)/restore(palette, base).
picogame_rand — seedable RNG
Section titled “picogame_rand — seedable RNG”Rand(seed=None)(deterministic xorshift;None= time-seeded) ·.below(n)·.randint(a, b)·.random()·.chance(p)·.choice(seq)·.shuffle(lst)·.weighted(weights) -> index·.seed(s).Bag(items, rng)·.next()— shuffle-bag (7-bag) anti-streak randomizer.
picogame_save — NVM persistence
Section titled “picogame_save — NVM persistence”Save(key, schema, *, offset=0)·.defaults()·.load() -> dict·.save(values)·.reset(). Survives reboot/filesystem wipe.
picogame_audioout — one output device for any board
Section titled “picogame_audioout — one output device for any board”make_output(sample_rate=22050, pin=None)— returns this board’s audio output, chosen automatically: an I2S DAC (Fruit Jam TLV320) when the board hasI2S_BCLK, else a PWM output onpin(or the board default). Used by bothpicogame_audioandpicogame_synth, so a game needs no board-specific audio code. RaisesRuntimeErrorif no output exists.- The TLV320’s output select + the three volume trims are set from
settings.toml(PICOGAME_AUDIO_OUT,PICOGAME_DAC_VOLUME,PICOGAME_HP_VOLUME,PICOGAME_SPK_VOLUME— see Custom board); the driver’s defaults are deliberately quiet, so raise them toward 0 dB.PICOGAME_DEBUG = 1prints why a DAC failed to init.
picogame_audio — sample playback (PWM or I2S DAC)
Section titled “picogame_audio — sample playback (PWM or I2S DAC)”Audio(pin=None, voices=4, sample_rate=22050, channels=1, bits=16, signed=True)·.load(path)·.play(sample, *, voice=None, loop=False, volume=1.0)·.sfx(sample, volume=1.0)·.music(sample, loop=True, volume=1.0)·.stop(voice=None)·.stop_music()·.deinit()·.is_playing.tone(frequency=440, ms=120, sample_rate=22050, volume=0.6)— square-wave beep sample.
picogame_synth — synthio music & SFX
Section titled “picogame_synth — synthio music & SFX”- Waveforms:
sine()·saw()·triangle()·square()·noise(). note(midi, waveform=None, attack=0.005, decay=0.06, sustain=0.0, release=0.08, amplitude=0.6, bend=None, cutoff=None)— build a reusable instrument note (midi60 = middle C;cutoff= low-pass Hz).pitch_bend(semitones, ms, waveform=None, once=True)— an LFO for a note’sbend(slide / laser zap).Synth(pin=None, sample_rate=22050, buffer_size=2048, music_level=0.4, sfx_level=0.7)·.sfx(n)·.press(n)·.release(n)·.music(midi_track)·.stop_music()·.set_levels(music=None, sfx=None)·.mute(on)·.available— self-guarding init: on audio-less firmware or a failed init (tight heap, claimed pin) the instance runs as silent no-ops instead of raising; no try/except needed in games.Drone(synth, waveform=None, amplitude=0.35, attack=0.03, release=0.12)·.start()·.set(frequency, amplitude=None)·.stop()— a continuously-held note (engine/siren/drone): press once, then feedset(freq, amp)each frame so synthio tracks the live pitch/amplitude.load_midi(path, sample_rate=22050, waveform=None, envelope=None, tempo=120, ppqn=240)— load a MIDI file into a playable track.
picogame_sfx — signature SFX kit (over picogame_synth)
Section titled “picogame_sfx — signature SFX kit (over picogame_synth)”Kit(synth)— build a ready-made, hardware-tuned SFX set once from a liveSynth(silent no-op without audio). Fire by event:.blip()·.coin()·.powerup()·.zap()(your fire) ·.pew()(enemy fire) ·.jump()·.hit(rotate=True)(brightness rotates on rapid fire) ·.hurt()·.boom()·.explosion(). Call.tick()once per frame — drives the coin/powerup arpeggios and the priority + protected-window arbitration through the single SFX voice. Volume via theSynth:.set_levels()/.mute().
picogame_cutscene — full-screen image / story-scene player
Section titled “picogame_cutscene — full-screen image / story-scene player”palette(pg, rgb)— build the wire palette once (from a bake_cutscene.py palette module, RGB triplets, or wire ints).show(pg, display, buffer, path, pal=None, w=320, h=240, scale=None, band=24, bg=0)— stream an image in row bands. The source band usesw*bandbytes for PAL8 orw*band*2for RGB565, in addition to any render buffer passed in.scale=Nonederives an integer upscale from the display.play(pg, display, buffer, btn, path, pal=None, ..., caption=None, caption_lines=None, auto_hold=0, clock=None)— show it, overlay an optional caption bar, and wait for A/B (or auto-advance afterauto_holdticks).
picogame_stream — stream sprite frames from flash
Section titled “picogame_stream — stream sprite frames from flash”StreamSheet(pg, path, w, h, frames, palette, transparent=None)·.use(i)(select a frame, loaded on demand) ·.close()— keep big sheets on flash instead of RAM.
picogame_arena — anti-fragmentation buffer
Section titled “picogame_arena — anti-fragmentation buffer”Arena(pixels)·.alloc(nbytes, align=1) -> memoryview·.canvas(w, h, transparent=None) -> Canvas·.reset()·.mark() -> m/.release(m)(nested LIFO lifetimes: mark on entering a mode, release on leaving — run-level buffers survive) ·.free() -> int. Grab one big buffer up front, hand out slices.
picogame_debug — RAM watermarks + FPS overlay (testing aid)
Section titled “picogame_debug — RAM watermarks + FPS overlay (testing aid)”enabled— module flag (default False: calls ship as no-ops; flip True while testing).ram(tag)— gc.collect() + print[RAM] <tag>: free N alloc Mat a transition (boot/battle/menu) — the on-device leak/fit diagnostic.Watch(scene, clock=None, every=30, x=2, y=2)·.step()each frame ·.hide()/.show()·.remove()— a cornerFPS 30 FREE 31koverlay (one live text bitmap, re-rendered only on change). Pass yourClockasclock=for a true FPS reading;every/x/yare keyword args.
picogame_scene — declarative level loader
Section titled “picogame_scene — declarative level loader”load(pg, scene, display=None, strip_h=None, font=None, bank=None) -> View— build a scene from a baked SCENE dict.load_bank(pg, bank)— build a shared asset bank once (reuse across levels).View:.tile_xy(px, py)·.group(tag)·.point(name)·.in_zone(x, y, tag=None)·.is_solid(tx, ty)·.tile_has(tx, ty, prop)·.play(sound_id)·.tick(dt).
picogame_mode7 — Mode-7 perspective floor
Section titled “picogame_mode7 — Mode-7 perspective floor”Camera(fov=0.66)·.draw(canvas, texture, x, y, angle, horizon, height, y_off=0)— drive the CCanvas.mode7floor from a friendly camera pose (position in world/tile units, heading in radians,height= how high the camera sits).texturedims must be powers of two, one world unit = one tile. Draw into a 0-RAMStripDrawview. See /helpers/pseudo-3d/.
picogame_iso — isometric projection
Section titled “picogame_iso — isometric projection”IsoView(ox, oy, tw, th)(tw/th= tile half-width/half-height; 2:1 diamond →th = tw//2) ·.to_screen(gx, gy, h=0)·.depth(gx, gy, h=0)(back-to-front painter’s key) ·.screen_to_grid(sx, sy)·.cube_faces(gx, gy, height_px)(top/right/left faces of a raised block) ·.emit_blocks(cells, tv, tc)(alloc-free batch: writes flat-shaded cube triangles for many blocks straight into int16/uint16 buffers for ONECanvas.fill_trianglescall; returns the triangle count) — the cheapest pseudo-3D there is: integer add/shift only, no divide, no C dependency, which is why it runs well on the RP2040. Unlocks iso RPG / strategy / tactics / builder. Static boards: render once + dirty-rect the movers (30 fps);emit_blocksis for rebuild-every-frame scenes (~2× faster than a Pythoncube_facesloop). See /helpers/pseudo-3d/.
picogame_ray — first-person raycaster
Section titled “picogame_ray — first-person raycaster”Raycaster(world, wall_colors, sky, floor, fov=0.66, stride=2)·.cast(px, py, ang, sw, sh)(once/frame) ·.draw(view, vx, vy, vw, vh)(StripDraw callback) ·.solid(x, y)(wall test) ·.attach(sd)(temporal repaint) ·.project_sprite(sx, sy)(billboard) — DDA walls via the nativepg.raycastcaster (integer 16.16 C on device, Python in the sim) into a 0-RAMStripDrawview (~22-30 fps).stride= perf/quality knob;attach(sd)+always_dirty=Falserepaints only the changed column band (still/slow ~30 fps). See /helpers/pseudo-3d/.