Following up on my last post, I did manage to finally fix and build the old Christmas Carol code. As expected, it only took me a couple of hours, maybe less, to get the code to assemble. That's with not fixing any of the old bugs -- I decided that for the sake of stability and expediency, I wouldn't mess with it.
As explained in the previous post, the issues had to do with the assembler changing behaviour. Normally this would be a bad thing to do for something already in production and in wide use. In practice, however, back then there were very few people using the assembler, and these were actual bugs that were fixed to improve its function.
There were two major bugs that tripped Christmas Carol:
- QEQU directive uniqueness was fixed to be global
NOT directive behaviour was changed from bitwise to logical
The first one is silly and it should have never been there. The QEQU directive (Quiet Equals, which assigns a global constant label without adding an entry in the symbol table), was always intended to be global, of course. However, it was given inadvertently the behaviour of the QSET directive (Quiet Set, which assigns a global variable label without adding an entry in the symbol table).
Consequently, a label defined with QEQU could change its value with multiple assignments throughout the code.
I had misunderstood this behaviour to be by design (don't ask ), thinking that the values were localized to modules or procedures. Thus, my entire codebase had a bunch of "constants" with the same label assigned different values in multiple procedures and library modules. Dozens of them. All over the place. Eek!
So you can imagine that as soon as I tried to assemble, I got dozens of errors, over a hundred of them! Fixing this was tedious, but very easy: just go one by one through all the QEQU directives in use and decide whether they were an actual constant which needed that constraint, or a re-usable label, in which case it should be changed to QSET.
Actually, it was easier than that. Assuming that the code had correct constants and variables in place, just not assigned with the correct directive -- and this is a safe assumption after all, since the game works and it's already out there -- I just changed all the QEQUs to QSETs. After doing that in a few seconds, I felt a bit dirty by doing it without thinking, so I went over them one by one anyway to review and put back a few QEQUs which were actual unique constant labels.
The second issue was a bit more subtle. Everything assembled by now, so I could run the game, but it sort of behave weirdly, and had some strange graphical glitches here and there, and occasionally crashed completely into the weeds.
It took me a few minutes to remember that several years ago, the behaviour of the NOT directive in the assembler was changed from bitwise (in which all bits in a value are flipped from 1s to 0s and vice versa, a one's complement) to logical (in which it would return 1 if the expression evaluates to zero, and 0 if it evaluates to non-zero).
This threw off all the bitwise masks composed in my code. All of a sudden, something like this:
BTAB_COLOR_MSK QEQU $1007.btab_mask QSET (NOT BTAB_COLOR_MSK)
which originally treated BTAB_COLOR_MSK as a bit-mask and then complemented its bits to set .btab_mask to 0xEFF8 (the 1s complement of 0x1007); it now treated it as a boolean value and set .btab_mask to 0x0000 (false) because the value of BTAB_COLOR_MSK evaluated to non-zero (true).
The result is all sorts of masks used to clear or set registers and variables getting corrupted with stupid wrong values. This caused the game to fail or misbehave in mysterious and seemingly random ways.
Anyway, once I remembered this change in the tool, it was simple and easy to know what was going on and fix it. I just did search-and-replace over everything that had a NOT directive in it, to use a new special "complement" macro I created, ~(). So this:
.btab_mask QSET (NOT BTAB_COLOR_MSK)
.btab_mask QSET ~(BTAB_COLOR_MSK)
I then just added the special macro to the already existing macro library:
;; ======================================================================== ;;;; ~(val) ;;;; Implements a bitwise NOT operator that applies a One's Complement. This ;;;; is useful for composing masks by negating bit fields. ;;;; ;;;; NOTE: The AS1600 assembler's behaviour was changed to treat NOT as a ;;;; logical operator, so this is now required for One's Complement. ;;;; ;;;; ARGUMENTS ;;;; val A value to complement. ;;;; ======================================================================== ;;MACRO ~(val) ((%val%) XOR -1)ENDM
I had done this already in all my development branches of the game, in used for the "Special Edition" versions I made for contest winners and the like, but hadn't done it here. It worked like a charm.
OK, so after that, just a couple of hours later I had a valid source code that I could successfully assemble. Now it was time to actually do the customizations promised to CharonPDX for his charity event.
I made three simple customizations:
- Updated the "ROM-only splash" screen to mention the charity event:
- Renamed the game in the "title" screen to "Cascadian Carol vs. The Ghost Of Christmas Presents" to reflect the gamer group to which CharonPDX belongs:
- Added a new page to the "credits" sequence to dedicate the game:
And that's it. I just bundled that up into a ZIP archive and sent it over to CharonPDX. He's going to be playing an assortment of games, mostly Desert Bus, and perhaps a wee-bit of Christmas Carol, for charity during a 25 hour gaming marathon. So, don't forget to go visit his event page and post a donation. I did mine, where's yours? It's all for a good cause!
As the game says ...