Vector hardware · Game development
From an empty folder to a cartridge you can sell — C and 6809 assembly, with every number in the text measured against a ROM that really ran.
Build the habit the rest of this book depends on: never believe the picture, always read the numbers, and know exactly what each number does and does not mean. By the end you can answer "did that change help?" with a figure.
On a raster machine you debug by looking. Here you cannot, for three reasons that compound:
The ground truth is the vector list: the segments the machine drew, in order, with their intensity. Everything in this chapter reads that list.
| Tool | What it does |
|---|---|
vecdump | runs a ROM headless; per frame reports the vector list, counts, beam travel, cycles and RAM |
vecshot.py | draws a dumped frame to PNG — the figures in this book |
vecplay.py | runs a ROM against a written input script and captures chosen frames |
vectest.py | the seven acceptance checks; exits non-zero on failure |
vecdump game.bin -f 300 -q # summary only
vecdump game.bin -f 300 -F 60,120 # dump the vector list for two frames
vecshot.py game.vec -H 600 # render them
vectest.py game.bin -f 300 # pass or fail
Here is one frame of this chapter's program, drawing a single square:
LINE 0 38350 -> 6300 38350 i=95
dot 6300 38350 -> 6300 38350 i=95
dot 6300 38350 -> 6300 38350 i=95
LINE 6300 38350 -> 6300 30700 i=95
...
A square is four lines and eight dots. The dots are lit segments of zero length — the beam standing still between the sides. They cost no beam travel but they are in the list, and counting them as vectors inflates line art roughly threefold. vecdump reports the two separately for this reason, and never adds them together.
This is not a hypothetical. The first version of the tool counted every lit segment as a vector and reported a square as 12 vectors. Every "vectors per frame" figure it produced for line art was three times too large.
This is the most important paragraph in the chapter.
While your frame's work fits inside the budget, Wait_Recal absorbs whatever is left over. The measured frame length is then about 30,000 cycles no matter what you did inside it. Here is the same program with a workload that doubles and doubles again:
| workload | cycles per frame |
|---|---|
| 0 | 30,015 |
| 10 | 30,015 |
| 20 | 30,441 |
| 40 | 57,221 |
| 80 | 110,781 |
Between 0 and 10 the workload went from nothing to ten units of real work and the measurement did not move by a single cycle. A change that halved your workload in that region shows up as no change at all.
The way out is to take two readings well above the budget and use the difference. That cancels the frame's fixed cost, the call overhead, and the absorption. In the table above, 40 → 80 gives 53,560 cycles for 40 units of work: 1,339 cycles each, and the same slope holds at every point beyond the budget. Chapter 06 uses exactly this to compare C against assembly, and chapter 27 makes it routine.
boxes.c draws BOXES squares and nothing else. The point is not the picture: it is that every number changes in a way you can predict.
source ../../tools/env.sh
make sweep
boxes vectors dots beam cycles
1 4.0 8.0 24480 30017
4 16.0 32.0 97920 30017
16 64.0 128.0 391680 32283
Four vectors per square, exactly. Beam travel exactly four and sixteen times the single-square figure. When your measurement scales the way arithmetic says it should, both the tool and your model of the machine are working.
Note the cycles column doing precisely what the section above warned about: one box and four boxes are identical to the cycle, despite four times the drawing, and only at 16 does the frame overrun far enough to show anything at all.
moveto_d is relative. It moves the beam from where it already is, not to an absolute position. Without a reset0ref() first, the second square is drawn relative to the end of the first, the third relative to that, and they walk off the screen. This chapter's program had that bug, and here is how it announced itself:
BOXES=1andBOXES=4reported identical vector counts and identical beam travel.
The numbers said it before the picture did — one square looks much like another, but two identical measurements from different programs is impossible. That is the whole argument for measuring.
The usable screen is not ±128. At the default scale one unit passed to moveto_d is 255 DAC units, which puts the usable area at roughly x ±64, y ±80. Place a shape outside it and the beam clips against the edge silently. The symptom is a square with three sides — which is exactly what appeared when this chapter's grid was first laid out at ±70.
A single benchmark reading is worthless. See the section above. The first figure produced for chapter 06 was wrong by a factor of two and looked entirely plausible.
Not every part of a tool is equally tested. vecplay's button lines are verified end to end; its stick lines are not — injected analog values do not reach the game in this emulator build (chapter 22). A tool you wrote yourself earns trust one feature at a time, and the untested ones should say so out loud rather than look identical to the tested ones.
Nothing in this chapter needs a Vectrex, and that is the point: the acceptance run is automatic and can be part of a build. What it cannot tell you is whether the result is legible — brightness, contrast and the tube's own geometry are chapter 30.
Take the sweep table above and predict, before running it, the vectors, dots and beam travel for BOXES=9. All three are exactly predictable from the numbers you already have. Then run make BOXES=9 and check. If you are wrong, you have found either a bug or a gap in your model of the machine, and both are worth more than a correct guess.