All SDK docs

Calibrating a Console

Why the beam is calibrated per console, how to open the calibration screen, and what each field fixes.

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 0

A missing field keeps its compiled default; with no file at all, everything is the default.

FieldDefaultOn screenWhat it is
zero35 (0x23)ZEROThe value primed into the zero reference, the beam's origin. The one that breaks text; see below.
bright127BRIGHTThe starting intensity. A game that sets its own intensity overrides it.
scale160SCALEThe draw scale divisor. Larger = shorter strokes.
t1_tail_q8640TAILHow far the ramp keeps going after the timer expires, in 1/256 of a count (640 = 2.5 cycles).
aspect_q8256ASPECTThe screen's shape: x against y, 256 = 1:1.
win_x, win_y15500WIN X / WIN YWhat the tube shows, as half extents in deflection units.
hold_y_min, hold_y_max4, 15file onlyY sample-and-hold window for swept text and the re-zero.
neg_rate_x, neg_rate_y0file onlyTrim for negative DAC rates: the DAC does not deviate the same at +k as at −k.
drift_x, drift_y0file onlyPer-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.cfg on the card first (511 spaces and a newline will do). Without it the menu says NOT SAVED and 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.cfg and 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 seeWhat it means
The octagon closesLong strokes arrive where they were aimed
The spokes meet in one pointEvery jump back to the centre lands there
Each spoke ends on its cornerJumps and strokes agree about distance
Opposite spokes make straight linesX 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 seeAdjust
The 40-stroke square opens, the 4-stroke one does notTAIL: the fixed error per stroke
Both shrink or grow togetherSCALE
Both fine, but the whole picture is too small or too largeSCALE

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 seeWhat it means
Every ring fails by the same amountThe fixed per-stroke term: TAIL
The failure grows with the radiusA term proportional to length: SCALE
It appears suddenly past some radiusA 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

  1. ZERO first, on the text. It moves everything else.
  2. TAIL, until the 40-stroke square closes like the 4-stroke one.
  3. SCALE, for size.
  4. 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).