~rabbits/uxn-playdate

22cae7648276a71367d9c833f9bb0853ef85a964 — neauoire 5 months ago c8a7e65
Porting to new uxn code
M Makefile => Makefile +16 -36
@@ 1,26 1,29 @@
.PHONY: sim format

HEAP_SIZE      = 8388208
STACK_SIZE     = 61800

PRODUCT = Varvara.pdx

# Locate the SDK
ifeq (,$(PLAYDATE_SDK))
$(error Please set the PLAYDATE_SDK environment variable to your SDK directory)
endif
# ~/Documents/playdate/PlaydateSDK-1.12.3
SDK = ${PLAYDATE_SDK_PATH}

SDK := $(PLAYDATE_SDK)
ifeq ($(SDK),)
	SDK = $(shell egrep '^\s*SDKRoot' ~/.Playdate/config | head -n 1 | cut -c9-)
endif

# Download Uxn
ifeq (,$(wildcard uxn))
$(shell git clone https://git.sr.ht/~rabbits/uxn)
ifeq ($(SDK),)
$(error SDK path not found; set ENV value PLAYDATE_SDK_PATH)
endif

VPATH += uxn/src devices
######
# IMPORTANT: You must add your source folders to VPATH for make to find them
# ex: VPATH += src1:src2
######

VPATH += src src/devices

# List C source files here
SRC = main.c uxn/src/uxn-fast.c devices/apu.c devices/ppu.c
SRC = src/main.c src/uxn.c src/devices/apu.c src/devices/ppu.c

# List all user directories here
UINCDIR = 


@@ 40,31 43,8 @@ ULIBDIR =
# List all user libraries here
ULIBS =

include $(PLAYDATE_SDK)/C_API/buildsupport/common.mk

TARGET_ROM_SRC ?= uxn/projects/examples/devices/screen.tal
.PHONY: $(TARGET_ROM_SRC)
TARGET_ROM ?= boot.rom

all sim: $(PRODUCT)/$(TARGET_ROM)

uxn/src/uxn-fast.c uxn/src/uxnasm.c: uxn/build.sh

build/uxnasm: uxn/src/uxnasm.c
	mkdir -p $(dir $@)
	cc $(CFLAGS) $^ -o $@

$(PRODUCT)/$(TARGET_ROM): build/uxnasm $(TARGET_ROM_SRC)
	mkdir -p $(dir $@)
	$^ $@
include $(SDK)/C_API/buildsupport/common.mk

sim: all
	$(PLAYDATE_SDK)/bin/PlaydateSimulator $(abspath $(PRODUCT))

format:
	clang-format -i main.c
	clang-format -i devices/apu.h
	clang-format -i devices/apu.c
	clang-format -i devices/ppu.h
	clang-format -i devices/ppu.c
	$(SDK)/bin/PlaydateSimulator $(abspath $(PRODUCT))


A etc/controller.rom => etc/controller.rom +0 -0
A etc/controller.tal => etc/controller.tal +169 -0
@@ 0,0 1,169 @@
( Controller:
	Buttons should highlight on press and display the button and key bytes. )

|00 @System &vector $2 &wst $1 &rst $1 &pad $4 &r $2 &g $2 &b $2 &debug $1 &halt $1
|20 @Screen &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1
|80 @Controller &vector $2 &button $1 &key $1

|0000

@center
	&x $2 &y $2
@frame
	&w $2 &h $2 &x0 $2 &y0 $2 &x1 $2 &y1 $2

|0100 ( -> )

	( theme )
	#0fff .System/r DEO2
	#0f0f .System/g DEO2
	#0f0f .System/b DEO2
	( find center )
	.Screen/width DEI2 #01 SFT2 .center/x STZ2
	.Screen/height DEI2 #01 SFT2 .center/y STZ2
	( place controller )
	#0068 .frame/w STZ2
	#0030 .frame/h STZ2
	.center/x LDZ2 .frame/w LDZ2 #01 SFT2 SUB2 .frame/x0 STZ2
	.center/y LDZ2 .frame/h LDZ2 #01 SFT2 SUB2 .frame/y0 STZ2
	.frame/x0 LDZ2 .frame/w LDZ2 ADD2 .frame/x1 STZ2
	.frame/y0 LDZ2 .frame/h LDZ2 ADD2 .frame/y1 STZ2
	( vectors )
	;on-button .Controller/vector DEO2
	( frame )
	.frame/x0 LDZ2 .frame/y0 LDZ2
	.frame/x1 LDZ2 .frame/y1 LDZ2
		#03 ;line-rect JSR2

	,draw-controller JSR

BRK

@on-button ( -> )

	,draw-controller JSR

	( print stack on start button )
	.Controller/button DEI #08 EQU [ JMP BRK ] #010e DEO

BRK

@draw-controller ( -- )

	.Controller/button DEI STH

	( d-pad )
	.frame/x0 LDZ2 #0010 ADD2 .Screen/x DEO2
	.frame/y0 LDZ2 #0010 ADD2 .Screen/y DEO2
	;controller-icn/dpad-up .Screen/addr DEO2
	#03 STHkr #04 SFT #01 AND SUB .Screen/sprite DEO
	.Screen/y DEI2 #0010 ADD2 .Screen/y DEO2
	;controller-icn/dpad-down .Screen/addr DEO2
	#03 STHkr #05 SFT #01 AND SUB .Screen/sprite DEO
	.Screen/y DEI2 #0008 SUB2 .Screen/y DEO2
	.Screen/x DEI2 #0008 SUB2 .Screen/x DEO2
	;controller-icn/dpad-left .Screen/addr DEO2
	#03 STHkr #06 SFT #01 AND SUB .Screen/sprite DEO
	.Screen/x DEI2 #0010 ADD2 .Screen/x DEO2
	;controller-icn/dpad-right .Screen/addr DEO2
	#03 STHkr #07 SFT #01 AND SUB .Screen/sprite DEO
	.Screen/x DEI2 #0008 SUB2 .Screen/x DEO2
	;controller-icn/dpad .Screen/addr DEO2
	#03 .Screen/sprite DEO

	( options )
	.center/y LDZ2 #0009 ADD2 .Screen/y DEO2
	.center/x LDZ2 #0009 SUB2 .Screen/x DEO2
	;controller-icn/option .Screen/addr DEO2
	#03 STHkr #03 SFT #01 AND SUB .Screen/sprite DEO
	.center/x LDZ2 #0004 ADD2 .Screen/x DEO2
	;controller-icn/option .Screen/addr DEO2
	#03 STHkr #02 SFT #01 AND SUB .Screen/sprite DEO

	( buttons )
	.center/y LDZ2 .Screen/y DEO2
	.center/x LDZ2 #0018 ADD2 .Screen/x DEO2
	;controller-icn/button .Screen/addr DEO2
	#03 STHkr #01 SFT #01 AND SUB .Screen/sprite DEO
		.Screen/y DEI2 #000a ADD2 .Screen/y DEO2
		;font-hex/b .Screen/addr DEO2
		#03 .Screen/sprite DEO

	.center/y LDZ2 .Screen/y DEO2
	.center/x LDZ2 #0024 ADD2 .Screen/x DEO2
	;controller-icn/button .Screen/addr DEO2
	#03 STHr #01 AND SUB .Screen/sprite DEO
		.Screen/y DEI2 #000a ADD2 .Screen/y DEO2
		;font-hex/a .Screen/addr DEO2
		#03 .Screen/sprite DEO

	.center/x LDZ2 #0010 SUB2 .Screen/x DEO2
	.center/y LDZ2 #0010 SUB2 .Screen/y DEO2
	#01 .Screen/auto DEO
	.Controller/button DEI2 ,draw-short JSR
	#00 .Screen/auto DEO

JMP2r

( generics )

@draw-short ( short* -- )

	SWP ,draw-byte JSR

@draw-byte ( byte -- )

	DUP #04 SFT ,draw-hex JSR 

@draw-hex ( char -- )

	#00 SWP #0f AND #30 SFT2 ;font-hex ADD2 .Screen/addr DEO2
	#03 .Screen/sprite DEO

JMP2r

@line-rect ( x1* y1* x2* y2* color -- )

	STH
	DUP2 ,&ver-y2 STR2 ,&hor-y2 STR2
	DUP2 ,&ver-x2 STR2 ,&hor-x2 STR2
	DUP2 ,&ver-y1 STR2 ,&hor-y1 STR2
	DUP2 ,&ver-x1 STR2 ,&hor-x1 STR2
	( horizontal )
	[ LIT2 &hor-x2 $2 ] INC2 [ LIT2 &hor-x1 $2 ]
	&hor
		DUP2 .Screen/x DEO2
		[ LIT2 &hor-y1 $2 ] .Screen/y DEO2 STHkr .Screen/pixel DEOk
		[ LIT2 &hor-y2 $2 ] .Screen/y DEO2 DEO
		INC2 GTH2k ,&hor JCN
	POP2 POP2
	( vertical )
	[ LIT2 &ver-y2 $2 ] [ LIT2 &ver-y1 $2 ]
	&ver
		DUP2 .Screen/y DEO2
		[ LIT2 &ver-x1 $2 ] .Screen/x DEO2 STHkr .Screen/pixel DEOk
		[ LIT2 &ver-x2 $2 ] .Screen/x DEO2 DEO
		INC2 GTH2k ,&ver JCN
	POP2 POP2
	POPr

JMP2r

@controller-icn
	&dpad       ffff ffff ffff ffff
	&dpad-up    7eff e7c3 ffff ffff
	&dpad-down  ffff ffff c3e7 ff7e
	&dpad-left  7fff efcf cfef ff7f
	&dpad-right feff f7f3 f3f7 fffe
	&option     0000 7eff ff7e 0000
	&button     3c7e ffff ffff 7e3c

@font-hex
	007c 8282 8282 827c 0030 1010 1010 1010
	007c 8202 7c80 80fe 007c 8202 1c02 827c
	000c 1424 4484 fe04 00fe 8080 7c02 827c
	007c 8280 fc82 827c 00fe 0202 0408 1010
	007c 8282 7c82 827c 007c 8282 7e02 827c
	&a 007c 8202 7e82 827e &b 00fc 8282 fc82 82fc
	007c 8280 8080 827c 00fc 8282 8282 82fc
	00fe 8080 fe80 80fe 00fe 8080 f080 8080

A old/Makefile => old/Makefile +70 -0
@@ 0,0 1,70 @@
.PHONY: sim format

HEAP_SIZE      = 8388208
STACK_SIZE     = 61800

PRODUCT = Varvara.pdx

# Locate the SDK
ifeq (,$(PLAYDATE_SDK))
$(error Please set the PLAYDATE_SDK environment variable to your SDK directory)
endif

SDK := $(PLAYDATE_SDK)

# Download Uxn
ifeq (,$(wildcard uxn))
$(shell git clone https://git.sr.ht/~rabbits/uxn)
endif

VPATH += uxn/src devices

# List C source files here
SRC = main.c uxn/src/uxn.c devices/apu.c devices/ppu.c

# List all user directories here
UINCDIR = 

# List user asm files
UASRC = 

# List all user C define here, like -D_DEBUG=1
UDEFS = 

# Define ASM defines here
UADEFS = 

# List the user directory to look for the libraries here
ULIBDIR =

# List all user libraries here
ULIBS =

include $(PLAYDATE_SDK)/C_API/buildsupport/common.mk

TARGET_ROM_SRC ?= uxn/projects/examples/devices/screen.tal
.PHONY: $(TARGET_ROM_SRC)
TARGET_ROM ?= boot.rom

all sim: $(PRODUCT)/$(TARGET_ROM)

uxn/src/uxn.c uxn/src/uxnasm.c: uxn/build.sh

build/uxnasm: uxn/src/uxnasm.c
	mkdir -p $(dir $@)
	cc $(CFLAGS) $^ -o $@

$(PRODUCT)/$(TARGET_ROM): build/uxnasm $(TARGET_ROM_SRC)
	mkdir -p $(dir $@)
	$^ $@

sim: all
	$(PLAYDATE_SDK)/bin/PlaydateSimulator $(abspath $(PRODUCT))

format:
	clang-format -i main.c
	clang-format -i devices/apu.h
	clang-format -i devices/apu.c
	clang-format -i devices/ppu.h
	clang-format -i devices/ppu.c


A old/main.c => old/main.c +415 -0
@@ 0,0 1,415 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include "pd_api.h"
#include "uxn/src/uxn.h"
#include "devices/apu.h"
#include "devices/ppu.h"

#define POLYPHONY 4
#define BOOT_ROM_FILENAME "boot.rom"
#define SNAPSHOT_FILENAME "uxn.sna"
#define SNAPSHOT_VERSION 1

static PlaydateAPI *pd = NULL;
static SoundSource *apu_sources[POLYPHONY];
static Uxn u;
static Device *devctrl, *devconsole, *devscreen, *devmouse, *devdatetime, *devaudio0;
static Ppu ppu;
static Apu apu[POLYPHONY];

static const char *errors[] = {
	"Working-stack underflow",
	"Return-stack underflow",
	"Working-stack overflow",
	"Return-stack overflow",
	"Working-stack division by zero",
	"Return-stack division by zero",
	"Execution timeout"};

int
uxn_halt(Uxn *u, Uint8 error, Uint16 addr)
{
	pd->system->error("Halted: %s#%04x, at 0x%04x\n", errors[error], u->ram[addr], addr);
	return 0;
}

static void
docolors(Device *d)
{
	int i, sum;
	for(i = 0; i < 4; ++i) {
		sum = ((d->dat[0x8 + i / 2] >> (!(i % 2) << 2)) & 0x0f);
		sum += ((d->dat[0xa + i / 2] >> (!(i % 2) << 2)) & 0x0f);
		sum += ((d->dat[0xc + i / 2] >> (!(i % 2) << 2)) & 0x0f);
		ppu.color_is_white[i] = sum > 0x17;
	}
	ppu_clear(&ppu);
	render_all(&ppu);
}

static uint8_t
getcolors(int i)
{
	return (ppu.color_is_white[i] ? 0xf0 : 0) + (ppu.color_is_white[i + 1] ? 0x0f : 0);
}

static int
system_talk(Device *d, Uint8 b0, Uint8 w)
{
	if(!w) { /* read */
		switch(b0) {
		case 0x8 ... 0xd: d->dat[b0] = getcolors((b0 & 1) << 1); break;
		}
	} else { /* write */
		switch(b0) {
		case 0x8 ... 0xd: docolors(d); break;
		}
	}
	return 1;
}

static int
console_talk(Device *d, Uint8 b0, Uint8 w)
{
	if(w && b0 > 0x7)
		pd->system->logToConsole("%c", d->dat[b0]);
	return 1;
}

static int
screen_talk(Device *d, Uint8 b0, Uint8 w)
{
	/*
	if(w && b0 == 0xe) {
		Uint16 x = peek16(d->dat, 0x8);
		Uint16 y = peek16(d->dat, 0xa);
		Uint8 layer = d->dat[0xe] & 0x40;
		ppu_pixel(&ppu, !!layer, x, y, d->dat[0xe] & 0x3);
		if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 1); 
		if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 1); 
		pd->graphics->markUpdatedRows(y, y);
	} else if(w && b0 == 0xf) {
		Uint16 x = peek16(d->dat, 0x8);
		Uint16 y = peek16(d->dat, 0xa);
		Uint8 layer = d->dat[0xf] & 0x40;
		Uint8 *addr = &d->mem[peek16(d->dat, 0xc)];
		if(d->dat[0xf] & 0x80) {
			ppu_2bpp(&ppu, !!layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20);
			if(d->dat[0x6] & 0x04) poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 16); 
		} else {
			ppu_1bpp(&ppu, !!layer, x, y, addr, d->dat[0xf] & 0xf, d->dat[0xf] & 0x10, d->dat[0xf] & 0x20);
			if(d->dat[0x6] & 0x04) poke16(d->dat, 0xc, peek16(d->dat, 0xc) + 8); 
		}
		if(d->dat[0x6] & 0x01) poke16(d->dat, 0x8, x + 8); 
		if(d->dat[0x6] & 0x02) poke16(d->dat, 0xa, y + 8); 
		pd->graphics->markUpdatedRows(y, y + 7);
	}
	*/
	return 1;
}

static int
file_talk(Device *d, Uint8 b0, Uint8 w)
{
	/*
	Uint8 read = b0 == 0xd;
	if(w && (read || b0 == 0xf)) {
		char *name = (char *)&d->mem[peek16(d->dat, 0x8)];
		Uint16 result = 0, length = peek16(d->dat, 0xa);
		Uint16 offset = peek16(d->dat, 0x4);
		Uint16 addr = peek16(d->dat, b0 - 1);
		SDFile *f = pd->file->open(name, read ? (kFileRead | kFileReadData) : (offset ? kFileAppend : kFileWrite));
		if(f) {
			pd->system->logToConsole("%s %s %s #%04x, ", read ? "Loading" : "Saving", name, read ? "to" : "from", addr);
			if(pd->file->seek(f, offset, SEEK_SET) != -1)
				result = read ? pd->file->read(f, &d->mem[addr], length) : pd->file->write(f, &d->mem[addr], length);
			pd->system->logToConsole("%04x bytes\n", result);
			pd->file->close(f);
		} else {
			pd->system->logToConsole("unable to open %s", name);
		}
		poke16(d->dat, 0x2, result);
	}
	*/
	return 1;
}

static int
audio_talk(Device *d, Uint8 b0, Uint8 w)
{
	Apu *c = &apu[d - devaudio0];
	if(!w) {
		if(b0 == 0x2)
			d->dat[0x2] = 0x0;
			/* poke16(d->dat, 0x2, c->i); */
		else if(b0 == 0x4)
			d->dat[0x4] = apu_get_vu(c);
	} else if(b0 == 0xf) {
		c->len = PEEK16(d->dat, 0xa);
		c->addr = PEEK16(d->dat, 0xc);
		c->volume[0] = d->dat[0xe] >> 4;
		c->volume[1] = d->dat[0xe] & 0xf;
		c->repeat = !(d->dat[0xf] & 0x80);
		apu_start(c, PEEK16(d->dat, 0x8), d->dat[0xf] & 0x7f);
	}
	return 1;
}

static int
nil_talk(Device *d, Uint8 b0, Uint8 w)
{
	(void)d;
	(void)b0;
	(void)w;
	return 1;
}

static int
update_datetime(lua_State *L)
{
	// DateTime
	int i;
	for(i = 0; i < 11; ++i)
		devdatetime->dat[i] = pd->lua->getArgInt(i + 1);
	return 0;
}

static int
update_controller(lua_State *L)
{
	// Buttons
	PDButtons state, pushed, released;
	pd->system->getButtonState(&state, &pushed, &released);
	if(pushed || released) {
		devctrl->dat[2] = 0x00;
		if(state & kButtonA)
			devctrl->dat[2] |= 0x01;
		if(state & kButtonB)
			devctrl->dat[2] |= 0x02;
		if(state & kButtonUp)
			devctrl->dat[2] |= 0x10;
		if(state & kButtonDown)
			devctrl->dat[2] |= 0x20;
		if(state & kButtonLeft)
			devctrl->dat[2] |= 0x40;
		if(state & kButtonRight)
			devctrl->dat[2] |= 0x80;
		uxn_eval(&u, PEEK16(devctrl->dat, 0));
	}

	// Crank
	unsigned char crank = pd->system->getCrankChange();
	if(crank) {
		devmouse->dat[7] = crank;
		uxn_eval(&u, PEEK16(devmouse->dat, 0));
		devmouse->dat[7] = 0x00;
	}

	return 0;
}

static int
update_mouse(lua_State *L)
{
	int i;
	for(i = 0; i < 5; ++i) {
		devmouse->dat[i + 2] = pd->lua->getArgInt(i + 1);
	}
	uxn_eval(&u, PEEK16(devmouse->dat, 0));
	return 0;
}

static int
update_screen(lua_State *L)
{
	uxn_eval(&u, PEEK16(devscreen->dat, 0));
	return 0;
}

static int
audio_callback(void *context, int16_t *left, int16_t *right, int len)
{
	return apu_render((Apu *)context, u.ram, left, right, len);
}

static int
varvara_init_once(void)
{
	int i;
	SoundChannel *ch = pd->sound->getDefaultChannel();
	for(i = 0; i < POLYPHONY; ++i) {
		apu_sources[i] = pd->sound->channel->addCallbackSource(ch, audio_callback, &apu[i], 1);
	}
	pd->sound->channel->setVolume(ch, 1.0);
	pd->sound->channel->setPan(ch, 0.0);
	return 1;
}

static int
post_load_init(void)
{
	if(!ppu_init(&ppu, (uint32_t *)pd->graphics->getFrame()))
		return 0;
	render_all(&ppu);
/*
	uxn_port(&u, 0x0, system_talk);
	devconsole = uxn_port(&u, 0x1, console_talk);
	devscreen = uxn_port(&u, 0x2, screen_talk);
	devaudio0 = uxn_port(&u, 0x3, audio_talk);
	uxn_port(&u, 0x4, audio_talk);
	uxn_port(&u, 0x5, audio_talk);
	uxn_port(&u, 0x6, audio_talk);
	uxn_port(&u, 0x7, nil_talk);
	devctrl = uxn_port(&u, 0x8, nil_talk);
	devmouse = uxn_port(&u, 0x9, nil_talk);
	uxn_port(&u, 0xa, file_talk);
	devdatetime = uxn_port(&u, 0xb, nil_talk);
	uxn_port(&u, 0xc, nil_talk);
	uxn_port(&u, 0xd, nil_talk);
	uxn_port(&u, 0xe, nil_talk);
	uxn_port(&u, 0xf, nil_talk);
*/
	return 1;
}

static int
load(Uxn *u, char *filepath)
{
	SDFile *f = pd->file->open(filepath, kFileRead | kFileReadData);
	if(f == NULL) {
		pd->system->error("Failed to open rom.");
		return 0;
	}
	pd->file->read(f, u->ram + PAGE_PROGRAM, sizeof(u->ram) - PAGE_PROGRAM);
	pd->system->logToConsole("Loaded %s\n", filepath);
	return post_load_init();
}

static int
reset(Uxn *u, char *filepath)
{
	free(u->ram);
	if(!uxn_boot(u, calloc(0x10000, 1))) {
		pd->system->error("Failed to start uxn.");
		return 0;
	}
	memset(&ppu, 0, sizeof(ppu));
	memset(&apu, 0, sizeof(apu));
	if(!load(u, filepath))
		return 0;

	uxn_eval(u, PAGE_PROGRAM);
	return 1;
}

static int
menu_reset(lua_State *L)
{
	if(!reset(&u, BOOT_ROM_FILENAME))
		pd->system->error("Failed to reset Uxn.");
	return 0;
}

static int
freeze(void)
{
	SDFile *f = pd->file->open(SNAPSHOT_FILENAME, kFileWrite);
	if(f == NULL) {
		pd->system->error("Failed to open snapshot for writing.");
		return 0;
	}
	int version = SNAPSHOT_VERSION;
	/* clang-format off */
	if(pd->file->write(f, &version, sizeof(version)) != sizeof(version)
			|| pd->file->write(f, &u, sizeof(u)) != sizeof(u)
			|| pd->file->write(f, &ppu, sizeof(ppu)) != sizeof(ppu)
			|| pd->file->write(f, &apu, sizeof(apu)) != sizeof(apu)) {
		pd->system->error("Failed to write snapshot data.");
		pd->file->close(f);
		return 0;
	}
	pd->file->close(f);
	/* clang-format on */
	return 1;
}

static int
thaw(void)
{
	SDFile *f = pd->file->open(SNAPSHOT_FILENAME, kFileRead | kFileReadData);
	if(f == NULL)
		return 0;
	int version;
	/* clang-format off */
	if(pd->file->read(f, &version, sizeof(version)) != sizeof(version)
			|| pd->file->read(f, &u, sizeof(u)) != sizeof(u)
			|| pd->file->read(f, &ppu, sizeof(ppu)) != sizeof(ppu)
			|| pd->file->read(f, &apu, sizeof(apu)) != sizeof(apu)) {
		pd->file->close(f);
		return 0;
	}
	pd->file->close(f);
	/* clang-format on */
	if(version != SNAPSHOT_VERSION)
		return 0;
	return post_load_init();
}

struct lua_function_pair {
	lua_CFunction f;
	const char *name;
};

static struct lua_function_pair fns[] = {
	{update_datetime, "varvaraDateTime"},
	{update_controller, "varvaraController"},
	{update_mouse, "varvaraMouse"},
	{update_screen, "varvaraScreen"},
	{menu_reset, "varvaraReset"}};

int
eventHandler(PlaydateAPI *playdate, PDSystemEvent event, uint32_t arg)
{
	switch(event) {
	case kEventInit:
		pd = playdate;
		pd->display->setRefreshRate(60);

		if(!varvara_init_once())
			pd->system->error("Failed to initialize Varvara.");

		if(!thaw() && !reset(&u, BOOT_ROM_FILENAME))
			pd->system->error("Failed to reset Uxn.");

		break;

	case kEventInitLua: {
		const char *err;
		int i;
		for(i = 0; i < sizeof(fns) / sizeof(struct lua_function_pair); ++i) {
			if(!pd->lua->addFunction(fns[i].f, fns[i].name, &err))
				pd->system->error("Failed to create function: %s", err);
		}
		break;
	}

	case kEventTerminate:
	case kEventLowPower:
		freeze();
		break;

	default:
		break;
	}
	return 0;
}

A src/devices/apu.c => src/devices/apu.c +95 -0
@@ 0,0 1,95 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include "apu.h"

#define NOTE_PERIOD (SAMPLE_FREQUENCY * 0x4000 / 11025)
#define ADSR_STEP (SAMPLE_FREQUENCY / 0xf)

/* clang-format off */

static uint32_t advances[12] = {
	0x80000, 0x879c8, 0x8facd, 0x9837f, 0xa1451, 0xaadc1,
	0xb504f, 0xbfc88, 0xcb2ff, 0xd7450, 0xe411f, 0xf1a1c
};

/* clang-format on */

static int32_t
envelope(Apu *c, uint32_t age)
{
	if(!c->r) return 0x0888;
	if(age < c->a) return 0x0888 * age / c->a;
	if(age < c->d) return 0x0444 * (2 * c->d - c->a - age) / (c->d - c->a);
	if(age < c->s) return 0x0444;
	if(age < c->r) return 0x0444 * (c->r - age) / (c->r - c->s);
	c->advance = 0;
	return 0x0000;
}

int
apu_render(Apu *c, uint8_t *dat, int16_t *left, int16_t *right, int len)
{
	int32_t s;
	if(!c->advance || !c->period) return 0;
	while(len--) {
		c->count += c->advance;
		c->i += c->count / c->period;
		c->count %= c->period;
		if(c->i >= c->len) {
			if(!c->repeat) {
				c->advance = 0;
				break;
			}
			c->i %= c->len;
		}
		s = (int8_t)(dat[c->addr + c->i] + 0x80) * envelope(c, c->age++);
		*left++ = s * c->volume[0] / 0x180;
		*right++ = s * c->volume[1] / 0x180;
	}
	return 1;
}

void
apu_start(Apu *c, uint16_t adsr, uint8_t pitch)
{
	if(pitch < 108 && c->len)
		c->advance = advances[pitch % 12] >> (8 - pitch / 12);
	else {
		c->advance = 0;
		return;
	}
	c->a = ADSR_STEP * (adsr >> 12);
	c->d = ADSR_STEP * (adsr >> 8 & 0xf) + c->a;
	c->s = ADSR_STEP * (adsr >> 4 & 0xf) + c->d;
	c->r = ADSR_STEP * (adsr >> 0 & 0xf) + c->s;
	c->age = 0;
	c->i = 0;
	if(c->len <= 0x100) /* single cycle mode */
		c->period = NOTE_PERIOD * 337 / 2 / c->len;
	else /* sample repeat mode */
		c->period = NOTE_PERIOD;
}

uint8_t
apu_get_vu(Apu *c)
{
	int i;
	int32_t sum[2] = {0, 0};
	if(!c->advance || !c->period) return 0;
	for(i = 0; i < 2; ++i) {
		if(!c->volume[i]) continue;
		sum[i] = 1 + envelope(c, c->age) * c->volume[i] / 0x800;
		if(sum[i] > 0xf) sum[i] = 0xf;
	}
	return (sum[0] << 4) | sum[1];
}

A src/devices/apu.h => src/devices/apu.h +27 -0
@@ 0,0 1,27 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include <stdint.h>

#define SAMPLE_FREQUENCY 44100

typedef struct {
	uint32_t count, advance, period, age, a, d, s, r;
	uint16_t addr, i, len;
	int8_t volume[2];
	uint8_t pitch, repeat;
} Apu;

int apu_render(Apu *c, uint8_t *dat, int16_t *left, int16_t *right, int len);
void apu_start(Apu *c, uint16_t adsr, uint8_t pitch);
uint8_t apu_get_vu(Apu *c);
void apu_finished_handler(Apu *c);

A src/devices/ppu.c => src/devices/ppu.c +109 -0
@@ 0,0 1,109 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include "ppu.h"

int
ppu_init(Ppu *p, uint32_t *framebuffer)
{
	p->framebuffer = framebuffer;
	return 1;
}

static inline void
render(Ppu *p, int offset, int count)
{
	uint32_t *in = &p->pixels[offset * 3];
	uint32_t *out = &p->framebuffer[offset];
	while(count--) {
		*(out++) = in[0] ^ (in[2] & (in[0] ^ in[1]));
		in += 3;
	}
}

void
render_all(Ppu *p)
{
	render(p, 0, STRIDE_WORDS * HEIGHT);
}

void
ppu_pixel(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t color)
{
	if(x >= WIDTH_PIXELS || y >= HEIGHT) return;
	int offset = y * STRIDE_WORDS + x / 32;
	uint32_t *px = &p->pixels[offset * 3 + layer];
	uint32_t mask = 1 << ((x & 0x18) + (7 - (x & 0x7)));
	if(color & 0x01)
		p->pixels[offset * 3 + layer] |= mask;
	else
		p->pixels[offset * 3 + layer] &= ~mask;
	if(layer) {
		if(color)
			p->pixels[offset * 3 + 2] |= mask;
		else
			p->pixels[offset * 3 + 2] &= ~mask;
	}
	render(p, offset, 1);
}

void
ppu_clear(Ppu *p)
{
	uint32_t background = p->color_is_white[0] ? 0xffffffff : 0;
	int i;
	for(i = 0; i < HEIGHT * STRIDE_WORDS * 3; i += 3) {
		p->pixels[i] = background;
		p->pixels[i + 2] = 0;
	}
}

static uint8_t blending[5][16] = {
	{0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
	{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
	{1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
	{2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2},
	{1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}};

void
ppu_1bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy)
{
	uint16_t v, h;
	for(v = 0; v < 8; v++)
		for(h = 0; h < 8; h++) {
			uint8_t ch1 = (sprite[v] >> (7 - h)) & 0x1;
			if(ch1 || blending[4][color])
				ppu_pixel(p,
					layer,
					x + (flipx ? 7 - h : h),
					y + (flipy ? 7 - v : v),
					blending[ch1][color]);
		}
}

void
ppu_2bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy)
{
	uint16_t v, h;
	for(v = 0; v < 8; v++)
		for(h = 0; h < 8; h++) {
			uint8_t ch1 = ((sprite[v] >> (7 - h)) & 0x1);
			uint8_t ch2 = ((sprite[v + 8] >> (7 - h)) & 0x1);
			uint8_t ch = ch1 + ch2 * 2;
			if(ch || blending[4][color])
				ppu_pixel(p,
					layer,
					x + (flipx ? 7 - h : h),
					y + (flipy ? 7 - v : v),
					blending[ch][color]);
		}
}

A src/devices/ppu.h => src/devices/ppu.h +46 -0
@@ 0,0 1,46 @@
/*
Copyright (c) 2021 Devine Lu Linvega
Copyright (c) 2021 Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#include <stdint.h>

#define WIDTH_PIXELS 400
#define STRIDE_PIXELS (52 * 8)
#define STRIDE_WORDS (STRIDE_PIXELS / 32)
#define HEIGHT 240

/*
 * framebuffer is the Playdate's raw framebuffer.
 *
 * pixels is the buffer for Varvara's two graphics layers and are grouped in
 * three sets of 32 bits.
 *
 * pixels[0] carries the background color of the first 32 pixels,
 * pixels[1] carries the foreground color, and
 * pixels[2] carries the mask that switches between the two.
 *
 * Zero color bits are white, ones are black.
 * Zero mask bits mean that the background color is shown, one is for the
 * foreground color.
 */

typedef struct Ppu {
	uint32_t pixels[HEIGHT * STRIDE_WORDS * 3];
	uint32_t *framebuffer;
	int color_is_white[4];
} Ppu;

int ppu_init(Ppu *p, uint32_t *framebuffer);
void ppu_pixel(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t color);
void ppu_1bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy);
void ppu_2bpp(Ppu *p, uint8_t layer, uint16_t x, uint16_t y, uint8_t *sprite, uint8_t color, uint8_t flipx, uint8_t flipy);
void ppu_clear(Ppu *p);
void render_all(Ppu *p);

A src/devices/screen.c => src/devices/screen.c +184 -0
@@ 0,0 1,184 @@
#include <stdlib.h>

#include "../uxn.h"
#include "screen.h"

/*
Copyright (c) 2021 Devine Lu Linvega, Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

UxnScreen uxn_screen;

static Uint8 blending[5][16] = {
	{0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
	{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
	{1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
	{2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2},
	{1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}};

static void
screen_write(UxnScreen *p, Layer *layer, Uint16 x, Uint16 y, Uint8 color)
{
	if(x < p->width && y < p->height) {
		Uint32 i = x + y * p->width;
		if(color != layer->pixels[i]) {
			layer->pixels[i] = color;
			layer->changed = 1;
		}
	}
}

static void
screen_blit(UxnScreen *p, Layer *layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy, Uint8 twobpp)
{
	int v, h, opaque = blending[4][color];
	for(v = 0; v < 8; v++) {
		Uint16 c = sprite[v] | (twobpp ? sprite[v + 8] : 0) << 8;
		for(h = 7; h >= 0; --h, c >>= 1) {
			Uint8 ch = (c & 1) | ((c >> 7) & 2);
			if(opaque || ch)
				screen_write(p,
					layer,
					x + (flipx ? 7 - h : h),
					y + (flipy ? 7 - v : v),
					blending[ch][color]);
		}
	}
}

static void
layer_clear(UxnScreen *p, Layer *layer)
{
	Uint32 i, size = p->width * p->height;
	for(i = 0; i < size; i++)
		layer->pixels[i] = 0x00;
	layer->changed = 1;
}

void
screen_palette(UxnScreen *p, Uint8 *addr)
{
	int i, shift;
	for(i = 0, shift = 4; i < 4; ++i, shift ^= 4) {
		Uint8
			r = (addr[0 + i / 2] >> shift) & 0x0f,
			g = (addr[2 + i / 2] >> shift) & 0x0f,
			b = (addr[4 + i / 2] >> shift) & 0x0f;
		p->palette[i] = 0x0f000000 | r << 16 | g << 8 | b;
		p->palette[i] |= p->palette[i] << 4;
	}
	p->fg.changed = p->bg.changed = 1;
}

void
screen_resize(UxnScreen *p, Uint16 width, Uint16 height, Uint32 *pixels)
{
	Uint8
		*bg = realloc(p->bg.pixels, width * height),
		*fg = realloc(p->fg.pixels, width * height);
	if(bg) p->bg.pixels = bg;
	if(fg) p->fg.pixels = fg;
	if(pixels) p->pixels = pixels;
	if(bg && fg && pixels) {
		p->width = width;
		p->height = height;
		screen_clear(p);
	}
}

void
screen_clear(UxnScreen *p)
{
	layer_clear(p, &p->bg);
	layer_clear(p, &p->fg);
}

void
screen_redraw(UxnScreen *p, Uint32 *pixels)
{
	Uint32 i, size = p->width * p->height, palette[16];
	for(i = 0; i < 16; i++)
		palette[i] = p->palette[(i >> 2) ? (i >> 2) : (i & 3)];
	for(i = 0; i < size; i++)
		pixels[i] = palette[p->fg.pixels[i] << 2 | p->bg.pixels[i]];
	p->fg.changed = p->bg.changed = 0;
}

int
clamp(int val, int min, int max)
{
	return (val >= min) ? (val <= max) ? val : max : min;
}

/* IO */

Uint8
screen_dei(Uint8 *d, Uint8 port)
{
	switch(port) {
	case 0x2: return uxn_screen.width >> 8;
	case 0x3: return uxn_screen.width;
	case 0x4: return uxn_screen.height >> 8;
	case 0x5: return uxn_screen.height;
	default: return d[port];
	}
}

void
screen_deo(Uint8 *ram, Uint8 *d, Uint8 port)
{
	switch(port) {
	case 0x3:
		if(!FIXED_SIZE) {
			Uint16 w;
			PEKDEV(w, 0x2);
			screen_resize(&uxn_screen, clamp(w, 1, 1024), uxn_screen.height, uxn_screen.pixels);
		}
		break;
	case 0x5:
		if(!FIXED_SIZE) {
			Uint16 h;
			PEKDEV(h, 0x4);
			screen_resize(&uxn_screen, uxn_screen.width, clamp(h, 1, 1024), uxn_screen.pixels);
		}
		break;
	case 0xe: {
		Uint16 x, y;
		Uint8 layer = d[0xe] & 0x40;
		PEKDEV(x, 0x8);
		PEKDEV(y, 0xa);
		screen_write(&uxn_screen, layer ? &uxn_screen.fg : &uxn_screen.bg, x, y, d[0xe] & 0x3);
		if(d[0x6] & 0x01) POKDEV(0x8, x + 1); /* auto x+1 */
		if(d[0x6] & 0x02) POKDEV(0xa, y + 1); /* auto y+1 */
		break;
	}
	case 0xf: {
		Uint16 x, y, dx, dy, addr;
		Uint8 i, n, twobpp = !!(d[0xf] & 0x80);
		Layer *layer = (d[0xf] & 0x40) ? &uxn_screen.fg : &uxn_screen.bg;
		PEKDEV(x, 0x8);
		PEKDEV(y, 0xa);
		PEKDEV(addr, 0xc);
		n = d[0x6] >> 4;
		dx = (d[0x6] & 0x01) << 3;
		dy = (d[0x6] & 0x02) << 2;
		if(addr > 0x10000 - ((n + 1) << (3 + twobpp)))
			return;
		for(i = 0; i <= n; i++) {
			screen_blit(&uxn_screen, layer, x + dy * i, y + dx * i, &ram[addr], d[0xf] & 0xf, d[0xf] & 0x10, d[0xf] & 0x20, twobpp);
			addr += (d[0x6] & 0x04) << (1 + twobpp);
		}
		POKDEV(0xc, addr);   /* auto addr+length */
		POKDEV(0x8, x + dx); /* auto x+8 */
		POKDEV(0xa, y + dy); /* auto y+8 */
		break;
	}
	}
}

A src/devices/screen.h => src/devices/screen.h +33 -0
@@ 0,0 1,33 @@
/*
Copyright (c) 2021 Devine Lu Linvega, Andrew Alderwick

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

#define FIXED_SIZE 1

typedef struct Layer {
	Uint8 *pixels, changed;
} Layer;

typedef struct UxnScreen {
	Uint32 palette[4], *pixels;
	Uint16 width, height;
	Layer fg, bg;
} UxnScreen;

extern UxnScreen uxn_screen;

void screen_palette(UxnScreen *p, Uint8 *addr);
void screen_resize(UxnScreen *p, Uint16 width, Uint16 height, Uint32 *pixels);
void screen_clear(UxnScreen *p);
void screen_redraw(UxnScreen *p, Uint32 *pixels);

Uint8 screen_dei(Uint8 *d, Uint8 port);
void screen_deo(Uint8 *ram, Uint8 *d, Uint8 port);
int clamp(int val, int min, int max);

A src/main-old.c => src/main-old.c +237 -0
@@ 0,0 1,237 @@

#include "pd_api.h"
#include "uxn.h"
#include "devices/apu.h"
#include "devices/ppu.h"

static PlaydateAPI *pd = NULL;

Uxn u;
Ppu ppu;

/*
static unsigned char rom[] = {
	0xa0, 0x01, 0x39, 0x94, 0x80, 0x18, 0x17, 0x21, 0x94, 0x80, 0xf7, 0x0d, 0x22, 0x80, 0x22, 0x36, 
	0x80, 0x01, 0x3f, 0xa0, 0x00, 0x08, 0x39, 0x80, 0x28, 0x37, 0x80, 0x24, 0x36, 0x80, 0x01, 0x3f, 
	0xa0, 0x00, 0x08, 0x39, 0x80, 0x2a, 0x37, 0x80, 0x16, 0x80, 0x26, 0x17, 0xa0, 0x01, 0x47, 0x80, 
	0x2c, 0x37, 0x80, 0x01, 0x80, 0x2f, 0x97, 0x17, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 
	0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x40, 0x48, 0x48, 0x00, 
	0xe0, 0x18, 0x04, 0x04, 0x02, 0x12, 0x12, 0x40, 0x40, 0x44, 0x23, 0x20, 0x18, 0x07, 0x00, 0x02, 
	0x02, 0x22, 0xc4, 0x04, 0x18, 0xe0, 0x00
};

*/

unsigned char rom[] = {
	0xa0, 0x4c, 0xfd, 0x80, 0x08, 0x37, 0xa0, 0x4c, 0xf3, 0x80, 0x0a, 0x37, 0xa0, 0xdc, 0xf2, 0x80, 
	0x0c, 0x37, 0xa0, 0x01, 0x4b, 0x80, 0x20, 0x37, 0x80, 0x22, 0x36, 0x80, 0x01, 0x3f, 0x80, 0x00, 
	0x31, 0x80, 0x24, 0x36, 0x80, 0x01, 0x3f, 0x80, 0x02, 0x31, 0x80, 0x22, 0x36, 0xa0, 0x00, 0x20, 
	0x39, 0xa0, 0x01, 0x5d, 0x35, 0x80, 0x24, 0x36, 0xa0, 0x00, 0x10, 0x39, 0xa0, 0x01, 0x80, 0x35, 
	0x80, 0x36, 0x80, 0x26, 0x17, 0x80, 0x01, 0x80, 0x68, 0x0e, 0x00, 0x80, 0x00, 0x80, 0x62, 0x0e, 
	0x80, 0x00, 0x30, 0xaf, 0xa0, 0x00, 0x00, 0x28, 0x80, 0x41, 0x0d, 0xef, 0xa0, 0x00, 0x00, 0x28, 
	0x80, 0x39, 0x0d, 0x6f, 0x80, 0x00, 0x80, 0x04, 0x10, 0x26, 0x38, 0x38, 0xa0, 0xff, 0xff, 0x38, 
	0x80, 0x00, 0x31, 0x80, 0x02, 0x30, 0xaf, 0xa0, 0x00, 0x00, 0x28, 0x80, 0x29, 0x0d, 0xef, 0xa0, 
	0x00, 0x00, 0x28, 0x80, 0x21, 0x0d, 0x6f, 0x80, 0x00, 0x80, 0x05, 0x10, 0x26, 0x38, 0x38, 0xa0, 
	0xff, 0xff, 0x38, 0x80, 0x02, 0x31, 0x80, 0x01, 0x80, 0x17, 0x0e, 0x00, 0x80, 0x04, 0x90, 0x80, 
	0x00, 0x08, 0x04, 0x11, 0x80, 0xbc, 0x0c, 0x80, 0x05, 0x90, 0x80, 0x00, 0x08, 0x04, 0x11, 0x80, 
	0xd4, 0x0c, 0xa0, 0x01, 0xc9, 0x80, 0x2c, 0x37, 0x80, 0x00, 0x30, 0x80, 0x28, 0x37, 0x80, 0x02, 
	0x30, 0x80, 0x2a, 0x37, 0x80, 0x2f, 0x97, 0x17, 0x6c, 0x00, 0x1f, 0x3f, 0x38, 0x38, 0x38, 0x78, 
	0x7f, 0x00, 0xfe, 0xfe, 0x7e, 0x77, 0x77, 0xe3, 0xc3, 0x00, 0x0f, 0x1f, 0x3b, 0x7b, 0x77, 0xe7, 
	0xc7, 0x00, 0xfc, 0xfe, 0x8f, 0x87, 0x07, 0x0e, 0xfc, 0x7f, 0x00, 0x00, 0x0f, 0xff, 0x7f, 0x07, 
	0x00, 0x03, 0x01, 0x00, 0xff, 0xf0, 0xf8, 0xff, 0x00, 0x87, 0x00, 0x00, 0xff, 0x7f, 0x7f, 0xff, 
	0x00, 0xf0, 0x00, 0x00, 0xe0, 0xfc, 0xfc, 0x80, 0x00, 
};


/* Varvara */

static const char *errors[] = {
	"underflow",
	"overflow",
	"division by zero"};

int
uxn_halt(Uxn *u, Uint8 instr, Uint8 err, Uint16 addr)
{
	pd->system->error("%s %s, by %02x at 0x%04x.\n", (instr & 0x40) ? "Return-stack" : "Working-stack", errors[err - 1], instr, addr);
	return 0;
}

static int
emu_error(char *msg, const char *err)
{
	pd->system->error("Error %s: %s\n", msg, err);
	return 0;
}

static int
emu_load(unsigned char *r)
{
	int i, len = sizeof(rom);
	for(i = 0; i < len; i++){
		u.ram[PAGE_PROGRAM + i] = rom[i];
	}
	return 1;
}

static void
console_deo(Uint8 *d, Uint8 port)
{
	if(port == 0x8)
		pd->system->logToConsole("%c", d[port]);
}

Uint8
screen_dei(Uint8 *d, Uint8 port)
{
	switch(port) {
	case 0x2: return 0x1;
	case 0x3: return 0x90;
	case 0x4: return 0x00;
	case 0x5: return 0xf0;
	default: return d[port];
	}
}

void
screen_deo(Uint8 *d, Uint8 port)
{
	switch(port) {
	case 0xe: {
		Uint16 x, y;
		Uint8 layer = d[0xe] & 0x40;
		PEKDEV(x, 0x8);
		PEKDEV(y, 0xa);
		ppu_pixel(&ppu, !!layer, x, y, d[0xe] & 0x3);
		if(d[0x6] & 0x01) POKDEV(0x8, x + 1); /* auto x+1 */
		if(d[0x6] & 0x02) POKDEV(0xa, y + 1); /* auto y+1 */
		pd->graphics->markUpdatedRows(y, y);
		break;
	}
	case 0xf: {
		Uint16 x, y, dx, dy, addr;
		Uint8 i, n, twobpp = !!(d[0xf] & 0x80);
		Uint8 layer = d[0xe] & 0x40;
		PEKDEV(x, 0x8);
		PEKDEV(y, 0xa);
		PEKDEV(addr, 0xc);
		n = d[0x6] >> 4;
		dx = (d[0x6] & 0x01) << 3;
		dy = (d[0x6] & 0x02) << 2;
		if(addr > 0x10000 - ((n + 1) << (3 + twobpp)))
			return;
		if(d[0xf] & 0x80) {
			ppu_2bpp(&ppu, !!layer, x, y, u.ram + addr, d[0xf] & 0xf, d[0xf] & 0x10, d[0xf] & 0x20);
		} else {
			ppu_1bpp(&ppu, !!layer, x, y, u.ram + addr, d[0xf] & 0xf, d[0xf] & 0x10, d[0xf] & 0x20);
		}
		POKDEV(0xc, addr); /* auto addr+length */
		POKDEV(0x8, x + dx); /* auto x+8 */
		POKDEV(0xa, y + dy); /* auto y+8 */
		pd->graphics->markUpdatedRows(y, y + 7);
		break;
	}
	}
}

static Uint8
emu_dei(Uxn *u, Uint8 addr)
{
	Uint8 p = addr & 0x0f, d = addr & 0xf0;
	switch(d) {
		case 0x20: return screen_dei(&u->dev[d], p);
	}
	pd->system->logToConsole("%02x", addr);
	return u->dev[addr];
}

static void
emu_deo(Uxn *u, Uint8 addr, Uint8 v)
{
	Uint8 p = addr & 0x0f, d = addr & 0xf0;
	Uint16 mask = 0x1 << (d >> 4);
	u->dev[addr] = v;
	switch(d) {
	case 0x10: console_deo(&u->dev[d], p); break;
	case 0x20: screen_deo(&u->dev[d], p); break;
	}
}

static int
emu_start (void){
	if(!uxn_boot(&u, (Uint8 *)calloc(0x10300, sizeof(Uint8)), emu_dei, emu_deo))
		return emu_error("Boot", "Failed");
	if(!emu_load(rom))
		return emu_error("Load", "Failed");
	memset(&ppu, 0, sizeof(ppu));
	if(!ppu_init(&ppu, (uint32_t *)pd->graphics->getFrame()))
		return 0;
	if(!uxn_eval(&u, PAGE_PROGRAM))
		return emu_error("Boot", "Failed to start rom.");
	return 1;
}

/* Misc */

static int update(void* userdata);
const char* fontpath = "/System/Fonts/Asheville-Sans-14-Bold.pft";
LCDFont* font = NULL;

#ifdef _WINDLL
__declspec(dllexport)
#endif

int eventHandler(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg)
{
	(void)arg; // arg is currently only used for event = kEventKeyPressed

	if ( event == kEventInit )
	{
		pd = playdate;
		pd->display->setRefreshRate(60);
		if(!emu_start())
			emu_error("Varvara", "Start failed.");
		const char* err;
		font = pd->graphics->loadFont(fontpath, &err);
		if ( font == NULL )
			pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontpath, err);
		// Note: If you set an update callback in the kEventInit handler, the system assumes the game is pure C and doesn't run any Lua code in the game
		pd->system->setUpdateCallback(update, pd);
	}
	return 0;
}

#define TEXT_WIDTH 86
#define TEXT_HEIGHT 16

int x = (400-TEXT_WIDTH)/2;
int y = (240-TEXT_HEIGHT)/2;
int dx = 1;
int dy = 2;

static int update(void* userdata)
{
	pd = userdata;

/*
	pd->graphics->clear(kColorWhite);
	pd->graphics->setFont(font);
	pd->graphics->drawText("Hello World!", strlen("Hello World!"), kASCIIEncoding, x, y);
*/

	uxn_eval(&u, GETVEC(&u.dev[0x20]));
/*
	x += dx;
	y += dy;
	
	if ( x < 0 || x > LCD_COLUMNS - TEXT_WIDTH )
		dx = -dx;
	
	if ( y < 0 || y > LCD_ROWS - TEXT_HEIGHT )
		dy = -dy;
        */
	pd->system->drawFPS(0,0);

	return 1;
}


A src/main.c => src/main.c +245 -0
@@ 0,0 1,245 @@
#include "pd_api.h"
#include "uxn.h"
#include "devices/apu.h"
#include "devices/ppu.h"

static PlaydateAPI *pd = NULL;

Uxn u;
Ppu ppu;

/*
static unsigned char rom[] = {
	0xa0, 0x01, 0x39, 0x94, 0x80, 0x18, 0x17, 0x21, 0x94, 0x80, 0xf7, 0x0d, 0x22, 0x80, 0x22, 0x36, 
	0x80, 0x01, 0x3f, 0xa0, 0x00, 0x08, 0x39, 0x80, 0x28, 0x37, 0x80, 0x24, 0x36, 0x80, 0x01, 0x3f, 
	0xa0, 0x00, 0x08, 0x39, 0x80, 0x2a, 0x37, 0x80, 0x16, 0x80, 0x26, 0x17, 0xa0, 0x01, 0x47, 0x80, 
	0x2c, 0x37, 0x80, 0x01, 0x80, 0x2f, 0x97, 0x17, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 
	0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x40, 0x48, 0x48, 0x00, 
	0xe0, 0x18, 0x04, 0x04, 0x02, 0x12, 0x12, 0x40, 0x40, 0x44, 0x23, 0x20, 0x18, 0x07, 0x00, 0x02, 
	0x02, 0x22, 0xc4, 0x04, 0x18, 0xe0, 0x00
};

*/

unsigned char rom[] = {
	0xa0, 0x4c, 0xfd, 0x80, 0x08, 0x37, 0xa0, 0x4c, 0xf3, 0x80, 0x0a, 0x37, 0xa0, 0xdc, 0xf2, 0x80, 
	0x0c, 0x37, 0xa0, 0x01, 0x4b, 0x80, 0x20, 0x37, 0x80, 0x22, 0x36, 0x80, 0x01, 0x3f, 0x80, 0x00, 
	0x31, 0x80, 0x24, 0x36, 0x80, 0x01, 0x3f, 0x80, 0x02, 0x31, 0x80, 0x22, 0x36, 0xa0, 0x00, 0x20, 
	0x39, 0xa0, 0x01, 0x5d, 0x35, 0x80, 0x24, 0x36, 0xa0, 0x00, 0x10, 0x39, 0xa0, 0x01, 0x80, 0x35, 
	0x80, 0x36, 0x80, 0x26, 0x17, 0x80, 0x01, 0x80, 0x68, 0x0e, 0x00, 0x80, 0x00, 0x80, 0x62, 0x0e, 
	0x80, 0x00, 0x30, 0xaf, 0xa0, 0x00, 0x00, 0x28, 0x80, 0x41, 0x0d, 0xef, 0xa0, 0x00, 0x00, 0x28, 
	0x80, 0x39, 0x0d, 0x6f, 0x80, 0x00, 0x80, 0x04, 0x10, 0x26, 0x38, 0x38, 0xa0, 0xff, 0xff, 0x38, 
	0x80, 0x00, 0x31, 0x80, 0x02, 0x30, 0xaf, 0xa0, 0x00, 0x00, 0x28, 0x80, 0x29, 0x0d, 0xef, 0xa0, 
	0x00, 0x00, 0x28, 0x80, 0x21, 0x0d, 0x6f, 0x80, 0x00, 0x80, 0x05, 0x10, 0x26, 0x38, 0x38, 0xa0, 
	0xff, 0xff, 0x38, 0x80, 0x02, 0x31, 0x80, 0x01, 0x80, 0x17, 0x0e, 0x00, 0x80, 0x04, 0x90, 0x80, 
	0x00, 0x08, 0x04, 0x11, 0x80, 0xbc, 0x0c, 0x80, 0x05, 0x90, 0x80, 0x00, 0x08, 0x04, 0x11, 0x80, 
	0xd4, 0x0c, 0xa0, 0x01, 0xc9, 0x80, 0x2c, 0x37, 0x80, 0x00, 0x30, 0x80, 0x28, 0x37, 0x80, 0x02, 
	0x30, 0x80, 0x2a, 0x37, 0x80, 0x2f, 0x97, 0x17, 0x6c, 0x00, 0x1f, 0x3f, 0x38, 0x38, 0x38, 0x78, 
	0x7f, 0x00, 0xfe, 0xfe, 0x7e, 0x77, 0x77, 0xe3, 0xc3, 0x00, 0x0f, 0x1f, 0x3b, 0x7b, 0x77, 0xe7, 
	0xc7, 0x00, 0xfc, 0xfe, 0x8f, 0x87, 0x07, 0x0e, 0xfc, 0x7f, 0x00, 0x00, 0x0f, 0xff, 0x7f, 0x07, 
	0x00, 0x03, 0x01, 0x00, 0xff, 0xf0, 0xf8, 0xff, 0x00, 0x87, 0x00, 0x00, 0xff, 0x7f, 0x7f, 0xff, 
	0x00, 0xf0, 0x00, 0x00, 0xe0, 0xfc, 0xfc, 0x80, 0x00, 
};


/* Varvara */

static const char *errors[] = {
	"underflow",
	"overflow",
	"division by zero"};

int
uxn_halt(Uxn *u, Uint8 instr, Uint8 err, Uint16 addr)
{
	pd->system->error("%s %s, by %02x at 0x%04x.\n", (instr & 0x40) ? "Return-stack" : "Working-stack", errors[err - 1], instr, addr);
	return 0;
}

static int
emu_error(char *msg, const char *err)
{
	pd->system->error("Error %s: %s\n", msg, err);
	return 0;
}

static int
emu_load(unsigned char *r)
{
	int i, len = sizeof(rom);
	for(i = 0; i < len; i++){
		u.ram[PAGE_PROGRAM + i] = rom[i];
	}
	return 1;
}

static void
console_deo(Uint8 *d, Uint8 port)
{
	if(port == 0x8)
		pd->system->logToConsole("%c", d[port]);
}

Uint8
screen_dei(Uint8 *d, Uint8 port)
{
	switch(port) {
	case 0x2: return 0x1;
	case 0x3: return 0x90;
	case 0x4: return 0x00;
	case 0x5: return 0xf0;
	default: return d[port];
	}
}

static uint8_t blending[5][16] = {
	{0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
	{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
	{1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
	{2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2},
	{1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0}};

static void
screen_blit(Uint8 layer, Uint16 x, Uint16 y, Uint8 *sprite, Uint8 color, Uint8 flipx, Uint8 flipy, Uint8 twobpp)
{
	int v, h, opaque = blending[4][color];
	for(v = 0; v < 8; v++) {
		Uint16 c = sprite[v] | (twobpp ? sprite[v + 8] : 0) << 8;
		for(h = 7; h >= 0; --h, c >>= 1) {
			Uint8 ch = (c & 1) | ((c >> 7) & 2);
			if(opaque || ch)
				ppu_pixel(&ppu,
					layer,
					x + (flipx ? 7 - h : h),
					y + (flipy ? 7 - v : v),
					blending[ch][color]);
		}
	}
}

void
screen_deo(Uint8 *d, Uint8 port)
{
	switch(port) {
	case 0xe: {
		Uint16 x, y;
		Uint8 layer = d[0xe] & 0x40;
		PEKDEV(x, 0x8);
		PEKDEV(y, 0xa);
		ppu_pixel(&ppu, !!layer, x, y, d[0xe] & 0x3);
		if(d[0x6] & 0x01) POKDEV(0x8, x + 1); /* auto x+1 */
		if(d[0x6] & 0x02) POKDEV(0xa, y + 1); /* auto y+1 */
		pd->graphics->markUpdatedRows(y, y);
		break;
	}
	case 0xf: {
		Uint16 x, y, dx, dy, addr;
		Uint8 i, n, twobpp = !!(d[0xf] & 0x80);
		Uint8 layer = d[0xe] & 0x40;
		PEKDEV(x, 0x8);
		PEKDEV(y, 0xa);
		PEKDEV(addr, 0xc);
		n = d[0x6] >> 4;
		dx = (d[0x6] & 0x01) << 3;
		dy = (d[0x6] & 0x02) << 2;
		if(addr > 0x10000 - ((n + 1) << (3 + twobpp)))
			return;
		if(addr > 0x10000 - ((n + 1) << (3 + twobpp)))
			return;
		for(i = 0; i <= n; i++) {
			screen_blit(!!layer, x + dy * i, y + dx * i, &u.ram[addr], d[0xf] & 0xf, d[0xf] & 0x10, d[0xf] & 0x20, twobpp);
			addr += (d[0x6] & 0x04) << (1 + twobpp);
		}
		POKDEV(0xc, addr); /* auto addr+length */
		POKDEV(0x8, x + dx); /* auto x+8 */
		POKDEV(0xa, y + dy); /* auto y+8 */
		pd->graphics->markUpdatedRows(y, y + 7);
		break;
	}
	}
}

static Uint8
emu_dei(Uxn *u, Uint8 addr)
{
	Uint8 p = addr & 0x0f, d = addr & 0xf0;
	switch(d) {
		case 0x20: return screen_dei(&u->dev[d], p);
	}
	pd->system->logToConsole("%02x", addr);
	return u->dev[addr];
}

static void
emu_deo(Uxn *u, Uint8 addr, Uint8 v)
{
	Uint8 p = addr & 0x0f, d = addr & 0xf0;
	Uint16 mask = 0x1 << (d >> 4);
	u->dev[addr] = v;
	switch(d) {
	case 0x10: console_deo(&u->dev[d], p); break;
	case 0x20: screen_deo(&u->dev[d], p); break;
	}
}

static int
emu_start (void){
	if(!uxn_boot(&u, (Uint8 *)calloc(0x10300, sizeof(Uint8)), emu_dei, emu_deo))
		return emu_error("Boot", "Failed");
	if(!emu_load(rom))
		return emu_error("Load", "Failed");
	memset(&ppu, 0, sizeof(ppu));
	if(!ppu_init(&ppu, (uint32_t *)pd->graphics->getFrame()))
		return 0;
	if(!uxn_eval(&u, PAGE_PROGRAM))
		return emu_error("Boot", "Failed to start rom.");
	return 1;
}

/* Misc */

static int update(void* userdata);
const char* fontpath = "/System/Fonts/Asheville-Sans-14-Bold.pft";
LCDFont* font = NULL;

#ifdef _WINDLL
__declspec(dllexport)
#endif

int eventHandler(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg)
{
	(void)arg; // arg is currently only used for event = kEventKeyPressed

	if ( event == kEventInit )
	{
		pd = playdate;
		pd->display->setRefreshRate(60);
		if(!emu_start())
			emu_error("Varvara", "Start failed.");
		pd->graphics->clear(kColorBlack);
		const char* err;
		font = pd->graphics->loadFont(fontpath, &err);
		if ( font == NULL )
			pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontpath, err);
		// Note: If you set an update callback in the kEventInit handler, the system assumes the game is pure C and doesn't run any Lua code in the game
		pd->system->setUpdateCallback(update, pd);
	}
	return 0;
}

#define TEXT_WIDTH 86
#define TEXT_HEIGHT 16

int x = (400-TEXT_WIDTH)/2;
int y = (240-TEXT_HEIGHT)/2;
int dx = 1;
int dy = 2;

static int update(void* userdata)
{
	pd = userdata;
	uxn_eval(&u, GETVEC(&u.dev[0x20]));
	pd->system->drawFPS(0,0);
	return 1;
}


A src/uxn.c => src/uxn.c +112 -0
@@ 0,0 1,112 @@
#include "uxn.h"

/*
Copyright (u) 2022 Devine Lu Linvega, Andrew Alderwick, Andrew Richards

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

/* clang-format off */

#define PUSH8(s, x) { if(s->ptr == 0xff) { errcode = 2; goto err; } s->dat[s->ptr++] = (x); }
#define PUSH16(s, x) { if((j = s->ptr) >= 0xfe) { errcode = 2; goto err; } k = (x); s->dat[j] = k >> 8; s->dat[j + 1] = k; s->ptr = j + 2; }
#define PUSH(s, x) { if(bs) { PUSH16(s, (x)) } else { PUSH8(s, (x)) } }
#define POP8(o) { if(!(j = *sp)) { errcode = 1; goto err; } o = (Uint16)src->dat[--j]; *sp = j; }
#define POP16(o) { if((j = *sp) <= 1) { errcode = 1; goto err; } o = src->dat[j - 1]; o += src->dat[j - 2] << 8; *sp = j - 2; }
#define POP(o) { if(bs) { POP16(o) } else { POP8(o) } }
#define POKE(x, y) { if(bs) { u->ram[(x)] = (y) >> 8; u->ram[(x) + 1] = (y); } else { u->ram[(x)] = y; } }
#define PEEK16(o, x) { o = (u->ram[(x)] << 8) + u->ram[(x) + 1]; }
#define PEEK(o, x) { if(bs) { PEEK16(o, x) } else { o = u->ram[(x)]; } }
#define DEVR(o, x) { o = u->dei(u, x); if (bs) o = (o << 8) + u->dei(u, ((x) + 1) & 0xFF); }
#define DEVW(x, y) { if (bs) { u->deo(u, (x), (y) >> 8); u->deo(u, ((x) + 1) & 0xFF, (y)); } else { u->deo(u, x, (y)); } }
#define JUMP(x) { if(bs) pc = (x); else pc += (Sint8)(x); }

int
uxn_eval(Uxn *u, Uint16 pc)
{
	unsigned int a, b, c, j, k, bs, instr, errcode;
	Uint8 kptr, *sp;
	Stack *src, *dst;
	if(!pc || u->dev[0x0f]) return 0;
	while((instr = u->ram[pc++])) {
		/* Return Mode */
		if(instr & 0x40) {
			src = u->rst; dst = u->wst;
		} else {
			src = u->wst; dst = u->rst;
		}
		/* Keep Mode */
		if(instr & 0x80) {
			kptr = src->ptr;
			sp = &kptr;
		} else {
			sp = &src->ptr;
		}
		/* Short Mode */
		bs = instr & 0x20 ? 1 : 0;
		switch(instr & 0x1f) {
		/* Stack */
		case 0x00: /* LIT */ PEEK(a, pc) PUSH(src, a) pc += 1 + bs; break;
		case 0x01: /* INC */ POP(a) PUSH(src, a + 1) break;
		case 0x02: /* POP */ POP(a) break;
		case 0x03: /* NIP */ POP(a) POP(b) PUSH(src, a) break;
		case 0x04: /* SWP */ POP(a) POP(b) PUSH(src, a) PUSH(src, b) break;
		case 0x05: /* ROT */ POP(a) POP(b) POP(c) PUSH(src, b) PUSH(src, a) PUSH(src, c) break;
		case 0x06: /* DUP */ POP(a) PUSH(src, a) PUSH(src, a) break;
		case 0x07: /* OVR */ POP(a) POP(b) PUSH(src, b) PUSH(src, a) PUSH(src, b) break;
		/* Logic */
		case 0x08: /* EQU */ POP(a) POP(b) PUSH8(src, b == a) break;
		case 0x09: /* NEQ */ POP(a) POP(b) PUSH8(src, b != a) break;
		case 0x0a: /* GTH */ POP(a) POP(b) PUSH8(src, b > a) break;
		case 0x0b: /* LTH */ POP(a) POP(b) PUSH8(src, b < a) break;
		case 0x0c: /* JMP */ POP(a) JUMP(a) break;
		case 0x0d: /* JCN */ POP(a) POP8(b) if(b) JUMP(a) break;
		case 0x0e: /* JSR */ POP(a) PUSH16(dst, pc) JUMP(a) break;
		case 0x0f: /* STH */ POP(a) PUSH(dst, a) break;
		/* Memory */
		case 0x10: /* LDZ */ POP8(a) PEEK(b, a) PUSH(src, b) break;
		case 0x11: /* STZ */ POP8(a) POP(b) POKE(a, b) break;
		case 0x12: /* LDR */ POP8(a) PEEK(b, pc + (Sint8)a) PUSH(src, b) break;
		case 0x13: /* STR */ POP8(a) POP(b) c = pc + (Sint8)a; POKE(c, b) break;
		case 0x14: /* LDA */ POP16(a) PEEK(b, a) PUSH(src, b) break;
		case 0x15: /* STA */ POP16(a) POP(b) POKE(a, b) break;
		case 0x16: /* DEI */ POP8(a) DEVR(b, a) PUSH(src, b) break;
		case 0x17: /* DEO */ POP8(a) POP(b) DEVW(a, b) break;
		/* Arithmetic */
		case 0x18: /* ADD */ POP(a) POP(b) PUSH(src, b + a) break;
		case 0x19: /* SUB */ POP(a) POP(b) PUSH(src, b - a) break;
		case 0x1a: /* MUL */ POP(a) POP(b) PUSH(src, (Uint32)b * a) break;
		case 0x1b: /* DIV */ POP(a) POP(b) if(a == 0) { errcode = 3; goto err; } PUSH(src, b / a) break;
		case 0x1c: /* AND */ POP(a) POP(b) PUSH(src, b & a) break;
		case 0x1d: /* ORA */ POP(a) POP(b) PUSH(src, b | a) break;
		case 0x1e: /* EOR */ POP(a) POP(b) PUSH(src, b ^ a) break;
		case 0x1f: /* SFT */ POP8(a) POP(b) PUSH(src, b >> (a & 0x0f) << ((a & 0xf0) >> 4)) break;
		}
	}
	return 1;
err:
	return uxn_halt(u, instr, errcode, pc - 1);
}

/* clang-format on */

int
uxn_boot(Uxn *u, Uint8 *ram, Uint8 (*dei)(struct Uxn *, Uint8), void (*deo)(struct Uxn *, Uint8, Uint8))
{
	Uint32 i;
	char *cptr = (char *)u;
	for(i = 0; i < sizeof(*u); i++)
		cptr[i] = 0x00;
	u->ram = ram;
	u->wst = (Stack *)(ram + 0x10000);
	u->rst = (Stack *)(ram + 0x10100);
	u->dev = (Uint8 *)(ram + 0x10200);
	u->dei = dei;
	u->deo = deo;
	return 1;
}

A src/uxn.h => src/uxn.h +44 -0
@@ 0,0 1,44 @@
/*
Copyright (c) 2021 Devine Lu Linvega

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/

typedef unsigned char Uint8;
typedef signed char Sint8;
typedef unsigned short Uint16;
typedef signed short Sint16;
typedef unsigned int Uint32;

#define PAGE_PROGRAM 0x0100

/* clang-format off */

#define GETVEC(d) ((d)[0] << 8 | (d)[1])
#define POKDEV(x, y) { d[(x)] = (y) >> 8; d[(x) + 1] = (y); }
#define PEKDEV(o, x) { (o) = (d[(x)] << 8) + d[(x) + 1]; }

/* clang-format on */

typedef struct {
	Uint8 dat[254], err, ptr;
} Stack;

typedef struct Uxn {
	Uint8 *ram, *dev;
	Stack *wst, *rst;
	Uint8 (*dei)(struct Uxn *u, Uint8 addr);
	void (*deo)(struct Uxn *u, Uint8 addr, Uint8 value);
} Uxn;

typedef Uint8 Dei(Uxn *u, Uint8 addr);
typedef void Deo(Uxn *u, Uint8 addr, Uint8 value);

int uxn_boot(Uxn *u, Uint8 *ram, Dei *dei, Deo *deo);
int uxn_eval(Uxn *u, Uint16 pc);
int uxn_halt(Uxn *u, Uint8 instr, Uint8 err, Uint16 addr);