Title Image

Part 6: Demos and Prototypes

My apologies for the long hiatus... I've had some interruptions in real life for my hobby work. In particular, I switched jobs, and I moved to a new apartment. On the plus side, I now know that MESS runs pretty well on Vista. (Guess where I'm working?)

I've switched gears a bit with the project, and am now focusing on creating the actual engine prototype instead of modeling small elements. This means there's some serious debugging sessions going on, but it also means I should have executable stuff to look for every new article.

So, let's have a look at the implementation of my demo prototype.


Demo

Screenshots and graphics are nice to look at, but having an actual working game engine would be even better. Unfortunately, this isn't a simple matter.

Because a CRPG is so file-dependent, I must have data for it to load and interact with that is identical or closely matches the final game data. So I can't follow a classic "engine first, content later" methodology, because it has to have something to go on.

At the same time, I can't design all the content first. Especially on a vintage system! It may turn out that the engine is unable to meet the needs of the content, which would require a great deal of revision.

So, what's the solution? Well, design a smaller, much smaller, prototype of the game. At the same time, this can also serve as a demo for the game, something to distribute and give people an idea of what it will be like to play it before the final version is completed.

My reasoning here is:

The demo is, essentially, the beginning of the full game, around 5% of the complete content. This introduces a prospective player to the game, lets them play up to a point and then requires the full version to continue. An import utility would "import" your demo game, so someone can can pick up where they left off when they have the final version.

Some things that the demo won't test or do: I've decided to make all my work on the demo available for download; you can find them on this page. Aside from keeping everyone's interest, I also think this is a good way to get some external testing. Try it on your various TI configurations! See if you get the "OPERATIONS COMPLETE!" message, or if something strange happens. Let me know if something doesn't seem to be working. I've done my best to ensure this runs on a normal TI, so hopefully there won't be too much difficulty.


Source Control

All programming projects, regardless of complexity or platform, should have some form of source control.

I've often noticed (and been guilty of it as well) that many inexperienced technical professionals usually think that vintage programming is "easy". It reminds me of the English Literature professors who think they could knock out a best-selling novel in a week, if they chose to do so.

The truth of the matter, though, is that any given project is difficult, regardless of the platform. This is especially true in software development. Vintage software may have been written by smaller development teams, but the work was not any easier! In fact, it was often more difficult.

One problem is the general unreliability of older hardware. Nostalgia tends to cloud our memories; we forget, for example, how HOT a vintage computer gets. I remember that I would stop using my TI simply because doing so warmed up my room too much on a hot summer day. And you sometimes wondered if you could keep your coffee cup warm on the cartridge slot panel. There was also the occasional crash, what is called the "blue screen of death" on modern systems, usually caused by a cartridge not inserted quite right, jogging the PE Box cable by accident, or even just an inexplicable momentary fault. Several hours of work could be lost in a single bad moment, or even files damaged if something occured during a read/write operation. This is one reason I develop in emulation.

So, as part of my source control for my CRPG, I took a few steps:

For the conversion of the tagged object file into a series of memory-image files, I use the original SAVE utility provided with the Editor/Assembler cartridge.

There's a couple different programs available for this, including Art Green's RAG Linker, which actually handles the operation much more smoothly. I noticed, though, that it broke a file that was smaller than 8 kilobytes into two seperate files for reasons unknown. So I'm sticking with the original for now.


Debugging

Unfortuantely, the one thing really lacking with vintage programming is solid debugging and diagnostic tools.

Most of the software developed by Texas Instruments was, ironically, not developed on the TI-99/4a directly, but on their 990 mini-computer. It had the same processor but it actually was capable, thanks to the DX10 operating system, of running multiple programs at once. So many TI developers would have their software running in tandem with debuggers or memory editors. Unfortuntately, a real-time debugger on a TI-99/4a is nearly impossible.

The Editor/Assembler does have a debugger. I have not tried to use it, but I've read about other people's experiences. The problem is that it must load the debugger with your program, which means a very large program would not work with it. It also involves some guesswork to identify where the problem is; like most old machines, the TI doesn't recover from a crash situation very well.

So how to debug without a good debugger?

Well, probably the absolute first and best is code review. This involves reading over your source code, following along with what's happening, and looking for any problems or oddities. This technique is still used even with modern programming, albeit in a smaller-scale fashion. (You can be sure no one code reviewed the whole of Vista, which is several million lines of code.)

The main problem with code review, though, is the same problem that writers face; personal bias. More often than not, your eye can skip over code that would break but you automatically assume it would "work fine". Like writers, shelving your work for a decent period of time before picking it up again can mitigate the problem; you have a fresher perspective of the code and are more likely to spot problems. But probably the best method is to have someone else do the code review.

The second method is available to us with one emulator; the very useful on-screen debugger provided with PC99. It's a very nice way to be able to view the contents of memory, registers, and other elements while a TI program is running, and even set up breakpoints based on expected values.

The downsides are:

The final method is the way I usually have to do it; code in my own artifical breakpoints into the code, along with memory displays as needed. This is a very sloppy and time-consuming method, since it means you have to re-compile your source in entirety. However, sometimes it's the only way to find out what exactly is going on in the environment.

If a particular piece of code seems to be the source of problems, you can break it out into a "model" program that is quicker and easy to compile and edit to find the problems. If you fix it in the model, then hopefully it will work now with the main program.

I had quite a few bugs to hunt in my initial source code, which took me some time to plow through. Probably the worst was the trouble with DSRLNK. I had thought that this routine preserved the PAB address located in the scratch-pad, and I had written my code on this assumption. It turns out, though, that it doesn't! Fortunately, since I was using a custom DSRLNK, I was able to add a few lines to do just what I needed. Most of the work in my verion 0.15 demo (now available for download) is being done in three files: The INIT_S file is the start of the program, where it sets up all things for the first and only time. For example, this is where the video mode is set to enhanced graphics mode.

Also done in this section is the loading of the first game file, called PBD. This file contains all the file information for the game, such as where particular files are located in terms of disk and disk drive, and whether a swap needs to be prompted for prior to access. This file will be custom-edited in the final version through the use of a configuration program. For now, any changes needed are done directly using a hex-editor.

The first file loaded is interesting because the first major hurdle here is, where to load the file from? I could have built in a bias for DSK1, the first floppy drive. However, this would make the game break completely if you attempted to run it from a RAMdisk or hard drive configuration.

So the first thing it does it call the disk controller and find out what the last accessed device was, which is where the program initially loaded from. It then constructs the PAB from that information:
DSRADR EQU  >8356
CRULST EQU  >83D0
SAVADD EQU  >83D2
B4     BYTE 4
PBDPAB DATA >0500,>1300,0,>0500,>0008
       TEXT '                                        '
PBD    TEXT '.PBD'
*
* LOAD PAB BLOCK FROM SOURCE DRIVE
       MOV  @CRULST,R12
       MOV  @SAVADD,R9
       LI   R8,PBDPAB+9
       SBO  0
       AI   R9,4
       MOVB *R9,R4
       MOVB *R9+,*R8+
       SRL  R4,8
GETORG MOVB *R9+,*R8+
       DEC  R4
       JNE  GETORG
       SBZ  0
       MOVB @PBD,*R8+
       MOVB @PBD+1,*R8+
       MOVB @PBD+2,*R8+
       MOVB @PBD+3,*R8
       AB   @B4,@PBDPAB+9
       LI   R0,>12A0
       LI   R1,PBDPAB
       LI   R2,50
       BLWP @VMBW
       AI   R0,9
       MOV  R0,@DSRADR
       BLWP @DSRLNK
Very elegant!

I also set up the number of potential open files by calling the disk controller subroutine to limit it to one. This particular technique is a little different than the one presented by Bruce Harrison in his Art of Assembly articles. He uses the version called by BASIC, which is a little more cumbersome than just calling the subroutine directly.

Here's what that looks like:
B1     BYTE 1
*
* SET NUMBER OF ACTIVE FILES TO ONE
       LI   R0,PAB
       MOV  R0,@DSRADR
       LI   R1,>0116
       BLWP @VSWW
       MOVB @B1,@>834C
       BLWP @SUBLNK
One important note here is that you must not call this BEFORE accessing the last drive for the first file above. Calling subprograms uses the same technique as file manipulations, and it wipes out the entry in the controller card.

You may be wondering "What's SUBLNK?" Well, that's a short modification I made to the DSRLNK routine. I thought that having the linkage type stored in the DATA word after the DSRLNK call was a bit wasteful. So instead, I encoded it into the DSRLNK itself; SUBLNK just feeds the value 10 instead of 8 into DSRLNK. You can easily replace the above with:
       BLWP @DSRLNK
       DATA 10
You may also be asking "What's VSWW?" Well, since I wrote my own video utilities, I added a few extra: This lets me fetch a whole word out of VDP or write one there without having to involve R2, like I would if I used VMBW. VMBS lets me write a single character in the high byte of R1 continuously for R2 bytes. You can find the code for these in the UTIL_S file.

You may also wonder why I'm using BLWP for my video utilities, since common knowledge has always indicated that using BL was better. Well, my reasoning there was that for the most part, all my video operations are not timing-critical. And it's useful to have R0-R2 in the parent routine preserved rather than destroyed. If there's a section where speed is essential for video, you'd best not use ANY branching at all, but rely on the direct ports and code the work in directly.

An interesting point with the INIT_S routine is that since nothing here is ever re-visted in the program, the codespace that it occupies can be, in fact, reclaimed as buffer space later. Caution needs to be taken, though, that you don't overwrite code you WILL need. The source listings that the assembler can generate as an option is useful for seeing how large a block of code can be. In this case, there's nearly 300 reclaimable bytes here, not too shabby at all.

The LOAD_S is the next step in the program. This file takes care of loading files that are changed more frequently. The program jumps back here anytime it needs to do a reload of a map or anything new.

Most of the data loading is fairly straight-forward, but there's some special cases that can come up. One is the potential to change the colors in the graphics sets. I had some cases come up with maps where I really wanted some different colors, but it was wasteful to add a whole new identical set in a different color. So I used some of the map header space to store "color change" instructions. This section is as follows:
LDG1   LI   R0,>1228
       LI   R1,STRING
       LI   R2,8
       BLWP @VMBR
       MOV  *R1,*R1
       JEQ  LDMG
       MOV  R1,R3
       LI   R4,4
LDG2   MOV  *R3+,@WORK
       JEQ  LDMG
       MOV  @WORK,R1
       ANDI R1,>00F0
       ORI  R1,>0001
       SWPB R1
       MOV  @WORK,R2
       ANDI R2,>000F
       SLA  R0,3
       LI   R0,>2000
       CLR  R5
       MOVB @WORK,R5
       SRL  R5,5
       A    R5,R0
       BLWP @VMBS
       DEC  R4
       JNE  LDG2
* Next section...
LDMG
Up to four color changes can be done, with up to 16 consecutive characters alterable. If it encounters a zero in any of the color change words, it immediately stops and moves on with operations.

The final file to look at is where a lot of my disk operations occur, the FILE_S file. This contains disk operation commands specific to the program.

The primary one is BLDPAB, a subprogram which constructs the PAB block for a file from the PDB data block in VDP, and also calls the DSRLNK to either open the file, or load/save the memory-image. It also contains a call to DSKMSG, a subprogram that, at present, does not exist. This subprogram will prompt the user to insert the proper disk, if a swap was indicated in configuration. Since the demo is running off a single disk, this utility is at present not needed.

There's a number of times in the program that only a single record needs to be loaded from a file. So I wrote the LD1REC program to handle this. It opens the file, reads the record, and closes it. For situations where multiple records need to be loaded, the code to handle this is handled directly in the parent routine. It's not good to be calling subprograms for disk operations if you can avoid them; it adds to the overhead.

The DSRLNK routine used in my program was originally written by Travis Watford, and referenced in the Art of Assembly articles. I copied the code and was delighted to find that it worked perfectly. (And is a heck of a lot more readable than the original TI ROM DSRLNK.) I made a few adjustments to it. One was so that it did not look for a DATA word following the DSRLNK call, and that a subprogram could be called through it through SUBLNK instead. The other was to preserve the PAB address stored at address @>8356 in R7 for the bulk of the DSRLNK operations. This lets me call DSRLNK multiple times for one file without having to restore the address every time. Here's the complete listing:
GPLWS  EQU  >83E0
VWS    EQU  >8320
VDPWA  EQU  >8C02
VDPRD  EQU  >8800
VDPWD  EQU  >8C00
DSRADR EQU  >8356
NLEN   EQU  >8354
CRULST EQU  >83D0
SAVADD EQU  >83D2
DSRWS5 EQU  VWS+10
DSRWS0 EQU  VWS+1
DSRWRK BSS  64
DSRLNK DATA VWS,DSR
SUBLNK DATA VWS,SUB
SPACE  DATA >2020
B46    BYTE >2E
B170   BYTE >AA
*
SUB    LI   R5,10
       JMP  DSR0
DSR    LI   R5,8
DSR0   SZCB @SPACE,R15
       MOV  @DSRADR,R0
       MOV  R0,R7
       MOVB @DSRWS0,@VDPWA
       NOP
       MOVB R0,@VDPWA
       AI   R0,-8
       MOVB @VDPRD,R1
       MOVB R1,R3
       JEQ  DSR9
       SRL  R3,8
       SETO R4
       LI   R2,DSRWRK
DSR1   INC  R4
       CI   R4,7
       JH   DSR9
       C    R4,R3
       JEQ  DSR2
       MOVB @VDPRD,R1
       MOVB R1,*R2+
       CB   R1,@B46
       JNE  DSR1
DSR2   CLR  @CRULST
       MOV  R4,@NLEN
       INC  R4
       A    R4,@DSRADR
       LWPI GPLWS
       CLR  R1
DSR2A  LI   R12,>0F00
DSR3   SBZ  0
DSR3A  AI   R12,>0100
       CLR  @CRULST
       CI   R12,>2000
       JEQ  DSR8
CRUOK  MOV  R12,@CRULST
       SBO  0
       LI   R2,>4000
       CB   *R2,@B170
       JNE  DSR3
       A    @DSRWS5,R2
       JMP  DSR5
DSR4   MOV  @SAVADD,R2
       SBO  0
DSR5   MOV  *R2,R2
       JEQ  DSR3
       MOV  R2,@SAVADD
       INCT R2
       MOV  *R2+,R9
       MOVB @NLEN+1,R5
       JEQ  DSR7
       CB   R5,*R2+
       JNE  DSR4
       SRL  R5,8
       LI   R6,DSRWRK
DSR6   CB   *R6+,*R2+
       JNE  DSR4
       DEC  R5
       JNE  DSR6
DSR7   INC  R1
       BL   *R9
       JMP  DSR4
       SBZ  0
       LWPI VWS
       MOV  R7,@DSRADR
       MOVB @DSRWS0,@VDPWA
       NOP
       MOVB R0,@VDPWA
       NOP
       MOVB @VDPRD,R1
       SRL  R1,13
       JNE  DSR10
       RTWP
DSR8   LWPI VWS
       MOV  R7,@DSRADR
DSR9   CLR  R1
DSR10  SWPB R1
       MOVB R1,*R13
       SOCB @SPACE,R15
       RTWP
Feel free to use this, or any other utilities in your programs. Although it's always nice to get a line of credit in that case.


Conclusion

I'm probably not going to be updating monthly from now on. Instead, I will publish a new article when I have achieved a new milestone with the demo worth looking at. Until then!