All posts

v0.2.0 — Multibank ROMs, EPROM programmer, typed variables, and scrolling

Multibank ROMs up to 4 MB, a built-in EPROM programmer dialog, DRAW_VECTOR brightness fixes, typed variables (u8/i8/u16/i16), and horizontal level scrolling via SET_CAMERA_X.

releasevectrexvpymultibankepromhardwaretypesscrolling

This release brings multibank ROM support over the finish line, adds a one-click EPROM programmer to the IDE, and fixes a long-standing issue where SET_INTENSITY had no effect on DRAW_VECTOR brightness. The Core compiler is now deprecated — Buildtools is the recommended backend for all projects.


Multibank ROMs — up to 4 MB

The standard Vectrex cartridge is 32 KB. With a bank-switching circuit (a latch plus a few logic gates on the cartridge PCB), the hardware can address ROMs of up to 4 MB. VPy has always been designed with 4 MB as the ceiling — bank switching is automatic, and your game code never mentions banks at all. You only configure the ROM size:

META ROM_TOTAL_SIZE = 65536   # pick your target size (see table below)
META ROM_BANK_SIZE  = 16384   # always 16 KB per bank

The compiler divides the ROM into 16 KB banks. Banks 0 through N−2 are switched in at $0000–$3FFF on demand; the last bank (helpers) sits fixed at $4000–$7FFF and is always visible. Bank switching happens automatically — DRAW_VECTOR, SHOW_LEVEL, UPDATE_LEVEL, and PLAY_SFX all switch to the right bank and back without any VPy code involvement.

Supported ROM sizes and recommended chips

ROM_TOTAL_SIZEBanksCapacityRecommended chipNotes
32768232 KB27C256Single-bank; no switching needed
65536464 KB27C512✅ Fully tested, recommended starting point
1310728128 KB27C010
26214416256 KB27C020
52428832512 KB27C040 / 29F040 Flash
1048576641 MB27C080 / 29F080
20971521282 MBM27C160
41943042564 MB29F320 FlashMaximum supported size

Current status: The 4-bank (64 KB) configuration is fully tested. Larger configs compile and the bank-switching infrastructure is in place, but cross-bank symbol resolution for more than 4 banks is still being completed.

The LEVEL_LOADED bug

During testing we found a subtle but nasty bug: SHOW_LEVEL_RUNTIME used CMPX #0; BEQ DONE to detect "no level loaded yet". That works fine unless the level asset happens to be the first object assembled in its bank — which gives it address $0000. The guard fires, the level is never drawn, and there is no error message.

The fix is a dedicated 1-byte LEVEL_LOADED flag: LOAD_LEVEL sets it to 1; SHOW_LEVEL checks it with TST >LEVEL_LOADED; BEQ DONE before loading the pointer. The null-pointer guard is gone.


EPROM programmer in the IDE

The IDE now has a dedicated EPROM Programmer dialog. Click the button in the toolbar, insert your blank chip into the TL866II+, and write your ROM without leaving Vectrex Studio.

What the dialog provides

Chip selection — choose your EPROM model from the list (27C256, 27C512, 27C010, etc.). The selection is remembered between sessions.

Write options — three checkboxes let you tune the programming behaviour, all persisted across sessions:

OptionWhat it does
Skip eraseDon't blank-erase before writing (useful for chips already erased)
Skip verifyDon't read back and verify after writing (faster, less safe)
UnprotectRemove write-protection before programming

minipro detection — on macOS, if minipro is not installed the dialog shows a Install via Homebrew button that runs brew install minipro for you. On other platforms the install path is shown.

Live log — the full minipro output streams into a scrollable log panel inside the dialog so you can see progress and any errors without switching to a terminal.

Workflow

  1. Compile your project (produces build/mygame.bin).
  2. Click EPROM Programmer in the toolbar.
  3. Select the chip type and adjust options if needed.
  4. Insert your blank EPROM into the TL866II+ and click Write.

The .bin file from the last successful build is used automatically.


SET_INTENSITY now controls DRAW_VECTOR brightness

DRAW_VECTOR renders .vec vector assets using a custom draw loop called DSWM (Draw Sync With Mirrors). On real Vectrex hardware, line brightness is set by the T1 timer and the VIA Port A DAC. For months, calling SET_INTENSITY(64) before DRAW_VECTOR("ship") had no visible effect — vectors always drew at full brightness.

The root cause was a chain of small issues:

  1. SET_INTENSITY didn't write to DRAW_VEC_INTENSITY — it updated the BIOS variable but the DSWM loop was reading a separate RAM cell that was never written.
  2. VIA Port A + Z-axis strobe wasn't being setDSWM was missing the write to Port A and the CB2 strobe that tells the hardware DAC to latch the value.
  3. T1 setup was in the wrong place — the T1 timer load must immediately precede CLR VIA_t1_cnt_hi to take effect; a displaced load was silently ignored.
  4. Per-path intensity from .vec data — each path in a .vec file carries an intensity FCB byte. DSWM was only reading it on the first path.

All four issues are now fixed. SET_INTENSITY correctly programs the DAC before each DRAW_VECTOR call, and the per-segment FCB values in .vec files are respected.

def loop():
    # Dim background vectors
    SET_INTENSITY(40)
    DRAW_VECTOR("background")
 
    # Bright foreground objects
    SET_INTENSITY(127)
    DRAW_VECTOR("player")
    DRAW_VECTOR("enemies")

Typed variables — u8, i8, u16, i16

VPy now has four integer types declared with Python type-hint syntax. Before this release, every variable was an implicit i16 (2 bytes). Now you can pick the right size for the job:

health: u8 = 100        # 8-bit unsigned — 0–255
direction: i8 = 1       # 8-bit signed  — -128 to +127
score: u16 = 0          # 16-bit unsigned — 0–65535
pos_x: i16 = 0          # 16-bit signed  — -32768 to +32767
TypeSizeRange
u81 byte0–255
i81 byte-128 to +127
u162 bytes0–65535
i162 bytes-32768 to +32767

Arrays scale too — a u8 array of 10 elements uses 10 bytes instead of 20. All code without type annotations continues to compile unchanged (i16 default).

One known edge case: const arrays with a u8 annotation can cause issues in complex games. Keep const arrays untyped as a workaround.


Level scrolling — SET_CAMERA_X

.vplay levels can be wider than the Vectrex screen. The new SET_CAMERA_X(x) builtin moves the viewport horizontally through the world. Objects outside the visible range are culled automatically — nothing extra needed in game code.

camera_x: i16 = 0
 
def loop():
    joy = J1_X()
    if joy > 20:
        camera_x = camera_x + 2
    if joy < -20:
        camera_x = camera_x - 2
    camera_x = clamp(camera_x, 0, 800)   # keep inside world bounds
    SET_CAMERA_X(camera_x)
    SHOW_LEVEL()

Object positions in .vplay files are world coordinates. SHOW_LEVEL() subtracts CAMERA_X from each object's X before drawing. LOAD_LEVEL() always resets CAMERA_X to 0.

Only horizontal scrolling is supported in this release.


Core compiler deprecated

The Core compiler (vectrexc, core/) is now in maintenance mode. It will not receive new features. All active development targets the Buildtools pipeline (vpy_cli, buildtools/).

If you have an existing Core project, migration is straightforward:

  1. Create a .vpyproj file next to your .vpy file (see the compiler docs for the format).
  2. In the IDE Settings panel, switch the backend selector to Buildtools (New).
  3. Rebuild — the output should be identical for single-bank projects.

Buildtools handles both 32 KB (single-bank) and 64 KB+ (multibank) games. For new projects, always start with Buildtools.