This article follows one from the Winter 2006 issue on Emulating Biological Systems 1 of 2. Part 2 of this will deal with the control of AI. This article looks at how the AI Factory's generic testbed architecture was utilised to develop the aquarium product. If you examine the specification of this system (as described in the article Advanced Diagnostic Tracing) you might wonder how such an architecture, evidently geared towards turn-based games, could be utilised for the emulation of fish. This article shows how.
As indicated above, the generic testbed architecture has a specification that is tailor-made for turn-based games such as chess. To hook any such game into the testbed console basically just requires the definition of the following key core primitives:
Core Engine Calls, demanded by the Fireball API
Fb_CalculateMove | This calculates the move to play. |
Fb_PlayMove | This plays a defined move. This is usually called from Fb_AddMoveToHistory, which plays the move and adds it to the move history. |
Fb_InitialiseNewgame | This starts a new game. |
Core Testbed Functions (needed only by the testbed console)
Ft_DisplayMove | This displays a move (for a move listing). |
Ft_DisplayGame | This displays the current game state. |
Ft_InputMove | This allows the input of a move from the console. |
Once you have these core functions you can plug them into the generic (fireball) testbed to create a complete game-playing system.
A key data entity also required by this is the Fireball class object iFb_EngGameState, which holds all the information required for a game. For chess this would be the board, players, clocks and move history.
With these components in place the fireball testbed system provides the following user ready-made testbed console with the following commands.
B | Show current Game State (e.g. board position) | |
V | Show current legal moves | |
M | Calculate and play Move | |
A | Analyse and select move (but not play) | |
P | Play last move calculated by engine (from "A") | |
/ | Input move | |
N | New game | |
^S | Save game | |
^O | Load game | |
= | Move forward through move history | |
- | Take back move | |
> | Move to end of game | |
< | Move to start of game | |
H | Show game history |
Is this Suitable?
This clearly does not look like the kind of framework you would choose for creating and developing an animated emulation of fish. Even if it was possible, why would you choose to? This looks so different that it would seemingly offer no practical advantage. However there are good reasons, as follows:
Mapping Biological Emulation into a Turn-based Testbed
The most obvious point to make here is that fish do not take turns or play "moves" to do things! However it is possible to map this to the fireball framework, as follows:
iFb_EngGameState | |
This is the "game" state and is used to hold the environment (a tank in this case), the fishes and the history of the emulation. | |
Fb_CalculateMove | |
This is set to take the current environment and advance it by a fixed time interval, 1/30 Second in this case. | |
Fb_PlayMove | |
This plays a step in the emulation. In the limited sense this just means advance the current environment by a fixed time interval. | |
Fb_InitialiseNewgame | |
This initialises the tank environment, prior to emulation. | |
Right away it can be seen that Fb_PlayMove here seems to be the same as Fb_CalculateMove. They appear to be doing the same job. In this case Fb_CalculateMove is not just determining the "move", but actually "playing" it. From this it seems that there is only one such move to play anyway, which simply advances time by 1/30 second, a "run" move.
What does this give us?
If you plugged the above functionality into the testbed it would allow a fixed known aquarium to be emulated and its behaviour could be seen by single stepping the "move", a time increment of 1/30 second, using the "M" (move) command. This would give very big "move" histories. Given the general architecture of the testbed, this would immediately allow the use of "-" (takeback), so that the user could step back to a previous frame.
At first glance this looks as if it might not be possible, as it appears to require an inverse emulation predicting earlier events, until you understand how the testbed works. If you do a takeback, the testbed resets to the initial state and winds forward "N-1" steps. For chess this is obviously ok, as you simply and quickly just re-play the moves. For the Aquarium this means that every previous event that happened in the tank needs to be re-calculated. If you need to examine some defect that occurred after an hour's game time emulation, then that would be slow (maybe taking a few seconds). However most analysis of the aquarium can be examined without needing to do takebacks after long emulations.
Defining an Aquarium State
The architecture above is geared towards games that have well-defined initial game positions, such as chess. In this case clearly there could be any number of possible initial aquarium states, requiring tools to allow this, or start-up configuration files. The latter option is not very user-friendly, so this is instead embedded in the console. The generic testbed console allows commands to be added, so that fish and objects can be added and positioned. It could be quite complex, resulting in the creation of an entire development configuration product, which would probably not be re-cycled into the final product entity. In the testbed this is atomised by embedding aquarium manipulation operations into the game history. These might give a history of the following type:
Move history:
1. | EMove_LoadFish Type [Yellow Tang] | |
2. | EMove_AddRandomFish | |
3. | EMove_AddRandomFish | |
4. | EMove_AddObject : Plant Type 1 | |
5. | EMove_MoveObject : Plant Type 1 | |
6. | EMove_AddFood | |
7. | Run | |
8. | Run | |
9. | EMove_AddFood |
The "moves" 1 to 6 and 9 here are in respond to commands issued at the console. These have the same "move" status as "Run" which simply advances the emulation by 1/30 second. They are just different "move" types. Therefore a complete aquarium can be defined by just a move history. These setup moves can plug into the Aquarium API, testing that they work correctly. Since these are embedded into the testbed console, they can be freely tested and scripts of move histories created to generally re-test the API (good for picking up order-dependant defects). The command console therefore becomes a one-to-one testbed for the engine API.
Hooking into the Testbed Generic Debugging
Biological emulation of fish requires a significant volume of code to model the physics, decision-making and complex rendering issues (see Emulating Biological Systems 1 of 2 ). To work on this requires good visibility of what is happening. A single fish and single 1/30 sec interval may involve a substantial number of events that need to be tracked. These include:
1. | The animation phase (see the 3-stage animation scheduling in the article above). | |
2. | The tracking and prediction of potential collisions. | |
3. | The decision making of the fish. |
All this information could be spewed out to the console for every run cycle, but this is information overload if you are just tracking one aspect of the emulation.
This is where the debug switches in the generic console are valuable. The aquarium engine has substantial embedded diagnostic tracing, using colour console output, which can be turned on and off from the console. Of course, an interruptable continuous "Aquarium run" command is also needed, so the user is not obliged just to single step, and so this is added as a console command.
An example of a Diagnostic Run
With single-stepping, takeback and switchable display diagnostics, it is possible to examine events in great detail. A typical example usage might be that the Aquarium is observed until some curious event occurs. When this happens the emulation can be interrupted and the emulation backspaced to reach that event. Typing the console command "takeback move" allows the emulation to apparently run backwards until the problem point is reached.
Once there, diagnostics can be switched on and the events then scrutinised in fine detail.
In the example above a seahorse is shown during multiple phases of the tail curl-up, as the seahorse gradually swims forward. The user only gets to see single phases at a time, but these are shown overlapped above to represent multiple stages. In this case intervals of 8x 1/30 seconds are shown. The user can toggle the -/+ keys to flick between successive frames. The text overlap shows a fragment of a console window that might be used to track the calculations that led to this animation.
This can be used to track more complex swimming behaviour. In the example below the Emperor Angelfish is observed to swim up diagonally and then rotate about the yaw axis and drift downwards. This behaviour can be examined in detail, switching key diagnostics on and off. E.g. if some unexpected behaviour is observed, then the frame is "taken back" and an added diagnostic switched on and the frame re-run to see more information.
This provides a diagnostic development system that is highly flexible and reliable. The user can freely run the emulation backwards and forwards in the problem area. Defective sequences can be easily recorded and saved, and then loaded and re-analysed later.
Running in Fast Time
Sometimes defective behaviour takes too long to happen, in which case you need to speed up time. This is easy to arrange. A few lines of code are added to the "extended" console commands to add a "run continuous animation" command (essentially a game "auto-play", endlessly playing moves). This can reference a switch to run 10 emulation steps at a time, before displaying. The Aquarium can now be seen in fast time. When a defect is observed the animation is interrupted and the frame position stepped back to detect the point where the defect occurred. This can be saved as a generic "game", and may contain thousands of "moves" (frames). This can then be re-analysed again later. Once found the sequence can be played and replayed again and again, stepping forwards and backwards through the sequence.
This mechanism is also useful for observing long-term fish behaviour without needing to observe each moment in real time.
This may still not be enough to trap some particular defect, so it can be explicitly trapped by the emulation and set to cause a break to the console when it occurs. The move/frame history can then be saved as if saving a game history.
Conclusion
This has proved to be a very effective development system, providing generic core tools shared by other game engines that did not have to be developed specifically for this work, which saves much time as otherwise emulation-specific core tools would need to be both written and tested. It might then be some time before you can totally rely on such bespoke tools as they are new and therefore vulnerable to bugs. Having these already "off-the-shelf" proven core components "ready-to-use" removes uncertainty and saves much initial added development time.
Added to this the generic framework has ready-made slots to easily allow the addition of engine-specific commands. Once defined these are automatically supported by the console.
Finally the generic framework for colour-supported text diagnostics, supported by console-controlled switches, provides a very powerful tool for analysing behaviour.
Net, this is a powerful flexible system that provides easy scrutiny of the emulation, encouraging thorough testing. Even though tailor made for a rather different kind of application, the generic testbed architecture has, in the end, proven ideal for this work.
Jeff Rollason: September 2007