Phoenix 7.6 by Patrick Davidson - Internal Documentation                

This is the internal documentation for the game Phoenix.  For information
on using the game, see the file 'PHOENIX.TXT'.  This document explains the
internal workings of the program.  It is mainly intended for people who
want to modify the game.
                             
 _____________________________________ Table of Contents

 1. Introduction ..................................................... 18
 2. Building the program ............................................. 50
 3. Overview of data structures ...................................... 71  
 4. Overview of program flow......................................... 226   
 5. File-by-file description ........................................ 294
 6. How to create new levels ........................................ 448
 7. How to create new enemies ....................................... 495
 8. Coding and commenting style ..................................... 547

 _____________________________________ Introduction

Phoenix is free/open source software.  This means that everyone is allowed
to develop modified versions of the game.

I chose this for the simple reason of trying to maximize the usefulness of
the program.  This provides many benefits, such as:

1) Allowing people who want to have a slightly different game to make it
   themselves with a minimum of effort.
2) Allowing intermediately-skilled programmers to learn from the design
   of the game.
3) Allowing the more advanced programmers to develop substantially different
   games based on this one more easily than writing new games from scratch.
4) Allowing the program to continue even if I stop supporting it myself.
                           
However, even though several people have talked to me about making such
modified games, nobody has actually released one yet.  To try to make it
easier for people to do that, I am releasing this document which describes
the internals of the game, and have also added many additional comments to
the code itself.

This document only provides an overview of the working of the game.  More
detailed information about specific functions is present in the comments of
the source files themselves.

This document assumes that the reader has at least basic familiarity with
programming the 68K-based calculators in assembly.  If you are a complete
beginner, this is probably not the best resource for learning to program the
calculators.

 ______________________________________ Building the program

The same source files are used for all three versions of the game.  The
target is selected by the file 'WHICH.H' which identifies the calculator
to compile for.  The source files contain conditional code to assemble
correctly for all three calculators.  All three versions are built by
assembling the main source file 'PHOENIX.ASM' which includes all of the
other necessary files.  For the TI-92, the developer version of Fargo
obviously should be installed.  For the other calculators, you must have
the 'TIOS.H' supplied with TEOS, and I recommend using makeprgm.exe as a
linker, as it's more reliable than the DoorsOS one.

The supplied batch file 'BUILD.BAT' will build for all three calculators
automatically, and creates ZIP archives of each calculators files.  This
assumes that the Fargo build tools are in your path, and the TEOS
environment variable points to where TEOS is.

There is now also a shell script 'build.sh' to do the same thing under
UNIX.  Read the comments at the beginning of it to see what you must
install to build with it.

 ______________________________________ Overview of data structures

All of the game's variables, as well as the screen buffer, are kept on the
stack.  When the game starts, this space is allocated on the stack, and the
A5 register points to the start of this space (where the screen buffer is).
Additionally, A6 always points to $600000, the beginning of the I/O
registers.

The data structures on the stack are defined in 'PHOENIX.H'.  It begins
with definition of the structures for player bullets, enemy bullets, and
enemies.  These definitions define the offsets of the various data items
regarding each in the records for them, and the size of each record.  The
data for each is stores in arrays on the stack.

After the structure structure definitions are the main variables.  This
is done with a macro that reserves specified amounts of space for each
variable.  For the most part, the comments describe the variables fairly
well; where they're unclear, the exact use of a variable is explained in
the code that uses it.  Included in this data are arrays for bullets,
enemy bullets, and enemies.  Each of these array simply consists of one
record followed by another.

On all calculators, the display buffer is 5120 bytes in size.  This
represents a display of size 256 by 160 pixels.  The coordinates which are
actually displayed are:

TI-92 and TI-92 Plus      (16, 21) - (239, 140)
TI-89                     (48, 21) - (207, 119)

As expected, this is with (0, 0) being the upper left corner.  The area the
player can move into is:

TI-92 and TI-92 Plus      (16, 96) - (239, 140)
TI-89                     (48, 96) - (207, 121)

Since the data structures for the player, bullets, enemy bullets, and enemies
are all very similar, I will describe them all here.  Where there are more
than one item of a certain type, the structures are stored in a simple array
with one element after the next.  Here is a table of where the data is, as
offsets from the variable start in A5, like all variables:

Offset            Structure size    Number of items
                                    
player_ship       16                1
enemies_data      18                26
enemy_bullets     16                16
bullets_data      16                num_b (varies from 16 to 24)

The "object" data structure has the following fields (for the player's
ship, the names are different, but the offsets are the same):

?_type   offset   0
?_dmg    offset   2
?_x      offset   4
?_w      offset   6
?_y      offset   8
?_h      offset   10
?_image  offset   12
?_data   offset   14

These structures are all defined in PHOENIX.H.  The ? is 'b' for player
bullets, 'e' for enemies, and 'eb' for enemy bullets.  Even though you can
use the numerical offsets, it is generally a better idea to use the defined
symbols for the offsets instead, to increase readability and decrease the
chance of errors.

Also, ?_size is defined to be the total size of the structure.  When moving
between structures or referring to the previous/next one, you should always
use these values so that the code will still work if the structure size is
changed.  While it's unlikely that anyone will ever remove or rearrange any
existing fields, there's a much better chance of fields being added.

The first item, ?_type, specifies what type the object is.  If this type is
zero, that means that this slot in the array is empty; an object is destroyed
by setting this to 0.  For the player's ship only, the type is not used in
this way but instead is simply a number from 0 to 2 which is the number of
the ship the player has.

If the type is not zero, it is an offset for the code to control the object.
All objects are defined with code to control them; this control code will
update the object's coordinates, and can also change things like the type (to
switch control code) as well as image and size if appropriate.  This system
is designed to give maximum flexibility to each type of object; adding a new
type of behavior is as simple as writing one new control routine.

EXCEPTION: For bullets only, the structure is different.  For a bullet, the
type is set to $1868 to indicate that it is the special "smart bomb" and any
other non-zero value is the bullets velocity (the first byte is the X-velocity,
and the second byte is the Y-velocity).  There is a standard control routine
for all bullets.

The type value is actually a 16-bit relative pointer to the control code.
This is the offset from a given address to the control routine.  For each
kind of object, the file that contains its control code also has a macro to
assign these pointers to symbols, which should be used to refer to the
object's type:  for enemies, 'ENEMIES2.ASM'; for the player's bullets,
'BULLETS.ASM'; and for enemy bullets, 'EBULLETS.ASM'.  The offset is defined
relative to the main processing routine for each object type, so that it can
be accessed with a single jump to a PC-relative indexed address.

Previous versions of the game had used double-pointers for this, where the
type values are just indexes into a table, and the numerical values were
entered into the code.  This system is not only smaller and faster, but also
much easier to work with; be glad that it changed.

Since the type is a 16-bit relative pointer, problems may occur if the
program ever becomes larger than 32 kilobytes.  In the cases of the objects,
all relevant routines are close together, so the size of a specific section
would have to reach 32 kilobytes to cause problems.  However, 16-bit relative
pointers are used in other places as well, over more widely separated areas,
a good reason to keep the game small.  (Actually, the only such cases are
probably the shop, free ship, and end game calls from the level handler; if
you really want to make the game huge, you may need to rearrange the code to
make those closer together).

Now on to the other variables, which should be much simpler.  The ?_dmg
variable is an amount of damage.  For a player bullet or enemy bullet, this
holds the amount of damage the object will do.  For an enemy ship, this is
the amount of damage it can take before being destroyed.  Note that the only
collisions detected are between player bullets and enemies, enemy bullets and
the player, and between the player and scenery or enemies (player to enemy
collisions will damage only the player).  Collision between enemy bullets and
enemies, for example, don't do anything.

The next four variables are quite simple.  ?_x and ?_y are the coordinates of
the upper right corner of the object.  ?_w and ?_h are the width and height,
respectively.  These are used for collision detection so it is important to
get them right.  To make an enemy be off the screen (e.g. at the beginning of
a level before it enters), use a Y coordinate of -300 to assure that the
image won't be drawn and no collisions will occur with other objects.

The ?_image field is the sprite "handle".  This is an offset to the sprite's
image, which is used to refer to the sprite for most sprite drawing
operations.  The sprite is drawn by a separate routine from your control
code, so you should not draw it yourself.  It is always drawn if the object
has a positive Y-coordinate.  If you want an object to be invisible, use the
in_Fake sprite handle here.

The ?_data field provides 2 bytes of storage for any data you want the
object to have, such as a status, countdown timer, velocity, etc.  This is
not used anywhere outside your control routines.

EXCEPTION: For bullets, there is a static allocation of this data also.
The first byte of it is the counter (used only for the bullets that have
varying X-velocities) and the second byte is the type.  Type is 0 for normal
bullets (ones which move in a straight line), 1 for bullets with varying
X-velocities (such as the Quadruple Cannon), and 2 for bullets that change
velocity in the Y-direction (arches, where -1 is added to the Y-velocity
each frame).

For enemies, there is also an e_dstry field, which is a relative pointer to
the destruction routines.  This code will be called when the enemy is
destroyed, and normally changes the type to an explosion, but can also do
special things.

 ______________________________________ Overview of program flow

The first step is the "low-level initialization".  If the program is a
nostub program, this first back up registers, then the LCD.  It installs
crash protection on all calculators.  Then, it calls the main game code.
After the game returns, the crash protection is removed, the interrupt
mask, frequency, and vectors are restored, and the LCD and registers are
restored if it's a nostub program.  In the event of a crash, an error
message is shown displaying the register contents, as well as the program's
start address and the contents of the top of the stack.  When the error
screen is exited, the code for exiting the game is executed.

The second step is to check for a saved game.  If one is present, it is
restored, and then execution proceeds to the main loop.

Otherwise, the next step is to initialize the screen and allocate the data
buffer on the stack.  After that, the custom interrupt 5 handler, which is
used for synchronizing the game, is installed.

Then the title screen is displayed.  This handles keypresses in the obvious
way, and (if not on a TI-89) also calls the regular screen side routines to
draw the sides.

Once the main loop is approached, the next step is to set the interrupt
frequency (again used to keep constant speed) and disable Auto-Int 1 for
best performance.  After that, the main loop, which handles all of the
game-play actions, is entered.  These routines are described with brief
comments in the 'PHOENIX.ASM' source file, along with detailed descriptions
in their own source files and the next section of the document.

During gameplay, the main loop executes through and through, mainly just
calling the gameplay routines each frame.  However, there are some specific
flow characteristics that are important:

1) Level loading.  When there are no more enemies left, the level loading
routine is called.  This looks up level from a list.  Some levels actually
are code, which is run to set up the level (or perform special functions).
Other levels are simply lists of which enemy types are present, and how many
of each should be created.

2) The shop.  The shop is actually entered by the level loader, and really
consists of a level which you simply when immediately.  However, the code
for its loader displays the shop screen.  When this screen is shown,
Auto-Int 1 is turned back to allow TIOS keyboard reading.  It is deactivated
again after exiting.

3) Enemies and enemy bullets.  Each of these things is processed using a 
jump table that contains specific routines for each type of item in
the array.  Refer to the specific source files, and their summaries below,
to see how each type of object works.  

4) Player bullets.  For these, one standard routine goes through all of the
bullets and proccesses them.

The first word of each object's structure is always its type.  This type is
an offset into the jump table (0 indicates that that element of the array is
unused, and thus no routine should be called; negative numbers have various
special meanings).  Since the jump table is a list of word offsets, the
types are always multiples of 2 so they can be used to directly index the
table.

4) Winning the game.  If you win the game, your score will be calculated
(and, of course, you get a 0 total if you used the cheat key) and you can
enter your name if you got a high score.  For the high score entry, and
display, Auto-Int 1 is switched off to allow keyboard reading.  It's turned
back on once you exit the high scores, at which point the game simply wraps
back to the beginning.

 ______________________________________ File-by-file description

This section contains a brief description of the functions of every file.
For more infromation, see the files themselves.  Files which contain any code
that varies between calculators are identified with (*) after the filename.

-------- BULLETS.ASM

This file contains code to move and display the player's bullets.  Each of
these two functions is invoked once per frame in the main loop.

-------- COLLIDE.ASM

This file contains the true collision detection code; it tests for collisions
between any objects by first seeing if their boundaries overlap, and if so
then checking if the image data actually hits by drawing the images in a
temporary buffer.

-------- DISPLAY.ASM (*)

This file contains basic display routines.  This includes synchronizing the
game timing to the interrupt, copying the display buffer to the screen,
clearing the display buffer, and setting up the in-game display.

-------- EBULLETS.ASM

This code handles enemy bullets.  It includes both checking for collisions
between enemy bullets and the player, as well as the routines to draw enemy
bullets on the screen and move them.

-------- EDESTROY.ASM

This contians the enemy destruction routines; they are called when an enemy
is destroyed.  Normally, they simply change the enemy to an explosion, but
they can do other things such as releasing a new enemy.

-------- ENDGAME.ASM (*)

This contains the code to calculate your score, prompt for your name if you
get a high score, and display the high score table.

-------- ENEMIES.ASM

This contains code to display enemies on the screen and handle collisions
between enemies and the player's bullets.  It also includes the skeleton of
the routine to move enemies; that is, it runs through the enemy array, and
calls enemy control code for each enemy.

-------- ENEMIES2.ASM

This file contains the actual enemy-specific control code for each type of
enemy.

-------- ESHOOT.ASM

This file contains firing routines which are called from the enemy control
code in ENEMIES2.ASM to fire enemy bullets.

-------- FREESHIP.ASM (*)

This file displays the screen offering the user a free ship.

-------- INIT.ASM (*)

This file sets up crash protection, which will display an error message and
dump regsiter contents if an exception is reached, and then allows the user
to exit safely (sometimes).  It also restores the timer rate, interrupt mask,
and interrupt 5 vector after the program exits.  If the program is built in
nostub mode, this file also backs up the registers and LCD data.  This file
calls the main rest of the program as a subroutine.

-------- LEVELS.ASM

This file contains the code which initializes levels of the game.  This
includes level-loading code, data for describing levels, and an interpreter
for that data.  Specifically, initializing a level consists of loading the
enemy array, and also setting the pointer to the coordinate table, if one
will be used by those enemies.  It also contains enemy data blocks (used only
within it) that specify types of enemies; in this case, the type data 
specified control routines, images, strengths, and additional data for the
control routine.  The shop is actually a special level in which the shop code
is called as the loading routine.  This level has 0 enemies so you win
immediately and progress to the next level.

-------- LIB.ASM (*)

Contains primitive routines for reading the keyboard, display text, and
generating random numbers.

-------- MEGABOSS.ASM

Contains control code for the megaboss, as well as the enemies it spawns.

-------- PHOENIX.ASM (*)

This is the main source file.  It includes all of the other files, sets up a
new game by initializing the game data, and contains the main loop.

-------- PHOENIX.H

Defines the games variables, as described above and below.

-------- PLAYER.ASM (*)

Handles movements of the player's ship, displays the player's ship, and
tests for collisions with the walls.

-------- SAVEGAME.ASM

Handles saving and restoring the game.  This is done without using any extra
memory by hiding the variables on the last part of the stack.

-------- SHOOT.ASM (*)

Fires the player's weapon.  Basically, it checks for pressing the weapon
selection and fire buttons, and inserts bullets in the bullet area when you
shoot.

-------- SHOP.ASM (*)

As you can guess, this displays the shop, and allows you to select and
purchase the items.

-------- SIDES.ASM (*)

Scrolls and displays the sides of the screen.

-------- SPRITES.ASM

Contains the simple "sprite" displaying routine used throughout the game,
as well as all sprites in the game in binary format.

-------- TITLE.ASM (*)

Display the title screen, allowing the user to view various pages of
information, and select difficulty level and (on the TI-89) speed.

-------- VERSION.H

This files contains definitions and macros holding the programs version
number which are used elsewhere in the program.  These are kept in a separate
file so that files containing things like the title screen code won't need to
be edited for each new version.

This file also contains identification used for recongnizing saved games; if
you are modifiying Phoenix, be sure to change it as indicated so other
versions won't try to restore your saved games (and vice versa).

-------- WHICH.H (*)

This identifies the target calculator as "ti89", "ti92" or "ti92plus" by
setting that define to 1.  This is set before assembling to choose which
calculator to build for.

 ______________________________________ How to create new levels

1) Prepare a level loader.

   A. Easy Way (table-based)

   Put a list of enemies for this level in LEVELS.ASM.  Under the section
   with the header "****************** LEVEL LOADERS", put in something
   that looks like this:

   Level_Label:
        LVLDB 2,standard_data_bank
        LENTRY 10,Enemy_1
        LENTRY 10,Enemy_2

   The LVLDB line first holds the number of entries, then the data bank of
   coordinates to use (if you are using only enemies that don't use a data
   bank, just use "LVL" in place of LVLDB.  The next lines are just entries
   for each type of enemy, first put the number of enemies of the type, then
   the type name.

   B. Hard Way (code-based)

   Write the level loading code in LEVELS.ASM.  This code should initialize
   the number of enemies remaining, the enemy data array, and the coordinate
   table (if needed).  This is very simple if you use the Load_Enemy_Info
   routine in that file; see the comment above it for details.  See the
   comments for 'Load_Level' at the end of LEVELS.ASM for details on how the
   level loader should work, and on its initial register values.

2) Add the level to the level table.  IThis is done simply by adding an
   invocation of the LEVEL macro (the format should be clear from the
   existing entries).  This is also in LEVELS.ASM; the table begins a few
   lines past the header "************************** LEVEL TABLE".  If you
   used method A above, then use 'LDATA' instead of 'LEVEL'.

3) Put this level in the list of levels (which is at the top of LEVELS.ASM)
   to be played for one or more difficulty levels, in LEVELS.ASM.  You do 
   this by simply adding a dc.b directive storing the index byte you gave the
   level in (2) wherever you want the level to be played.

4) If you are using an enemy that follows a formation, and want to create a
   new formation for your level, you also will need to create a coordinate
   data bank.  For the standard enemies, the data bank simply contains the
   X and Y coordinates of each enemy; one word for the X coordinate, then one
   for the Y coordinate, repeated for each enemy.

 ______________________________________ How to create new enemies

1) Obtain image(s) for the enemy.  You can use images already used for other
   enemies, or make your own.  The images are kept in SPRITES.ASM.  The
   format of the sprites should be clear from looking at the existing ones.
   If not, there are also some comments about it in SPRITES.ASM.

2) Write the enemy's code.  This is the code which is called for the enemy
   every frame and can move it.  This code is kept in ENEMIES2.ASM.  The
   comment for Move_Enemies at the beginning of ENEMIES.ASM describes what
   these routines should do.

   Each enemy should be assigned a type symbol, its "name".  The code for
   the enemy should be under the label C_name.  At the end of ENEMIES2.ASM,
   add a called to the ETYPE macro with the name to generate the type.  This
   sets the symbol to the offset of the enemy's code; this symbol is stored
   in the enemy array.

   You may, of course, create multiple enemy types for the same enemy; that
   is, the enemy will change types to change directions or for different
   phases of its flight.  These things can usually also be done with only
   one type, using the data field to keep track of the enemy's state.

   Of course, if you want to make a new enemy which behaves like existing
   ones, but just has things like image and strength changed, this step is
   not necessary.

   Note that in the current version of Phoenix, enemies will never collide
   with the player.  The player's drawing routine will detect the collision
   if the player is actually in contact with the enemy (that is, a pixel-to-
   pixel collision) and damage the player.  However, the enemy will not be
   damaged by the collision.  If you plan to modify the game so that enemies
   may collide with the player, you should take this into consideration; it
   might be a good idea to add regular player-to-enemy collision detection
   like the current collision detection between the player and the enemy
   bullets.

3) To make the enemy shoot, you can use the enemy firing routines in
   ESHOOT.ASM.  Refer to the comments in that file to see how you use them.
   Of course, you can also make your own firing routines.

   If you want to create a new type of enemy bullet, add it to the file
   EBULLETS.ASM.  You can refer to the comments and existing code in that
   file to see how to add an enemy bullet; the process is essentially the
   same as steps 1 and 2 of creating and enemy.

4) Add it to the enemy data bank at LEVELS2.ASM.  This stores the description
   of a specific enemy type, including its code offset, its image, and the
   amount of damage it can take.  Entering the enemy here is not completely
   necessary, but allows it to be loaded with the Load_Enemy_Info routine
   that greatly simplifies level loaders (if you are using the simpler
   table-based method of level loading, you must do this as the loader
   only uses such entries).

5) Create new levels featuring this enemy, or modify existing levels to use
   this enemy.

6) If you want anything special to happen when the enemy is destroyed, such
   as a unique explosion, releasing a smaller enemy, or something like that,
   add the code for that destruction to EDESTROY.ASM, and modify the level
   loader to install it in the enemy data structure.

 ______________________________________ Phoenix coding and commenting style

This section describes the coding and commenting style currently used in
Phoenix.  It's mainly here to specify precisely what should be clear from
examining the code.  The only reason to follow this style is to be consistent
with the rest of the program.

-------- Code formatting

Tabs should be set to 8 spaces, with soft tabs (i.e. a TAB actually puts
spaces to bring the cursor to the new position.  Mnemonics and most
directives begin at the first tab stop (column 9) and arguments at the
second tab stop (column 17).

An exception to this is the conditional assembly directives, which should
start in column 4.

Directives (other than dc.x) should be in all capital letters.  Instructions
should be in all lowercase letters, except for capital letters in symbols
that they reference.

Lines should be a maximum of 77 characters in length.

-------- Block comments

Ever major section of the code begins with a block comment describing it.
The main block comment consists of asterisk in the first 45 columns,
followed by a space and then the description of the section in all capital
letters.

Additional explanation of the block follows on subsequent lines.  Each line
of additional commenting begins with on asterisk, a space, and then the
text.  If any additional comments below the main comment line are present,
there should be one line of 8 asterisks to mark the end of it.  Multiple
paragraphs in this explanation should be separated with lines containing
only an asterisk.  There should also be one such line at the beginning and
at the end.

There should always be a blank line before and after a block comment.

-------- Individual comments

Comments for individual lines begin after a semicolon in column 41, at the
fifth tab stop.  If necessary to write more than will fit on a line, start
the rest at column 49 in a blank line, or better yet, consider moving the
explanation to the block comment.

-------- Variables

All global variables are defined in 'phoenix.h'.  Space for them is reserved
in the stack by the 'rs' macro used there.  Variable names should be all
lowercase, using underscores to separate different words.

Structure offsets are also defined there, as equates, and should also be
lowercase with words separated by underscores.

-------- Label names

Major labels (e.g. those reference by other sections of the program) should
have each word capitalized, and separate words by underscores.  All other
labels should have words separated by underscores, and all in lowercase.

-------- Register use

The A5 register should always point to the data are, and A6 should always
point to $600000.  Other specific sections, such as enemy handlers, have
additional requirements.  Unlike the requirements above, this one is very
important, as these values are depended upon in many places.
