How I documented The Sentinel over seven long months
People often ask me how I create my disassemblies, so for my last project I kept notes; a lot of notes. Welcome to my disassembly diary for The Sentinel.
Between September 2025 and April 2026, I disassembled and documented Geoff Crammond's epic BBC Micro game, The Sentinel. I wrote down pretty much every step in the entire process, starting with the original game disc and ending, some seven months later, with fully documented source code and over 50 deep dive articles, all lovingly presented via a comprehensive website and a buildable GitHub repository.
You can see my diary for the whole project below. You can click on a date to read the diary entry for that day, or you can see all of the code changes from that day by clicking on the "Code" link to see the GitHub diffs.
The diary entries are very terse; they're technical notes, not Samuel Pepys. They contain the barest of information and aren't exactly a riveting read, but as a body of work they comprehensively describe the project. They also link through to the documented source code, so not only can you see the in-progress disassembly in GitHub, but you can compare it with the end result as well.
On top of the diary entries, I've also written about the tools that I use for my disassemblies. Spoiler alert: it's all extremely manual and the tooling is about as primitive as it can be. This is intentional.
You can also see the project progression below, highlighted in 5% increments. I like to keep track of progress in minute detail; see the article on keeping track of progress for more about the tools and philosophies behind this.
There are some more notes after the table that you might find useful, but for now, here's a summary of every day in my disassembly diary.
| Diary entry | Description | Diffs |
|---|---|---|
| 2025-09-03 | Get game discs, extract binaries, create repository, create skeleton build process, create simple py8dis disassembly, start identifying code blocks | Code |
| 2025-09-04 | Implement loader unpacking with COPYBLOCK, finish identifying code blocks, copy py8dis output into main source code file, switch to using text editor, add headers to sections (source start, configuration variable, workspaces, code, saving) | Code |
| 2025-09-05 | Apply house style to OS calls, identify subroutine entry points that aren't in the sub_* format (e.g. JSR destinations, labels after RTS), convert all other labels to five characters (letter + hex), insert subroutine headers before all sub_ labels, create an empty progress spreadsheet, start at 0% progress | Code |
| 2025-09-06 | Identify maths lookup tables using Excel, convert them from data blocks into FOR loops, convert some of the calculated labels into Lxxxx labels | Code |
| 2025-09-07 | Document the Entry routine (the first documented routine!) | Code |
| 2025-09-09 | Document ConfigureMachine and ClearMemory, identify main title loop, start trying to identify subroutines called by main title loop (e.g. SetColourPalette, ResetVariables, DrawTitleScreen, DrawObject, SpawnCharacter3D, SpawnSecretCode3D), realise that some code is the same as in Revs (Multiply8x8, GetAngleInRadians) | Code |
| 2025-09-10 | Identify more Revs routines (GetRotationMatrix, Multiply8x16, Multiply16x16) and copy comments from Revs, reach 5% progress, identify and document more Revs code (Absolute16Bit, Negate16Bit, ScanKeyboard, parts of Divide16x16), add arctan and sin labels | Code |
| 2025-09-11 | Document GetAngleFromCoords, GetHypotenuse, tanHalfAngle and MultiplyCoords, lay out zero page and workspace variables, identify GetNextSeedNumber | Code |
| 2025-09-14 | Document GetNextSeedNumber, identify PrintTextToken, insert text token labels, reach 10% progress | Code |
| 2025-09-15 | Document all the text tokens and text token routines, document ReadKeyboard, ReadCharacter and EnableKeyboard | Code |
| 2025-09-16 | Document PrintNumber, PrintDigit, Print2DigitBCD, PrintLandscapeNum, ReadNumber, PrintInputBuffer, StringToNumber, DigitToNumber, InitialiseSeeds, start looking at GenerateLandscape and GetTileData, identify tileData table, reach 15% progress, work out structure of tileData table | Code |
| 2025-09-17 | Document ProcessTileData, identify SmoothTileCorners and SmoothTileData | Code |
| 2025-09-18 | Start documenting SmoothTileCorners and SmoothTileData, more thinking around stripData | Code |
| 2025-09-19 | Finish documenting SmoothTileCorners, work out obfuscated jump code for JumpToPreview | Code |
| 2025-09-22 | Finish documenting GenerateLandscape, clarify "tile corner" vs "tile" in commentary | Code |
| 2025-09-23 | Start analysing SpawnEnemies, GetEnemyCount and AddEnemiesToTiles, work out landscape colour variables | Code |
| 2025-09-24 | Continue analysing AddEnemiesToTiles, document GetHighestTiles, identify maxAltitude, xTileMaxAltitude and zTileMaxAltitude, reach 20% progress, document GetObjectNumber, identify objectType, objectTypes and objectFlags, work out that there are 64 objects, start looking at GetTilesAtAltitude | Code |
| 2025-09-26 | Document GetTilesAtAltitude, identify xObject, yObjectHi and zObject | Code |
| 2025-09-30 | Split out SpawnPlayer, SpawnTrees and CheckSecretCode, document SpawnObjectOnTile, work out data packing in tileData, rename xObject, yObjectHi and zObject, work out object types 3 and 6 | Code |
| 2025-10-01 | Realise y-coordinate is 16-bit so we have yObjectHi and yObjectLo, revamp terminology to talk about altitude rather than height, identify SpawnObjectBelow, finish documenting SpawnPlayer | Code |
| 2025-10-02 | Document SpawnTrees, split off CheckSecretCode parts 1 and 2, start documenting CheckSecretCode, identify GetNextSeedAsBCD and FinishLandscape | Code |
| 2025-10-03 | Finish documenting CheckSecretCode, reach 25% progress, document PreviewLandscape, starting adding "???" to unknowns | Code |
| 2025-10-04 | Realise that seed numbers aren't random but are predictable, rename routines accordingly | Code |
| 2025-10-07 | Mention other sources of information (Simon Owen, Level 7), identify objRotationSpeed, objRotationTimer and objectYawAngle, finish documenting PlayGame, document FlushSoundBuffers | Code |
| 2025-10-08 | Separate landscape and game data where memory is reused, add comments to workspace variables, identify objectPitchAngle | Code |
| 2025-10-09 | Document GetSineAndCosine and DivideBy16, thoughts on GetRotationMatrix, document key logger, identify sights-related variables, start analysing GetSightsVector | Code |
| 2025-10-10 | Start analysing SetCrackerSeed and CorruptSecretCode, work out that objectType can contain key presses, add speculative names to sights routines | Code |
| 2025-10-13 | Convert "tile slope" terminology to "tile shape" | Code |
| 2025-10-14 | Identify key processing routines, categorise sound routines, document GetPlayerEnergyBCD, finish documenting FinishLandscape, first attempt to understand FocusOnKeyAction, start looking at DrainObjectEnergy | Code |
| 2025-10-15 | Identify MakeSoundEnvelope, DefineEnvelope, envelopeData and soundNumberData, document UpdateIconsScanner and DrawIcon, identify iconBuffer and iconRowAddr, document DisplayIconBuffer | Code |
| 2025-10-16 | Add another COPYBLOCK to source, remove unused labels, reach 30% progress, finish identifying object numbers, identify enemyData variables, document ProcessVolumeKeys and ProcessPauseKeys, identify UpdateScannerNow and GetIconRowAddress | Code |
| 2025-10-17 | Document PerformHyperspace, UpdatePlayerEnergy and DeleteObject, rename sentinelHasWon, reach one-third progress | Code |
| 2025-10-19 | Rename numberOfEnemies and maxNumberOfEnemies, start analysing IRQHandler | Code |
| 2025-10-20 | Document duplicate tile data code in GetTileViewAngles, document use of SHEILA addresses for hardware scrolling, document ResetScreenAddress | Code |
| 2025-10-21 | Identify screen-scrolling routines at ScrollPlayerView and ShowScreenBuffer, document ShowBufferBlock, ShowBufferRow and ShowBufferColumn, reach 35% progress | Code |
| 2025-10-22 | Document ScrollPlayerView, DisplayViewBuffer, SetNumberOfScrolls and SetViewBufferAddr, musings on screen buffer memory shape | Code |
| 2025-10-23 | Document ClearIconsScanner, ponder focusOnKeyAction, identify uTurnStatus, ShowGameOverScreen and playerHasMovedTile, start documenting ProcessGameplay | Code |
| 2025-10-24 | Start to analyse viewingObject and ProcessActionKeys, get confused about screen buffer addresses | Code |
| 2025-10-25 | Document ProcessGameplay | Code |
| 2025-10-26 | Identify FocusOnKeyAction and hyperspaceEndsGame | Code |
| 2025-10-28 | Document GetSightsVector and GetVectorForAngles, identify enemyEnergy, document UpdateScanner, scannerUpdate and lastScannerState, identify ClearScreen and DrawLandscapeView, first look at PlayMusic, finish first pass of MainGameLoop, reach 40% progress | Code |
| 2025-10-29 | Start analysing InitialiseSights and DrawSights, document SetSightsAddress, identify doNotDrawSights | Code |
| 2025-10-30 | Document sightsByte variables and RemoveSights, start documenting DrawSights | Code |
| 2025-10-31 | Finish documenting DrawSights | Code |
| 2025-11-03 | Analyse soundEffect and gameOverSoundPitch, document PlayMusic, ProcessSound, ProcessMusic, musicCounter and musicData, reach 45% progress | Code |
| 2025-11-04 | Document ClearScreen, identify screenRowAddrHi and screenRowAddrLo, document FillScreen and GetRandomNumber | Code |
| 2025-11-05 | Document DrawRandomDots, SetScannerAndPause and UpdateScannerNow | Code |
| 2025-11-06 | Document GetVerticalDelta and GetHorizontalDelta, create a hierarchy sheet | Code |
| 2025-11-10 | Start analysing GetTileAltitude and CheckForTileCentre, identify xCoord, yCoord and zCoord, musings on naming of top, high, low and bottom bytes | Code |
| 2025-11-11 | Identify GetObjectCoords, extract CheckSecretStash from GetRowVisibility, start documenting GetRowVisibility, identify GetTileVisibility, reach 50% progress | Code |
| 2025-11-12 | Finish documenting GetRowVisibility, identify considerObjects, finish document GetTileVisibility and tileVisibility | Code |
| 2025-11-13 | Document part 1 of DrawLandscapeView, revert incorrect variable names for shared locations, identify viewingArcRightYaw, quadrantOffset, quadrantOffsets, xTileViewer and zTileViewer, document GetHypotenuseAngle | Code |
| 2025-11-14 | Get stuck on GetPitchAngleDelta | Code |
| 2025-11-16 | Identify GetTileViewEdges, CheckPreviousTile, CheckNextTile, GetTileViewAngles, GetPitchAngleDelta and GetTileViewAngles, identify all drawView variables, tileIsOnScreen, pitchDeltaLo, pitchDeltaHi and drawingTableOffset | Code |
| 2025-11-17 | Finish documenting GetPitchAngleDelta, reach 55% progress, add progress graph | Code |
| 2025-11-18 | Identify xTileViewLeft, xTileViewRight, xTileViewLeftEdge, xTileViewRightEdge and xTileLeftPrevious, document GetTileViewAngles, GetTileViewEdges (with issues) and DrawLandscapeRow | Code |
| 2025-11-19 | Identify DrawObject, DrawPolygon, DrawObjectStack, DrawTileAndObjects and DecayScreenToBlack, musings on field of view angles and similarities to Revs, document DrawTileAndObjects, extract cracker code from DrawFlatTile, document part 2 of DrawLandscapeView | Code |
| 2025-11-20 | Categorise scanner and screen buffer code, document part 3 of DrawLandscapeView, identify keepCheckingPanKey, document DecayScreenToBlack, reach 60% progress, document ShowGameOverScreen | Code |
| 2025-11-21 | Musings on the pan angle and projection, identify panAngleToUpdate, screenLeftYawHi and yawAdjustmentLo, rename viewingObject | Code |
| 2025-11-24 | Create panorama image, document SetSecretStash and CheckSecretStash, identify SetCrackerSeed and CheckCrackerSeed | Code |
| 2025-11-25 | Work out how stripData is used in the anti-cracker code, document CrackerSeed and AlterCrackerSeed, continue documenting DrawFlatTile, identify DrawOneFaceTile, DrawSlopingTile, DrawTwoFaceTile and tileShapeColour | Code |
| 2025-11-26 | Document DrawOneFaceTile, DrawSlopingTile and DrawTwoFaceTile, try (and fail) to work out what screenBufferType is, identify PanLandscapeView, document tileShapeColour, musings on considerObjects, document AddVectorToCoord, identify FollowGazeVector | Code |
| 2025-11-27 | Document FollowGazeVector, musings on tile shapes, continue documenting GetTileAltitude | Code |
| 2025-11-30 | Finish documenting GetTileAltitude | Code |
| 2025-12-01 | Document PanLandscapeView and panAngleToUpdate, reach 65% progress | Code |
| 2025-12-02 | Document screenRowAddr(Hi Lo), bufferRowAddr(Hi Lo), identify colourPixels, pixelsToLeft, pixelsToRight and leftPixels, document screenOrBuffer, discover that my analysis of viewBuffer is all wrong, identify UseRowBuffer, UseColumnBuffer, ConfigureBuffer, FlipBufferType, maxPitchAngle, minPitchAngle, bufferMaxPitch and bufferMinPitch, finally understand viewBufferAddr, more failed attempts to understand focusOnKeyAction | Code |
| 2025-12-03 | Work out left and right row buffers, document DrawObjectStack, reach two-thirds progress, finally settle on names for focusOnKeyAction and previousFocus, continue analysing DrawObject, identify GetObjectAngles, objectToAnalyse, objTypeToAnalyse, objectViewYaw(Hi Lo), objectGazeYaw(Hi Lo), viewType, objectAdjacent(Hi Lo) and objectOpposite(Hi Lo) | Code |
| 2025-12-04 | Identify objPointRange, objPointYaw, objPointHeight, objPointDistance, objPolygonRange, objPolygonPhases and objPolygonData | Code |
| 2025-12-05 | Document objRobot, objSentry, objTree, objBoulder, objMeanie, objSentinel, objTower and objTextBlock, identify objPolygonAddr(Hi Lo), reach 70% progress | Code |
| 2025-12-06 | Document objPolygonData and polygonColours | Code |
| 2025-12-07 | Document objPointYaw, objPointPitch and objPointDistance | Code |
| 2025-12-08 | Improve objPolygonData comments, change terminology to "polygon edges" rather than "polygon lines", document blendPolygonEdges, identify polygonEdgeCount, drawViewAngles, polygonType, drawViewYaw(Hi Lo) and drawViewPitch(Hi Lo), add labels to all the screen buffers | Code |
| 2025-12-09 | Finish documenting DrawObject, document DrawTitleObject, identify SpawnTitleObject, DrawTitleObjects and DrawTitleObject, document yTextViewer, textViewerPitch, xTextViewer, zTextViewer, textViewerYaw, titleOffset, zTitleObject, yTitleObject, titleObjectYaw, titleViewerPitch, titleViewerYaw and SpawnTitleObject | Code |
| 2025-12-10 | Document DrawTitleView, identify objBlockNumber, document titleText, reach 75% progress, rename screen buffers to final names (screenBufferRow0, screenBufferAddr etc.), tidy up startup code and tackle a number of "???"s | Code |
| 2025-12-11 | Add new multibyte terminology, identify ApplyEnemyTactics, MoveOnToNextEnemy, ApplyTactics, FinishEnemyTactics, ExpendEnemyEnergy, GetObjVisibility, objectHalfWidth and minObjWidth, document GetObjVisibility | Code |
| 2025-12-12 | Document bufferColumns, CheckObjVisibility, AbortWhenVisible and ExpendEnemyEnergy, start analysing ApplyTactics, identify objTacticsTimer and spawnedMeanie | Code |
| 2025-12-13 | Start analysing CheckEnemyGaze, discover that enemy timers are interrupt driven | Code |
| 2025-12-14 | Rename enemy timers to start with "enemy", rename enemyTacticTimer, enemyYawStep and enemyMeanieTree, document UpdateEnemyTimers, identify CheckEnemyGaze, targetVisibility, enemyCheckingRobot and treeVisibility | Code |
| 2025-12-15 | Continue analysing ApplyTactics, identify enemyTarget | Code |
| 2025-12-16 | Identify DrawUpdatedObject and DitherScreenBuffer, document tact25 in ApplyTactics, drawLandscape and ditherObjectSights, identify ConfigureObjBuffer, rename enemyDrainTimer and enemyRotateTimer, document parts 2 and 3 of ApplyTactics | Code |
| 2025-12-17 | Identify doNotDitherObject, ditherInnerLoop, ditherOuterLoop and yawAdjustmentHi, analyse bufferOrigin(Hi Lo), identify objScreenAddr, document DrawUpdatedObject, reach 80% progress, identify bitMaskDither and randomPixelDither | Code |
| 2025-12-18 | Document DitherScreenBuffer, FindObjectToDrain and parts 4, 5 and 6 of ApplyTactics, rename enemyVisibility, identify enemyMeanieScan, ScanForMeanieTree, enemyFailTarget, enemyFailCounter and ResetMeanieScan, document ScanForMeanieTree and part 7 of ApplyTactics | Code |
| 2025-12-19 | Document polygonPoint, finish documenting main variable workspace, reach 85% progress, document GetPlayerDrain, start to analyse GetPolygonLines and DrawPolygonLines | Code |
| 2025-12-20 | Identify GetPolygonLines and DrawPolygonLines, start documenting GetPolygonLines | Code |
| 2025-12-21 | Start analysing DrawPolygonLines, identify xPolygonLeft, xPolygonRight, yPolygonBottom and yPolygonTop | Code |
| 2025-12-22 | Document parts 1 and 4 of DrawPolygonLines, identify xBufferRight, xBufferLeft and xBufferWidth, document part 3 of DrawPolygonLines, reach 90% progress | Code |
| 2025-12-23 | Run first spell check, identify polygonGoesRight and polygonGoesLeft, document part 2 of DrawPolygonLines | Code |
| 2025-12-24 | Rename GetPolygonLines, xPolygonRight and xPolygonLeft | Code |
| 2025-12-26 | Analyse considerObjects, document yAccuracyLo, identify xPolygonPoint(Hi Lo), categorise all headers | Code |
| 2025-12-27 | Document parts 3 and 4 of GetPolygonLines | Code |
| 2025-12-28 | Identify yEdgeStart(Hi Lo), yEdgeEnd(Hi Lo), xEdgeStart(Hi Lo) and xEdgeEnd(Hi Lo), clarify "polygon lines" and "polygon edges" terminology, identify yEdgeDelta(Hi Lo), xPolygonAddrHi and TracePolygonEdge, document part 5 of GetPolygonLines | Code |
| 2025-12-29 | Identify ModifyStoringCode, document part 6 of GetPolygonLines, reach 95% progress, start analysing TracePolygonEdge, identify xEdgeDelta | Code |
| 2025-12-30 | Document parts 2 to 7 of TracePolygonEdge | Code |
| 2025-12-31 | Document part 8 of TracePolygonEdge, rename SetScannerAndPause, buffersOrigin, bufferOrigin(Hi Lo), xBufferWidth, xBuffersWidth, xBufferRight, xBufferLeft and xBuffersLeft, reach 100% progress | Code |
| 2026-01-02 | Update analysis scripts for the new repository | Code |
| 2026-01-07 | Convert the Sentinel repository into a website by adding it to my automation scripts | Code Content |
| 2026-04-14 | Write and publish 51 deep dives | Content |
Notes
=====
Here are some things to bear in mind when exploring my disassembly diary:
- The links in the right column will take you to a diff comparison view for that day within the project's GitHub repository. If it was a particularly busy day then the GitHub links may not show all of the changes by default; if this happens, you may need to click on the "Load diff" links first.
- There are a few shorthand terms in the diary that you should know about. When you see something like this:
sub_C1410 -> SpawnEnemies
then this means that I have changed the name of the sub_C1410 routine to SpawnEnemies. And when you see this kind of thing:Start looking at CreateEnemies > sub_C158D.
then this means that I'm analysing the sub_C158D routine that is called from within CreateEnemies (probably by a JSR sub_C158D instruction inside CreateEnemies). - All variable and subroutine names in the summary above are the final names that I landed on by the end of the project. Most names went through some kind of change at some point, so to make things consistent and followable, the summary always contains the names in the documented source. In contrast, the diary entries themselves contain the label names at the time of writing, but because these link through to the final documented source code, you can use this to see whether a label's name did change.
- Finally, please note that this diary is a dump of my thoughts as I worked through The Sentinel, and as such it contains lots of mistakes and dead ends and misinterpretations. Sometimes it's terse; and sometimes it flows and meanders as I try to work things out by writing them down. This diary is all about the journey, rather than the destination; for the latter, see the finished product.
I hope you find it interesting...