The Vectrex beam is analog. Every console has its own DAC offset, sample-and-hold capacitors, analog switches and integrators, and they drift with age and temperature. A game built with this SDK draws with one set of beam constants, and those constants belong to the console, not to the game. That set is the calibration.
The compiled defaults were measured on one console. On a console whose analog parts differ, the same image can draw text that leans into diagonals, columns that cascade to one side, or glyphs that fall apart, while native cartridges look fine. That is not a broken image. It is an uncalibrated console.
Where the calibration lives
config/uvm2.cfg on the SD card. Every SDK game reads it at start-up, before its own main runs. Calibrate once from any game and every game on that console uses it.
It is plain text, one field per line, so you can read and edit it on a PC:
scale 140
t1_tail_q8 640
zero 25
bright 84
hold_y_min 4
hold_y_max 15
neg_rate_x 0
neg_rate_y 0
drift_x 0
drift_y 0A missing field keeps its compiled default; with no file at all, everything is the default.
| Field | Default | On screen | What it is |
|---|---|---|---|
zero | 35 (0x23) | ZERO | The value primed into the zero reference, the beam's origin. The one that breaks text; see below. |
bright | 127 | BRIGHT | The starting intensity. A game that sets its own intensity overrides it. |
scale | 160 | SCALE | The draw scale divisor. Larger = shorter strokes. |
t1_tail_q8 | 640 | TAIL | How far the ramp keeps going after the timer expires, in 1/256 of a count (640 = 2.5 cycles). |
aspect_q8 | 256 | ASPECT | The screen's shape: x against y, 256 = 1:1. |
win_x, win_y | 15500 | WIN X / WIN Y | What the tube shows, as half extents in deflection units. |
hold_y_min, hold_y_max | 4, 15 | file only | Y sample-and-hold window for swept text and the re-zero. |
neg_rate_x, neg_rate_y | 0 | file only | Trim for negative DAC rates: the DAC does not deviate the same at +k as at −k. |
drift_x, drift_y | 0 | file only | Per-jump drift compensation. Off on purpose: its model was never settled. |
The screen saves every field, including those it does not show, so hand-edited values survive a save.
Settings that belong to a game (refresh rate, start-up menu, rotation, audio output) live in config/<GAME>.CFG, and only for games that declare them. See Low-level API.
Opening the calibration screen
Hold buttons 2 and 3, and launch the game with button 4 as usual. The calibration screen opens before the game. It works in every game built with this SDK, C and VPy alike. Some games also open it from their own menu, and those show their own settings too.
Buttons 2 and 3 are used because in the multicart menu button 4 launches the game and button 1 goes back, so a combination with button 1 never reaches the game.
Debug Cart: the BIOS menu opens it directly: button 3 for calibration, button 4 to start. That BIOS can only overwrite the file, not create it, so put a 512-byte
config/uvm2.cfgon the card first (511 spaces and a newline will do). Without it the menu saysNOT SAVEDand the setting lasts until power-off.
Controls
- Up / down chooses a field.
- Left / right changes it, continuously while held.
- Button 1 cycles the figure (AUTO, TEXT, WHEEL, RINGS) without leaving the field you are on, so one value can be judged on more than one figure.
- Button 4 saves to
config/uvm2.cfgand starts the game. Release it and press it again: the press that launched the game does not count.
Saving creates config/ and the file if they do not exist.
Reading the screen
FIGURE decides what is drawn. AUTO shows text while ZERO is selected and the wheel and squares otherwise. TEXT, WHEEL and RINGS force one figure. One zero cannot serve two scales: text and rings are very different sizes.
With ZERO selected: lines of text
A small high-score table drawn the way a game draws text: chained strokes, with no re-zero between glyphs, so the error shows. A long line above the rows and another to their left are the reference; each is a single ramp and barely carries the error.
Adjust ZERO until every row runs parallel to the top line and every column stands parallel to the left one.
Other fields: the wheel and the two squares
The wheel, an octagon with eight spokes, has one right answer anybody can see:
| What you see | What it means |
|---|---|
| The octagon closes | Long strokes arrive where they were aimed |
| The spokes meet in one point | Every jump back to the centre lands there |
| Each spoke ends on its corner | Jumps and strokes agree about distance |
| Opposite spokes make straight lines | X and Y move alike, positive and negative |
The two squares are the same size: the top one is 4 long strokes, the bottom one is 40 short ones. Any difference between them comes from the number of strokes, not their length:
| What you see | Adjust |
|---|---|
| The 40-stroke square opens, the 4-stroke one does not | TAIL: the fixed error per stroke |
| Both shrink or grow together | SCALE |
| Both fine, but the whole picture is too small or too large | SCALE |
The dots along the bottom square are its 40 joints: a stroke's ends are brighter than its middle. They are not a fault.
RINGS: an explosion burst
Concentric rings thrown out from the centre, one born per frame, up to fifty, each sixteen strokes, like the death star explosion in Star Wars:
| What you see | What it means |
|---|---|
| Every ring fails by the same amount | The fixed per-stroke term: TAIL |
| The failure grows with the radius | A term proportional to length: SCALE |
| It appears suddenly past some radius | A limit, not a slope |
RINGS overruns the frame on purpose: fifty rings is 800 vectors, like the real explosion. Read the FIT/OVER readout before adjusting anything. A list replayed a piece at a time and deformed geometry look alike.
A good order
- ZERO first, on the text. It moves everything else.
- TAIL, until the 40-stroke square closes like the 4-stroke one.
- SCALE, for size.
- BRIGHT last, to taste.
The zero: the one that breaks text
Every ramp measures its velocity from the zero reference. If the value held there is not this console's true zero, every ramp carries a constant extra velocity. A long stroke barely shows it, because it is one ramp. A row of text is dozens of short ramps with no re-zero in between, so the error accumulates: the row leans into a diagonal, and the next row starts from a different place.
The default, 0x23, comes from Vectorblade, an open-source Vectrex game that ships it as the factory value of a per-console calibration. The zero block only partially charges the reference, so the level it reaches depends on each console's parts too. That is why the value is calibrated, not fixed. To try a value by hand, write zero 7 in config/uvm2.cfg, but the screen is the better tool, because the text shows the effect live.
The screen's shape: ASPECT, WIN X, WIN Y
These three fields are about the glass, not the beam's accuracy. Select any of them and the screen shows a square with a circle inside and a frame:
- ASPECT (256 = 1:1) stretches x against y. It is right when the square is square on the glass and the circle round. Measure with a ruler if unsure.
- WIN X / WIN Y are the frame's half width and height. They are right when the frame just touches the edges of what the tube shows (about ±18000 × ±20500 on the console it was measured on).
vpy3d games use ASPECT automatically. They use the window only when the game asks for it, because a wider window changes what a game composed in the default square shows. The defaults (256 and 15500) change nothing.
A diagonal or a dot with the brightness up
A blanked beam is not invisible: what matters is how long it stays. A fast dark jump leaves a faint thread, but a slow sweep or a parked beam leaves a line or a dot. Two such artifacts were fixed in the SDK and are listed here so you recognise them in an old build:
- A corner-to-corner diagonal: the frame filler ran with the zero clamp released. Fixed; the filler now keeps the clamp on.
- A dot that follows the stick: the old digital joystick read probed the comparator with the DAC. Fixed; axes now come from the analog read alone.
If you see either, rebuild the game with the current SDK.
What calibration does not reach
Some console-dependent timings are compile-time only, fixed at measured values: the Y sample-and-hold window on ordinary strokes, how often the beam re-zeroes, and blank and settle times. If a console still draws badly after calibration, those are the next suspects. Bring a photo and, if possible, a dump of the counters (see Debugging).