بنقرة واحدة
port-example
Port a PVSnesLib example to OpenSNES SDK, preserving identical visual output
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Port a PVSnesLib example to OpenSNES SDK, preserving identical visual output
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
| name | port-example |
| description | Port a PVSnesLib example to OpenSNES SDK, preserving identical visual output |
| argument-hint | <pvsneslib_path> (e.g., graphics/Effects/Transparency) |
| allowed-tools | Bash(*), Read, Write, Edit, Glob, Grep |
Port a PVSnesLib example to OpenSNES SDK, preserving identical visual output.
/port-example <pvsneslib_path> # Port from PVSnesLib path
/port-example graphics/Effects/Transparency # Relative to pvsneslib/snes-examples/
$PVSNESLIB_HOME/snes-examples/
Set PVSNESLIB_HOME to your local PVSnesLib clone (e.g. export PVSNESLIB_HOME=~/workspace/pvsneslib).
Read ALL source files from the PVSnesLib example:
*.c — Main source codedata.asm — Asset includeshdr.asm — ROM header (ignore, OpenSNES has its own)Makefile — CRITICAL: extract exact gfx4snes/gfxconv flags*.bmp / *.png — Source assets*.it / *.brr — Audio assetsIdentify:
-e flag in gfxconv)?Our gfx4snes supports BMP natively via -t bmp. This is the SAFEST approach
because it eliminates any palette conversion issues.
res/background.pic res/background.pal res/background.map: res/background.bmp
@$(GFX4SNES) -a -s 8 -u 16 -e 1 -p -m -t bmp -i $<
Copy BMPs directly from PVSnesLib to res/:
cp $PVSNESLIB_HOME/snes-examples/<path>/*.bmp res/
ImageMagick's convert alters indexed palettes. PIL preserves them exactly.
from PIL import Image
img = Image.open('source.bmp')
img.save('res/output.png') # Preserves indexed palette (mode P)
CRITICAL: Do NOT use PIL's quantize() on the converted image. If the image
has more colors than the target (e.g., 19 colors but -u 16), let gfx4snes
handle the reduction with -a (palette rearrangement). PIL's quantizer creates
an entirely different palette that produces wrong colors.
PVSnesLib uses $(GFXCONV), we use $(GFX4SNES). Same tool, same flags.
Only change: replace -t bmp with -t png if you converted to PNG (or keep BMP).
Common flag patterns:
# 4bpp BG (16 colors), with tilemap, palette entry 1
-a -s 8 -u 16 -e 1 -p -m -t bmp
# 2bpp BG3 Mode 1 (4 colors), with tilemap
-s 8 -o 2 -u 4 -p -m -t bmp
# 8bpp Mode 3 (256 colors), with tilemap
-s 8 -o 256 -u 256 -p -m -t bmp
# Mode 7
-p -m -M 7
# Sprites 16x16, 4bpp
-s 16 -o 16 -u 16 -p -t bmp
# Sprites with transpose for OBJ VRAM layout
-s 32 -o 16 -u 16 -p -T -X 64 -Y 64 -P 2
# 2bpp pre-processed (e.g., superscope)
-s 8 -m -p -u 4 -o 4
-a may output multi-bank palettesWhen -a is used, gfx4snes may split colors across multiple palette banks.
The .pal file will contain ALL banks (e.g., 128 colors = 8 banks × 16).
The assembly DMA loader MUST load the full palette file, not just 16 colors.
The -e N offset ensures tilemap entries reference the correct banks.
Use the assembly DMA loader pattern for all asset loading. This handles SUPERFREE bank bytes correctly (C's dmaCopyVram hardcodes bank $00).
;--- Data sections (SUPERFREE = linker places optimally) ---
.section ".rodata1" superfree
tiles:
.incbin "res/background.pic"
tiles_end:
tilemap:
.incbin "res/background.map"
tilemap_end:
palette:
.incbin "res/background.pal"
palette_end:
.ends
;--- Assembly DMA loader (handles bank bytes) ---
.section ".loader" superfree
loadGraphics:
php
; Set VMAIN for word increment
sep #$20
lda #$80
sta.l $2115
; DMA tiles to VRAM
rep #$20
lda #<vram_addr>
sta.l $2116
lda #(tiles_end - tiles)
sta.l $4305
lda #tiles
sta.l $4302
sep #$20
lda #:tiles ; bank byte from LINKER — the key!
sta.l $4304
lda #$01
sta.l $4300 ; mode: word write
lda #$18
sta.l $4301 ; dest: VMDATAL ($2118)
lda #$01
sta.l $420B ; start DMA ch0
; DMA palette to CGRAM
sep #$20
lda #<start_color>
sta.l $2121 ; CGADD
rep #$20
lda #(palette_end - palette)
sta.l $4305
lda #palette
sta.l $4302
sep #$20
lda #:palette
sta.l $4304
lda #$00
sta.l $4300 ; mode: byte write
lda #$22
sta.l $4301 ; dest: CGDATA ($2122)
lda #$01
sta.l $420B
; DMA tilemap to VRAM (same pattern as tiles)
; ...
plp
rtl
.ends
| PVSnesLib | OpenSNES | Notes |
|---|---|---|
consoleInit() | consoleInit() | Same |
setMode(BG_MODE1, 0) | setMode(BG_MODE1, 0) | Same |
setScreenOn() | setScreenOn() | Same |
WaitForVBlank() | WaitForVBlank() | Same |
bgInitTileSet(bg, ...) | Assembly DMA loader | See Phase 3 |
bgInitMapSet(bg, ...) | Assembly DMA loader | See Phase 3 |
bgSetScroll(bg, x, y) | bgSetScroll(bg, x, y) | Same (bg 0-indexed) |
bgSetDisable(bg) | setMainScreen(LAYER_BG1 | ...) | Use bitmask |
bgSetEnableSub(bg) | setSubScreen(LAYER_BGx) | Macro in video.h |
setColorEffect(a, b) | colorMathInit() + library calls | See below |
padsCurrent(0) | padHeld(0) | Held buttons |
padsDown(0) | padPressed(0) | New presses |
oamSet(...) | Direct oamMemory[] writes | oamSet has framesize=158! |
oamSetEx(...) | Direct oamMemory[512+] writes | High table |
hdmaSetup(ch, ...) | hdmaSetup(ch, ...) | Same |
hdmaEnable(ch) | hdmaEnable(1 << ch) | BITMASK not channel number! |
PVSnesLib's setColorEffect(CM_SUBBGOBJ_ENABLE, CM_MSCR_BACK | CM_MSCR_BG1) becomes:
colorMathInit();
colorMathSetSource(COLORMATH_SRC_SUBSCREEN);
colorMathSetOp(COLORMATH_ADD);
colorMathEnable(COLORMATH_BG1 | COLORMATH_BACKDROP);
/* BG tilemap address: shift word addr right by 8, OR with size */
REG_BG1SC = (0x2000 >> 8) | 0x00; /* SC_32x32 at VRAM $2000 */
/* Tile base: low nibble = BG1, high nibble = BG2 */
REG_BG12NBA = 0x01; /* BG1 at $1000, BG2 at $0000 */
REG_BG34NBA = 0x10; /* BG3 at $0000, BG4 at $1000 */
console — ALWAYS required (NMI handler)
sprite — ALWAYS with console (oamUpdate in NMI)
dma — ALWAYS with sprite (OAM DMA)
background — if using bgSetScroll or BG library functions
input — if using padPressed/padHeld
colormath — if using color math effects
hdma — if using HDMA effects
text — if displaying text (also needs dma)
text4bpp — if using 4bpp text (Mode 1/3)
snesmod — if using audio (also needs console)
Minimum viable: console sprite dma
Typical game: console sprite dma input background
OPENSNES := $(shell cd ../../../.. && pwd) # Adjust depth to reach SDK root
TARGET := example_name.sfc
ROM_NAME := EXAMPLE NAME HERE # 21 chars max
USE_LIB := 1
LIB_MODULES := console sprite dma input background
CSRC := main.c
ASMSRC := data.asm
# Optional: SNESMOD audio
# USE_SNESMOD := 1
# SOUNDBANK_SRC := res/music.it res/sfx.it
include $(OPENSNES)/make/common.mk
# Graphics rules (copy flags from PVSnesLib Makefile)
res/bg.pic res/bg.pal res/bg.map: res/bg.bmp
@echo "[GFX] Converting background..."
@$(GFX4SNES) <exact PVSnesLib flags> -i $<
combined.asm: res/bg.pic
clean: clean-example
.PHONY: clean-example
clean-example:
@rm -f res/*.pic res/*.pal res/*.map res/*.inc res/*_data.as
make clean && make./tests/compiler/run_tests.sh --allow-known-bugsOPENSNES_HOME=$(pwd) ./tests/examples/validate_examples.sh --quickluna mcp;
Category C protocol)hdmaEnable(1 << 3); // CORRECT: enable channel 3
hdmaEnable(3); // WRONG: enables channels 0+1 !
oamSet() has framesize=158. More than 2-3 calls per frame = jerky movement.
Write directly to oamMemory[] instead:
extern u8 oamMemory[];
extern volatile u8 oam_update_flag;
oamMemory[id*4 + 0] = (u8)x;
oamMemory[id*4 + 1] = (u8)y;
oamMemory[id*4 + 2] = tile;
oamMemory[id*4 + 3] = attr; // vhoopppc
oam_update_flag = 1;
PVSnesLib ASM functions ported verbatim have SWAPPED stack offsets.
f(a, b) → our compiler: b at lower SP offset, a at higher.
hdmaSetup() hardcodes bank $00 for ROM addresses. SUPERFREE const data
may be in bank $01+. Use non-const tables (RAM = bank $7E = $00 mirror)
or hdmaSetupBank() with explicit bank.
gfx4snes generates 32×28 tilemap (1792 bytes). SC_32x32 needs 2048 bytes. Unwritten rows 28-31 show VRAM garbage. Pad .map to 2048 bytes or use 256×256 source images.
NMI overhead (~8600 cycles) leaves ~41K cycles. DMA = 8 cycles/byte.
For large transfers, use forced blank (REG_INIDISP = 0x80) before DMA.
unsigned int is 4 bytes, unsigned long is 8 bytesNOT the x86 convention. Use u16 / u32 explicitly.
PIL's quantizer creates an entirely different palette. Either:
-t bmp)img.save() without quantize)NMI handler reads joypads directly. Don't call padUpdate().
Use padPressed(0) for new presses, padHeld(0) for held buttons.
Each static const array gets its own SUPERFREE section. If bank $00
($8000-$FFFF = 32KB) fills up, data spills to bank $01+ and
lda.l $0000,x reads garbage. Combine related const arrays.
Copy .it files from PVSnesLib example to res/.
USE_SNESMOD := 1
SOUNDBANK_SRC := res/music.it res/sfx.it
PVSnesLib audio API maps 1:1:
spcLoad(0) → spcLoad(0)spcPlay(0) → spcPlay(0)spcPlaySound(sfx) → spcPlaySound(sfx)spcProcess() → spcProcess()res/:label bank bytes-e flag)hdmaEnable(1 << ch) not hdmaEnable(ch)oamSet() in hot loops (use oamMemory[])make clean && make passesvalidate_examples.sh --quick passes