Debugging A-code games

Since A-code sources get translated into ANSI C for compiling and linking, debugging can performed on the C level as well as on the A-code level.

C-level debugging

C-level debugging rarely required, but if necessary, can be done using standard debugging tools after building derived C sources into an executable with the -g option. If using gcc it is also useful to add -gdwarf-2 -g3, since that makes GNU debugger gdb understand macro names in the C code.

However, derived C sources are not human-friendly because they use numerical refnos to reference game entities. To assist code comprehension, the acdc translator has the -d command line option, which adds to the generated C code printout (on stderr) of A-code source lines being executed. This makes game debugging, be it with with gdb (or similar), or just by visual inspection, much easier.

A-code level debugging

Most A-code debugging takes place on the level of the A-code language itself. While there is no A-code debugger, there are some useful debugging tools.

Runtime procedure call trace

As already noted, acdc's -d command line option has the effect of showing at run-time on stderr A-code lines effectively being executed. This display includes the name of the source file and the line number of the code line being shown. Since the display is on stderr, it can be diverted into a file, regardless of the game's build. In the console mode, where by default both stdout and stderr are shown to the player, game response to a command comes after this listing of source lines, so that the game is still playble. Thus, for example:

 ? out
[...]
repeat.acd:934    ifeq context, none
repeat.acd:935       ifnear door1
repeat.acd:936          and
repeat.acd:937       ifeq waterfall, opened
repeat.acd:938          and
repeat.acd:939       ifeq dwarven, 0
repeat.acd:943       iflt stage, adventuring
repeat.acd:946       input
 You're at end of road again.

 ?

Cross-reference lists

The acdctranslator's -x option makes it emit a cross-reference file .xrf. This file can be further processed by the Perl script sortxrefs supplied as a part of the A-code sources package, which read the .xrf file and produces three files, suffixed respectively with .xrefs, .nrefs and .rrefs.

The .nrefs file list game's named entities in alphabetical order, associating each with the refno assigned to it by the translator.

The .rrefs file also lists entity names and their associated refnos, but this time in the refno order.

Finally, the .xrefs file is the most useful one of the lot. It is sorted on entity names and shows where each entity occurs in the source code, differentiating between its declaration and its use. Here is a brief extract from adv770.xrefs (the whole file runs to over 41 thousand lines):

         aurora.borealis    TXT      7879 text.acd  
         aurora.borealis    txt     11756 at.acd    
          automatic.gate    TXT      2590 text.acd  
          automatic.gate    txt       690 actions.acd
          automatic.gate    txt      9155 at.acd    
          automatic.gate    txt      9177 at.acd    
               available   STATE      597 defs.acd  
               available   state    15701 at.acd
               available   state    15714 at.acd
                 awarded   STATE      598 defs.acd
                 awarded   state    15715 at.acd
                 awarded   state    15717 at.acd
                 awarded   state     2154 procs.acd 
                     axe    OBJ       799 objects.acd
                     axe    obj       702 actions.acd
                     axe    obj       726 actions.acd
                     axe    obj       778 actions.acd
                     ...    ...            ...

So, for example, the object AXE is declared (type is in capitals!) on line 799 in the file objects.acd and referenced (type in lower case) in lines 799, 702, 726, 778... in the file actions.acd.

Game data dumps

The minor directive DUMPDATA, dumps the current state of the game to standard error. The default display contains no symbolic names (because they are not known to the kernel), so interpreting it requires constant reference to the above mentioned cross-reference files. This is not at all convenient.

However, if the game is translated int C with the -d option, or if it's source code defines the special variable ENTNAME, entity names are passed on to the kernel and are used in the dump. Here are some fragments of such a dump:

================= OBJECTS =================
....
   5 it               0    1000000000000000 at    0
   6 keys             0    1001000000000000 at   65 building
   7 lamp             0    1001000000000000 at   65 building
....
================= PLACES =================
  63 road             0    0101110000000000
  64 hill             0    0101000000000000
....
================= WORDS =================
 192 again
 193 carry
 194 drop
....
================= VARIABLES =================
....
 322 penalties         0    0000000000000000
 323 here             63 => road
 324 there            63 => road
 325 status            1    0100000000000000
....
================= TEXTS =================
 502 intro             0
 503 typo              0 cyclic
....
 693 plant.2           0 tied to   27 plant
...

For each entity, the dump gives its refno, its name and (if appropriate) its value, followed by (if appropriate) its properties bit screen (a.k.a. flags). For objects, their location is given (as location refno and name, if any). If a variable is a pointer to another entity, the entity pointed to is shown (its refno is the variable's value). Finally, if a text has morphing features, it's type is shown and, for typed texts, the entity to which they are tied.

By default all of the game's data is dumped, but one can choose to dump only data pertaining to a particular type of vaue-bearing entity by adding OBJECT, PLACE (or LOCATION), VARIABLE (or VARS) or TEXT as an argument to DUMPDATA.

Game data is dumped either to stderr or, if the game is being logged (i.e. was invoked with the -l command line option), to the log file.

As the simplest possible use, you can define SHOWDATE as a verb with a corresponding action:

verb dumpdata
action dumpdata
  dumpdata
  quit

Or, more sensibly, you can make DUMP one of optional "wizard" actions (see below).

The CHECKPOINT minor directive

CHECKPOINT minor directive is another debugging tool. When executed, it reports its own location (file name and line number) in the A-code source.

 ? n

=== Checkpoint: procs.acd, line 37 ===
 You are at the end of the road again.

 ?

What makes this useful is the fact that changing executable A-code source has no effect on its ability to restore saved games. Thus it is safe to add CHECKPOINT statements in a suspect piece of code in order to track problems in a saved game.

Constructing non-integral "wizard" mode

A-code's unusual feature of procedure groups permits construction of debugging commands (a.k.a. the wizard mode) as an optional add-on by using the INCLUDE? major directive. Once again this is made more useful by upward compatibility of saved games.

While A-code permits entities being used before being declared, it is useful to place all executable code after all declarative code. (This does not, of course, preclude entities being used before their declarations, since A-code texts can embed references to game entities.) If that's how code is arranged, adding an optional source file (via the INCLUDE? major directive) in between declarations and executable code has two interesting effects:

  • It has no effect on compatibility with games saved by the same code, but without the optional file. (See the section on upward game compatibility for an explanation). Note, however, that the reverse does not apply. If the optional code defines any additional entities (objects, places, variables or morphing texts), games saved by a version with that optional code cannot be loaded by the version without it.
  • The INCLUDE? major directive allows procedures (including REPEAT, ACTION and AT ones) to pre-empt procedures of the same name within the rest of the game's code. Such an intercepting "wizard" procedure can do its stuff and (a) QUIT, terminating the current command loop, (b) RETURN, terminating execution of the procedure group of that name (i.e. skipping the rest of the so-named procedures, or (c) PROCEED, letting the rest of the procedure group execute as it would do without the optional code.

For example, suppose the game contains

verb find
action find
  quip "You'll have to find thing out for yourself. "

Suppose further that the optional code contains

variable entname
action find
   ifeq status, 2  # Player trying to find something
      ifflag arg2, object
         say "f:Object {ARG2} "
         locate entname, arg2
         quip "is at {ENTNAME}." # Terminates command loop
      else
         quip "{ARG2} is not an object!"
      fin
   fin

This would modify the behaviour of the command FIND to show the location of of a nominated object, or, if no object nominated, to do exactly what it would do without the optional code. For ease of debugging, optionally included code should also have commands to toggle "wizard" mode on and off and other wizard mode commands whould simply PROCEED if the wizard mode is off.

There can, of course, be more than one optionally included file, positioned at various places in the source code, as appropriate. Nor is it necessarily the case that such includes must come before other code. E.g. my A-code port of Adv350 (written in order to experiment with mobile NPCs) has an extensive set of "wizard" tools, which is included after the NPC movement and actions code. It defines its own vocabulary list, that can be displayed by VOCABULARY WIZARD:

 ? voc wiz

 Wizard (i.e. debug) commands:
 
   close cave                - triggers the next cave closure stage
   decrement <entity>        - decrement state value of object or location
   data [obj|loc|var|text]   - show game's data
   fetch <object> (obtain)   - fetch the object from wherever
   find <object>             - go where the object is
   first                     - forces first dwarf, if axe not seen
   fly <location> (teleport) - go to the named location
   glow                      - toggle magical illumination
   increment <entity>        - increment state value of object or location
   next                      - go to the next higher location
   notbeen                   - show locations not yet visited
   previous                  - go to the next lower location
   runout                    - sets event clock to zero
   [show] {numbers|npcs}     - toggles loc number/npcs repeated display
   show <entity>             - Displays entity's current value
   where                     - shows where one is (and came from)
   where treasure            - shows valued objects (sorted on seen flag)
   where water               - shows water-holes
   where <object>            - shows object's location
   wizard {on|off}           - switches wizard mode on or off
 
 ?

The optional include file debug.acd can be found in the A-code source of Adv350 available at https://mipmip.org/adv350 in the file opt/debug.acd.

If using the advbld script to build Adv350 (or Adv770), the -W script option copies opt/debug.acd to where it will be found by an optional include, builds the game and then deletes the file copy.


Back to the documentation index
To the Mipmip home page
Feel free to leave a comment!
Mike Arnautov (27 March 2023)