Vectrex game project

July 29, 2013

I found a way to use the data-section: aslink has a feature to create different files for different areas. When I write this in ctr0.s:

	.bank ram(BASE=0xc880,SIZE=0x26a,FSFX=_ram)
	.area .data  (BANK=ram)
	.area .bss   (BANK=ram)

The linker creates two files when compiling for test.s19: test.s19, for the text and other areas, and test_ram.s19 for the data and bss areas. Then I converted both with "srec_cat" to a binary file (with "-offset -0xc880" for the test_ram.s19 file, to avoid creating a file 52 kB file). Finally I appended both with "cat test.bin test_ram.bin > output.bin". Now I can copy the areas in the startup code in crt0.s, from the end in the ROM to the RAM:

	ldx	#l_.text
	ldy	#0xc880
	ldb	#0x26a
copyData:
	lda	,x+
	sta	,y+
	decb
	bne	copyData

Still not perfect, because the css-area is just RAM initialized with 0, so no need to copy it from flash, but no problem, because I have plenty of ROM compared to the 874 bytes of available RAM, which are even shared with the hardware stack of the 6809. And sometimes the compiler generates strange errors like "error: unable to find a register to spill in class 'Q_REGS'" for perfectly valid code, if I use too complex expressions, but I could workaround it by using temporary variables and simplifying the expressions.

But now I can use the C compiler as usual. My idea for the game is a port of the nice puzzle game Bloxorz. I ask the author Damien Clarke, and he allowed to name it the same and to use the same levels.

First I tried to draw individual lines for each square edge, but this was too slow and it starts flickering on the real Vectrex (but was not bad in the emulator). It was perfect when I combined the horizontal and vertical lines to the minimum number of lines I needed to draw the grid. A first test, photographed without ambient light:

This was implemented with a trial-and-error formula for the isometric projection, adding a fraction of y for to x and vice versa. Wikipedia has a good article to make it look better. With Wolfram Alpha I multiplied the matrices:

and got this:

xs = x * cos(b) - z * sin(b)
ys = y * cos(a) + z * cos(b) * sin(b) + x * sin(a) * sin(b)

(x, y, z) is the world coordinate input and (xs, ys) is the screen coordinate output. For b the Wikipedia article says it has to be 45° for the real isometric projection, but it looks more interesting if it is a bit rotated, so I used 22°. Floating point calculations are slow on the 6809, but I needed to scale it anyway. With a scale factor of 16 and rounding the coefficients, the integer calculation is this:

int x3d(int x, int y, int z)
{
	x -= LEVEL_WIDTH / 2 - 4;
	return 14 * x - 6 * z;
}

int y3d(int x, int y, int z)
{
	y -= LEVEL_HEIGHT / 2;
	return 3 * x + 13 * y + 8 * z;
}

Both coordinates are shifted to the center (the screen center is (0, 0)) and the x coordinate is shifted a bit to the right, because of the diagonal projection. For more complex levels it was still too slow (framerate drop from the standard 50 Hz to below 40 Hz), so I implemented the line drawing function with inline assembler.

For the block rotating, I used 3D rotation matrices, as explained in this Wikipedia article. For a smooth animation, it would need floating point or fixed-point calculations, but I was too lazy to implement a fixed-point calculation, or to figure out how to use the Vectrex ROM functions for it, and wrote a small Java program which created all animations of the block.

Adding the sound was easy, because all you need to do is to create an array with a pointer to the ADSR envelope, a pointer to an frequency effect (some samples are available in the Vectrex ROM) and a list of notes and lengths. The call an initialization and play function each frame and the BIOS plays your song.

The compiled game:

bloxorz-rom.zip

You can play it on your Vectrex. In case you don't have one already, use the ParaJVE emulator. This is how it looks like:

By the way: It is impossble to change the "(C) GCE" text at the start. This is used as the ROM identification, which is tested by the BIOS on start. Only the year can be changed. If it is not detected, then the built-in game Mine Storm is started. Maybe this is one reason why not more other game publishers created games for the Vectrex, like they did for other video game consoles?

So far there are only 3 levels implemented, and no features like the unlock code for levels or display of the number of moves, menu as in the Flash implementation. And still some minor problems, like that short lines are brighter than long lines. I'll fix this and add the rest someday, but this is it for the Retrochallenge Competition 2013 Summer Games.

Source code (needs some cleanup and more comments):

bloxorz.tgz

If you want to program for the Vectrex, too, first read this Vectrex programming tutorial. Then download and read the Vectrex Programmers Manual, part 1 and part 2 and use it as a reference.

July 21, 2013

My first Vectrex program is running:

It was quite some work, because first I had to figure out how to use the GCC6809 compiler for the Vectrex, because it is too time consuming and error prone to write programs in assembler. First I downloaded the source code for gcc-4.3-4 and the gcc6809 patch from the GCC6809 Google Code page, but it didn't compile on my 64 bit Debian system. The author of the compiler provided a patch (was a problem with my 64 bit, worked on 32 bit systems) and pointed me to the current repository for it (the Google Code page is outdated). In the "build-6809" directory is a Makefile and with "make everything" I could compile the compiler successfully. Always fun to compile compilers :-)

But the standard GCC program linked with a CRT0 startup code for another retro system, which was not compatible with the Vectrex, because I needed a different entry point, a special header and different startup code. This is were the hard work started. I read the documentation of the compiler (found it only in archive.org), studied the source code and still had no idea how it worked.

This was the point were I remembered some of my hacking knowledge and used "strace -f" on the compiler, for tracing all system calls of a program and all child processes. Filtering for the execve calls showed me exactly which programs were started with which parameters. First "cc1" was started, which compiles a C file to an assembler source file. Then as6809 compiles this assembler file to a relocatable binary object file. And finally aslink links the object file of my program with the object file of the CRT0 startup code and creates a s19 output file. I found a program which converts this s19 file to a plain binary file, and I created my own crt0.S startup file. Now a simple compile script can compile my C program, the startup code and link it all together:

/usr/local/libexec/gcc/m6809-unknown-none/4.3.4/cc1 test.c -dumpbase test.c -mint8 -auxbase test -O3 -o test.s
/usr/local/bin/as6809 -l -og test.s mv test.rel test.o
/usr/local/bin/aslink -nws -b .text=0x0 test.s19 crt0.o test.o -k /usr/local/lib/gcc/m6809-unknown-none/4.3.4/ -k /usr/local/lib/gcc/m6809-unknown-none/4.3.4/../../../../m6809-unknown-none/lib/  -l as-libgcc.a -l as-libg.a -l as-libc.a -l as-libgcc.a echo | ./srec2bin /mnt/test.bin -s test.s19

Note the "-mint8" parameter. This makes the "int" datatype 8 bits width and helps to improve the performance, because 16 bit math needs more time and more instructions on the 6809 CPU.

After the usual one-line drawing hello world program, I wanted to draw something more complex. I started Inkscape and created some text. The Vectrex can't draw round vectors and it should be not too many vectors, because of the limited ROM size and the time required to draw it (you have only 30,000 cycles per frame, if you want 50 Hz refresh rate). But it was not too difficult to enter some text in Inkscape, lock it in on layer with some transparency setting, then trace it manually with lines in another layer:

A quick hack in Python converted the SVG file to the required Vectrex packet style list.

With the big vector list for the text I discovered another pitfall of the Vectrex: the integrator. The lines are drawn relative to the current analog output position. If you draw too many line, it gets all wobbly and inaccurate. From time to time you have to reset the integrator to the zero position (center of the screen). In the ParaJVE 0.7.0 emulator you can draw arbitrary many vectors without problems, but as soon as you try it on the real thing, you see the problems. This was the reason why I splitted the text in three parts. You can still see a slightly different position of the "allenge!" part in the Youtube video compared to the perfect position in the emulator. Maybe I have to adjust some potentiometers in the Vectrex to calibrate it better.

Finally I added some sine wave moving. In C it is much easier to write the code, and even more important, to read and understand it after some time, than in assembler. This is all it needs to create the animation you can see in the Youtube video:

typedef unsigned char uint8_t;
#include "hello.c"
#include "retroch.c"
#include "allenge.c"

const uint8_t sinWave[] = {
	0, 1, 3, 4, 6, 7, 9, 10, 11, 12, 14, 15, 16, 17, 17, 18, 19, 19, 19, 19, 20, 19, 19, 19, 19,
	18, 17, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 4, 3, 1, 0, -1, -3, -4, -6, -7, -9, -10, -11,
	-12, -14, -15, -16, -17, -17, -18, -19, -19, -19, -19, -20, -19, -19, -19, -19, -18, -17,
	-17, -16, -15, -14, -12, -11, -10, -9, -7, -6, -4, -3, -1
};

int main()
{
	uint8_t c = 0;
	uint8_t i = 0;
	while (1) {
		// wait for frame boundary (one frame = 30,000 cyles = 50 Hz)
		frwait();

		// set high intensity and beam position
		uint8_t x = sinWave[c];
		intens(0x7f);
		positd(x, 20);

		// draw Hello
		pack1x(helloPath, 0);

		// zero the integrators and set active ground
		zergnd();

		// 180 phase shift position
		uint8_t c2 = c + sizeof(sinWave) / 2;
		if (c2 >= sizeof(sinWave)) c2 -= sizeof(sinWave);
		x = sinWave[c2];

		// slightly lower intensity and draw rest
		intens(0x5f);
		positd(x, -20);
		pack1x(retrochPath, 0);
		zergnd();
		positd(x + 4, -18);
		pack1x(allengePath, 0);

		// update position
		if (++c == sizeof(sinWave)) c = 0;
	}
	return 0;
}

void exit(int status) {
	while (1);
}

The included C files are the vector data and look like this:

uint8_t helloPath[] = {
	0, 38, -75,  // "0" means draw blank line
	255, -25, 0,  // "255" means draw solid line
	255, 0, 39,
	255, 26, 1,
	0, -26, -1,
	255, -28, 1,
	0, 28, -41,
	255, -28, 1,
	0, 20, 60,
    ... lots of more data
	1  // packet terminator
};

This is the cartridge file: test.zip. Unpack it and start it in ParaJVE, or on the real hardware.

There are still a problem with the compiler: I can't use pre-initialized variables, because the "data"-section has to be copied to 0xc880, but the variable data has to be linked at the end of the program and then my startup code can copy it to the RAM. I don't know how to tell the linker that it should use 0xc880 for "data" for the C variables which references it, but that it must not create an output file of 52 kB which has the data physically at 0xc880, but I'll figure this out. Then it should be easy to write the game, and still 10 days left until the end of the challenge.

July 14, 2013

My cartridge is working, see http://www.crazycartridge.org for details. Now writing the game for the Retrochallenge Competition 2013 Summer Games.

December 8, 2012

Got my new Vectrex from eBay:

There is a humming and the contacts of the joypad buttons are not so good anymore, maybe I can fix it. Now I can develop a better version of my universal C64 cartridge (enhancing it to be a cartridge for more systems than the C64) and then use it as a programmable cartridge for the Vectrex.


29. Juli 2013, Frank Buß