AI Misbehaviour




Graftgold Memories.

 I have written how I began Z80 several times for various articles or interviews but thought I would give a more detailed account.

My very first computer was a ZX80. I ordered a self build kit and was amazed at how small it was. It was easy to put together and worked first time. I used to program it while watching the tv in the evenings after work.  I soon realised that BASIC was not good enough for games when I filled the machine with a submarine game without finishing it. I wanted to know how the machine worked and was disappointed that building it was more like putting Lego together than doing electronics. We had a really good technical section at Witham library at the time so I borrowed a stack of books on digital electronics and Z80 assembly programming. I ordered a couple of books on z80 programming and the z80 chipset so I had a permanent reference. My goal was to work out how the machine worked and how to get round the issue that when you ran a program on the ZX80 the display was turned off. I wanted to program a video game and that needed moving graphics that were displayed while the program was running, I started to dissemble the ROM program by hand to see what it was doing. I soon found out why the screen blanked. To cut costs rather than use a chip to display a character set on the screen the designers had used a feature of the ZX80 chip. The chip was designed to refresh dynamic memory which had to be read and rewritten at regular intervals to keep its values. So in between running z80 code the chip output consecutive addresses on half the address bus. The other half was formed from the I register.  The ZX80 was cleverly designed to use this system to sequentially fetch the character codes that made up the character screen map. The code was jammed on the address bus along with the base address of the character data and data from a timer chip that addressed the slice of the character by counting from 0-7. This formed the address of the character slice which was read and put to the data bus. The refresh mechanism then sent this character slice to the video output. Timing was the key, the slices were all delivered in sync with the raster. This was bad news. It meant a pixel screen map did not exist, it was created on the fly from the character map.
No wonder the screen went blank when the CPU was running code.  

All was not lost. I figured a could run code in the screen blank time which occurs from when the raster has finished displaying a frame until it starts at the top of a new frame.  I tried this out and it worked. The only trouble was the code had to run in exactly the time of the screen blank. Any branch in the code had to be the same timing on each path. I did not know of a way to check the time and just wait until the time to restore the screen display in time for the next frame. If the time was slightly out the screen would roll like an old fashioned cathode ray tv set out of tune. I managed to get an asteroid floating across the screen but it was sheer hard work. Any program change put the timing out.
Then a friend at work gave me a copy of a Space Invaders game for the ZX80 which had solved the timing issue. I was about to reverse engineer this when the ZX81 was announced. This made all the work I had done redundant as the ZX81 could run and display. I was so disappointed that I had been trumped I did not program for ages but watched the progress of home computers and games. When the ZX Spectrum was announced I decided to be one of the first to get it and to immediately see what I could do with it. This time I bought a book of the complete disassembly of the Spectrum ROM code rather than waste time trying to do it myself. This book was like a bible and out of it came how to use ROM  routines such as those used to save and load game data that I used in Avalon.

My playing with the ZX80 had put me in a good position. I knew Z80, how microcomputers worked, and had a few useful utilities I had written such as a Z80 hex loader. This was the first thing I got working on the Spectrum. There were no assemblies available for a while. The loader allowed me to write  hex in BASIC rem statements. It allowed address labels so I could type hex that could be loaded anywhere . Also the addresses and relative jumps did not have to all change whenever I changed the code. It meant that I had to assemble the instruction codes by hand. I did this quickly using a set of look up tables I made into a little crib book. The Hex Autoloader was the first program I tried to sell. I think it sold two copies. However it was worth a lot to me as it enabled me to write my first Spectrum games. By the time I wrote Avalon various assembler programs were on the market but each had limiting factors such as needing most of the memory to run, so were not suitable for writing a large program. So I stuck with the Hex Autoloader but used a disassembler to give me a source listing. I had to hack the disassembler to read my label list and interface with a printer. Being able to print out source code was a boon, I could check my hand assembly and work out change more easily.
I envied Andrew working on the Dragon the  C64 with proper assemblers. Looking back I wonder how on earth I got a whole program working. Unlike Andrew I used to write huge chunks of code after dry running them on paper. I would then run them using a little monitor program Andrew had written  that allowed us to inspect or alter memory while the program was running. hat was super efficient at tuning up a game. I would put all the tuning variables together so I could tweak them while playing the game. You just had to remember to write down the values before the game crashed erasing all your values. I especially liked to watch memory as the code ran. You soon saw of a loop was writing memory it shouldn't. It is a shame modern dev. systems do not have this window on the machine approach


Deepest Blue

Last session I had coded the sound engine and installed it in the game with a few sound effects to test it. I was having behavioural issues with the AI. In particular bases to process resources were not being built and hence no resources were processed so after running for a while the game world stagnated and nothing seemed to be happening. 

I noticed that their were traffic jams at some of the important bases such as ports and shipyards. Ships were getting delayed while trying to deliver goods needed to make new bases. I thought this may be one of the causes of bases not being built so added a queuing mechanism that allowed for a more orderly approach. The idea was that as soon as one ship had undocked another would be approaching like a busy airport. When a ship wants to dock it requests permission from the base. I added a landing ticket system whereby each ship gets a ticket like a supermarket counter queue system. The ships wait positions are orchestrated by their ticket number so they line up in order and shuffle forward to the next place as the front of the queue is served.  The system works well and prevents the pileups that were often occurring. 

The bases still did not get built. Behavioural issues ae far harder than individual routines going wrong. They may be caused by a bug on any of the routines that make up the behaviour or may be a design fault or an emergent property of routines that individually seem to run ok.  So I starting looking first at all the component parts:
Were the resources being discovered?
Were they recorded a wanted?
Were the bases being ordered from the shipyards?
Were they built?
Were the missions to deploy them being scheduled?
Were the missions being assigned?
Were they being executed correctly?
Much of this could be tested by putting in breakpoints and seeing when they were triggered.

I have a simple market economy to drive the game. This did not seem to be working correctly making bases too expensive. I went over the code and tweaked a few routines revising the way price was determined.  I let supply and demand affect the prices but this is moderated by a supply from Earth that determines an upper price, its always available but cost more as has to be lifted from Earth. Once resources are available from space they start to undercut the Earth prices. 

I then found that the shipyards were too busy building ships and other parts such as Sentinels which monitor for ships entering sectors. So I put some limiting code to weight the importance of bases with other things. I found that too many ships were being produced. That turned out to be because whatever ship was ordered it was building a freighter. So its other needs were not fulfilled and it kept ordering warships or miners until there were hundreds of them. At least the game seemed capable of running large numbers of ships in the same sector. I went back to the ship designer and made sure I had at least 1 of each class of ship and base. I hadn't used the ship editor in ages and that promptly crashed so had to figure out what it was up to an mend it. I added a fighter with huge engines and found it was uncontrollable so had to calm down the acceleration.
Base parts awaiting collection

To make the ships more efficient I changed the AI to look for a new mission before returning to base if the ship was not damaged. Now the AI ships can perform sequences of missions rathe than return to the start after each one. If there are no suitable missions they return to a home base and park.

I decided to make the scenarios tighter by reducing the size of each star system by reducing the number of sectors. This also had the effect of the AI finding other systems more quickly. The Ai were now exploring multiple systems but still the bases were not building.

I noticed sometimes the AI survey missions were not finding all features in a sector. I was also finding that the surveying was too long. I reduced the number of survey points drastically and refined the mechanism. At each survey point the survey ship has to do a radar scan in each of 12 directions to build a radar map. Then the ship has to investigate and scan any unidentified object. I found issues when changing from AI to player so refined the routine to use the same variables. The AI can now take over from where a player left off or vice versa. I also made the search pattern more viable in an enemy sector where you do not want to survey from the occupied middle of a sector.
Survey display showing long range radar

Hey, bases are now getting built and even upgrading. Now to find out why resource missions are still lacking. They are only scheduled when there is a suitable processing base so do not occur at first. I may change that by allowing raw resources to be imported by Earth rather than processed resource such as Gold. I am getting desperate to get onto the fighting aspect once the infrastructure is self constructing I can get thongs to destroy it. That is why the system need resilience and I am testing booting the infrastructure from scratch. I want it so you can get down to your last base and still have a chance of recovery. Rebuild is easier as you can reuse resource from all the smashed.  

The first encounters with the enemy are now occurring, that is pretty exciting. I have no heavy gunships in the game yet although all the code is implemented. The idea is that once enemies start attacking an arms race will ensue with bigger ships working in squads then fleets.  

Programming Tips.

Focus.

I added a simple way of steering the ship by clicking on any part of the space screen. The ship steers towards the clicked direction. This was fine except when I tried to turn the ignition off be clicking on an ignition button control. I had forgotten that my main game display did not work like a strict form and used raw input rather than input supplied to a focussed control. So as the engines switched off the ship started drifting towards the click position and did not stop as had no engine power.   My form system had identified the ignition control as the current focussed control but the gane had still used the raw input. I treat the generic space background scene as a control the size of the screen so I could fix this by simply asking if the scene had focus and ignored the click when another control had focus. .

I got the idea of focus from using Visual Basic. My forms are build up of rectangular objects which overlay each other. A Window object defines all or part of the screen and can contain other windows,  controls or a special control called Scene that is a 3d display.  When the mouse button or keyboards is pressed the tree of form objects is scanned top downwards to find the foremost control under the cursor position. So it starts at the master window and if its in that checks to see which child window  it is in. The process stops when the screen object does not have any children. That screen object "owns" the input mouse or keypress . If the focus changes form a previously focussed object an event is sent to the old object saying it no longer has focus. An event is sent to the new object saying it has focus. This allows controls to change there appearance according to focus state, for example highlighting a button when in focus.

This system only ever allows one control to be active at any time. That is good news for network playing as only one control hit has to be sent across the net in any update cycle.






Comments

Popular Posts