VPy is a Python-inspired language that compiles to MC6809 assembly for the Vectrex console. All values are 16-bit integers. No floats, no booleans, no runtime exceptions.
Program Structure
Every VPy program has two special functions:
def main():
# Called once at startup — initialization only
SET_INTENSITY(127)
def loop():
# Called every frame — game logic goes here
draw_player()main()runs once when the cartridge starts.loop()runs every frame (~50 fps). All drawing and input goes here.WAIT_RECAL()is automatically injected at the start ofloop()by the compiler — do not write it yourself.
Minimal example
META TITLE = "HELLO"
def main():
SET_INTENSITY(100)
def loop():
PRINT_TEXT(-50, 0, "HELLO WORLD")Variables and Constants
Global variables
Declared at the top level, persisted across frames:
player_x = 0
player_y = 0
score = 0Local variables
Declared inside a function, scoped to that call:
def update_player():
dx = joy_x * 2 # local
player_x = player_x + dx # player_x is globalConstants
const declares a compile-time constant stored in ROM, not RAM:
const MAX_ENEMIES = 8
const GROUND_Y = -70
const LEVEL_NAMES = ["LEVEL 1", "LEVEL 2", "LEVEL 3"]Types and Values
VPy supports variable-sized types (⚠️ Experimental Phase) and defaults to 16-bit integers.
Default type: 16-bit integer
Untyped variables default to 16-bit signed (i16):
x = 0 # i16: range -32768 to +32767
counter += 1- All arithmetic is 16-bit (values wrap modulo 65536).
0is false, any non-zero value is true.- No floating point. No booleans — use
1and0.
⚠️ Experimental: Variable-Sized Types
VPy now supports explicit type declarations using Python's type hint syntax:
health: u8 = 100 # 8-bit unsigned (0–255)
direction: i8 = 1 # 8-bit signed (-128 to +127)
score: u16 = 1000 # 16-bit unsigned (0–65535)
position: i16 = -100 # 16-bit signed (-32768 to +32767)Status: 🔧 Experimental — fully implemented but not all edge cases tested. Use with caution.
Benefit: Saves ~20% RAM (~200 bytes per game) while remaining backward compatible (untyped variables default to 16-bit i16).
x = 42 # decimal
x = 0xFF # hexadecimal
x = 0b1010 # binary
x = -7 # negative (compiled as 0 - 7)Operators
| Category | Operators |
|---|---|
| Arithmetic | + - * / % |
| Bitwise | & | ^ ~ << >> |
| Comparison | == != < <= > >= |
| Logical | and or not |
| Compound | += -= *= |
Chained comparisons work: 0 < x < 100 expands to (0 < x) and (x < 100).
Control Flow
# if / elif / else
if score > 100:
level = 2
elif score > 50:
level = 1
else:
level = 0
# while loop
while lives > 0:
lives -= 1
# for loop
for i in range(8):
draw_enemy(i)
# switch / case
switch state:
case 0:
draw_title()
case 1:
draw_game()
# break, continue, return work as expectedFunctions
def fire_bullet(x, y, direction):
bullet_x = x
bullet_y = y
bullet_dir = direction- Up to 4 positional parameters.
- Variables declared in
main()are not accessible inloop()and vice versa — each function has separate scope.
Arrays
Arrays are declared with const (stored in ROM) or as globals (stored in RAM):
const coords = [10, 20, 30, 40]
val = coords[1] # read by index
scores = [0, 0, 0] # mutable array in RAM
scores[0] = 999META Directives
META TITLE = "PANG" # ROM header title (max 16 chars)
META MUSIC = music1 # background music asset
META ROM_TOTAL_SIZE = 65536 # enable multibank (optional)
META ROM_BANK_SIZE = 16384 # bank size (optional, default 16KB)Key Built-in Functions
Frame Control & Drawing
| Function | Description |
|---|---|
SET_INTENSITY(n) | Set beam brightness (0–127) |
MOVE(x, y) | Move beam to position without drawing |
DRAW_LINE(x0, y0, x1, y1, intensity) | Draw a line segment |
DRAW_CIRCLE(cx, cy, diam, intensity) | Draw a circle (16 segments) |
DRAW_CIRCLE_SEG(segs, cx, cy, diam, intensity) | Draw a circle with custom segment count |
DRAW_POLYGON(x0, y0, x1, y1, ..., intensity) | Draw a polygon |
DRAW_VECTOR("name", x, y) | Draw a .vec vector asset at position (x, y) |
DRAW_VECTOR_EX("name", x, y, mirror, intensity) | Draw a .vec asset with mirror and intensity override |
DRAW_RECT(x, y, w, h, intensity) | Draw a rectangle outline |
DRAW_FILLED_RECT(x, y, w, h, intensity) | Draw a filled rectangle |
Input
| Function | Description |
|---|---|
J1_X() / J1_Y() | Joystick 1 axes (-128 to 127) |
J1_BUTTON_1() / J1_BUTTON_2() / J1_BUTTON_3() / J1_BUTTON_4() | Joystick 1 buttons |
J2_X() / J2_Y() | Joystick 2 axes |
J2_BUTTON_1() / J2_BUTTON_2() / J2_BUTTON_3() / J2_BUTTON_4() | Joystick 2 buttons |
J2_ANALOG_X() / J2_ANALOG_Y() | Raw analog joystick 2 input |
J2_DIGITAL_X() / J2_DIGITAL_Y() | Digital direction input (-1, 0, +1) |
UPDATE_BUTTONS() | Poll and update button states |
Text & Debug
| Function | Description |
|---|---|
PRINT_TEXT(x, y, "text") | Print text on screen |
PRINT_NUMBER(x, y, number) | Print a 16-bit integer |
DEBUG_PRINT(val) | Print value to IDE debug panel |
DEBUG_PRINT_LABELED("label", val) | Debug output with label |
Audio
| Function | Description |
|---|---|
PLAY_MUSIC("name") | Start playing a .vmus asset |
STOP_MUSIC() | Stop current music |
PLAY_SFX("name") | Play a .vsfx sound effect |
AUDIO_UPDATE() | Update audio engine state |
MUSIC_UPDATE() | Update music playback |
Level System
| Function | Description |
|---|---|
LOAD_LEVEL("name") | Load a .vplay level file |
SHOW_LEVEL() | Render the current level |
UPDATE_LEVEL() | Update level state |
GET_LEVEL_WIDTH() / GET_LEVEL_HEIGHT() | Get level dimensions |
GET_LEVEL_TILE(x, y) | Get tile value at position |
Math Functions
| Function | Description |
|---|---|
abs(x) | Absolute value |
min(a, b) / max(a, b) | Min/max of two values |
clamp(v, lo, hi) | Clamp value between bounds |
sin(a) / cos(a) / tan(a) | Trigonometric (angle 0..127) |
sqrt(x) / pow(x, y) | Square root and power |
atan2(y, x) | Two-argument arctangent |
rand() / rand_range(min, max) | Random number generation |
Utilities
| Function | Description |
|---|---|
wait(frames) | Wait for N frame cycles |
beep() | Emit a beep sound |
fade_in() / fade_out() | Fade display in/out |
peek(addr) / poke(addr, val) | Memory read/write (constant addresses only) |
asm("code") | Inline MC6809 assembly |
Known Limitations
- Structs: Parsed but not compiled. Use parallel arrays instead.
- Enums: Not supported. Use named constants.
len(array): Returns 0. Track array sizes manually.- Recursion: Unsafe — no stack overflow protection.
DRAW_TO(x, y): Not implemented. UseMOVE()+DRAW_LINE()instead.