Commodore 64 · 1984 · Novagen Software
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.
| What | Bytes | Of 64 KB |
|---|---|---|
| Program | 41,627 | 63.5 % |
| Code | 8,699 | 13.3 % |
| Graphics | 16,172 | 24.7 % |
| Level data | 1,104 | 1.7 % |
| Sound | 128 | 0.2 % |
| Text | 2,368 | 3.6 % |
| Tables | 2,052 | 3.1 % |
| Variables | 11,104 | 16.9 % |
| Screen, bitmap, colour, stack, I/O | 14,678 | 22.4 % |
| ROM the game runs under | 8,192 | 12.5 % |
| Unused | 1,039 | 1.6 % |
| Tier | silver (claimed) |
|---|---|
| Coverage | 100 % of the bytes the game uses have a description |
| Build analysed | Novagen release as a PC64 program file (ENCOUNTR.P00), crunched; no trainer; possibly a crack (the level-select check is NOPed and seven junk bytes run at game start) |
| Copy | agent-draft |
| Tools | emulator: VICE 3.10 via vice-mcp 3.11.0 (macOS arm64 GUI build), disassembler: regenerator2000 0.9.20, python: 3.9.6, host: macOS 26.5 (Darwin 25.5.0), Apple silicon |
| Model | claude-fable-5-1 |
| Kit version | 0.0.7 |
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:
The sources disagree in places: the wiki says "seventeen UFO types" and "10–19 enemies per level"; the comparison blog says two adversary types (saucers and homing drones) with two new saucer attack strategies per level; the manual says eight levels. Each is a claim to check, not a fact.
| Feature | Status | Where |
|---|---|---|
| Title screen: ENCOUNTER, BY PAUL WOAKES, © 1984 NOVAGEN SOFTWARE, SCORE / LAST SCORE / HIGH SCORE, F5 NOVICE, F7 BEGIN GAME | live | title loop at $9CBA (orientation) |
| F5 cycles skill level: Novice, Advanced, Expert (wiki); the title shows the current one | traced | $9D2B-$9D4B: F5 ($C5 = 6) steps $62 through 2 NOVICE (default), 1 ADVANCED, 0 EXPERT and copies the name from hidden title rows 20-21 ($6B20 + 16 × skill) to $6B88; skill picks a column of the tables at $8F80 |
| F7 begins the game | live | $9D27 cmp #$03 (F7's $C5 code) → $9D68 |
| Number keys restart at a level already completed in this session (manual, wiki: 1–8) | differs (live) | $9CD4 maps keys 8..1 through the table at $6931 to $A8 = index × 16; the comparison with the highest level reached ($68CB) at $9CE3 is followed by two NOPs instead of a branch, so any level can be chosen at any time |
| Attract / demo mode when the title is left alone: the game plays itself | live | title loop counts $F7/$F8/$F9 down and jumps to $9D7A, which sets $A8 = $70 and starts play through $9E46; seen at level 2 with $A8 = $70 and no input given (reference/demo-level2.png) |
| Last score and high score kept across games | traced | $ABED copies the SCORE line of the title screen to LAST SCORE ($6AC2), compares $8BA0-$8BA3 with the high score $8BA4-$8BA7 in BCD and rewrites the HIGH SCORE digits at $6B12 |
| Feature | Status | Where |
|---|---|---|
| First-person view: blue sky with clouds, horizon line, green ground, black cylindrical obelisks (pillars) on a flat plain | live | reference/play-level1-start.png; sky, horizon and ground are $D021 bands from the raster chain $B2FE-$B3EA, clouds and the hill line are sprites 2-7 ($B9A5), obelisks are composited characters ($AD76) |
| Joystick in port 2: forward, reverse, turn left and right, and diagonals rotate (manual); fire button shoots; hold for continuous fire (wiki) | traced (turning right seen live) | $BC31 reads $DC00; $A1F7 dispatches up → $B508 forward, down → $B56C reverse, right → $B4D8, left → $B4F0; fire → $ACB6 with an 8-pass lock $5C, so holding fire repeats |
| Status row: score (7 digits), L<level>, E<enemies remaining>, S<shields> (S4 at the start of a game) | live | screen row 0 at $6000, wide font |
| Radar (octagonal scanner under the view) shows enemies relative to the player at centre | traced (dot seen live) | sprite 0 is the blip, placed at $AB21-$AB44 from the enemy's bearing and range through a polar-to-cartesian conversion and clamped to the scanner box |
| Console lights either side of the radar: yellow = saucer present, blue flashing = saucer firing, red flashing plus a low whine rising in pitch as it approaches = homing missile (manual) | traced | $AA70 recolours five cells of rows 19/21/23: row 21 yellow (7) at $A466 when a saucer launches, back to 8 at $A8CC; row 23 light blue ($0E) at $AA22 when it fires, reset at $A0C6; row 19 red/pink at $A7FB/$A800 while a missile lives; the whine at $A811 uses range $31 |
| Shields: start with four; losing the last ends the game; maximum nine (manual, wiki) | live (four at start, plus one at the last kill, game over on the death after S0: six deaths in the demo run) | the count is the status digit at $6026/$6027: $9F89 writes 4, $ABDC subtracts one on death, $ABDA goes to game over at zero, $A91D adds one when the last enemy of a level dies, capped at 9 |
| Obelisks block movement, are indestructible and stop both player and enemy shots (wiki); 64 of them on the field (comparison blog) | traced | objects 32-95 are 64 obelisks of type 3 on a 32-unit grid with a cell map at $8A00; $AC30 stops shots and enemy shots (deflecting them), $B50C refuses a player move within $43 units and $B53F plays the bump |
| Homing missiles are not stopped by obelisks and must be shot (manual) | traced (two missile spawns counted live) | the missile is object 2 driven by $A7F0, which never calls the obelisk test $AC30; $A894 tests it against the player's shots |
| Enemies appear one at a time from a white warp gate (wiki) | live (the flash rectangle $BA04 ran 483 passes for 13 spawns) | one enemy slot (object 2); $A42C spawns a saucer and shows object 7 (shape family $E0) as the warp-in flash from the spawn until $20 passes after the saucer starts moving (live: passes 52 to 131 of a recorded spawn, the saucer starting at 100) |
| Saucer behaviours: evasion, scattered shots, clouds of fire, permanent fire (wiki); two new strategies per level (blog) | traced | 16 types; type = random in 0..2 × level − 1 ($A42C), the first two spawns after a level-up being the two new types ($9E); dispatch table $AAB5 has four handlers: default $A5D6, burst fire $A563, script fire $A5C7, permanent fire $A70D; fire scripts at $8C00 |
| Each level has 10–19 enemies (wiki) | confirmed | $AA8F: a random byte masked to $10-$1F into $8BA8 (BCD); E11 in the snapshot, E15 on another run |
| UFO point values 100–1600 (wiki) | traced | table at $8BB0: (type + 1) × 100 for 16 types; a missile scores $8BAC = 500; the award is paid level times ($A1 at $AAC8, $A8B2) |
| Explosion colour sequence white, yellow, orange, red, brown, black, the same for every enemy (blog) | traced | $A14F indexes the nibble-pair table at $8CF0 by the fragment lifetime; $B467 blends the two nibbles on alternate frames |
| Sound: two simultaneous voices for enemy indicators; missile whine; engine noise in the transition (blog) | traced | voice 3 saucer hum from range ($A5F2), voices 2 and 3 missile whine ($A811), voice 1 engine loop ($A231/$B415), warp tone ramp ($A4CD) |
| Space pauses and resumes; F1 aborts the game (manual, wiki) | differs (live) | F1 aborts to the title ($A118); SPACE pauses ($A12A → $A13B) but the pause ends on a joystick direction, not on SPACE |
| Feature | Status | Where |
|---|---|---|
| When the enemy counter reaches zero a stargate opens: a black rectangular hole whose position shows on the scanner; the player must line up with its centre (manual) | live (opened once in the demo after the count was poked to 1) | $A8B2 at count zero sets $69 and adds a shield; $A2A1 activates object 3 at the last enemy's X for $F0 passes; $BA04 draws it as a rectangle rising from the horizon; $A4EC tests entry with $AD26 |
| Warp phase: propelled at high speed through a hail of spheres to avoid (manual); the wiki calls it a 30-second missile gauntlet | live (351 passes of $A713 before a sphere hit) | $A713/$A757: scene $80, speed ramps by $A5 toward $A7E8[level], spheres (shapes $A7E0) spawn in slots 8-23 every 3 passes, about four 256-frame periods long |
| Success awards an extra shield, maximum nine; missing the gate or hitting a sphere returns the player to the previous level and costs one shield (manual) | differs (partly) | the extra shield is awarded at the last kill ($A91D), before the gate; missing the gate ($A552) returns to play with a fresh enemy count and no shield change; a sphere hit runs the ordinary death ($AB9A), which does cost a shield |
| Eight levels (manual); odd and even levels alternate colour schemes (blog) | traced | level index $63 runs $70 (level 1) down to $00 (level 8) in steps of $10; scene records at $8010 + $63 alternate a coloured sky (odd) and a black sky (even); $A798 increments the level digit |
| Feature | Status | Where |
|---|---|---|
| POKE 30430,0 ($76DE) or POKE 34722,0 ($87A2) for invulnerability (wiki, marked untested there) | open | $76DE is inside the sprite-image block at $7000 and $87A2 inside the maths tables; neither is code on the death path, so these POKEs most likely belong to a different memory layout (another crack or the tape version); not tested |
Found in the code, not in the manual.
How to get from the contributor's own copy to the analysed state. Someone else must be able to follow this exactly.
ENCOUNTR.P00, a PC64-format program file (26-byte C64File header, filename ENCOUNTR, then a PRG). Stripping the header gives a 20,263-byte PRG loading at $0801 ending at $5726. The BASIC stub is one line, 1001 SYS 2066, so the machine-code entry is $0812. The program is crunched: the loader at $0812 installs a decruncher in the stack page ($00FA–$01A0) and the tape buffer ($0333–$03D9) and jumps to $0100. No trainer, cracktro or menu appears; the title screen credits "Paul Woakes", "© 1984 Novagen Software". Whether this is the original release or a cracked copy is unknown: the cruncher is scene-style (self-modifying page copy, $01 = $30 all-RAM during depack) and the title screen shows nothing beyond the publisher's own credits.
The kit works from work/encounter.prg (the PRG with the P00 header removed: tail -c +27 encounter_0_original.p00 > encounter.prg).
work/encounter.prg (vice_autostart with the path). The loader depacks in under a second; the title screen appears with SCORE / LAST SCORE / HIGH SCORE and F5 NOVICE, F7 BEGIN GAME. Turn warp mode off (autostart leaves it on).work/title.vsf at the title screen.SCNKEY and reads $C5; a short tap can be missed). Play begins immediately: a first-person cockpit over a green plain with a blue sky, status row 0000000 L1 E11 54 (score, level 1, enemies remaining, a countdown), crosshair in the centre, black pillars on the horizon.work/play-level1.vsf, taken a few seconds into level 1 with no joystick input given yet. This is the image the disassembler is loaded on.F5 selects NOVICE before F7; the snapshot was taken at the default (not novice) setting. Not yet explored.
Observed in play-level1.vsf:
| What | Value | Meaning |
|---|---|---|
$01 | $36 | BASIC ROM banked out, KERNAL and I/O in: RAM at $A000–$BFFF holds game code |
$0314/$0315 | $B34E | IRQ vector in RAM: the game's raster interrupt chain |
$0316–$0319 | $FE66, $FE47 | BRK and NMI vectors left at KERNAL defaults |
$FFFA–$FFFF (ROM) | $FE43, $FCE2, $FF48 | hardware vectors are the KERNAL's; IRQ goes through $0314 |
$DD00 | $96 | VIC bank 1: $4000–$7FFF |
$D018 | $8F | screen at $6000, character set at $7800 |
$D011 | $1B | text mode, 25 rows, screen on |
$D01A | raster bit set | raster IRQ enabled; $D012 = 255 at the sampled moment |
| CIA1 ICR | all sources disabled by $DC0D = $1F at $9F16 | only the raster interrupt runs during play |
The IRQ handler at $B34E is a chain of short stages: each stores a colour into $D021, rewrites $0314 to the next stage and $D012 to the next raster line, acknowledges, and returns. That is how the sky, horizon and ground bands are drawn on a plain text screen.
Program counter samples during play scatter over $ADxx–$B8xx, so the main loop lives in the block under the banked-out BASIC ROM. The init code is at $9C00. Nothing is reloaded from disk: the whole game is resident after the single depack, and the title screen returns without a reload.
SYS 2066 runs $0812: SEI, $01 = $30 (all RAM), copy 166 bytes from $082A to $00FA and from $08D0 to $0333, then JMP $0100. The decruncher in the stack page and tape buffer expands the packed data downwards from $55E5 over $0801 onwards, and finishes at $0960. That routine is a self-modifying page copy that moves the whole depacked image up by $3000 (every page $0D→$3D, $0C→$3C ... wrapping) and jumps to $9C00. The init at $9C00 copies 34 pages from $3E00 down to $1E00, writes the vector pair $9C3E into $8000–$8003, calls the KERNAL IOINIT, copies 160 bytes of initial variables from $8B00 into zero page $02–$A1, sets $01 = $36, selects VIC bank 1, and enters the title screen. Two checkpoints (at $0960 and $9C00) each hit exactly once on a fresh run, which is how the chain was confirmed. Not annotated further, by policy.
Loading play-level1.vsf back with vice_snapshot_load after a hard reset and a second autostart came back with $01 = $37, the CPU in the KERNAL screen-scroll routine and a garbage screen. Autostarting the PRG again and pressing F7 is faster and reliable; the snapshot file itself is fine for the disassembler and for offline sweeps.
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.