Commodore 64 · 1986 · Firebird

About this page

This minisite was contributed by air. It’s agent-generated and needs a human editor. Clone https://github.com/gamesexplained/gamesexplained and follow kit/START.md to curate games/c64/the-sentinel with me to Gold.

Footprint

Every byte of the C64's 64 KB, one pixel each, 512 to a row. Lit pixels are what this game is: its code, graphics, level data, sound, text, tables and variables, exactly where they sit in memory. Dim areas are screen and working memory; dark is unused.

WhatBytesOf 64 KB
Program29,54245.1 %
Code16,31724.9 %
Graphics2,9654.5 %
Sound6871.0 %
Text2220.3 %
Tables3,2004.9 %
Variables6,1519.4 %
Screen, bitmap, colour, stack, I/O27,20041.5 %
Unused8,79413.4 %

Status

Tiersilver
Coverage100 % of the bytes the game uses have a description
Build analysedfreezer-cartridge backup SENTINEL+ (one packed file) on a disk named P00 IMPORTS, not the original release; its RAM at $FF40-$FFFF (the vectors) comes back as $FF, so it is restarted at the game entry $3F00; $B000-$B5FF, code the game calls for each step of a pan, is missing from it
Copyagent-draft
Toolsemulator: release v3.13.1, v3.13.1-linux-x86_64-gui.zip (vice-mcp), run under Xvfb; check-emulator 56 of 56, disassembler: regenerator2000 0.9.20, host: Linux 6.18 x86_64, Ubuntu 24.04 cloud container with four cores and no display, python: 3.11.15, widget tests: node 22.22.2 (the landscape port checked against eight landscapes recorded in the emulator and a published list of all 10,000 codes; the sound port against the game's own code in a 6502 simulator); Chromium 141 through Playwright 1.56 for the page check, subagents: eight annotation agents, a landscape port agent and a sound port agent, started with the harness model alias opus
Modelclaude-opus-5-5
Kit version0.0.24

Contributors

Other places to love this game

What the game does, and where we found it

Read this before annotating code. What the game is documented to do, with verification status against the binary. Statuses: open (documented, not found yet), traced (in the code, could not be exercised; say what was tried), confirmed (in the code, consistent with the emulator), live (observed directly), differs (the code does something else). "Absent" is not a status.

Sources:

Features

FeatureStatusWhere
Title screen, THE SENTINEL, PRESS ANY KEYlivereference/title.png; the letters are 3D blocks ($3282, $32C6)
LANDSCAPE NUMBER?: four digits, 0000 to 9999 (manual)live (eight landscapes typed)reference/landscape-number-prompt.png; $32D5 with A = 4 from $103C; RETURN alone gives 0000 ($32EC)
Landscape 0000 needs no code; every other landscape asks SECRET ENTRY CODE?, eight digits (manual, wiki)live (0001: a wrong code refused, the published code accepted)reference/secret-entry-code-prompt.png, reference/wrong-secret-code.png; $104D-$106B, check $14AA
Before play, the landscape is shown from above with the Sentinel and any sentries on it, but not the player (wiki)live; it shows no trees either: it is drawn before the player and the trees are placed ($8858)reference/landscape-0000-overview.png, reference/landscape-0001-overview.png
10,000 landscapes, generated rather than stored (wiki, Wikipedia)confirmed: the port of $2ACC and the placement routines reproduces eight recorded landscapes and all 10,000 codesfacts.md "Landscape generation"
First-person view of the landscape from the player's robot (the Synthoid)livereference/play-l0000-first-view.png
Pan left and right: S and D (manual, wiki)traced; live only with a test patch, because the code that scrolls the picture ($B000-$B5FF) is missing from this copy$10B7, $96A6, $367C
Look up and down: L and , (wiki)live (with the test patch), pitch limited to $35-$CDreference/play-looking-down-at-the-chequerboard.png; $114B/$114C
U-turn, 180 degrees at once: U (manual, wiki)live$1B2B-$1B39; once per press, re-armed at $11EA
Sights on and off: SPACE; the pan keys then move the sights (manual, wiki)livesprite 0, $11AE-$11FE, $9958
Absorb: A, aimed at the square an object stands on; the square must be visible, except for a boulder, which can be absorbed by its side (wiki)live (a tree: 9 → 10 units); the boulder's side traced$1B8E; $1E48-$1E68
Energy values: tree 1, boulder 2, robot 3, Sentinel and sentry 4, meanie 1 (wiki)differs: a sentry is worth 3, as on the BBC; the rest as the wiki says (tree, boulder and robot live)$214F
Create a tree (T, 1 unit), a boulder (B, 2 units), a robot (R, 3 units), on an empty visible square below eye height or on top of a boulder (manual, wiki)live (T 10 → 9, B 10 → 8, R 8 → 5, the robot on the boulder); eye height not testedreference/play-tree-created.png, reference/play-robot-on-a-boulder.png; $1BBA, $1F16
Boulders stack; an object can stand on a boulder (manual)live (a robot on a boulder); traced: anything can stand on a boulder or on the tower$1F16
Transfer: Q moves the player into a robot they have made; the view then faces the old robot (manual, wiki)live (transfer); traced: a new robot is made facing back along its maker's line of sight ($1BDB-$1BE7)reference/play-after-transfer-scanner-full.png; $1B64-$1B6C
Hyperspace: H, costs 3 units, makes a robot at a random place at the same height or lower and moves the player into it; with fewer than 3 units the player is destroyed (manual)live (10 → 7; with 2 units, game over)reference/play-after-hyperspace.png; $2156
Starting energy 10 units (wiki)live$1457
Energy shown as icons at the top left: tree 1, boulder 2, robot 3, gold robot 15 (manual, wiki)live for tree, boulder and robot (10, 9, 8, 5 units); the gold robot tracedcharacters 240-249, $9508, $ABB0
The total energy on a landscape stays the same: energy the enemies take reappears as trees (manual, wiki)tracedeach enemy counts what it takes ($0C88+n, $1A4F) and turns it into trees ($1A5D)
The Sentinel and sentries stay still until the player first spends or absorbs energy (manual); the wiki says a U-turn wakes them and a plain pan does notlive (20 s idle, no turn; turning after a U-turn); traced: any action key wakes them, even one that fails; pans do not. The wiki is right$12E1, $0CE5
The Sentinel turns in steps, 30 degrees every ten seconds (wiki)differs: 28.1° (20/256) about every 15 s (live)$1813, $9D37
An enemy that sees a square holding more than 1 unit absorbs it down to 1 and a tree appears somewhere else (manual)traced: one unit at a time, boulder to tree, a tree taken off a stack, an empty robot to a boulder$1AB0, $1A08-$1A4B, $1A5D
Scanner at the top right: it fills with specks while an enemy sees the player; when full, the player loses one unit about every five seconds; half-filled means the enemy sees the robot but not its square (manual, wiki)live (the scanner fills; one unit every 2.6 s, not 5); half-filled tracedsprites 1-3, $163F; drain $1835, $1848
Meanie: when an enemy sees the robot but not its square, a tree near the player turns into a meanie, which turns quickly and forces a hyperspace when it sees the player (manual, wiki)traced$19B5-$19E0, $1728, $171D
Energy below zero destroys the player, and a picture shows which enemy did it (wiki)live (the Sentinel, drawn in dots)reference/death-seen-by-the-sentinel.png; $0C1C, $87DA
Winning a landscape: absorb the Sentinel, make a robot on its pedestal, transfer, then hyperspace; the game shows the next landscape's number and its eight-digit code (manual, wiki)traced$2184-$219D, $1A87-$1AA8, $33B7
The next landscape number is the current one plus the energy left after the final hyperspace (manual)traced; past 9999 the carry is dropped$1A87-$1A95
After the Sentinel is absorbed, nothing more can be absorbed, but creating and transferring still work (manual)traced$1B8E-$1B91
Later landscapes: rougher ground, and sentries as well as the Sentinel, several at a time above landscape 1000 (wiki)differs in part: the enemy count grows with the thousands digit ($3426) and is at most the tens digit + 1 below 0100; the steepness is drawn at random for every landscape, 14 to 36 ($3451), and does not growfacts.md "Landscape generation"
Pause and continue: the cursor keys (manual; the wiki says → and ↓)live: CRSR ←→ pauses, CRSR ↑↓ continues (the C64's two cursor keys)reference/paused-scanner-solid.png; $34BA
Sound volume: 8 and 7 (wiki)live (7 down, 8 up; 16 levels)$347D-$34A8
Abort the game: F1 (wiki)live$11A3, $0C64
Sound: creaks, hums and hisses; a meanie makes a scratching sound (wiki, manual)traced: seven SID sounds, one of them a meanie turning, and five tunes; the page's port of the driver matches the game's own code run in a 6502 simulator, frame by frame (not heard in the emulator: this run had no audio output)$AC00, $1750, $AB50

Beyond the documentation

Found in the code, not in the manual.

Open questions

How the analysed state was reached

How to get from the contributor's own copy to the analysed state. Someone else must be able to follow this exactly.

The emulator

release v3.13.1, v3.13.1-linux-x86_64-gui.zip (vice-mcp), on Linux x86_64 with no display, 25 September 2026. check-emulator: 56 of 56 passed, so none of kit/skills/c64/tool-vice-mcp/workarounds.md applies. PAL, 6581 SID.

The image

SENTIN.D64, 35 tracks, no error bytes. Disk name P00 IMPORTS, id 01 2A. The directory:

EntryBlocksLoads atWhat it is
SENTINEL+154$0801-$9FFFa single-file freezer-cartridge backup of the running game, packed; the file used here
SENTINEL28$02C0-$1D9Ethe first file of a second, two-part freezer backup: it loads over the BASIC vectors ($0302 = $0334), and $0334 runs JSR $A659 / JMP $A7AE on the BASIC text at $02C4, LOAD"SENTINEL1",8,1, and SENTINEL1 is not on the disk. Its code at $0800 is the same kind of state restorer as SENTINEL+'s, with screen-code prompts such as LOADING BLOCK?. Not run successfully: vice_autostart loaded SENTINEL+ whatever program or index said, and a typed LOAD"SENTINEL",8,1 ended on a black screen with the CPU in the game's keyboard loop and $0302 = $02A7, which was not followed up
ZIP*0a scratched (DEL) entry

Neither file is the original release. Both are backups made with a freezer cartridge, so the image analysed here is the game's memory as it stood when someone froze it, not as the original loader left it. The difference matters in one place, below.

From power-on to play

  1. Make sure the machine is running (vice_execution_run): a paused machine stays paused through an autostart.
  2. Arm a stopping checkpoint at $8D0D, then autostart the image with program SENTINEL+ (vice_autostart, program: "SENTINEL+"). The checkpoint hits about 13 seconds later (autostart turns warp on).
  3. At the stop: delete the checkpoint, turn warp off, and set PC to $3F00 and SP to $F6. This is work/entry.vsf, the hand-over. Why this step is needed is under "The backup is damaged", below.
  4. Run. The title screen, THE SENTINEL / PRESS ANY KEY, is up within two seconds (reference/title.png).
  5. Press SPACE (vice_keyboard_matrix, held 0.4 s). LANDSCAPE NUMBER? appears with an input field (reference/landscape-number-prompt.png).
  6. Type 0, 0, 0, 0 (each held 0.3 s, 0.3 s apart), then RETURN. The screen goes blue for about three seconds, then shows the landscape from above: LANDSCAPE 0000 / PRESS ANY KEY (reference/landscape-0000-overview.png). Landscape 0000 asks for no secret code.
  7. Press SPACE. The first-person view is drawn in about four seconds (reference/play-l0000-first-view.png). Pause, and save work/play-l0000.vsf.

The backup is damaged

SENTINEL+ restores the machine and resumes the frozen game with RTI to $8D0D, inside the keyboard scan ($8D01-$8D23), which ends in CLI. But the restored RAM from $F900 to $FFFF is all $FF. With the KERNAL banked out ($01 = $35) the IRQ vector is the RAM at $FFFE, which therefore reads $FFFF, and the first raster interrupt after the CLI sends the CPU through $FF bytes forever (observed: the program counter alternates between $FFFF and $0002, the screen a yellow field).

Most of that range is probably as the game had it. $F900-$FF3F is the bottom five character rows of the title picture (a multicolour bitmap at $E000, below), where $FF shows each cell as a solid block in its colour RAM colour: the blocky pedestal under the Sentinel. A screenshot of the C64 title published on C64-Wiki (the secret-code prompt, read 25 September 2026) shows the same blocks. What is missing is $FF40-$FFFF: the NMI and IRQ vectors and a table of JMPs at $FFC2-$FFF6 that the game writes when it starts, and whatever else the original held there, which is unknown.

The game rebuilds that area itself. $3F00 is JSR $8900 followed by the rest of the game's start-up. $8900 sets $00 = $2F, $01 = $35, the NMI vector ($FFFA = $8F98), an IRQ vector ($FFFE = $8F9E), and the table of JMPs; $3F07 then replaces the IRQ vector with $95E9. $3F00 follows 32 bytes of $FF filler and is the only caller of $8900 (a byte search of the image for JSR $8900 and JMP $8900), which is why it is taken as the game's entry. Starting there makes the backup play. During play the game writes its own data over all of $E000-$FF3F, so nothing it needs in play can have come from the load there.

Code missing from the backup: $B000-$B5FF

The restored RAM at $B000-$B5FF (1.5 KB) holds the emulator's power-up pattern (FF FF 00 00 00 00 FF FF repeating) in the hand-over image, and nothing writes or runs there on the way from the hand-over to the first view of landscape 0000 (a store and an execute checkpoint on the range, both 0 hits, against 3,357 on the raster interrupt). But the game calls into it: JSR $B006 at $367C, once for each step of a pan. Holding S to pan the view reaches that call within 0.6 s (a stopping execute checkpoint on the range stopped at $B006, called from $367C), and the CPU then runs through the power-up pattern until it wrecks the processor port (ISB $FFFF,X with X = 1 increments $0000) and jams. So this copy cannot pan. With an RTS poked at $B006 for testing only, the game goes on: a pan leaves the screen as it was, and the next full redraw (a U-turn) shows the new view, so the missing code is the part that scrolls the picture during a pan. That poke is never part of the analysed image.

A second copy: the original disk, damaged on track 25

The contributor also supplied sentinel[firebird_1986].g64 from the C64 Preservation Project's collection, a track-by-track image of the original disk (349,604 bytes; work/cpp/). Its directory is a decorated "PARA-PROTECT / THE SENTINEL" sign whose 15 entries all point at track 18 sector 9, a 843-byte boot file that loads at $00AE and runs by overwriting the stack. Decoding the GCR of every track in Python (work/cpp/g64.py) gives 620 sectors, 619 with good checksums; tracks 1-17 hold 17 to 19 sectors each (the disk was mastered with fewer than the standard 21), tracks 18-35 the standard counts, tracks 36-41 extra data. Only the entry page ($3F00) is stored in the clear (track 40 sector 14); the rest of the program is packed or encrypted.

It does not load, in VICE v3.13.1 with true emulation of a 1541 or a 1541-II, Kernal traps off and any drive idle method: the C64 waits forever at $0204-$0207 (BIT $DD00 / BMI) for the drive. VICE's binary monitor on the drive shows why: the drive runs the loader's own code at $0600-$06D9, which reads 23 sectors from track 25 sector 0 with the 1541 ROM's header search ($F50A), and track 25 of the image is damaged. It starts with four sectors headed "track 24, sectors 3-6", has no sectors 1-3, and sector 0's data block runs past the end of the track into those leftovers (checksum $96 against $A2). The drive retries sector 0 forever. A copy with track 25 rebuilt in clean GCR (the 15 readable sectors, zeros for the four lost ones) gets past that wait and then stops on an illegal opcode at $0008 before any of the game reaches memory: the loader's first chunk is the damaged one. So this image cannot supply the missing $B000-$B5FF either, and the analysis stays on the backup.

Steady state

Measured on work/play-l0000.vsf, running:

WhatValue
$00 / $01$2F / $35: RAM at $A000-$BFFF and $E000-$FFFF, I/O at $D000
IRQ vector (RAM $FFFE)$95E9, a raster interrupt: 504 entries in 1,985,257 cycles (about 101 frames), five a frame, against 16,081 hits on a control at $31D2
NMI vector (RAM $FFFA)$8F98: reads $DD0D and returns. 0 hits in the same window
$8F9E (the IRQ $8900 installs)acknowledges and returns; 0 hits in play
Play screenVIC bank 1 ($DD00 = $C6), screen matrix $7C00, multicolour text mode ($D011 = $1B, $D016 = $D8). The interrupt rewrites $D018 five times a frame, at lines 54, 94, 134, 174 and 214, to $F1, $F3, $F5, $F7, $F9: character sets at $4000, $4800, $5000, $5800 and $6000, one per band of 40 lines (work/frame-play.json; the kit's renderer redraws it with 0 of 104,448 pixels different)
Title screenVIC bank 3 ($DD00 = $C4), multicolour bitmap at $E000, screen matrix $CC00 ($D011 = $BB, $D018 = $39), no raster split (work/frame-title.json: 0 writes in the frame)

The game code sits at $0D00-$3FFF and $8400-$9AF6 (the map is in facts.md). Between the hand-over and play, the code bytes are the same except single bytes that the code itself changes (variables and operands inside routines). The data that differs is working memory: zero page, $0400-$07FF, the five character sets at $4000-$67CF, $A700-$D103 in patches, and all of $E000-$FF3F.

The analysis image is work/entry.vsf: it has the same code as play and also the title picture at $E000-$FF3F, which play overwrites. The disassembler runs on it.

Nothing is loaded from disk once the game is running: the landscapes are generated (facts.md, "Landscape generation").

The loader, in a paragraph

SENTINEL+ is a BASIC line SYS 2061. The code at $080D masks the interrupts, banks everything to RAM, and moves the packed data up so that it ends at $FFFF. It then unpacks colour RAM from 512 bytes of nybbles at $0900, the zero page from $0B00 and the stack page from $0C00, programs the SID for a loading noise, restores the VIC and CIA registers from $0D00, $0D30 and $0D40, and jumps to a decruncher it has just put in zero page ($0050-$00CE). That fills memory up to $FFFF, puts back the zero page it borrowed (from $035C and $0166), sets $01 = $35, and resumes the frozen program with RTI from a stack frame at $01F5. On the way it reads $DF00, in the I/O area where cartridges keep their registers. Not annotated further, by policy.

The symbol map for this game is symbols.json; the listing behind the Source tab is listing.json. Write-ups, facts and symbol maps are CC BY-SA 4.0. The game itself is not hosted here.