ISA Instruction Set Architecture
The Fantasy Pi VM is a 32-bit RISC processor with 16 general-purpose registers, 32 fixed-length instructions, and 4 condition flags.
Instruction Format
Every instruction is exactly 32 bits wide and follows this layout:
6b
4b
4b
4b
4b
10b
OP = opcode (6 bits) | COND = condition code (4 bits) | DST = destination register (4 bits) | SRCA/SRCB = source registers (4 bits each) | IMM10 = 10-bit signed immediate (10 bits)
For load and jump instructions the lower 22 bits form a signed 22-bit immediate (IMM22).
Condition Codes
The upper 4 bits of every instruction are a condition predicate. The instruction only executes if the predicate is true.
| Code | Mnemonic | Meaning | Flag Test |
|---|---|---|---|
0x0 | al | Always | true |
0x1 | eq | Equal / Zero | Z set |
0x2 | ne | Not equal | Z clear |
0x3 | gt | Signed greater than | Z clear && N clear |
0x4 | lt | Signed less than | N set |
0x5 | ge | Signed greater or equal | N clear |
0x6 | le | Signed less or equal | Z set || N set |
0x7 | cs | Carry set | C set |
0x8 | cc | Carry clear | C clear |
0x9 | mi | Minus / negative | N set |
0xA | pl | Plus / positive or zero | N clear |
0xB | vs | Overflow set | V set |
0xC | vc | Overflow clear | V clear |
ALU & Data-Processing Opcodes
| Opcode | Mnemonic | Operation | Flags | Description |
|---|---|---|---|---|
| 0x00 | nop | — | — | No operation. Advances PC by 4. |
| 0x01 | load | dst = simm22 | — | Load 22-bit signed immediate into destination register. |
| 0x02 | mov | dst = srcA | — | Copy value from source to destination. |
| 0x03 | add | dst = srcA + srcB | Z N C V | 32-bit unsigned addition with full flag update. |
| 0x04 | sub | dst = srcA - srcB | Z N C V | 32-bit subtraction. Sets flags for signed/unsigned comparisons. |
| 0x05 | mul | dst = srcA * srcB | Z N C V | 32-bit multiply. Lower 32 bits of the 64-bit result are written. |
| 0x06 | div | dst = srcA / srcB | — | Unsigned division. Division by zero returns 0. |
| 0x07 | mod | dst = srcA % srcB | — | Unsigned modulo. Modulo by zero returns 0. |
| 0x08 | and | dst = srcA & srcB | Z N | Bitwise AND. |
| 0x09 | or | dst = srcA | srcB | Z N | Bitwise OR. |
| 0x0A | xor | dst = srcA ^ srcB | Z N | Bitwise XOR. |
| 0x0B | not | dst = ~srcA | Z N | Bitwise NOT (complement). |
| 0x0C | shl | dst = srcA << (srcB & 31) | Z N C | Logical shift left. Shift amount is masked to 5 bits. |
| 0x0D | shr | dst = srcA >> (srcB & 31) | Z N C | Logical shift right (zero-fill). |
| 0x0E | sar | dst = (int32_t)srcA >> (srcB & 31) | Z N C | Arithmetic shift right (sign-extend). |
| 0x0F | cmp | srcA - srcB | Z N C V | Compare. Updates flags but does not write a result. |
Memory Opcodes
| Opcode | Mnemonic | Operation | Description |
|---|---|---|---|
| 0x10 | ld | dst = mem32[srcA + simm10] | Load 32-bit word from RAM or ROM mirror. Reads little-endian. |
| 0x11 | st | mem32[srcA + simm10] = srcB | Store 32-bit word to RAM only. Writes little-endian. |
| 0x12 | ldb | dst = mem8[srcA + simm10] | Load unsigned byte from RAM or ROM mirror. |
| 0x13 | stb | mem8[srcA + simm10] = srcB | Store byte to RAM only. |
| 0x14 | ldh | dst = mem16[srcA + simm10] | Load unsigned halfword (16-bit) from RAM or ROM mirror. |
| 0x15 | sth | mem16[srcA + simm10] = srcB | Store halfword to RAM only. |
| 0x16 | push | sp -= 4; mem32[sp] = dst | Push a 32-bit register onto the stack. |
| 0x17 | pop | dst = mem32[sp]; sp += 4 | Pop a 32-bit value from the stack into a register. |
Control Flow Opcodes
| Opcode | Mnemonic | Operation | Description |
|---|---|---|---|
| 0x18 | jmp | pc = (pc & 0xFFC00000) | imm22 | Unconditional jump. Target is 22-bit absolute within the current 4 MB region. |
| 0x19 | jeq | if Z: pc = target | Jump if equal (zero flag set). |
| 0x1A | jne | if !Z: pc = target | Jump if not equal. |
| 0x1B | jgt | if !Z && !N: pc = target | Jump if signed greater than. |
| 0x1C | jlt | if N: pc = target | Jump if signed less than. |
| 0x1D | call | lr = pc; pc = target | Call subroutine. Return address stored in LR (R14). |
| 0x1E | ret | pc = lr | Return from subroutine. |
| 0x1F | trap | trap(imm10) | System trap. See Trap Reference for all vectors. |
Flag Bits
Z — Zero
Set when the result of an ALU operation is zero.
N — Negative
Set when bit 31 of the result is 1 (signed negative).
C — Carry
Set on unsigned overflow (carry out of bit 31) or borrow on subtraction.
V — Overflow
Set on signed overflow (result sign differs from operands).
Assembly Example
; Set up stack and call main
.global _start
_start:
load sp, #0x000FFFFC ; STACK_TOP
call main_init
; Simple loop: sum 0..99 in r0
sum_loop:
load r0, #0 ; accumulator
load r1, #1 ; step
load r2, #100 ; limit
.loop:
add r0, r1
cmp r0, r2
jlt .loop
ret
SYS System Trap Reference
Traps are system calls invoked with the trap instruction.
The trap vector (0–1023) is passed in the lower 10 bits of the instruction.
Registers R0–R15 are used for arguments and return values according to the calling convention below.
HALT (0x00) internally; all other traps are dispatched to the kernel trap handler.
Graphics Traps (0x10 – 0x18)
| Vector | Name | Args (Regs) | Returns | Description |
|---|---|---|---|---|
| 0x10 | GFX_INIT | R0=w, R1=h | — | Initialize framebuffer. Defaults to 640×480 if args are 0. |
| 0x11 | GFX_PRESENT | — | — | Request frame presentation. VM pauses until next frame. |
| 0x12 | GFX_CLEAR | R0=color | — | Fill back buffer with 32-bit ARGB color. |
| 0x13 | GFX_DRAW_SPRITE | R0=spriteId, R1=x, R2=y, R3=blendMode | — | Draw a registered sprite at (x,y) using the given blend mode. |
| 0x14 | GFX_DRAW_IMAGE | R0=surfId, R1=x, R2=y, R3=blendMode | — | Draw a raw surface (image) at (x,y). |
| 0x15 | GFX_BITBLT | R0=srcId, R1=dstX, R2=dstY, R3=srcX, R4=srcY, R5=w, R6=h, R7=mode, R8=globalAlpha | — | Blit a rectangle from source surface to framebuffer. |
| 0x16 | GFX_DRAW_TILEMAP | R0=mapId, R1=scrollX, R2=scrollY | — | Render a registered tilemap with pixel scrolling. |
| 0x17 | GFX_SET_PALETTE | R0=palId, R1=idx, R2=color | — | Write one color into a palette. palId 0–15, idx 0–255. |
| 0x18 | GFX_DRAW_TEXT | R0=fontId, R1=strAddr, R2=(y<<16)|x, R3=color | — | Draw a null-terminated string from RAM. Address is relative to RAM base. |
Blend Modes
| Value | Name | Description |
|---|---|---|
| 0 | Copy | Opaque copy, no blending. |
| 1 | Alpha | Standard alpha blend (premultiplied source over destination). |
| 2 | ColorKey | Copy pixels that do not match the transparent color key. |
| 3 | Additive | Add source RGB to destination RGB, clamp to 255. |
Audio Traps (0x20 – 0x22)
| Vector | Name | Args | Description |
|---|---|---|---|
| 0x20 | AUDIO_INIT | — | Reset the software audio mixer. |
| 0x21 | AUDIO_PLAY | R0=sampleId, R1=channel, R2=volume | Play a sample on the given channel (0–7). Volume 0–255. |
| 0x22 | AUDIO_STOP | R0=channel | Stop playback on the given channel. |
Input Traps (0x30 – 0x32)
| Vector | Name | Args | Returns | Description |
|---|---|---|---|---|
| 0x30 | INPUT_POLL | — | — | Refresh the input state buffer in RAM at 0x00010000. |
| 0x31 | INPUT_KEY | R0=keycode | R0=0/1 | Query a single key (0–255). Returns 1 if pressed, else 0. |
| 0x32 | INPUT_GAMEPAD | R0=player | R0=buttons, R1=LX, R2=LY | Read gamepad state for player 0 or 1. Buttons are a 32-bit bitmask. |
Input State Layout (RAM 0x00010000)
struct InputState {
uint32_t keyboard_keys[8]; // 256 key bits
uint32_t gamepad_buttons[2]; // 2 players
int16_t gamepad_axis[2][4]; // LX, LY, RX, RY
uint32_t mouse_x;
uint32_t mouse_y;
uint32_t mouse_buttons;
};
Memory Traps (0x40 – 0x41)
| Vector | Name | Args | Description |
|---|---|---|---|
| 0x40 | MEM_COPY | R0=src, R1=dst, R2=len | Copy len bytes from src to dst within RAM. Uses memmove semantics (overlaps safe). |
| 0x41 | MEM_FILL | R0=dst, R1=value, R2=len | Fill len bytes starting at dst with value & 0xFF. |
Math Traps (0x50 – 0x52)
| Vector | Name | Args | Returns | Description |
|---|---|---|---|---|
| 0x50 | MATH_RAND | R0=seed (optional) | R0=next | Linear congruential generator. If seed != 0, re-seeds the generator. |
| 0x51 | MATH_SIN | R0=angle | R0=fixed16.16 | Sine of angle (0–65535 maps to 0–2π). Returns 16.16 fixed-point. |
| 0x52 | MATH_COS | R0=angle | R0=fixed16.16 | Cosine of angle (0–65535 maps to 0–2π). Returns 16.16 fixed-point. |
Debug Traps (0x60 – 0x61)
| Vector | Name | Args | Description |
|---|---|---|---|
| 0x60 | DEBUG_LOG | R0=value | Print R0 as unsigned decimal to the debug console / serial. |
| 0x61 | DEBUG_LOG_STR | R0=addr | Print a null-terminated string from RAM to the debug console. |
Core Traps
| Vector | Name | Args | Description |
|---|---|---|---|
| 0x00 | HALT | — | Stop the VM. The program ends. Handled internally by the VM. |
| 0x01 | SLEEP | R0=milliseconds | Delay execution for the given number of milliseconds. |
Trap Usage Example
; Clear screen to black and draw a sprite
load r0, #0xFF000000 ; black (ARGB)
trap 0x12 ; GFX_CLEAR
load r0, #0 ; sprite ID
load r1, #100 ; x
load r2, #200 ; y
load r3, #1 ; Alpha blend mode
trap 0x13 ; GFX_DRAW_SPRITE
trap 0x11 ; GFX_PRESENT
MEM Memory Map & Registers
Memory Layout
| Region | Start | End | Size | Purpose |
|---|---|---|---|---|
| RAM | 0x00000000 | 0x00FFFFFF | 16 MB | General-purpose memory, stack, variables, heap. |
| VRAM | 0x01000000 | 0x017FFFFF | 8 MB | Video memory: framebuffers, sprite sheets, tile data. |
| ROM | 0x02000000 | 0x03FFFFFF | 32 MB | Cartridge ROM mapped for read-only access via ld. |
Register File
The VM exposes 16 general-purpose 32-bit registers (R0–R15). Several registers have dedicated roles in the ABI:
| Register | Name | Role |
|---|---|---|
| R0 – R12 | — | General purpose. Arguments and return values start in R0. |
| R13 | SP | Stack pointer. Initialized to 0x000FFFFC on reset. |
| R14 | LR | Link register. Holds return address for call. |
| R15 | PC | Program counter. Reads as current + 4. |
Hardware Limits
Memory
16 MB RAM, 8 MB VRAM, 32 MB ROM maximum.
Display
640×480 default framebuffer. Up to 1920×1080 supported.
Sprites
256 max sprites. Max size 256×256 per sprite.
Tiles
1024 max tiles. 16 tilesets. 16 palettes × 256 colors.
Audio
8 PCM channels, 16-bit stereo, 44100 Hz.
Performance
~1 million VM cycles per frame at 60 FPS.
CPP C++ SDK Reference
The C++ runtime abstracts the virtual hardware through a thin header-only layer. You write game logic against these headers; the IDE generates asset IDs, scene IDs, GameObject type IDs, and named GameObject instance IDs automatically.
Engine Lifecycle
#include <fantasy_engine.h>
int main() {
fe::Engine_Init(); // Set up subsystems
fe::Engine_LoadScene(SCENE_FOREST_01);
while (true) {
fe::Engine_PollInput(); // Update input state
fe::Engine_Update(); // Run all GO_Update callbacks
fe::Engine_Draw(); // Draw tilemaps + GO_Draw callbacks
}
return 0;
}
Graphics API
| Function | Parameters | Description |
|---|---|---|
fe::Engine_Init() | — | Initialize the engine and all subsystems. |
fe::Engine_LoadScene(id) | uint32_t sceneId | Switch to a generated scene ID such as SCENE_FOREST_01, reset object handles, and spawn scene/map instances. |
fe::Engine_CurrentScene() | — | Return the currently loaded scene ID, or SCENE_NONE. |
fe::Scene_Load(id) | uint32_t sceneId | Alias for Engine_LoadScene. |
fe::Scene_Current() | — | Alias for Engine_CurrentScene. |
fe::Engine_PollInput() | — | Read hardware input and write to RAM input state. |
fe::Engine_Update() | — | Call all registered GameObject Update callbacks. |
fe::Engine_Draw() | — | Render tilemaps and call all Draw callbacks. |
fe::Engine_PlaySFX(id, ch) | uint32_t sampleId, uint8_t channel | Play a sound effect on the given channel. |
fe::Gfx_Clear(color) | uint32_t argb | Clear the back buffer. |
fe::Gfx_Present() | — | Flip buffers and wait for vsync. |
Sprite & Tilemap API
| Function | Parameters | Description |
|---|---|---|
fe::Sprite_Draw(id, x, y, flip) | uint32_t id, int x, int y, uint8_t flip | Draw a sprite with optional horizontal/vertical flip. |
fe::Sprite_DrawEx(id, x, y, mode, alpha) | + BlendMode mode, uint8_t alpha | Draw with blend mode and global alpha override. |
fe::Tilemap_DrawLayer(id, scrollX, scrollY) | uint32_t layerId, int sx, int sy | Render a tilemap layer with pixel scrolling. |
fe::Palette_SetColor(pal, idx, color) | uint8_t pal, uint8_t idx, uint32_t argb | Update a single palette entry at runtime. |
Input API
| Function | Parameters | Returns | Description |
|---|---|---|---|
fe::Input_IsPressed(btn) | uint32_t button | bool | True if button was pressed this frame (edge). |
fe::Input_IsHeld(btn) | uint32_t button | bool | True if button is currently held down. |
fe::Input_IsReleased(btn) | uint32_t button | bool | True if button was released this frame (edge). |
fe::Input_GamepadAxis(player, axis) | uint8_t p, uint8_t a | int16_t | Read analog axis value (LX=0, LY=1, RX=2, RY=3). |
Button Constants
BUTTON_UP
D-pad up
BUTTON_DOWN
D-pad down
BUTTON_LEFT
D-pad left
BUTTON_RIGHT
D-pad right
BUTTON_A
Primary action
BUTTON_B
Secondary action
BUTTON_START
Pause / menu
BUTTON_SELECT
Select / back
Audio API
| Function | Parameters | Description |
|---|---|---|
fe::Audio_Init() | — | Reset the mixer and clear all channels. |
fe::Audio_Play(sample, ch, vol) | uint32_t id, uint8_t ch, uint8_t vol | Start playback. Volume 0–255. |
fe::Audio_Stop(ch) | uint8_t ch | Stop a channel immediately. |
fe::Audio_SetVolume(ch, vol) | uint8_t ch, uint8_t vol | Change volume of an active channel. |
GameObject API
GameObjects are the primary gameplay entities. The IDE generates type IDs and a registration table from the GameObject Editor. You only implement the callback functions.
| Type / Function | Signature | Called |
|---|---|---|
GameObject | struct { uint16_t type_id; uint16_t instance_id; Vec2 pos; Vec2 vel; Rect collider; uint32_t sprite_id; uint32_t anim_id; uint32_t state; uint32_t flags; void* user_data; } | Entity instance structure. |
GO_InitFunc | void (*)(GameObject* self) | Once when the object is spawned. |
GO_UpdateFunc | void (*)(GameObject* self) | Every frame before drawing. |
GO_DrawFunc | void (*)(GameObject* self) | Every frame after tilemap render. |
GO_CollisionFunc | void (*)(GameObject* self, GameObject* other) | When two colliders intersect. |
GO_InteractFunc | void (*)(GameObject* self, GameObject* other) | On player-trigger interaction. |
fe::GO_GetInstance(id) | GameObject* (uint32_t instanceId) | Return a named scene/map instance in the currently loaded scene, or nullptr. |
Named Map Instances
When you paint on an Object layer in the Studio Map editor, the selected tile is stored as map data and a real
GameObjectInstance is attached to that map. Select the painted object and set its
Instance-Name in the right-side settings panel. During code generation the IDE writes
generated/gameobject_instances.h with one GOINST_*
constant per named instance.
// Object layer instance name in the Map editor: player
#include <fantasy_engine.h>
void Door_Update(GameObject* self) {
GameObject* player = fe::GO_GetInstance(GOINST_PLAYER);
if (player) {
Vec2 p = fe::GO_GetPos(player);
if (p.x > 120) {
fe::GO_SetPos(self, 200, self->pos.y);
}
}
}
GO_GetInstance(GOINST_PLAYER) returns a valid pointer only while the scene containing
that named map object is loaded. After Engine_LoadScene, reacquire the pointer instead
of caching it across scenes.
GameObject Registration Macro
// In a .cpp file — the IDE generates the registry automatically
#include "generated/gameobject_types.h"
struct EnemyData { int hp; int patrolDir; };
void EnemySlime_Init(GameObject* self) {
auto* d = static_cast<EnemyData*>(self->user_data);
d->hp = 3;
d->patrolDir = 1;
self->sprite_id = ASSET_SPR_ENEMY_SLIME;
self->collider = { 0, 0, 16, 16 };
}
void EnemySlime_Update(GameObject* self) {
auto* d = static_cast<EnemyData*>(self->user_data);
self->pos.x += d->patrolDir;
if (self->pos.x > 200 || self->pos.x < 50)
d->patrolDir = -d->patrolDir;
}
void EnemySlime_OnCollision(GameObject* self, GameObject* other) {
if (other->type_id == GOID_PLAYER) {
fe::Engine_PlaySFX(ASSET_SFX_HIT, 0);
}
}
Math & Utility
| Function | Parameters | Returns | Description |
|---|---|---|---|
fe::fp_mul(a, b) | int32_t a, int32_t b | int32_t | 16.16 fixed-point multiply. Result is 16.16. |
fe::fp_div(a, b) | int32_t a, int32_t b | int32_t | 16.16 fixed-point divide. Result is 16.16. |
fe::fp_from_int(i) | int32_t i | int32_t | Convert integer to 16.16 fixed-point. |
fe::fp_to_int(f) | int32_t f | int32_t | Convert 16.16 fixed-point to integer (truncates). |
fe::Math_Rand(seed) | uint32_t seed | uint32_t | Pseudo-random number. Seed 0 returns next value. |
fe::Math_Sin(angle) | uint32_t angle | int32_t | Sine in 16.16 fixed-point. Angle 0–65535 = 0–2π. |
fe::Math_Cos(angle) | uint32_t angle | int32_t | Cosine in 16.16 fixed-point. |
1.0 is represented as 0x00010000.
Use fe::fp_mul and fe::fp_div instead of raw * and / to keep the scale correct.
Complete Project Structure
src/
main.cpp // Engine_Init + main loop
player.cpp // GOID_PLAYER callbacks
enemy.cpp // GOID_ENEMY callbacks
generated/ // Auto-generated by IDE
asset_ids.h // #define ASSET_SPR_PLAYER 0
gameobject_types.h // #define GOID_PLAYER 0
gameobject_registry.cpp // Type table
map_forest_01_data.cpp // Static tile arrays
engine_config.h // Hardware limits as #defines
assets/
sprites/player.png
tilesets/ground.png
maps/forest_01.json
game.fproj