01 / first program
Start in five minutes
Babble is the C-like source language. Babel-8 is the bytecode machine that runs the result. Ordinary programs should start in Babble; raw Babel instructions are useful when inspecting generated code, generating random artifacts, or exploiting operations the high-level compiler does not expose yet.
u8 x = 28;
u8 y = 28;
fn main() {
palette(0, NAVY);
palette(1, CYAN);
layer(0);
fill(0); // opaque background using palette slot 0
loop {
layer(1);
clear(); // erase last frame's moving layer
if (held(LEFT)) { x--; }
if (held(RIGHT)) { x++; }
if (held(UP)) { y--; }
if (held(DOWN)) { y++; }
fill_rect(x, y, 4, 4, 1, REPLACE);
yield; // let the browser present this frame
}
}
The central trick: draw the permanent background once on layer 0, then clear and redraw only moving objects on layer 1. Clearing layer 1 reveals layer 0 instead of destroying it.
02 / what exists
The machine model
Artifact
Exactly 1,024 immutable bytes: 512 code bytes followed by 512 asset bytes.
Code
256 fixed instruction slots. Every instruction is exactly an opcode byte plus an operand byte.
CPU
Eight byte registers, two 10-bit index registers, an 8-bit instruction PC, and zero/carry flags.
Memory
1,024 writable RAM bytes, 512 immutable asset bytes, a 32-address return stack, and a 64-byte data stack.
Display
Four independently drawn 64×64 layers. Each pixel stores a palette slot and a separate visibility bit.
Devices
Eight buttons, four audio voices, four frame timers, a deterministic RNG, and a 32-bit frame counter.
The machine never faults. Addresses, registers, stacks, and the PC wrap. Division by zero has a defined result. All 256 opcode values do something deterministic. This is why arbitrary 1,024-byte files can be run without validating them first.
03 / compositor
Why there are four framebuffers
They are layers, numbered 0 through 3. Layer 0 is the bottom; layer 3 is the top.
Every drawing operation affects only the active layer selected by layer(n).
At presentation time, the compositor checks layer 3, then 2, then 1, then 0, and displays
the first visible pixel.
| Layer | Conventional use | How often to redraw |
|---|---|---|
| 0 | Background, map, terrain, star field | Once, or only when the room changes |
| 1 | Players, enemies, moving world objects | Clear and redraw every gameplay frame |
| 2 | Particles, flashes, projectiles, temporary effects | Clear/redraw or scroll independently |
| 3 | HUD, score, menus, cursor | Only when UI state changes |
Transparent is not black
Every layer pixel has a two-bit color slot and a separate visibility bit. A transparent pixel reveals the layer below. A visible pixel whose color is slot 0 covers lower layers with whatever master color slot 0 currently selects—often black.
clear() makes the active layer transparent. fill(0) makes it opaquely color 0. Those operations look similar on layer 0 but behave completely differently on upper layers.
Moving without damaging the background
// Draw once.
layer(0);
fill(NAVY); // Wrong: fill expects slot 0–3, not master color.
// Correct:
palette(0, NAVY);
layer(0);
fill(0);
// Every frame:
layer(1);
clear();
sprite1(x, y, hero, 2, REPLACE);
Flips, scrolling, rotation, inversion, row shift, and column shift affect only the active layer. They move colors and visibility together. Pixels revealed by scrolling are transparent.
04 / palette
Choosing colors
Screen pixels store only a slot from 0 to 3. The active palette maps those four slots to
four of the machine's 16 master colors. Calling palette(slot, masterColor)
changes the meaning of every existing pixel using that slot immediately.
palette(0, BLACK);
palette(1, CYAN);
palette(2, ORANGE);
palette(3, WHITE);
fill_rect(4, 4, 20, 10, 2, REPLACE); // uses slot 2 → ORANGE
#000000
#1B1F3B
#4C2A63
#8B3A62
#C94C4C
#E47D3E
#F2C14E
#F6E8B1
#2D6A4F
#40916C
#52B788
#48CAE4
#277DA1
#5A67D8
#C77DFF
#FFFFFF
Blend modes
| Constant | Value | Effect | Good for |
|---|---|---|---|
| REPLACE | 0 | Store source color and make visible | Normal drawing |
| XOR | 1 | destination XOR source | Blinking cursors, reversible effects |
| ADD | 2 | (destination + source) & 3 | Two-bit glow and additive effects |
| ERASE | 3 | Clear visibility; stored color remains | Reveal lower layers |
XOR and Add treat a transparent destination as color 0, then make it visible. Palette changes are effectively free color cycling because pixel data does not change.
05 / time
Frames, yield, sleep, and work
A host frame samples buttons, increments the frame counter, ticks timers and timed voices, then gives the CPU 16,384 work units. The CPU continues until it yields, sleeps, halts, reaches a breakpoint, or cannot fit the next atomic instruction into the remaining budget.
When to yield
Put yield; once at the end of the main game loop. It declares that the frame is
complete and execution should resume at the following instruction next frame.
fn main() {
setup();
loop {
read_input();
update_game();
draw_frame();
yield;
}
}
Without yield, a loop can run many times in one host frame until the work budget is exhausted. Button edges, timers, and the displayed image will not advance between those iterations.
Yield versus sleep
| Operation | Meaning |
|---|---|
| yield | Stop this frame; resume next frame. |
| sleep(n) | Stop now and skip n complete future frames before resuming. |
| sleep(0) | Behaves like yield. |
| timer_set | Starts a timer that ticks independently while your code continues or sleeps. |
| halt/return from main | Stop indefinitely until reset. |
Expensive instructions are atomic. A 16,384-cost full-layer hash either runs completely or waits for a fresh frame budget; it never produces a half-hashed state.
06 / controls
Held, pressed, and released
| Function | True when | Typical use |
|---|---|---|
| held(key) | The key is currently down | Movement or sustained action |
| pressed(key) | The key changed from up to down this frame | Menus, firing once, toggles |
| released(key) | The key changed from down to up this frame | Charge attacks, drag end |
Keys are LEFT, RIGHT, UP, DOWN, A, B, START, and SELECT.
Edges are sampled once per host frame. If you test pressed(A) repeatedly before yielding, it remains true for that entire frame.
07 / high-level language
Babble language reference
Values and declarations
u8 score = 0; // global byte
u8 map[64] = [1, 2, 3, 4]; // remaining elements start at zero
const SPEED = 2; // compile-time value
asset hero = [0x18, 0x3C, 0x7E, 0xDB, 0xFF, 0x24, 0x42, 0x81];
fn add(u8 left, u8 right) {
u8 result = left + right; // local byte
return result;
}
All scalar values are unsigned bytes. Arithmetic wraps modulo 256: 255 + 1
is 0 and 0 - 1 is 255. Arrays occupy consecutive RAM bytes. Assets occupy
immutable asset ROM and do not consume RAM.
Operators
| Group | Operators | Notes |
|---|---|---|
| Arithmetic | + - * / % | Unsigned byte results; division by zero gives zero, modulo zero preserves the dividend at machine level. |
| Bits | & | ^ ~ << >> | Shifts use a compile-time amount and only its low three bits. |
| Comparison | == != < <= > >= | Unsigned comparisons returning 0 or 1. |
| Boolean | ! && || | Zero is false; nonzero is true. |
| Assignment | = += -= *= /= %= ++ -- | All results wrap to a byte. |
Control flow
if (score >= 10) { score = 0; } else { score++; }
while (timer_get(0) != 0) { yield; }
for (u8 i = 0; i < 16; i++) {
map[i] = i * 3;
}
switch (mode) {
case 0: fill(0); break;
case 1: fill(1); break;
default: clear(); break;
}
loop {
if (pressed(B)) { break; }
if (!held(A)) { yield; continue; }
score++;
}
Functions and recursion
Functions accept up to four u8 parameters. Arguments travel through R0–R3
and the return value uses R0. Recursive calls are supported by saving the caller's static
parameter/local frame and live expression registers on the 64-byte data stack.
Recursion must be bounded. The return stack has 32 entries and the data stack has 64 bytes. Both wrap instead of faulting, so excessive recursion corrupts older caller state rather than producing a friendly error.
Compiler behavior
Globals and locals receive fixed RAM addresses. Initial values are emitted as startup instructions. The optional ROM optimizer folds constants, emits direct comparison branches, threads jumps, and removes redundant or unreachable instructions. The artifact is always 1,024 bytes even when only a few code slots are used.
08 / standard library
Every Babble builtin
| Function | Signature | What it does and what to watch |
|---|---|---|
| Layer | layer(n) | Select layer 0–3. Every later drawing or layer transform uses this layer until changed. |
| Clear | clear() | Make every active-layer pixel transparent. This reveals lower layers; it does not paint black. |
| Fill | fill(color) | Opaque-fill with palette slot 0–3. Pass a slot, not a master color constant. |
| Pixel | pixel(x, y, color, blend = REPLACE) | Draw one wrapped pixel on the active layer using a palette slot and blend mode. |
| Erase | erase(x, y) | Clear one visibility bit, exposing lower layers. |
| Read layer | read_layer(x, y) | Return slot 0–3 for a visible active-layer pixel or 255 when transparent. |
| Read screen | read_screen(x, y) | Return the final composited slot 0–3. It never returns transparency. |
| Horizontal line | hline(x, y, length, color) | Draw 1–64 pixels using Replace. Coordinates wrap. |
| Vertical line | vline(x, y, length, color) | Vertical equivalent of hline. |
| Rectangle | rect(x, y, width, height, color, blend) | Draw a wrapped outline, maximum 16×16 per instruction. Corners are attempted by touching edges. |
| Filled rectangle | fill_rect(x, y, width, height, color, blend) | Draw a wrapped filled rectangle, maximum 16×16 per instruction. |
| 1-bit sprite | sprite1(x, y, asset, color, blend) | Draw an 8-byte 8×8 sprite. One bits draw; zero bits are transparent. |
| 2-bit sprite | sprite2(x, y, asset, transparent, blend) | Draw a 16-byte packed 8×8 sprite; choose one source color as transparent. |
| Held | held(key) | Return 1 while a button is down. |
| Key alias | key(key) | Legacy alias for held(key). Prefer held in new source. |
| Pressed | pressed(key) | Return 1 on the up-to-down edge sampled for the current frame. |
| Released | released(key) | Return 1 on the down-to-up edge sampled for the current frame. |
| Random | random(mask = 0) | Advance deterministic RNG and XOR the low byte with the mask. The artifact seeds the sequence. |
| Frame | frame(byte = 0) | Read byte 0–3 of the 32-bit frame counter; byte 0 changes every frame. |
| Get timer | timer_get(timer) | Read timer 0–3. Nonzero timers decrement at the start of each host frame. |
| Set timer | timer_set(timer, frames) | Write an 8-bit countdown. Useful for cooldowns without blocking. |
| Voice | voice(voice, tone, waveform, volume) | Start/update an indefinite voice. Tone 0 or volume 0 is silent. |
| Beep | beep(voice, tone, waveform, durationCode) | Play at volume 15 for 1, 2, 4, 8, 16, 32, 64, or 128 frames. |
| Silence | silence(voice) | Stop one of four voices. |
| Silence all | silence_all() | Stop all four voices. |
| Palette | palette(slot, masterColor) | Map active slot 0–3 to master color 0–15. Existing pixels recolor immediately. |
| Sleep | sleep(frames) | End this frame and skip that many complete future frames. Devices still tick. |
| Yield | yield or yield() | End CPU execution for this frame and resume at the next instruction next frame. |
| 1-bit collision | collide1(x, y, asset, fullScreen = 0) | Test visible sprite bits against lower layers, or the full composite when requested. Does not draw. |
| 2-bit collision | collide2(x, y, asset, transparent, fullScreen = 0) | Collision test using a selected transparent source color. |
| Invert | invert() | XOR visible active-layer colors with 3. Transparent pixels remain transparent. |
| Scroll left | scroll_left(amount) | Move active layer left 1–64 pixels; vacated pixels become transparent. |
| Scroll right | scroll_right(amount) | Move active layer right. |
| Scroll up | scroll_up(amount) | Move active layer up. |
| Scroll down | scroll_down(amount) | Move active layer down. |
Constants accepted by builtins
Keys: LEFT RIGHT UP DOWN A B START SELECT
Blend modes: REPLACE XOR ADD ERASE
Waveforms: SQUARE TRIANGLE SAW SINE
Master colors: BLACK NAVY PURPLE PLUM RED ORANGE YELLOW CREAM FOREST GREEN MINT CYAN BLUE INDIGO VIOLET WHITE
09 / storage
RAM, assets, and stacks
RAM
RAM is 1,024 mutable bytes. Babble assigns globals, parameters, locals, and arrays fixed addresses. Array indexes are not bounds-checked; an out-of-range index continues into neighboring RAM and ultimately wraps at 1,024.
Asset ROM
Asset ROM is 512 immutable bytes stored in the second half of the artifact. Use it for sprites, maps, lookup tables, or arbitrary constants. Asset addresses wrap modulo 512. A one-bit sprite consumes 8 bytes; a two-bit sprite consumes 16.
X and Y index registers
Raw Babel code uses 10-bit X and Y for the full RAM address range,
asset access, block copying, sprite sources, and bulk operations. Direct LOAD
and STORE reach only addresses 0–255; LOADX/STOREX
reach all 1,024 bytes through X or Y plus an encoded offset.
Two separate stacks
| Stack | Capacity | Used by |
|---|---|---|
| Return | 32 instruction addresses | CALL, RET, Babble function calls |
| Data | 64 bytes | PUSH, POP, compiler-preserved recursive frames |
Both stacks are circular. Pushing when full overwrites the oldest logical entry. Popping when empty returns zero.
10 / patterns
Practical recipes
Stable background
Draw it once on layer 0. Never clear that layer during ordinary movement.
Moving actor
Every frame: select layer 1, clear, update position, draw actor, yield.
Screen flash
Fill layer 2, yield or sleep briefly, invert it, then clear it to reveal gameplay.
Palette animation
Keep pixel slots unchanged and rotate their master-color mappings with palette.
Cooldown
On action, set a timer. Permit the next action only when timer_get returns zero.
Collision without drawing
Select the actor layer, test its sprite against lower layers, then decide whether to move.
Palette cycling
switch (frame(0) & 15) {
case 0: palette(1, CYAN); break;
case 8: palette(1, VIOLET); break;
default: break;
}
Non-blocking fire cooldown
if (pressed(A) && timer_get(0) == 0) {
spawn_projectile();
beep(0, 110, SQUARE, 2);
timer_set(0, 12);
}
Recursive work followed by halt
u8 input = 10;
u8 result;
fn fib(u8 n) {
if (n < 2) { return n; }
return fib(n - 1) + fib(n - 2);
}
fn main() {
result = fib(input);
return; // return from main compiles to HALT
}
11 / sharp edges
Important quirks
Everything wraps
Bytes, coordinates, PC, memory addresses, index registers, and stacks wrap instead of faulting.
Color 0 is not transparent
Visibility is separate. Use erase/clear to reveal lower layers.
Coordinates wrap at 64
A sprite or rectangle crossing an edge appears on the opposite side.
Subtraction carry means no borrow
After compare/subtract, C=1 means the unsigned left side was greater than or equal to the right.
Input edges last a frame
pressed remains true until execution yields and the host samples another frame.
Random depends on the artifact
Changing or optimizing code changes the artifact seed and therefore the deterministic random sequence.
Layer 0 starts visible
At reset it is opaque color slot 0; layers 1–3 begin transparent.
Padding is executable
Unused code bytes are zero, which means LDI R0, 0, not invalid memory.
Branch addresses are instructions
Target 0x20 means instruction slot 32, stored at artifact byte offset 0x40.
Expensive operations are atomic
If an operation does not fit this frame's remaining budget, it waits untouched for the next frame.
Rectangle corners repeat
Raw rectangle edges attempt touching corners more than once; XOR/Add can expose that drawing order.
Decompiler output is pseudo-source
Names and structure are lost. Raw registers, goto, and asm helpers may not recompile.
12 / raw machine
Bytecode anatomy
Instruction n lives at code bytes 2n and 2n+1.
The first byte is the opcode; the second is an immediate, register selector, descriptor,
or branch destination depending on the opcode.
00: 00 2A LDI R0, 0x2A
01: 10 01 ADDI R0, 0x01
02: 48 05 STORE [0x05], R0
03: F1 00 HALT
Flags
Z means the most recent flag-setting result was zero. Addition sets
C on carry. Subtraction and comparison set C when no borrow was
required. Many movement, memory, drawing, and device instructions preserve flags.
Conditional move conditions
| Code | Name | Condition |
|---|---|---|
| 0 | Z | Zero |
| 1 | NZ | Not zero |
| 2 | C | Carry / no borrow |
| 3 | NC | No carry / borrow |
| 4 | GT | C and not Z, unsigned greater-than after CMP |
| 5 | LE | Not C or Z, unsigned less-than-or-equal |
| 6 | AL | Always |
| 7 | NV | Never |
13 / complete ISA
Every Babel-8 instruction
Ranges with eight opcodes use the opcode's low three bits to choose R0–R7. Ignored operand bits may contain anything. All operations below are valid for every operand byte.
00–0F · Data movement
| Opcodes | Instruction | Operation and flags |
|---|---|---|
| 00–07 | LDI Rv, imm8 | Load operand into Rv. Flags unchanged. |
| 08–0F | MOV Rv, Rs | Copy register selected by operand bits 2–0. Flags unchanged. |
10–1F · Immediate arithmetic
| Opcodes | Instruction | Operation |
|---|---|---|
| 10–17 | ADDI Rv, imm8 | Add operand, wrap to byte, update Z and carry C. |
| 18–1F | SUBI Rv, imm8 | Subtract operand, update Z and no-borrow C. |
20–2F · ALU and conditional moves
20–27 ALU Rv, Rs uses operand bits 7–4 for the operation and bits 2–0 for Rs.
| Mode | Name | Effect |
|---|---|---|
| 0 | ADD | Rv += Rs; Z and carry. |
| 1 | ADC | Rv += Rs + C; Z and carry. |
| 2 | SUB | Rv -= Rs; Z and no-borrow C. |
| 3 | SBC | Rv -= Rs + (C ? 0 : 1). |
| 4 | MUL | Multiply; C indicates overflow. |
| 5 | DIV | Unsigned division; divisor zero gives 0 and C=1. |
| 6 | MOD | Unsigned remainder; divisor zero preserves Rv and sets C. |
| 7 | AND | Bitwise AND; update Z, clear C. |
| 8 | OR | Bitwise OR; update Z, clear C. |
| 9 | XOR | Bitwise XOR; update Z, clear C. |
| A | CMP | Flags from Rv - Rs without writing a register. |
| B | MIN | Unsigned minimum; update Z, preserve C. |
| C | MAX | Unsigned maximum; update Z, preserve C. |
| D | SWAP | Exchange Rv and Rs; Z from new Rv. |
| E | TEST | Flags from Rv & Rs without writing; clear C. |
| F | MOVF | Copy Rs to Rv and update Z. |
28–2F CMOV Rv, Rs copies Rs only when operand bits 5–3 select a true condition: Z, NZ, C, NC, GT, LE, always, or never. CMOV never changes flags.
30–3F · Shifts and unary transforms
| Opcodes/mode | Instruction | Effect |
|---|---|---|
| 30–37 / 0 | SHL | Logical left shift by operand low 3 bits. |
| 30–37 / 1 | SHR | Logical right shift. |
| 30–37 / 2 | ROL | Rotate left. |
| 30–37 / 3 | ROR | Rotate right. |
| 38–3F / 0 | INC | Increment; C when old value was FF. |
| 38–3F / 1 | DEC | Decrement; C when old value was 00. |
| 38–3F / 2 | NEG | Two's-complement negate; C for 80. |
| 38–3F / 3 | NOT | Bitwise invert. |
| 38–3F / 4 | CLZ | Count leading zero bits; zero input returns 8. |
| 38–3F / 5 | BITREV | Reverse all eight bits. |
| 38–3F / 6 | SWAPNIB | Exchange high and low nibbles. |
| 38–3F / 7 | ABS | Absolute value treating input as signed byte. |
40–5F · Memory access
| Opcodes | Instruction | Addressing |
|---|---|---|
| 40–47 | LOAD Rv, [imm8] | Read RAM address 0–255. Flags unchanged. |
| 48–4F | STORE [imm8], Rv | Write RAM address 0–255. |
| 50–57 | LOADX Rv | Bit 7 selects X/Y, bit 6 RAM/asset, low 6 bits offset. |
| 58–5F | STOREX Rv | Bit 7 selects X/Y; low 7 bits offset; destination is RAM. |
Effective RAM addresses wrap at 1,024; asset addresses wrap at 512.
60–6F · Index registers
| Opcode | Instruction | Effect |
|---|---|---|
| 60 | SETXL | Set low byte of X. |
| 61 | SETXH | Set high two bits of X from operand low two bits. |
| 62 | SETYL | Set low byte of Y. |
| 63 | SETYH | Set high two bits of Y. |
| 64 | ADDX | Add signed operand to X, wrap at 1,024. |
| 65 | ADDY | Add signed operand to Y. |
| 66 | XFROM | Load X from an adjacent wrapping register pair. |
| 67 | YFROM | Load Y from a register pair. |
| 68 | XTO | Write X low byte/high two bits to a register pair. |
| 69 | YTO | Write Y to a register pair. |
| 6A | SWAPXY | Exchange X and Y. |
| 6B | INCX | Increment X. |
| 6C | DECX | Decrement X. |
| 6D | INCY | Increment Y. |
| 6E | DECY | Decrement Y. |
| 6F | CMPXY | Z when equal; C when X ≥ Y. |
70–7F · Control flow
| Opcode | Instruction | Effect |
|---|---|---|
| 70 | JMP addr | Always branch to absolute instruction address. |
| 71 | JZ addr | Branch when Z=1. |
| 72 | JNZ addr | Branch when Z=0. |
| 73 | JC addr | Branch when C=1. |
| 74 | JNC addr | Branch when C=0. |
| 75 | CALL addr | Push already-advanced PC on return stack, then branch. |
| 76 | RET | Pop return stack into PC. |
| 77 | YIELD | End current host frame. |
| 78–7F | DJNZ Rv, addr | Decrement selected register and branch if nonzero. |
80–8F · Pixels and reads
80–87 PIXEL Rx: operand low 3 bits select y register; bits 4–3 pen; bits 6–5 blend; bit 7 increments x register after drawing.
88–8F PGET Rd: operand selects x and y registers; bit 6 reads the composite instead of active layer. Transparent active-layer reads return FF; composite reads always return 0–3.
90–9F · Shapes
| Opcodes | Instruction | Descriptor |
|---|---|---|
| 90–93 | HLINE Rb | Length=(low 6 bits)+1; pen=high 2 bits. |
| 94–97 | VLINE Rb | Same descriptor, vertical. |
| 98–9B | RECT Rb | Width=low nibble+1; height=high nibble+1. |
| 9C–9F | FILLRECT Rb | Same dimensions, filled. |
Base register supplies x; the next supplies y. Rectangles take pen and blend from the following two registers. All coordinates wrap.
A0–AF · Compositor and whole-layer operations
| Opcode | Instruction | Effect |
|---|---|---|
| A0 | LAYER | Select operand low two bits. |
| A1 | CLEAR | Bit 7: transparent clear; otherwise opaque fill with low two bits. |
| A2 | INVERT | Visible colors XOR 3. |
| A3 | FLIPX | Mirror active layer horizontally. |
| A4 | FLIPY | Mirror vertically. |
| A5 | SCROLLL | Scroll left by low 6 bits + 1. |
| A6 | SCROLLR | Scroll right. |
| A7 | SCROLLU | Scroll up. |
| A8 | SCROLLD | Scroll down. |
| A9 | ROTCW | Rotate 90° clockwise. |
| AA | ROTCCW | Rotate 90° counter-clockwise. |
| AB | NOISE | RNG-fill layer; bit 7 makes generated zero transparent. |
| AC | TILE1 | Tile one-bit 8×8 source from asset X or RAM Y. |
| AD | TILE2 | Tile packed two-bit source. |
| AE | ROWSHIFT | Circularly shift row R0 by signed R1. |
| AF | COLSHIFT | Circularly shift column R0 by signed R1. |
B0–BF · Sprite blitting
B0–B7 BLIT1 Rx draws an 8-byte one-bit sprite. Operand chooses y register, pen, blend, and asset-X versus RAM-Y source.
B8–BF BLIT2 Rx draws a 16-byte packed two-bit sprite. Operand chooses y register, transparent source color, blend, and source memory.
C0–CF · Random and machine reads
C0–C7 RAND Rv advances RNG and writes low RNG byte XOR operand.
| READ selector | Source | Argument |
|---|---|---|
| 0 | Current input mask | Ignored |
| 1 | Held key boolean | Low 3 bits choose key |
| 2 | Pressed edge mask | Ignored by raw machine |
| 3 | Released edge mask | Ignored |
| 4 | Frame-counter byte | Low 2 bits |
| 5 | System state | Low 5 bits |
| 6 | Voice state | Low 2 bits |
| 7 | Palette master color | Low 2 bits |
System selectors 0–8 read PC, packed flags, X low/high, Y low/high, return depth, data depth, and active layer. Selector 10 reads sleep; 11–14 timers; 15–18 voice tones; 19–22 remaining timed-voice frames; other reserved selectors return zero.
D0–DF · Audio, palette, timers, and sleep
| Opcodes | Instruction | Effect |
|---|---|---|
| D0–D7 | VOICE Rtone | Operand: voice, waveform, volume. Indefinite until changed or silenced. |
| D8–DB | BEEP voice | Operand: tone register, waveform, power-of-two duration code. |
| DC | PALETTE | Bits 5–4 active slot; bits 3–0 master color. |
| DD | TIMER | Bit 7 read/write, bits 6–5 timer, low 3 bits register. |
| DE | SLEEP | Immediate complete future frames; zero yields. |
| DF | SILENCE | Bit 7 all voices, otherwise selected voice. |
E0–EF · Bulk, cellular, collision, and hash operations
| Opcode | Instruction | Effect |
|---|---|---|
| E0 | FILLRAM | Fill all 1,024 RAM bytes with operand. |
| E1 | XORRAM | XOR every RAM byte with operand. |
| E2 | ADDRAM | Add operand to every RAM byte. |
| E3 | INVERTRAM | Bitwise-invert every RAM byte. |
| E4 | COPYBLOCK | Copy operand+1 bytes from RAM X to RAM Y using temporary-buffer semantics. |
| E5 | ROTATERAM | Circularly rotate all RAM by signed operand. |
| E6 | COLLIDE1 | Test one-bit sprite at R0,R1 against lower layers or full screen. |
| E7 | COLLIDE2 | Two-bit collision with transparent color descriptor. |
| E8 | LIFE | One Conway's Life generation over RAM as a circular 128×64 bitfield. |
| E9 | TOTALISTIC | Totalistic cellular generation; operand bits are next-state rule. |
| EA | XORNEIGHBOURS | Each RAM bit becomes left XOR right XOR above XOR below XOR operand bit 0. |
| EB | FLOOD | Flood active layer from R0,R1; operand selects replacement pen/blend. |
| EC | ERODE | Erode active-layer visibility using toroidal eight-neighbor morphology. |
| ED | DILATE | Dilate visibility; operand low two bits color new pixels. |
| EE | HASHRAM | FNV-1a-32 of RAM into R0–R3 little-endian. |
| EF | HASHSCREEN | Hash composite, active layer, or every layer according to operand. |
F0–FF · Lifecycle and data stack
| Opcode | Instruction | Effect |
|---|---|---|
| F0 | NOP | No effect. |
| F1 | HALT | Stop until reset. |
| F2 | YIELD | End current frame. |
| F3 | WAITR | Sleep for frames held in operand-selected register. |
| F4 | RESETCPU | Reset CPU, index registers, flags, and both stacks; resume at PC 0. |
| F5 | RESETRAM | Zero all RAM. |
| F6 | RESETDISPLAY | Restore initial layers, palette, and active layer. |
| F7 | RESETALL | Reset mutable state except artifact, frame counter, and current/previous input. |
| F8–FF / 0 | PUSH Rv | Push byte on circular data stack. |
| F8–FF / 1 | POP Rv | Pop; empty returns zero. |
| F8–FF / 2 | PEEK Rv | Read top without popping. |
| F8–FF / 3 | DEPTH Rv | Read logical data-stack depth 0–64. |
14 / strange and powerful
Crazy memory operations
The E0–EF family is where Babel-8 stops behaving like a tiny ordinary CPU. These are raw machine instructions; most do not yet have direct Babble builtins. You can encounter them in random artifacts, hand-authored bytecode, disassembly, and decompiler output.
Instant transformations
FILLRAM,XORRAM,ADDRAM, andINVERTRAMtransform all 1 KiB in one atomic instruction.COPYBLOCKmoves up to 256 bytes and handles overlap as if a temporary copy was made.ROTATERAMrotates the entire memory image, useful for ring buffers, procedural state, or deliberate chaos.
RAM as a 128×64 cellular world
LIFE, TOTALISTIC, and XORNEIGHBOURS interpret RAM's
8,192 bits as a toroidal 128×64 grid. The source and destination are logically separate,
so a generation never reads partially updated neighbors.
Hashes as tiny signatures
HASHRAM and HASHSCREEN place a 32-bit FNV-1a hash in R0–R3.
They are useful for conformance tests, procedural decisions, state fingerprints, or
checking that two executions remain deterministic.
These operations consume substantial work. LIFE, TOTALISTIC, ERODE, and DILATE cost 8,192 units; hashing every layer costs the entire 16,384-unit frame budget.
15 / understanding programs
Debugger, optimizer, and decompiler
Debugger
Breakpoints can be attached to source lines or raw instruction addresses. A breakpoint pauses before execution. Step instruction runs one atomic operation; step frame samples input and runs until a frame boundary. The layer inspector shows any raw layer without mutating machine state.
ROM optimizer
Enable Optimize ROM to reduce instruction count. Optimization preserves source-level behavior but changes instruction addresses, timing cost, debugger traces, artifact bytes, and therefore the artifact-seeded RNG sequence.
Decompiler
Attempt decompile follows reachable branches, identifies function candidates, names
direct RAM locations, recognizes common compiler patterns, and emits pseudo-Babble. Unknown
operations become asm(). It cannot recover original names, comments, scopes, or
the author's exact control structures.