diff --git a/src/main.zig b/src/main.zig
index 59a1b95..c05f030 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -3,6 +3,8 @@ const std = @import("std");
 const Uxn = @import("uxn.zig");
 const Varvara = @import("varvara.zig");
 
+const DEBUG = true;
+
 pub fn main() !void {
     var gpa = std.heap.GeneralPurposeAllocator(.{}){};
     defer _ = gpa.deinit();
@@ -19,16 +21,17 @@ pub fn main() !void {
         const rom = try file.readToEndAlloc(allocator, 0xFF00);
         defer allocator.free(rom);
 
-        var varvara = Varvara.init(rom);
+        Uxn.setDebug(DEBUG);
 
-        var running = true;
-        while (running) {
-            std.debug.print("pc={X} code={X} op={s}\n", .{
+        var varvara = try Varvara.init(allocator, rom);
+        defer varvara.deinit();
+
+        while (varvara.uxn.eval()) {
+            if (DEBUG) std.debug.print("{s}\t\tpc {X}, code {X}\n", .{
+                Uxn.fmtInstrs(varvara.uxn.mem.m[varvara.uxn.pc .. varvara.uxn.pc +% 1]),
                 varvara.uxn.pc,
                 varvara.uxn.mem.m[varvara.uxn.pc],
-                Uxn.fmtInstrs(varvara.uxn.mem.m[varvara.uxn.pc .. varvara.uxn.pc +% 1]),
             });
-            if (varvara.uxn.eval()) running = false;
         }
     } else return error.NoRom;
 }
diff --git a/src/uxn.zig b/src/uxn.zig
index 1a032e2..0e230c1 100644
--- a/src/uxn.zig
+++ b/src/uxn.zig
@@ -1,11 +1,17 @@
 const std = @import("std");
 const Uxn = @This();
 
+var debug = true;
+
+pub fn setDebug(v: bool) void {
+    debug = v;
+}
+
 mem: Memory = .{ .m = undefined },
 ws: Stack = .{ .s = undefined },
 rs: Stack = .{ .s = undefined },
 dev: [0x100]u8 = undefined,
-pc: u16,
+pc: u16 = 0x100,
 
 deo8: *const fn (*Uxn, *Stack, u8, u8) void = deo8Stub,
 deo16: *const fn (*Uxn, *Stack, u8, u16) void = deo16Stub,
@@ -17,18 +23,43 @@ fn deo16Stub(_: *Uxn, _: *Stack, _: u8, _: u16) void {}
 fn dei8Stub(_: *Uxn, _: *Stack, _: u8) void {}
 fn dei16Stub(_: *Uxn, _: *Stack, _: u8) void {}
 
+pub fn init(allocator: std.mem.Allocator) !Uxn {
+    const mem = try allocator.alloc(u8, 0x10000);
+    errdefer allocator.free(mem);
+    for (mem) |*b| b.* = 0;
+    return .{
+        .mem = .{ .m = mem },
+        .ws = .{ .s = [_]u8{0} ** 0x100 },
+        .rs = .{ .s = [_]u8{0} ** 0x100 },
+        .dev = [_]u8{0} ** 0x100,
+    };
+}
+
+pub fn deinit(self: *Uxn, allocator: std.mem.Allocator) void {
+    allocator.free(self.mem.m);
+}
+
 const Memory = struct {
-    m: [0x10000]u8,
+    m: []u8,
 
     pub fn peek(self: *Memory, comptime T: type, idx: u16) T {
-        return switch (T) {
-            u8 => self.m[idx],
-            u16 => @as(u16, @intCast(self.m[idx])) << 8 | self.m[idx +% 1],
+        switch (T) {
+            u8 => {
+                const val = self.m[idx];
+                if (debug) std.debug.print("\x1b[33mmem peek:\x1b[0m\tidx {d}, type {s}, val {d}\n", .{ idx, @typeName(T), val });
+                return val;
+            },
+            u16 => {
+                const val = @as(u16, @intCast(self.m[idx])) << 8 | self.m[idx +% 1];
+                if (debug) std.debug.print("\x1b[33mmem peek:\x1b[0m\tidx {d}, type {s}, val {d}\n", .{ idx, @typeName(T), val });
+                return val;
+            },
             else => @compileError("expected u8 or u16, got " + @typeName(T)),
-        };
+        }
     }
 
     pub fn poke(self: *Memory, comptime T: type, idx: u16, val: T) void {
+        if (debug) std.debug.print("\x1b[33mmem poke:\x1b[0m\tidx {d}, type {s}, val {d}\n", .{ idx, @typeName(T), val });
         switch (T) {
             u8 => self.m[idx] = val,
             u16 => {
@@ -41,28 +72,37 @@ const Memory = struct {
 };
 
 test "memory peek/poke" {
-    var mem = Memory{ .m = undefined };
+    const m = try std.testing.allocator.alloc(u8, 0x10000);
+    defer std.testing.allocator.free(m);
+    var mem = Memory{ .m = m };
     mem.poke(u16, 0, 0xABCD);
     mem.poke(u8, 2, 0x69);
     try std.testing.expectEqual(0xABCD, mem.peek(u16, 0));
     try std.testing.expectEqual(0x69, mem.peek(u8, 2));
 }
 
-const Stack = struct {
+pub const Stack = struct {
     s: [0x100]u8,
     sp: u8 = 0,
 
     pub fn peek(self: *Stack, comptime T: type) T {
-        std.debug.print("\x1b[1;32mstack peek:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) });
-        return switch (T) {
-            u8 => self.s[self.sp -% 1],
-            u16 => @as(u16, @intCast(self.s[self.sp -% 2])) << 8 | self.s[self.sp -% 1],
+        switch (T) {
+            u8 => {
+                const val = self.s[self.sp -% 1];
+                if (debug) std.debug.print("\x1b[32mstack peek:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val });
+                return val;
+            },
+            u16 => {
+                const val = @as(u16, @intCast(self.s[self.sp -% 2])) << 8 | self.s[self.sp -% 1];
+                if (debug) std.debug.print("\x1b[32mstack peek:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val });
+                return val;
+            },
             else => @compileError("expected u8 or u16, got " + @typeName(T)),
-        };
+        }
     }
 
     pub fn poke(self: *Stack, comptime T: type, v: T) void {
-        std.debug.print("\x1b[1;32mstack poke:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) });
+        if (debug) std.debug.print("\x1b[32mstack poke:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), v });
         switch (T) {
             u8 => self.s[self.sp -% 1] = v,
             u16 => {
@@ -75,16 +115,23 @@ const Stack = struct {
 
     pub fn pop(self: *Stack, comptime T: type) T {
         self.sp -%= @intCast(@divExact(@typeInfo(T).int.bits, 8));
-        std.debug.print("\x1b[1;32mstack pop:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) });
         return switch (T) {
-            u8 => self.s[self.sp],
-            u16 => @as(u16, @intCast(self.s[self.sp])) << 8 | self.s[self.sp +% 1],
+            u8 => {
+                const val = self.s[self.sp];
+                if (debug) std.debug.print("\x1b[32mstack pop:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val });
+                return val;
+            },
+            u16 => {
+                const val = @as(u16, @intCast(self.s[self.sp])) << 8 | self.s[self.sp +% 1];
+                if (debug) std.debug.print("\x1b[32mstack pop:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val });
+                return val;
+            },
             else => @compileError("expected u8 or u16, got " + @typeName(T)),
         };
     }
 
     pub fn push(self: *Stack, comptime T: type, v: T) void {
-        std.debug.print("\x1b[1;32mstack push:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) });
+        if (debug) std.debug.print("\x1b[32mstack push:\x1b[0m\tsp {d}, type {s}, value {d}\n", .{ self.sp, @typeName(T), v });
         switch (T) {
             u8 => self.s[self.sp] = v,
             u16 => {
@@ -113,7 +160,7 @@ test "stack poke/peek/push/pop" {
 
 pub fn eval(self: *Uxn) bool {
     switch (self.mem.m[self.pc]) {
-        0x00 => return true, // BRK
+        0x00 => return false, // BRK
         0x01 => inc(&self.ws, u8, false), // INC
         0x02 => pop(&self.ws, u8, false), // POP
         0x03 => nip(&self.ws, u8, false), // NIP
@@ -256,8 +303,7 @@ pub fn eval(self: *Uxn) bool {
         0x7F => sft(&self.rs, u16, false), // SFT2r
 
         0x80 => {
-            self.ws.push(u8, self.mem.m[self.pc +% 1]);
-            std.debug.print("LIT: {d}\n", .{self.ws.peek(u8)});
+            self.ws.push(u8, self.mem.peek(u8, self.pc +% 1));
             self.pc +%= 1;
         }, // LIT
         0x81 => inc(&self.ws, u8, true), // INCk
@@ -293,8 +339,7 @@ pub fn eval(self: *Uxn) bool {
         0x9F => sft(&self.ws, u8, true), // SFTk
 
         0xA0 => {
-            self.ws.push(u16, self.mem.m[self.pc +% 1]);
-            std.debug.print("LIT2: {d}\n", .{self.ws.peek(u16)});
+            self.ws.push(u16, self.mem.peek(u16, self.pc +% 1));
             self.pc +%= 2;
         }, // LIT2
         0xA1 => inc(&self.ws, u16, true), // INC2k
@@ -330,8 +375,7 @@ pub fn eval(self: *Uxn) bool {
         0xBF => sft(&self.ws, u16, true), // SFT2k
 
         0xC0 => {
-            self.rs.push(u8, self.mem.m[self.pc +% 1]);
-            std.debug.print("LITr: {d}\n", .{self.rs.peek(u8)});
+            self.rs.push(u8, self.mem.peek(u8, self.pc +% 1));
             self.pc +%= 1;
         }, // LITr
         0xC1 => inc(&self.rs, u8, true), // INCkr
@@ -367,8 +411,7 @@ pub fn eval(self: *Uxn) bool {
         0xDF => sft(&self.rs, u8, true), // SFTkr
 
         0xE0 => {
-            self.rs.push(u16, self.mem.m[self.pc +% 1]);
-            std.debug.print("LIT2r: {d}\n", .{self.rs.peek(u16)});
+            self.rs.push(u16, self.mem.peek(u16, self.pc +% 1));
             self.pc +%= 2;
         }, // LIT2r
         0xE1 => inc(&self.rs, u16, true), // INC2kr
@@ -405,7 +448,7 @@ pub fn eval(self: *Uxn) bool {
     }
 
     self.pc +%= 1;
-    return false;
+    return true;
 }
 
 fn brk() void {}
@@ -565,7 +608,7 @@ fn dei(self: *Uxn, stack: *Stack, comptime T: type, comptime keep: bool) void {
 fn deo(self: *Uxn, stack: *Stack, comptime T: type, comptime keep: bool) void {
     const d = if (keep) stack.peek(u8) else stack.pop(u8);
     const v = if (keep) stack.peek(T) else stack.pop(T);
-    std.debug.print("DEO: dev {X}, val {X}\n", .{ d, v });
+    if (debug) std.debug.print("DEO: dev {X}, val {X}\n", .{ d, v });
     switch (T) {
         u8 => {
             self.dev[d] = v;
@@ -640,24 +683,30 @@ fn sft(stack: *Stack, comptime T: type, comptime keep: bool) void {
     }
 }
 
-// test "arithmetic instructions" {
-//     var uxn = Uxn{
-//         .mem = .{ .m = undefined },
-//         .ws = .{ .s = undefined },
-//         .rs = .{ .s = undefined },
-//         .pc = 0,
-//     };
-//     uxn.mem.m[0] = 0x18;
-//     uxn.mem.m[1] = 0x19;
-//     uxn.mem.m[2] = 0x1A;
-//     uxn.mem.m[3] = 0x1B;
-//     uxn.ws.push(u8, 4);
-//     uxn.ws.push(u8, 20);
-//     uxn.ws.push(u8, 6);
-//     uxn.loop();
-//     uxn.loop();
-//     try std.testing.expectEqual(18, uxn.ws.pop(u8));
-// }
+test "op" {
+    var uxn = try Uxn.init(std.testing.allocator);
+    defer uxn.deinit(std.testing.allocator);
+
+    const rom = [_]u8{
+        0x01, 0x01, 0x01, 0x01,
+        0x00,
+    };
+    @memcpy(uxn.mem.m[0x100 .. rom.len + 0x100], &rom);
+
+    std.debug.print("op.inc\n", .{});
+    uxn.ws.push(u8, 0x01);
+    uxn.ws.push(u8, 0xff);
+    uxn.ws.push(u8, 0xfe);
+    uxn.ws.push(u8, 0x00);
+    _ = uxn.eval();
+    try std.testing.expectEqual(0x01, uxn.ws.pop(u8));
+    _ = uxn.eval();
+    try std.testing.expectEqual(0xff, uxn.ws.pop(u8));
+    _ = uxn.eval();
+    try std.testing.expectEqual(0x00, uxn.ws.pop(u8));
+    _ = uxn.eval();
+    try std.testing.expectEqual(0x02, uxn.ws.pop(u8));
+}
 
 pub fn formatInstruction(
     bytes: []const u8,
@@ -667,55 +716,60 @@ pub fn formatInstruction(
 ) !void {
     _ = fmt;
     _ = options;
+    if (!debug) return;
     std.debug.assert(bytes.len == 1);
-    switch (bytes[0] & 0x1F) {
-        0x00 => return switch (bytes[0]) {
-            0x00 => try writer.writeAll("BRK"),
-            0x20 => try writer.writeAll("JCI"),
-            0x40 => try writer.writeAll("JMI"),
-            0x60 => try writer.writeAll("JSI"),
-            0x80 => try writer.writeAll("LIT"),
-            0xA0 => try writer.writeAll("LIT2"),
-            0xC0 => try writer.writeAll("LITr"),
-            0xE0 => try writer.writeAll("LIT2r"),
+    try writer.writeAll("\x1b[1;31m");
+    instr: {
+        switch (bytes[0] & 0x1F) {
+            0x00 => break :instr switch (bytes[0]) {
+                0x00 => try writer.writeAll("BRK"),
+                0x20 => try writer.writeAll("JCI"),
+                0x40 => try writer.writeAll("JMI"),
+                0x60 => try writer.writeAll("JSI"),
+                0x80 => try writer.writeAll("LIT"),
+                0xA0 => try writer.writeAll("LIT2"),
+                0xC0 => try writer.writeAll("LITr"),
+                0xE0 => try writer.writeAll("LIT2r"),
+                else => unreachable,
+            },
+            0x01 => try writer.writeAll("INC"),
+            0x02 => try writer.writeAll("POP"),
+            0x03 => try writer.writeAll("NIP"),
+            0x04 => try writer.writeAll("SWP"),
+            0x05 => try writer.writeAll("ROT"),
+            0x06 => try writer.writeAll("DUP"),
+            0x07 => try writer.writeAll("OVR"),
+            0x08 => try writer.writeAll("EQU"),
+            0x09 => try writer.writeAll("NEQ"),
+            0x0A => try writer.writeAll("GTH"),
+            0x0B => try writer.writeAll("LTH"),
+            0x0C => try writer.writeAll("JMP"),
+            0x0D => try writer.writeAll("JCN"),
+            0x0E => try writer.writeAll("JSR"),
+            0x0F => try writer.writeAll("STH"),
+            0x10 => try writer.writeAll("LDZ"),
+            0x11 => try writer.writeAll("STZ"),
+            0x12 => try writer.writeAll("LDR"),
+            0x13 => try writer.writeAll("STR"),
+            0x14 => try writer.writeAll("LDA"),
+            0x15 => try writer.writeAll("STA"),
+            0x16 => try writer.writeAll("DEI"),
+            0x17 => try writer.writeAll("DEO"),
+            0x18 => try writer.writeAll("ADD"),
+            0x19 => try writer.writeAll("SUB"),
+            0x1A => try writer.writeAll("MUL"),
+            0x1B => try writer.writeAll("DIV"),
+            0x1C => try writer.writeAll("AND"),
+            0x1D => try writer.writeAll("ORA"),
+            0x1E => try writer.writeAll("EOR"),
+            0x1F => try writer.writeAll("SFT"),
             else => unreachable,
-        },
-        0x01 => try writer.writeAll("INC"),
-        0x02 => try writer.writeAll("POP"),
-        0x03 => try writer.writeAll("NIP"),
-        0x04 => try writer.writeAll("SWP"),
-        0x05 => try writer.writeAll("ROT"),
-        0x06 => try writer.writeAll("DUP"),
-        0x07 => try writer.writeAll("OVR"),
-        0x08 => try writer.writeAll("EQU"),
-        0x09 => try writer.writeAll("NEQ"),
-        0x0A => try writer.writeAll("GTH"),
-        0x0B => try writer.writeAll("LTH"),
-        0x0C => try writer.writeAll("JMP"),
-        0x0D => try writer.writeAll("JCN"),
-        0x0E => try writer.writeAll("JSR"),
-        0x0F => try writer.writeAll("STH"),
-        0x10 => try writer.writeAll("LDZ"),
-        0x11 => try writer.writeAll("STZ"),
-        0x12 => try writer.writeAll("LDR"),
-        0x13 => try writer.writeAll("STR"),
-        0x14 => try writer.writeAll("LDA"),
-        0x15 => try writer.writeAll("STA"),
-        0x16 => try writer.writeAll("DEI"),
-        0x17 => try writer.writeAll("DEO"),
-        0x18 => try writer.writeAll("ADD"),
-        0x19 => try writer.writeAll("SUB"),
-        0x1A => try writer.writeAll("MUL"),
-        0x1B => try writer.writeAll("DIV"),
-        0x1C => try writer.writeAll("AND"),
-        0x1D => try writer.writeAll("ORA"),
-        0x1E => try writer.writeAll("EOR"),
-        0x1F => try writer.writeAll("SFT"),
-        else => unreachable,
+        }
+        if (bytes[0] & 0x20 == 0x20) try writer.writeByte('2');
+        if (bytes[0] & 0x40 == 0x40) try writer.writeByte('k');
+        if (bytes[0] & 0x80 == 0x80) try writer.writeByte('r');
     }
-    if (bytes[0] & 0x20 == 0x20) try writer.writeByte('2');
-    if (bytes[0] & 0x40 == 0x40) try writer.writeByte('k');
-    if (bytes[0] & 0x80 == 0x80) try writer.writeByte('r');
+    try writer.writeAll("\x1b[0m");
 }
 
 pub fn fmtInstrs(bytes: []const u8) std.fmt.Formatter(formatInstruction) {
diff --git a/src/varvara.zig b/src/varvara.zig
index 60110b9..5134afc 100644
--- a/src/varvara.zig
+++ b/src/varvara.zig
@@ -5,36 +5,57 @@ const Varvara = @This();
 
 uxn: Uxn,
 
-pub fn init(rom: []const u8) Varvara {
-    var uxn = Uxn{ .pc = 0x100 };
+allocator: std.mem.Allocator,
+metadata: ?[]const u8 = null,
+
+pub fn init(allocator: std.mem.Allocator, rom: []const u8) !Varvara {
+    var uxn = try Uxn.init(allocator);
+    uxn.deo8 = deo8;
+    uxn.deo16 = deo16;
+    uxn.dei8 = dei8;
+    uxn.dei16 = dei16;
     @memcpy(uxn.mem.m[0x100 .. rom.len + 0x100], rom);
-    return .{ .uxn = uxn };
+    return .{ .uxn = uxn, .allocator = allocator };
+}
+
+pub fn deinit(self: *Varvara) void {
+    self.uxn.deinit(self.allocator);
 }
 
 pub fn deo8(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u8) void {
-    const self: Varvara = @fieldParentPtr("uxn", uxn);
-    // _ = .{ self, stack, device, value };
-    _ = self;
-    _ = stack;
-    switch (device) {
+    const self: *Varvara = @fieldParentPtr("uxn", uxn);
+    _ = .{ self, stack, device, value };
+    switch (device) { // Console/write
         0x18 => {
-            std.io.getStdOut().writer().writeByte(value) catch |e| @panic(e);
+            std.io.getStdOut().writer().writeByte(value) catch |e| @panic(@errorName(e));
         },
         else => {},
     }
 }
 
 pub fn deo16(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u16) void {
-    const self: Varvara = @fieldParentPtr("uxn", uxn);
+    const self: *Varvara = @fieldParentPtr("uxn", uxn);
     _ = .{ self, stack, device, value };
+    switch (device) {
+        0x06, 0x07 => { // System/metadata
+            self.metadata = std.mem.span(@as([*:0]const u8, @ptrCast(&uxn.mem.m[value +% 1])));
+        },
+        else => {},
+    }
 }
 
-pub fn dei8(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u8) void {
-    const self: Varvara = @fieldParentPtr("uxn", uxn);
-    _ = .{ self, stack, device, value };
+pub fn dei8(uxn: *Uxn, stack: *Uxn.Stack, device: u8) void {
+    const self: *Varvara = @fieldParentPtr("uxn", uxn);
+    _ = .{ self, stack, device };
+    switch (device) {
+        else => {},
+    }
 }
 
-pub fn dei16(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u16) void {
-    const self: Varvara = @fieldParentPtr("uxn", uxn);
-    _ = .{ self, stack, device, value };
+pub fn dei16(uxn: *Uxn, stack: *Uxn.Stack, device: u8) void {
+    const self: *Varvara = @fieldParentPtr("uxn", uxn);
+    _ = .{ self, stack, device };
+    switch (device) {
+        else => {},
+    }
 }