Monday 17 October 2022

The missing Sol Tower - explained!

I thought I'd have a bit of a break from RE today, and go looking for the missing Sol Tower bug.

To recap, parsing the object table in the SUB CPU ROM lists 46 Sol Towers in the game. However, one of those Sol Towers, in Area 6 it turns out, does not appear in the game. Let's see why...

First, some background information on how these objects are implemented, starting with the SUB CPU. The SUB CPU is responsible for reading the map data from the ROMs and generating the background tile information for the map - one row at a time - before it scrolls onto the screen. As it generates each row, it looks up an object data table in its own ROM for any objects that are to appear on this row.

Any objects that are to appear are added to the (active/dynamic) object tables in shared RAM. These object tables keep track of all objects in the game, from enemies to bullets to the Solvalou; they contain state, coordinate, sprite, colour, timer and sundry information in a series of tables linked by object index. The SUB CPU fills out the object type and y coordinate tables.

Interestingly, the object index is actually hard-coded in the object ROM data as well, rather than searching for a free entry in the table. That's because the table is partitioned into different categories of objects, and a number of object entries are referenced directly by other objects, primarily the Solvalou, which needs to be at a fixed position in the table. And also relevant to this discussion is that the object state isn't actually initialised at this point, but rather relies on the state of the (now defunct) object previously at this location in the table (you can probably already see where this is going to go wrong).

Once an object has been added to the type table by the SUB CPU, it is handled by the MAIN CPU thereafter. The MAIN CPU processes objects with a type-specific function for each object type. The object handlers are actually patched into an array of functions called in a round-robin fashion. Initially the array consists of 'empty' handler functions that inspect the object type table and, depending on the object state, patch in the specific handler and execute it. These handlers can relinquish control back to the main routine and be called again in subsequent rounds. Ultimately each handler removes itself when the object is destroyed or scrolled off-screen by replacing itself with the 'empty' handler again.

It is worth noting that for ground-based objects, the object persists in the table after being destroyed - until the crater is scrolled off-screen.

So what should happen with the Sol Tower in question?

In Area 6 the SUB CPU is constructing row $4F and has a Sol Tower (type=$1D) in the object data table to be inserted as object index=$03 in the dynamic object tables. It writes $1D to the object type table and (incidentally) $001A to the object spriteY table.

The MAIN CPU then gets around to executing the handler for object $03, which is the 'empty' handler. It checks the object type table ($1D) and then the object state table, which should be set to $01 during the removal of the previous object. It then patches the Sol Tower handler function into the array, which initialises the state and is executed periodically until the tower is destroyed or scrolls off-screen.

What happens to the missing Sol Tower?

At the time the SUB CPU writes $1D to the object type table, the state is not $01, but either $02 or $03. This indicates that there is an active object still in the table at this index ($03).

Turns out, the Boza Logram (aka Dome Array) from row $77 was also object $03 but is yet to finish scrolling off the screen and hence be removed from the object tables. So the Boza Logram handler is still executing, impervious to the new value in the object type table. A few rounds later and the handler finishes scrolling, removes the object from the tables (including type), and unhooks itself. It is as if the Sol Tower never existed!

The Boza Logram bottom left (destroyed here) is the problem

The root cause of the problem is that the vertical distance (number of rows) between two objects (Boza Logram and Sol Tower) with the same object index ($03) is too small. Moving either the Boza Logram down one row ($77->$78), or the Sol Tower up one row ($4F->$4E), fixes the issue. Alternatively, moving the Boza Logram to object index $04 also fixes the issue (although it causes other problems). So technically it's not a coding bug, but a bug in the map data!

Moving the Sol Tower up 1 row causes it to appear

It begs the question of whether there are other (ground-based) objects in the map that are similarly missing from the game?

No comments:

Post a Comment