Atari Quill/Adventure Writer : File Format
This document aims to describe how the database for the Atari version of the Quill / Adventure Writer is laid out. Apart from 3 bytes in the header and 5 bytes in the main part of the database, none of which are referenced by the interpreter these explanations cover everything in the database.
Definitions
QuillA : The Quill for the ZX Spectrum, version A
QuillC : The Quill for the ZX Spectrum, version C
Condacts : Conditions & actions
General comments
- The Atari version sits somewhere between QuillA & QuillC in terms of functionality. Specifically, system messages are part of the database rather than being hard coded in the interpreter (QuillA) but it doesn't implement AUTO opcodes and object words (QuillC).
- The order of the blocks of data is not significant as far as the interpreter is concerned. Whether this is also true of the editor is unclear at this time.
- Text is (lightly) obfuscated by XORing with $FF.
- Colours in the header are copied to the shadow registers indicated on start up, so that display colours can be initialised per game.
- There is no text compression.
- The only text decoration provided is inverse video.
- Address tables are a sequence of word addresses, rather than the more efficient use of separate tables for low and high bytes of the addresses we often to see in 6502 programs.
- Condition and action opcodes shared some of the same byte values. Each list starts at zero, so opcode $00 is AT if it's a condition and INV if it's an action.
File format
The games are presented as Atari DOS segmented files, commonly refered to as .XEX files. Segments are stored in the files as a 6 byte header followed by the binary data. The Atari community will of course know this, but it's been included for the benefit of casual readers from elsewhere.
| Offset | Size | Value | Notes |
|---|---|---|---|
| 0 | 2 | $FFFF | Constant $FFFF indicates start of segment |
| 2 | 2 | Start | Start address of data block in memory |
| 4 | 2 | End | End address of data block in memory |
| 6 | End-Start+1 | Block of bytes | Binary data loaded from disk into memory, starting from the address specified |
The games contain 3 segments (based on a small sample, but I expect they are all the same).
- Block 1 runs from \$1D00 to $???? - This is the game database, clearly the end address varies by game
- Block 2 runs from $7C0D to $8A1B - This is the interpreter
- Block 3 runs from $02E0 to $02E1 - This is the usual Atari DOS autostart block. The interpreter is started after loading by calling (or jumping to) the address in this block. This will be $7C13 as the entry point is not at the start of the code.
Header format
The database starts at $1D00 with a 31 byte header, as follows:
| Offset | Address | Size | Notes |
|---|---|---|---|
| 0 | $1D00 | 1 | Unused by the interpreter |
| 1 | $1D01 | 1 | Text luminance (COLOR1, multiplied by 2) |
| 2 | $1D02 | 1 | Background colour (COLOR2) |
| 3 | $1D03 | 1 | Border colour (COLOR4) |
| 4 | $1D04 | 1 | Number of objects that can be carried |
| 5 | $1D05 | 1 | Object count |
| 6 | $1D06 | 1 | Location count |
| 7 | $1D07 | 1 | Message count |
| 8 | $1D08 | 1 | System message count |
| 9 | $1D09 | 2 | Event table address |
| 11 | $1D0B | 2 | Status table address |
| 13 | $1D0D | 2 | Object table address |
| 15 | $1D0F | 2 | Location table address |
| 17 | $1D11 | 2 | Message table address |
| 19 | $1D13 | 2 | System message table address |
| 21 | $1D15 | 2 | Movement table address |
| 23 | $1D17 | 2 | Vocabulary table address |
| 25 | $1D19 | 2 | Object start location table address |
| 27 | $1D1B | 2 | End of database |
| 29 | $1D1D | 2 | Unused by the interpreter |
Data blocks
Data starts at $1D1F (immediately after the header) with the condacts for the event table, then follows on as below. The order probably isn't important, at least not to the interpreter because of the addresses in the header and the indirection in the code. The order may be important to the editor, I have not checked. However, I suspect the order will always be the same.
Note that tables of pointers follow the data they reference in memory.
Where data is indirected using such a table, the address in the header points to this table, not the underlying data.
| Block | Type | Notes |
|---|---|---|
| EventCondacts | Condacts | Opcodes and data for events. |
| EventTable | Events | Words and condact addresses for events. These are the responses to what the player inputs. |
| StatusCondacts | Condacts | Opcodes and data for statuses. |
| StatusTable | Events | Words (comments) and condact addresses for statuses. These are the actions which run each turn, regardless of player input. |
| ObjectsText | Text | Object descriptions. |
| ObjectsAddressTable | Pointers | Start addresses for each object description. |
| LocationsText | Text | Location descriptions. |
| LocationsAddressTable | Pointers | Start addresses for each location description. |
| MessagesText | Text | Messages (game specific). |
| MessagesAddressTable | Pointers | Start addresses for each message. |
| SystemMessagesText | Text | Messages (system). |
| SystemMessagesAddressTable | Pointers | Start addresses for each system message. |
| ConnectionsData | Connections | Words and location numbers which define how locations are connected. |
| ConnectionsAddressTable | Pointers | Start addresses for each location's connections. |
| Vocabulary | Words | List of words and associated numbers the game implements. |
| Unreferenced | n/a | Unreferenced by the interpreter. Seems to be a block of 5 bytes, all zeros. |
| ObjectLocationTable | ObjectLocations | Start locations for all objects. |
See below for descriptions of each block type.
Block type : Pointers
Data items referenced by these pointers are numbered from 0 to N - 1, where N is the item count.
As noted above, addresses are stored as words (low,high) rather than being split across two tables. Therefore, the address for data item number I will be found at (start address from header + I * 2).
Block type : Text
This covers objects, locations, messages and system messages, which all share a common format.
Data starts at the address taken from the corresponding "Pointers" block.
Each string is stored as a zero terminated sequence of bytes, XORed with $FF. Note that the terminator bytes is also XORed, so is $FF in the database. Characters with the high bit set are inverse video in graphics 0, except $9B which is the newline character.
Block type : ObjectLocations
The address in the header points to a table of N + 1 bytes, where N is the number of objects. The last byte in the table is set to $FF and the interpreter depends on this to work as it doesn't check the object count in all cases.
| Value | Notes |
|---|---|
| $FC | Not created (252) |
| $FD | Worn (253) |
| $FE | Carried (254) |
| $FF | End of table (255) |
| <=$FB | Initial location (0-251) |
Block type : Words
The address in the header points to a table which consists of blocks of 5 bytes. Each block consists of 4 bytes for each word, XORed with $FF, followed by the word number. If the word number is $FF, that signifies the end of the table. The last word is "*". Words are space filled so they're all exactly 4 characters. Multiple words can have the same word value, making them aliases of each other.
Block type : Connections
Data starts at the address taken from the corresponding "Pointers" block.
There can be zero or more entries per location.
Each entry in the table points to a block of byte pairs, terminated with a $FF. The first byte in each pair is the word number (which, although not enforced in the interpreter, should relate to a direction word) and the second byte is the target location.
Block type : Events
The address in the header points to a table of 4 byte entries, as follows.
| Offset | Size | Notes |
|---|---|---|
| 0 | 1 | Word number of first word |
| 1 | 1 | Word number of second word |
| 2 | 2 | Address of condact block |
For the event table (or Vocabulary Action Table as the manual calls it) the word numbers are matched against what the player entered and the corresponding condact block executed. For the status table, the word numbers are ignored because the interpreter needs to run the whole table. The word numbers in this case are defined as comments in the manual.
The table is terminated with a single zero byte (word number 1 == 0).
Block type : Condacts
Each entry consists of zero more more condition opcodes and their arguments, followed by a $FF, then one or more action opcodes and their arguments. The entry is terminated with another $FF. In theory there could be zero action opcodes, but that's pointless and unlikely to exist in practice.
The interpreter knows how many arguments each opcode consumes and advances a pointer appropriately.
To make this clearer, here's a made up example. The player has entered KILL SMURF and these words matched an entry in the event table. Here is a sample condact.
| Offset | Byte | Notes |
|---|---|---|
| 0 | $08 | CARRIED (condition opcode) |
| 1 | $10 | SWORD (let's say object $10 is a sword) |
| 2 | $00 | AT (condition opcode) |
| 3 | $20 | LAIR (let's say location $20 is the smurf's lair) |
| 4 | $04 | PRESENT (condition opcode) |
| 5 | $11 | SMURF (let's say object $11 is an unslain smurf) |
| 6 | $FF | Mark end of condition block |
| 7 | $12 | MESSAGE (action opcode) |
| 8 | $25 | Let's say message $25 reads "You slay the foul creature with the sword." |
| 9 | $19 | SWAP (action opcode) |
| 10 | $11 | SMURF (the live smurf) |
| 11 | $12 | CORPSE (let's say object $12 is the murdered smurf) |
| 12 | $01 | DESC (action opcode), so the player gets to see the murdered smurf |
| 13 | $FF | Mark end of action block and hence condact entry |
Condition opcodes
| Byte | Opcode | Arguments | Notes |
|---|---|---|---|
| $00 | AT | 1 (location number) | Player at location number |
| $01 | NOTAT | 1 (location number) | Player not at location number |
| $02 | ATGT | 1 (location number) | Player at location > number |
| $03 | ATLT | 1 (location number) | Player at location < number |
| $04 | PRESENT | 1 (object number) | Object is present |
| $05 | ABSENT | 1 (object number) | Object is absent |
| $06 | WORN | 1 (object number) | Object is worn |
| $07 | NOTWORN | 1 (object number) | Object is not worn |
| $08 | CARRIED | 1 (object number) | Object is carried |
| $09 | NOTCARR | 1 (object number) | Object is not carried |
| $0A | CHANCE | 1 (percentage) | Percentage > random number |
| $0B | ZERO | 1 (flag number) | Flag == 0 |
| $0C | NOTZERO | 1 (flag number) | Flag != 0 |
| $0D | EQ | 2 (flag number, constant) | Flag == constant |
| $0E | GT | 2 (flag number, constant) | Flag > constant |
| $0F | LT | 2 (flag number, constant) | Flag < constant |
Action opcodes
| Byte | Opcode | Arguments | Notes |
|---|---|---|---|
| $00 | INV | 0 | List inventory |
| $01 | DESC | 0 | Describe current location |
| $02 | QUIT | 0 | Prompt player to quit game |
| $03 | END | 0 | End game, prompt player to try again |
| $04 | DONE | 0 | Stop processing events |
| $05 | OK | 0 | Print OK message |
| $06 | ANYKEY | 0 | Prompt player to press any key and wait for this to happen |
| $07 | SAVE | 0 | Prompt user for file name and save game state |
| $08 | LOAD | 0 | Prompt user for file name and load game state |
| $09 | TURNS | 0 | Print number of turns taken |
| $0A | SCORE | 0 | Print score (out of 100) |
| $0B | CLS | 0 | Clear screen |
| $0C | DROPALL | 0 | Drop all droppable items |
| $0D | PAUSE | 1 (ticks) | Pause for number of ticks (1 tick ~ 1/50th of a second) |
| $0E | SCREEN | 1 (value) | Set screen colour to value (COLOR2) |
| $0F | TEXT | 1 (value) | Set screen text luminosity to value * 2 (COLOR1) |
| $10 | BORDER | 1 (value) | Set screen border colour to value (COLOR4) |
| $11 | GOTO | 1 (location number) | Move player to new location |
| $12 | MESSAGE | 1 (message number) | Print message with specified number |
| $13 | REMOVE | 1 (object number) | Remove object with specified number, if worn |
| $14 | GET | 1 (object number) | Get object with specified number |
| $15 | DROP | 1 (object number) | Drop object with specified number, if carried |
| $16 | WEAR | 1 (object number) | Wear object with specified number if carried |
| $17 | DESTROY | 1 (object number) | Change location of object with specified number to NOTCREATED, removing it from play |
| $18 | CREATE | 1 (object number) | Change location of object with specified number to the current location, bringing it in to play |
| $19 | SWAP | 2 (object1, object2) | Swap locations of the two specified objects |
| $1A | PLACE | 2 (object number, location number) | Move specified object to specified location |
| $1B | SET | 1 (flag number) | Flag = TRUE ($FF) |
| $1C | CLEAR | 1 (flag number) | Flag = FALSE ($00) |
| $1D | PLUS | 2 (flag number, constant) | Flag += constant, but with a maximum of 255 |
| $1E | MINUS | 2 (flag number, constant) | Flag -= constant, but with a minimum of 0 |
| $1F | LET | 2 (flag number, constant) | Flag = constant |
| $20 | SOUND | 4 (voice, pitch, distortion, volume) | Set Atari sound registers |