WEB PORT ARCHITECTURE

Plain HTML + Canvas — no build tools, no frameworks

FILES

game.js
~2,550 lines. All game logic, rendering, input, and audio orchestration. Single file, no imports.
levels.js
35 decoded stage maps as JS arrays — one 13×13 tile-type grid per stage, extracted from ROM.
sound.js
Web Audio API synthesizer. Recreates 2A03 square/noise channels for all SFX.
tiles/chr_all.png
Full 8 KB CHR-ROM decoded to PNG. 32×16 grid of 8×8 tiles (9px cells with 1px border). BG bank tiles 0–255, sprite bank 256–511.
index.html
Game shell. Canvas wrapped in a clipping div that hides 8px top/bottom to replicate NTSC CRT overscan.
catalog.html
Visual reference: all metasprites, terrain tiles, palettes, and SFX playback — rendered live from chr_all.png.

COORDINATE SYSTEM

All game logic runs in NES pixel space (256×240). A single SCALE constant (= 3) maps NES pixels to CSS pixels at render time. Changing it resizes everything without touching any game code.

ConstantValueMeaning
NES_W / NES_H256 / 240Full NES PPU output size
SCALE3CSS pixels per NES pixel
FX / FY16 / 16Playfield top-left in NES pixels (2-tile border)
GW / GH13 / 13Playfield width/height in metatiles
META16Pixels per metatile (2×2 CHR tiles)
TANK_SZ16Tank sprite size in NES pixels
Tank and bullet positions are stored as the sprite center in NES pixels. Collision probes use the leading-edge corners, mirroring the ROM's MoveGridSnap routine.

RENDER PIPELINE

The renderer faithfully reproduces the NES PPU pipeline order: background first, then sprites on top.

CHR tile engine

Palette system

Per-frame render order

1Fill border regions (gray tile $FC)
2Fill playfield black (C.FIELD)
3Draw all 13×13 terrain metatiles (brick with sub-quadrant masking, steel, trees, water, ice)
4Draw eagle sprite
5Draw power-up sprite (if active)
6Draw entity sprites — tanks (CHR metasprites), spawn animation, death explosion
7Draw bullet sprites
8Draw trees overlay (painted after tanks so trees appear in front)
9Draw HUD — right panel: enemy tank icons, player lives, stage number
10Draw overlays — shield glint, GAME OVER scroll, grenade flash, freeze indicator

GAME LOOP

Runs via requestAnimationFrame locked to 60 fps. Each tick: read input → update all subsystems → render.

SubsystemROM referenceNotes
Input$9689 ReadControllersKeyboard → D-pad + A/B; gamepad API supported
Entity movement$DD30 MoveGridSnap8px snap grid; leading-edge collision probes
AI$DC7C EntityMovementAIHardcoded direction sequences + target seeking
Firing$E162 EnemyFireTick1/32 chance per frame per enemy; player fire on button press
Bullet movement$E604 BulletTerrainCollision4 px/frame; nametable shadow read for tile type
Bullet–entity hit$E70C EnemyBulletPlayerCollisionProximity ≤ 10px; armor hit counting
Bullet–bullet cancelOpposing bullets within 8px destroy each other
Power-ups$EA7E6 types; spawned on kill of flagged enemy
Shield / freezeFrame-count timers; freeze stops all enemy movement and AI
Spawn animation$DDA0Cycling tile pattern over ~60 frames before tank appears
Death animation$DE64 DeathAnimTickExpanding ring of colored rects, 4 phases
Stage clear$C728 CheckGameOverAll 20 enemies defeated → victory scroll
Game over$C728 CheckGameOverEagle destroyed or all lives lost → GAME OVER sprite scroll

BRICK DESTRUCTION MODEL

Each metatile cell stores a 4-bit mask (brickBits) representing which 8×8 quadrants remain: TL=bit0, TR=bit1, BL=bit2, BR=bit3. When a bullet hits, the mask for the struck quadrant(s) is cleared. The tile is re-classified based on which bits remain:

This exactly mirrors the ROM's $D745 SubTileBitmask system.

SOUND

All sound effects are synthesized at runtime using the Web Audio API — no audio files. Each SFX recreates the 2A03 channel behavior:

CONSTRUCTION MODE

A level editor (Famicom-exclusive feature) is implemented as a game state. The 13×13 grid is editable with cursor navigation; the current tile type cycles through all terrain types. Custom maps are stored in JS memory and passed directly to the stage loader — no serialization.

FIDELITY NOTES