29 October to 3 November 2025
Here's my disassembly diary for this part of my project to document The Sentinel on the BBC Micro. You can click on the following links to jump to a specific day in the diary:
- 29 October 2025 - Start analysing InitialiseSights and DrawSights, document SetSightsAddress, identify doNotDrawSights
- 30 October 2025 - Document sightsByte variables and RemoveSights, start documenting DrawSights
- 31 October 2025 - Finish documenting DrawSights
- 3 November 2025 - Analyse soundEffect and gameOverSoundPitch, document PlayMusic, ProcessSound, ProcessMusic, musicCounter and musicData, reach 45% progress
Please note that this diary is a dump of my thoughts as I disassembled and documented The Sentinel, and as such it contains lots of mistakes and dead ends and misinterpretations. This diary is all about the journey, rather than the destination; for the latter, see the finished product.
29 October 2025
===============
See all the GitHub commits and diffs for 29 October 2025.
Rename SetTileShape to GetTileShape, as the routine gets the shape and returns it in X, rather than setting a variable.
Continuing with the sights routines, let's look at SetupSights as that sets a few variables that are referenced in places like SetSightsAddress.
L0CC4(1 0) gets set to viewScreenAddr plus an offset of &0FA0 in SetSightsAddress, so this is presumably the screen address of the sights, so name it to sightsScreenAddr(1 0).
What is the offset of &0FA0 - is it the screen address of the sights when they are at (80, 95), which is where the xSights and ySights variables are initialised?
&0FA0 = 12.5 * 320, so it's in the middle of the screen on character row 12 of 24, presumably?
i.e. 12 * 320 + 160
I should also analyse exactly where that puts the sights for the various options of viewScreenAddr and find out.
Meanwhile, SetupSights would be better named to InitialiseSights, as that's what it does, so rename it.
SetSightsAddress caters for when the sights move into different screen bytes or character rows, in which case they add a 16-bit offset from two tables, the low byte in L3AC7 and the high byte in L3ACD, choosing the relevant value for the type of movement (which is passed to SetSightsAddress in X).
So rename the tables to sightsMoveAddrHi and sightsMoveAddrLo and document the routine.
ShowSights is actually DrawSights, so rename.
It does nothing if bit 7 of L0CD7 is set, so rename L0CD7 to doNotDrawSights.
30 October 2025
===============
See all the GitHub commits and diffs for 30 October 2025.
Continue with DrawSights and HideSights.
The sights are drawn and removed using a stash of screen bytes, which contain the screen contents behind the sights, so it's easy to remove the sights by reinstating the original pixel bytes from before the sights were drawn.
Storage for these pixel bytes, and their addresses in screen memory, are governed by these variables, so let's rename them:
- L0CC9 = sightsByteCount
- L3DE7 = sightsByte
- L49C1 = sightsByteAddrLo
- L3DF3 = sightsByteAddrHi
- L002A = sightsByteAddr
HideSights is called from DrawSights and actually scrubs the sights from the screen, so perhaps RemoveSights is a better name.
Document RemoveSights.
Started documenting DrawSights, which is more involved than you might imagine as it uses a stepping process to draw the sights byte by byte.
31 October 2025
===============
See all the GitHub commits and diffs for 31 October 2025.
Finishing off DrawSights.
Uses tables at L227F, L36BF, L3A8A, L5730 that contain pixel masks and pixel bytes for mode 5 (you get to recognise this kind of thing after a few games!), so convert them into binary EQUBs so we can see the bits.
While we're at it, L2277 to L228F are also clearly pixel masks of some kind, so convert them to binary and put them in the Graphics category.
And that's the sights routines finished.
3 November 2025
===============
See all the GitHub commits and diffs for 3 November 2025.
Let's try to identify the sound routines, as these often offer handy clues to what subroutines do.
We have previously identified sub_C355A and sub_C3505, as they check the values of soundCounter and soundCounter2, and we have SetSoundCounter2 as well that sets soundCounter2 and L0C73.
sub_C355A uses L0C73 and L0C74 and jumps to sub_C3505.
sub_C3505 uses L5850 and L0C70.
MainGameLoop calls sub_C355A repeatedly until soundCounter2 runs down (it does this twice).
Put a breakpoint on &355A - it's called all the time, so this might be some kind of background sound/music routine, call it ProcessSound for now? Seems to be called a lot, though, would expect this to be called once from an interrupt routine, so this may be totally wrong.
Breakpoint on &3505, gets called multiple times but only when making music, so this feels like a ProcessMusic routine, let's call it that for now.
soundCounter2 gets checked in ProcessMusic while soundCounter gets checked in ProcessSound, so is soundCounter2 actually a musicCounter? Rename anyway, if so this means the soundCounter2 loops in MainGameLoop are waiting for music to finish, which sounds sensible.
And that makes SetSoundCounter2 the routine that starts the music by setting the counter? So maybe call this PlayMusic? And presumably musicCounter specifies the tune number? Sounds a bit odd, but let's roll with it.
Possible values of A passed to PlayMusic are: 0, 25, 40, 50, 66.
Looking at when these are called, we can work out when these values are passed:
- 0 = hyperspace
- 25 = player has just transferred onto tower
- 40 = U-turn
- 50 = game over (it's called by DisplayGameOver, anyway)
- 66 = finish landscape
So it looks like these are the different tunes in the game after all, so that's good. And musicCounter counts up in ProcessMusic, so music isn't a background operation but a blocking one (hence the loops in MainGameLoop).
L0C73 seems to contain some kind of sound definition.
L0C73 = 3 when the sound is music (as ProcessSound calls ProcessMusic when L0C73 is 3, which is also what is set in PlayMusic).
Other values that have an impact in ProcessSound for L0C73 are 4 and 6, any other values make ProcessSound do nothing.
L0C73 = 6 when sentinel has won (in MainGameLoop), and again in DisplayGameOver, so this might be the sound when we die, which is a descending pitch of tones on the noise channel.
L0C73 = 0 (in FinishLandscape) when we've successfully completed landscape.
Only other time L0C73 is set is in sub_C191A, where it gets set to the value of Y, which looks like it can be 0 or 4, and the same value is set as the value of scannerUpdate, so is 4 a scanner-related sound?
So:
- 0 = no need to process sound?
- 3 = music
- 4 = scanner sound?
- 6 = game over sound
Not really sure what this is - some kind of flag that controls the way the sound is processed? Like an effects pedal or something?
Let's call it soundEffect for now, this will need changing I'm sure.
L0C74 is another sound variable that only seems to be used during the game over sound.
If L0C74 < 80 then ProcessSound makes no sound for the game over sound.
It's set to 0 at the start of the game, then set to 250 in DisplayGameOver, and decrements in ProcessSound for the game over sound only.
So this limits the game over sound to 250 - 80 = 170 iterations of ProcessSound.
This is also passed in Y to MakeSound-6, so this lowers the pitch of the sound over time (which is what happens in the game over sound).
So call it gameOverSoundPitch and finish documenting ProcessSound.
Now for ProcessMusic.
L5850 is where the music data is fetched from, so call it musicData.
&FF indicates the end of each bit of music, so split up the data.
Music plays on three note channels at the same time to produce chords or glissandos - very simple and very effective.
Document ProcessMusic and musicData.
>>> I have now broken through the 45% barrier <<<