Skip to navigation

Project progress: 45% to 50%

4 November to 11 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:

  • 4 November 2025 - Document ClearScreen, identify screenRowAddrHi and screenRowAddrLo, document FillScreen and GetRandomNumber
  • 5 November 2025 - Document DrawRandomDots, SetScannerAndPause and UpdateScannerNow
  • 6 November 2025 - Document GetVerticalDelta and GetHorizontalDelta, create a hierarchy sheet
  • 10 November 2025 - Start analysing GetTileAltitude and CheckForTileCentre, identify xCoord, yCoord and zCoord, musings on naming of top, high, low and bottom bytes
  • 11 November 2025 - Identify GetObjectCoords, extract CheckSecretStash from GetRowVisibility, start documenting GetRowVisibility, identify GetTileVisibility, reach 50% 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.

4 November 2025
===============

See all the GitHub commits and diffs for 4 November 2025.

Turns out "arpeggiated chord" is the technical term for a chord that's played with each note kicking in in a sequence with the notes being held, so update commentary for the game over music (it isn't a "discrete glissando").

Now to look at ClearScreen and its subroutines.

ClearScreen is relatively simple. It sets up screen addresses, clears the scanner row, and then:

Let's work through these subroutine calls.

Starting with sub_C2963, this sets up a bunch of variables depending on the argument A:

Looking through the code, A can be 0, 1 or 2, which is used as an index to get values from the variables: L298B, L298E, L2991.

Not sure where these variables are used, presumably in sub_C2202 as that's called next?

Next call is to sub_C2202, which takes arguments in A, X, Y.

In sub_C2202 we have a loop at C2238 that pokes 8 bytes, presumably into a character block (as this is a screen clearing routine).

T and U are the bytes to poke, (Q P) is the address to poke into, X is the character count.

Address is set up by this bit:

  LDA L3D83,X
  STA P
  LDA L3DB5,X
  STA Q

So L3D83 contains low byte of address, L3DB5 contains high byte.

zTile is used as storage for the index in X, starts at argument A and increments, and ClearScreen calls sub_C2202 with A = 0, so for clearing the whole screen, we work through the addresses from these tables like this:

  &60C0
  &6200
  &6340
  &6480

i.e. in increments of &140, or 320.

So these tables contain the addresses of character rows in screen memory, though the tables do wrap around from &7EC0 to &3F00 and then keep going, so this looks like it includes a whole bunch of other memory - overflow for the panning, used by the hardware scrolling maybe? Hmm.

Anyway, we can change them into FOR loops and rename them to screenRowAddrHi and screenRowAddrLo, as they appear to point to rows in screen memory (the view screen address is &60C0, as per viewScreenAddr).

Variables zTile and yTile are used in sub_C2202, but this has nothing to do with tiles, so rename them with a shared label. zTile is some kind of row number, so rename to screenRowNumber, and yTile might be columns, so try columnCounter? Rename them like this for now anyway.

L0C4C contains a value that is used to fetch the fill bytes U and T, so this must define the fill (0-3).

  • L2277 = fill with colour 0, colour 0, colour 3/0/3/0, colour 1
  • L227B = fill with colour 1, colour 0, colour 0/3/0/3, colour 1

So rename L0C4C to screenBackground, as this is what it defines.

And:

sub_C2202 clearly fills the screen, so call it FillScreen and finish the documentation.

Interestingly, the call to sub_C2963 doesn't set any variables that are used in the screen-clearing routine, so those variable aren't related to that aspect. I wonder what they are?

ClearScreen calls sub_C56D5 for background type 3 only (solid black background).

Looks like sub_C56D5 draws lots of dots on the screen, so this must draw the stars on the background of the landscape preview and secret code screens.

Rename sub_C56D5 to DrawStars, and amalgamate sub_C56D9 as an entry point, as it just sets a flag differently that controls an EOR instruction later on.

There's also a call to sub_C568E on each star iteration that seems to manage its own random number generator - presumably so it doesn't screw up the landscape seeds.

So rename sub_C568E to GetRandomNumber and the seed bytes to randomGenerator and shiftGenerator, and document the routine.

5 November 2025
===============

See all the GitHub commits and diffs for 5 November 2025.

Turns out DrawStars can draw both black dots and stars, so split it up into three routines: DrawStars, DrawBlackDots, DrawRandomDots.

Finish documenting DrawRandomDots.

Documented SetScannerUpdate, which contains a delay loop that adds a pause when transferring to a new tile.

Documented UpdateScannerNow and associated variables.

6 November 2025
===============

See all the GitHub commits and diffs for 6 November 2025.

Now for sub_C5DC4, which calculates the difference (delta) between y-coordinates and stores it in (L0084 L0081), so rename:

Ditto sub_C5DF5, which does the same for X- and z-coordinates, except it calculates both a signed and delta and an absolute delta:

There is also an x-coordinate offset that's non-zero when drawing the title screens and zero during gameplay, so let's name this as follows:

Document GetVerticalDelta and GetHorizontalDelta.

This doesn't lay out the variables in quite the correct way in zero page - it feels as if L0084 should be yDeltaAbsoluteHi and L0087 should be yDeltaHi - but that arrangement isn't borne out by the routines, so let's leave them as-is for now, and maybe a penny will drop later...

Time to create a hierarchy sheet!

Just from MainGameLoop down, all done manually in this case (for Revs I did it with regexes). Just take source, replace JSR with tab, insert a tab before "Name:" line, remove all comments, remove all lines that don't start with a tab, remove tab from before Name:, remove double blank lines and there's the call hierarchy, ready to be manually massaged into shape.

This gives us a structure to work down, from MainGameLoop through ProcessGameplay and ProcessActionKeys.

See initial version of the hierarchy sheet in Google Sheets.

So now we can follow the subroutines down from MainGameLoop, documenting them. until the whole game is done...

10 November 2025
================

See all the GitHub commits and diffs for 10 November 2025.

Follow subroutines down from MainGameLoop, starting with sub_C2463, which leads to this hierarchy:

As well as this hierarchy:

sub_C1DE6 extracts the tile shape and altitude from the tile data, so rename to ExtractTileData.

ExtractTileData calls sub_C1E98.

This calculates max(|L0037 - 128|, |L0039 - 128|).

So what are L0037 and L0039?

From sub_C24EA, we have a big clue in this bit of code, which is a 24-bit addition:

  LDA L0034,X
  ADC xSightsVectorLo,X
  STA L0034,X

 .C2589

  LDA L0037,X
  ADC xSightsVectorHi,X
  STA L0037,X
  LDA L003A,X
  ADC xDeltaHi,X
  STA L003A,X

This is slightly problematic. It implies that L0034 is some kind of xLo and L0037 is some kind of xHi, but it also implies that L003A is some kind of xHi as well, so it's possible that the latter is actually xTop (or that xLo is actually xBot).

It depends on how the scales relate to each other, but at least we know that L0037 through L003C contain three 24-bit coordinate values, so let's rename them generically for now:

I'm not sure if these are actually coordinates, but it's an unused generic name, so let's roll with it for now.

Also, I suspect these variables are used differently in different parts of the code - for example, C2DCE contains this where Y is zero:

  LDA (L003C),Y

And that doesn't tally with this usage, so let's just stick to renaming these variables within the sub_C2463 hierarchy.

Actually, doing these substitutions also gives us this code:

 .sub_C1EB5

  LDA #0
  STA xCoordLo
  STA yCoordLo
  STA zCoordLo
  LDA #&80
  STA xCoordHi
  STA zCoordHi
  LDA yObjectLo,X
  STA yCoordHi
  LDA xObject,X
  STA xCoordTop
  LDA yObjectHi,X
  STA yCoordTop
  LDA zObject,X
  STA zCoordTop
  RTS

Which means:

So maybe (yObjectHi yObjectLo) should be renamed (yObjectTop yObjectHi) to fit in?

Though there are only two bytes for the y-coordinate, so hmm, maybe (xCoordTop xCoordHi xCoordLo) should be (xCoordHi xCoordLo xCoordBot) instead?

That sounds a bit better, maybe, so let's go for this instead:

Then it looks like we also need to change (xSightsVectorHi xSightsVectorLo) to (xSightsVectorLo xSightsVectorBot), to fix this mismatch:

  LDA xCoordBot,X
  ADC xSightsVectorLo,X
  STA xCoordBot,X

 .C2589

  LDA xCoordLo,X
  ADC xSightsVectorHi,X
  STA xCoordLo,X
  LDA xCoordHi,X
  ADC xDeltaHi,X
  STA xCoordHi,X

And then I think we are done and everything matches Hi to Hi, Lo to Lo and Bot to Bot.

Aviator ended up with (xPlaneTop xPlaneHi xPlaneLo xPlaneBot), so this does have precedent.

sub_C1E98 calculates max(|xCoordLo - 128|, |zCoordLo - 128|), but what are xCoord and zCoord at this point? Not sure, so call this routine GetMaxXZCoordLo for now.

ExtractTileData extracts different levels of tile data depending on bit 7 of secondAxis (which probably needs a new name for this usage). Anyway, for the call to ExtractTileData from sub_C25C3, bit 7 is clear, so most of ExtractTileData isn't used, and instead this just gets the tile altitude.

And bit 6 of this byte is also used, as this code appears elsewhere:

 BIT secondAxis
 BVS C1D33

So the whole xCoord saga is for another usage of ExtractTileData, for when bit 7 is set, so let's park that as xCoord isn't relevant yet.

sub_C25C3 works through the landscape, calculating tile corner altitudes and maximum tile altitudes for tiles and sticking them in screen memory as a cache.

So call it GetTileAltitudes and document it (it's relatively straightforward code).

11 November 2025
================

See all the GitHub commits and diffs for 11 November 2025.

Some terminology changes, because this routine should clearly be named as follows:

So we should also rename the following, as these variables are always used for object coordinates:

Back to analysing sub_C2463 and down, the next routine to be called is sub_C24EA.

Gets called with what looks like a tile coordinate in L001A (it's set to 31, so it looks like a tile corner number or something).

L001A must be a row number, as the first thing sub_C24EA is add it to the high byte of &6000, which matches the storage layout from GetTileAltitudes, and L0018 must be a column number, so rename:

(Though note that these are shared variables with zBlock and xBlock, amongst others.)

sub_C24EA is actually three parts, with a part in the middle that checks the secret stash (we already documented this part).

So split into three separate subroutines, calling the middle part CheckSecretStash.

Also, it's pretty clear that sub_C24EA is working through a row of tile corners, and working out the vector from the player to the tile corner to work out whether the tile is potentially visible to the player, so let's rename.

Also, sub_C2463 loops through all the rows and calls sub_C24EA, so this feels correct too:

As always, we can change this later if need be.

Document part 1 of GetRowVisibility, which sets up variables and scales various values.

>>> I have now broken through the 50% barrier <<<

HALF WAY! It's all downhill from here, lol.

See the 50% progress sheet in Google Sheets.

That excellent YouTuber Martin Piper has just released a video looking at how the C64 version of the Sentinel works! It's interesting how similar and different it is at the same time - it uses character definitions to implement the Beeb's hardware scrolling.

I also love this different approach to archaeology - looking for graphical clues in the memory heatmap to track down the ray-casting in the visibility routines. It's uncanny how we are looking at the exact same part of the exact same game at the exact same time, and here I am not telling anyone about it... yet. :-)

I might have to add the C64 version to my site, but I've got to finish this one first!

Start documenting part 2 of GetRowVisibility.