Ring Technical Documentation: Creatures Ring

Introduction

Monster movement

Monsters in DM/CSB move about and respond to the party in rather complex ways. I have started a project to understand monster AI. Complete understanding will probably always elude me.

The intelligence of the DM monsters is contained almost wholly in a function named ProcessTimers29to41. As the name implies, it processes timer entries of type 29 through 41. The original function was a mass of spaghetti code with gotos all over and even into the middle of 'while' loops and god-knows-what. See Shape of Original AI Function . In an attempt to understand monster behaviour I first decided to unravel this function so that it could be drawn on 2-dimensional paper. As I redrew the function, I rewrote it, often duplicationg code and turning large pieces of it into separate functions (albeit with terrible-looking parameter lists). The results are seen at First Approximation, Second Approximation, and Third Approximation. After each small step I replayed three complete DM games and one rather long CSB game to make (kinda) sure I had not changed the actual operation of the function (I caught several mistakes this way).

Then I began adding some trace code, enabled with the Misc Menu item 'MonsterAI Trace'. The trace is intended to be used as follows:

  1. Play a game with recording enabled.
  2. Identify some part of the game that contains curious monster behabiour.
  3. Edit the recording and enable the AI trace (#AITraceOn-#AITraceOff) during the curious behaviour.
  4. Replay the game to get the trace listing.
  5. Edit the trace listing to delete all monsters other than the one of interest.
  6. Study the trace listing in combination with the flowcharts to see what is happening.
  7. Ask Paul to fix the flowcharts, fix the trace, explain the strange nomenclature.
  8. Repeat at step 4 until full understanding is obtained.
  9. Add a section to this documentation to make the new understanding public.

The entire sequence of steps ought not to take more than a couple of months. Then you can begin studying another curious behaviour. The first thing I wanted to see was how simple movement was accomplished in monsters on a level different from the party level. It taught me several things. See Simple Movement on Non-party level.

Here are some 'sub-problems', starting with the simplest.

Monster movement while party is on a different level

Movement of a monster on a level different from the party. Diagram

Interesting things learned:

  • When the party enters a level of the dungeon all the creatures on that level are put to sleep by removing their timer entries. They are stuck and will never move again. Then each monster is reawakened by creating for it a timer entry of type ?????? with a delay of ??????.
  • Each monster type has a characterisitc 'Time-per-move'. When a monster is on a different level from the party the time between moves is the larger of two values, namely: a) 16 times the difference in levels and b) 2 times the characteristic 'Time-Per-Move'. I imagine that this was implemented so that monsters would move more slowly and monsters far from the party would move more slowly yet, thus saving precious CPU time on the rather overworked Atari.
  • Type 37 Timer messages are used to keep the monsters active.
  • At each opportunity to move, the monster attempts to move in a random direction. If movement in that direction is not possible then the move is skipped.
  • The function PossibleMove() is called to determine if movement in a particular direction is possible.
  • All the code for moving monsters not on the party level is contained in the function InitialChecks() which is the very first thing that is done when a monster movement Timer expires.

The following was the result of a Monster AI trace. I entered the DM dungeon, went downstairs and opened the Mummy door, went back upstairs, enabled the Monster AI trace, and waited about 50 seconds. Then I exited and edited the trace to remove all but the Mummy. So the difference in levels was one because I was on level zero and the mummy was on level 1.

MonsterAI 01(06,02)@145 Perform Initial Checks-timeFunc=37, timeUntilNextUpdate=0
      MAI Not on party level. PossibleMove(Random()=EAST) returned false.
      MAI  No attempt to move monster. Que 37 timer
      MAI Setting timer 37.  Time equals:
      MAI   16 * difference in levels = 16
      MAI   2*MonsterType->timePerMove = 34
      MAI   The larger number.....equals 34
MonsterAI 01(06,02)@179 Perform Initial Checks-timeFunc=37, timeUntilNextUpdate=0
      MAI Not on party level. PossibleMove(Random()=NORTH) returned true.
      MAI MoveObject to (06,01) returned 0
      MAI Setting timer 37.  Time equals:
      MAI   16 * difference in levels = 16
      MAI   2*MonsterType->timePerMove = 34
      MAI   The larger number.....equals 34
MonsterAI 01(06,01)@213 Perform Initial Checks-timeFunc=37, timeUntilNextUpdate=0
      MAI Not on party level. PossibleMove(Random()=NORTH) returned true.
      MAI MoveObject to (06,00) returned 0
      MAI Setting timer 37.  Time equals:
      MAI   16 * difference in levels = 16
      MAI   2*MonsterType->timePerMove = 34
      MAI   The larger number.....equals 34
MonsterAI 01(06,00)@247 Perform Initial Checks-timeFunc=37, timeUntilNextUpdate=0
      MAI Not on party level. PossibleMove(Random()=EAST) returned true.
      MAI MoveObject to (07,00) returned 0
      MAI Setting timer 37.  Time equals:
      MAI   16 * difference in levels = 16
      MAI   2*MonsterType->timePerMove = 34
      MAI   The larger number.....equals 34
MonsterAI 01(07,00)@281 Perform Initial Checks-timeFunc=37, timeUntilNextUpdate=0
      MAI Not on party level. PossibleMove(Random()=SOUTH) returned false.
      MAI  No attempt to move monster. Que 37 timer
      MAI Setting timer 37.  Time equals:
      MAI   16 * difference in levels = 16
      MAI   2*MonsterType->timePerMove = 34
      MAI   The larger number.....equals 34
MonsterAI 01(07,00)@315 Perform Initial Checks-timeFunc=37, timeUntilNextUpdate=0
      MAI Not on party level. PossibleMove(Random()=WEST) returned true.
      MAI MoveObject to (06,00) returned 0
      MAI Setting timer 37.  Time equals:
      MAI   16 * difference in levels = 16
      MAI   2*MonsterType->timePerMove = 34
      MAI   The larger number.....equals 34
MonsterAI 01(06,00)@349 Perform Initial Checks-timeFunc=37, timeUntilNextUpdate=0
      MAI Not on party level. PossibleMove(Random()=SOUTH) returned true.
      MAI MoveObject to (06,01) returned 0
      MAI Setting timer 37.  Time equals:
      MAI   16 * difference in levels = 16
      MAI   2*MonsterType->timePerMove = 34
      MAI   The larger number.....equals 34

Monster movement when far from the party on the same level

Well, I have learned something very interesting. There are actually TWO! virtual timers running for each monster. They only use one physical timer entry but there are two things going on at once. Generally, there is a type 32 timer and a type 37 timer. When the physical timer is queued its type is set to the type of timer that will expire earlier and Byte 8 of the timer is set to the length of time until the other timer type will expire, so that when the timer is processed it can know, again, which type of physical timer should be queued. The two virtual timers can have periods that are unrelated to each other, thereby causing rather complex behaviour. I have no idea why the designers chose this (rather complex) method of keeping two timers going at once unless it was to save memory; the second timer costing only one byte (that was probably unused otherwise). At any rate, this solves a longstanding puzzle...."what on earth is that Byte 8 business?". And it makes talking about the two kinds of timers easier because they operate more-or-less independently of each other.

The type 32 timer appears to cause the monster to look about for the party. If the party is nowhere near then it will look again in a time specified by BITS 4-7 of word14 of the Monster Descriptor. Nothing random. Tick-tock-tick-tock like a clock. Since this is a 4-bit field, the longest time between type 32 timers will be 15 sixths of a second or about 2-1/2 seconds. Diagram If the monster does see the party......well, that is a story for another day. Right now we are discussing the easier problem of monsters far from the party.

The type 37 timers ????????