← game · web port architecture · asset catalog
ROM REVERSE ENGINEERING
Battle City (Namco, 1985) — Famicom / NES
ROM IDENTIFICATION
Field Value
File battlecity_famicom.nes (BattleCity (Japan).nes)
Format iNES
CPU MOS 6502 (2A03)
Mapper 0 (NROM-128) — 16 KB PRG mirrored at $8000–$BFFF and $C000–$FFFF
CHR-ROM 1 × 8 KB — 512 tiles, fixed pattern table
Mirroring Horizontal
Vectors RESET = $C070 NMI = $D400 IRQ = $C070
File size 24,592 bytes (16-byte iNES header + 16 KB PRG + 8 KB CHR)
A separate VS System ROM (mapper 99, 32 KB PRG) also exists — the arcade board ran a modified version with coin/credit handling, DIP-switch lives, and palette remapping. This analysis covers the Famicom version.
MEMORY MAP
CPU Range Size Contents
$0000–$00FF 256 B Zero-page — heavily used for game state, entity coords, timers
$0100–$01FF 256 B Stack
$0200–$02FF 256 B OAM shadow — 64 sprite entries (Y, tile, attr, X)
$0300–$03FF 256 B Game state: bullet slots, SFX flags, entity status
$0400–$05FF 512 B Nametable RAM shadow — 30 rows × 32 cols BG tile indices
$8000–$FFFF 16 KB PRG-ROM (mirrored — $8000 = $C000)
PPU $0000–$0FFF 4 KB CHR PT0 — background tiles
PPU $1000–$1FFF 4 KB CHR PT1 — sprite tiles (8×16 mode)
SCREEN LAYOUT
The PPU outputs 256×240 pixels (NTSC). Real CRT TVs hid ~8 px top and bottom (overscan); the web port replicates this crop.
Region Pixels Tiles
Top border y 0–15 2 rows — gray border tile $FC
Left border x 0–15 2 cols — gray border
Playfield x 16–223, y 16–223 26×26 CHR tiles = 13×13 metatiles (16 px each)
Right HUD x 224–255 4 cols — enemy tank count, lives, stage#
Bottom border y 224–239 2 rows — gray border
KEY SUBSYSTEMS
Stage data — $F000 LoadStageData / $F07A StageDataTable
36 stages × 91 bytes each = 3,276 bytes of nibble-encoded map data
Each stage: 13 rows × 7 bytes = 13 data nibbles + 1 padding nibble per row
Outer loop Y = $10→$D0 step $10 (rows); inner loop X = $10→$D0 step $10 (cols)
Each nibble → PlaceTileBlock: writes 4 nametable bytes for one 16×16 metatile
Eagle base wall placed separately (not in nibble table) by an independent nametable-write routine
Entity system
8 entity slots at zero-page $A0–$A7 (X coord) and RAM $0103–$010A (Y coord)
Slots 0–1: players; slots 2–7: enemies (up to 4 active at once, 20 total per stage)
10 bullet slots — slots 0/8 for P1, 1/9 for P2, 2–7/2–7 for enemies
Tank type (0–3) controls armor hits, speed tier, and score value
Power-up tank flagged at spawn counts 17/10/3 remaining enemies
Movement — $DD30 MoveGridSnap
Tanks snap to 8-pixel grid; probes two leading-edge corners before each step
Passability: tile type ≥ $12 → open; brick/steel/water block
Partial brick tiles store a 4-bit sub-quadrant mask — individual 8×8 quadrants can be cleared by bullets
Ice tiles reduce snap strength; tanks slide 1–2 extra pixels after releasing direction
AI — $DC7C EntityMovementAI
"Fast" tier (type $A0) processes every frame; others alternate frames
Direction change triggered when blocked or when RNG & $0F == 0
Two 9-entry hardcoded direction sequences (set 0/1) cycle through UP/LEFT/DOWN/RIGHT patterns
Target-seeking: $DDA0 CalcDirToTarget biases toward player position
Bullet terrain collision — $E604 BulletTerrainCollision
Reads nametable shadow to identify hit tile type (brick, steel, eagle)
Brick: clears one or two quadrant bits depending on bullet power level
Steel: only erased by armor-piercing (power-up) bullets
Eagle hit: starts $68 damage countdown → game over
Power-ups — $EA7E
6 types: Star (speed+power), Grenade (kill all enemies), Shield (invincibility), Freeze (stop enemies), Shovel (steel eagle wall), 1-Up (extra life)
Grenade triggers 8-frame white flash and kills all active enemies instantly
Shovel converts eagle brick wall to steel for a timed duration, then restores it
CHR / graphics
PT0 ($0000–$0FFF): 256 background tiles — terrain, font, HUD elements, NAMCOT logo
PT1 ($1000–$1FFF): 256 sprite tiles — tanks (4 dirs × 2 frames), bullets, explosions, power-ups, eagle
Sprites use NES 8×16 mode; each tank is a 2×2 metasprite (4 OAM entries)
9 background palette sets (ROM $D565): in-game, water animation (alternates every 32 frames), title, game-over/select, grenade/shovel flash
Sprite palettes fixed for all screens (ROM $D555)
Score & HUD
7-digit BCD score arrays in zero-page: P1 $15–$1B, P2 $1D–$23, Hi-score $3D–$43
Hi-score persists across soft resets (not power cycles — no battery)
Result screen tallies kills by tank type, awards bonus score per type
Extra life granted at every 20,000 points ($CF44 LivesGrantCheck)
TOOLS BUILT
Script Purpose
instruction_set.pyComplete 6502 opcode database with cycle counts and addressing modes
dis.pyTargeted disassembler — reads labels.csv + comments.csv, annotates output inline
extract_tiles.pyDecode 2bpp CHR-ROM → PNG tile sheet (32×16 grid, 9px cells)
extract_level_maps.pyDecode nibble-encoded stage data → tile type arrays
render_screen.pyComposite full screens from nametable + CHR + palette data
search_bytes.pyByte-pattern search with context and optional inline disassembly
xref.pyFind all call/jump/load references to a given address
decode_tables.pyFlat and two-level pointer table decoder