Retro Game Mechanics Explained
Because the console lacks dedicated framebuffers and coordinate registers, programmers must tally individual CPU machine cycles against the CRT electron beam to construct images scanline by scanline.
AI-assisted summary of Racing the Beam Explained - Atari 2600 CPU vs. CRT Television — original source. Check the original for full context.
Source published:
Key ideas
- The Atari 2600 operates with virtually no conventional video framebuffer, maintaining only 39 bits of active graphical data at any given scanline moment across its playfield and sprite registers. (0:00)
- Because the MOS 6507 CPU and the Television Interface Adapter (TIA) share a master clock, exactly one CPU machine cycle corresponds to three horizontal screen color clocks, creating a strict 76-cycle budget per scanline. (7:30)
- The TIA lacks dedicated X and Y coordinate hardware registers, forcing software to rely on cycle-timed writes to strobe registers and fine-motion registers (HMPx/HMOVE) to position graphics horizontally. (12:02)
- Large graphics such as 48-pixel-wide sprites bypass the 6507's three-register limit by exploiting the TIA's vertical delay registers (VDELP0 and VDELP1) as hardware holding buffers to cycle six bytes across a scanline. (33:35)
Detailed notes
## Background Published on YouTube by Retro Game Mechanics Explained on June 17, 2021, this video examines how the Atari 2600 displays graphics on a television screen despite having virtually no dedicated video graphics memory. Drawing on the video's description, the presentation details the low-level technical mechanics of how the console's microprocessor and display adapter generate images in real time.
## TL;DR Because the Atari 2600 contains only 39 bits of graphical register data and completely lacks a framebuffer or coordinate registers, the CPU must manually synchronize with the television's electron beam on a line-by-line basis. Developers positioned objects by counting CPU machine cycles from horizontal blanking intervals, combining coarse reset strobes with fine horizontal motion registers. Through detailed breakdowns of *E.T. the Extra-Terrestrial* and *Q*bert*, the video demonstrates how software exploited internal delay latches like VDEL to assemble 48-pixel sprites and rewrote playfield registers mid-scanline to create complex asymmetrical color layouts.
## Atmosphere & style The presentation carries a calm, meticulous, and deeply analytical tone, resembling an advanced computer engineering lecture that demystifies early computing hurdles. The narrator's delivery is measured and instructional, treating the platform's severe hardware constraints not as design flaws, but as fascinating puzzles solved through ingenious timing exploits. Paired with step-by-step register diagrams and cycle-by-cycle execution timelines, the experience gives viewers an appreciative, immersive look into the razor-thin margin of error retro programmers faced.
## Bulletin - [00:00](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=0s) The Atari 2600 operates with virtually no conventional video framebuffer, maintaining only 39 bits of active graphical data at any given scanline moment across its playfield and sprite registers. - [07:30](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=450s) Because the MOS 6507 CPU and the Television Interface Adapter (TIA) share a master clock, exactly one CPU machine cycle corresponds to three horizontal screen color clocks, creating a strict 76-cycle budget per scanline. - [12:02](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=722s) The TIA lacks dedicated X and Y coordinate hardware registers, forcing software to rely on cycle-timed writes to strobe registers and fine-motion registers (HMPx/HMOVE) to position graphics horizontally. - [33:35](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=2015s) Large graphics such as 48-pixel-wide sprites bypass the 6507's three-register limit by exploiting the TIA's vertical delay registers (VDELP0 and VDELP1) as hardware holding buffers to cycle six bytes across a scanline.
## Key takeaways - [00:00](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=0s) Instead of storing thousands of bits in a multi-line video buffer, the Atari 2600 holds only 39 bits of graphic data at any instant: 20 for the playfield, 8 per player sprite, and 1 per missile and ball sprite. - [02:10](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=130s) The 160-pixel playfield is grouped into 40 blocks of 4 pixels each, with the left and right halves sharing the same 20-bit register footprint via automatic mirroring or repeating. - [04:45](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=285s) Playfield data is split across three registers (PF0, PF1, and PF2) that render in alternating bit-endian order, requiring precise timing when constructing asymmetrical displays. - [07:30](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=450s) At 76 CPU cycles per scanline and 3 color clocks per cycle, writing an asymmetrical playfield consumes 18 cycles solely on zero-page store instructions, severely restricting processor time for game logic. - [10:31](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=631s) Because sprite graphics only exist for one scanline at a time, sprites span the entire height of the screen as vertical bars unless actively zeroed out or updated line by line. - [12:02](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=722s) Horizontal positioning requires strobing reset registers like RESP0 at precise cycle counts relative to H-blank, taking advantage of a known internal hardware delay. - [15:40](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=940s) The Atari 2600 contains no vertical sync or blanking hardware counters, requiring software to manually assert VSYNC for three scanlines and manage VBLANK intervals. - [18:19](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1099s) Because raw CPU cycle timing only provides a coarse 3-pixel placement resolution, fine horizontal alignment relies on the HMPx registers and HMOVE to shift positions from -7 to +8 pixels. - [22:38](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1358s) Games can save CPU cycles during multi-line rendering because TIA registers retain their values across scanlines until explicitly overwritten. - [24:26](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1466s) Constructing a seamless 48-pixel wide sprite requires interleaving two player sprites set to triple-copy mode, offset horizontally by 8 pixels. - [29:18](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1758s) Writing six distinct bytes across a single scanline exceeds the 6507 processor's three internal data registers (A, X, and Y), creating a beam-racing bottleneck. - [33:35](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=2015s) Enabling VDELP0 and VDELP1 turns the TIA's internal delay holding latches into a temporary staging register, allowing six bytes to be loaded and committed in real time. - [36:26](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=2186s) Mid-scanline writes to the single COLUPF register allow games like Q*bert to display multiple playfield colors on the same horizontal raster line. - [37:13](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=2233s) Mid-scanline modifications to playfield registers like PF2 maintain proper cube proportions in Q*bert, but introduce visible artifacts such as a central zig-zag seam and inverted shading.
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=0s) The Concept of "Racing the Beam" and the 39-Bit Frame
The Atari 2600 represents an extraordinary paradox of early computing: its internal hardware was exceptionally primitive, to the point where making functional software was an astonishing feat. As the narrator observes, “The biggest reason why this is such a commonly stated fact is that the console has basically no video memory.” Because dedicated video memory was nonexistent, the exact same microprocessor tasked with processing game rules and user input was also forced to dictate line-by-line rendering in real time. Everything had to align precisely with the cathode-ray tube's scanning electron beam as it swept across the phosphor screen, originating the legendary engineering phrase "racing the beam."
To grasp how minimal this setup is, consider the total bit count required to generate an active frame on an NTSC television screen. While an NTSC display outputs 160 horizontal pixels by 192 vertical lines (with up to 30 additional lines sitting in overscan), and accounts for a background, two player sprites, two missile sprites, and one ball sprite at a 1-bit monochrome bit-depth, intuitive calculations fall wildly short. A conventional framebuffer calculation multiplying 160 by 222 lines plus individual sprite tables yields roughly 35,857 bits. In reality, the Atari 2600 holds a mere 39 bits of graphic data at any given moment:
- **20 bits** allocated to the background layer (formally documented as the *playfield*) - **8 bits** for Player 0 sprite graphics - **8 bits** for Player 1 sprite graphics - **1 bit** for Missile 0 - **1 bit** for Missile 1 - **1 bit** for the Ball sprite
```mermaid flowchart TD accTitle: Atari 2600 39-Bit Scanline Memory Distribution accDescr: Diagram illustrating how the 39 bits of active graphic memory are divided between playfield and sprites.
subgraph VideoMemory["Total Video Memory: 39 Bits"] PF["Playfield: 20 bits"] P0["Player 0: 8 bits"] P1["Player 1: 8 bits"] M0["Missile 0: 1 bit"] M1["Missile 1: 1 bit"] BL["Ball: 1 bit"] end
PF --> Screen["Rendered Active Scanline"] P0 --> Screen P1 --> Screen M0 --> Screen M1 --> Screen BL --> Screen ```
[02:10](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=130s) Playfield Architecture and the Shared 20 Bits
The console reaches its full 160-pixel horizontal canvas by grouping horizontal pixels into low-resolution playfield "blocks." Rather than offering 160 individually addressable playfield bits, each scanline contains only 40 distinct blocks, each spanning 4 adjacent color pixels. Halving this across the screen reveals where the 20-bit register limit originates: the left half and right half of every scanline share the exact same 20-bit memory footprint. Built-in hardware modes allow developers to mirror or repeat those 20 bits across the right half automatically, a strategy heavily relied upon across the system's catalog.
```mermaid flowchart LR accTitle: Playfield Scanline Division and Block Resolution accDescr: Block structure of an Atari 2600 playfield line showing left and right half resolution.
subgraph LeftHalf["Left Screen Half (80 Pixels)"] LBlocks["20 Playfield Blocks (4 Pixels Each)"] end
subgraph RightHalf["Right Screen Half (80 Pixels)"] RBlocks["20 Playfield Blocks (4 Pixels Each)"] end
LeftHalf -->|"Repeated or Mirrored"| RightHalf ```
Creating asymmetrical layouts required programmers to perform a hair-raising trick mid-scanline. The CPU had to push pixel bits for the left screen half into memory right as the electron beam prepared to scan it, and then, within the sliver of time it took the beam to cross the screen, overwrite those same registers with completely new values before the beam hit the right side. Straying outside this tiny execution window broke the display: “If you write the second half of the screen's data too quickly, the first half gets corrupted because the beam is still drawing that half.” Conversely, writing too late corrupted the right side because the beam had already begun scanning it. Because this 20-bit pool was shared across all vertical scanlines as well as horizontally, this exact routine had to run continuously for every single scanline down the screen.
[04:45](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=285s) Register Mapping: PF0, PF1, and PF2
The 20 bits defining half of a playfield scanline are packed into three dedicated hardware registers: `PF0`, `PF1`, and `PF2`. Together they total 24 bits, leaving 4 bits unused. The hardware accesses these registers in an alternating bit-endian pattern that complicates layout code:
```mermaid flowchart LR accTitle: Playfield Register Bit Rendering Order accDescr: Sequence and bit orientation of PF0, PF1, and PF2 registers across 20 blocks.
subgraph Order["Playfield Bit Sequence (Left Half: 20 Blocks)"] direction LR PF0["PF0 (Upper 4 Bits)<br/>LSB to MSB (Backwards)"] PF1["PF1 (8 Bits)<br/>MSB to LSB (Forwards)"] PF2["PF2 (8 Bits)<br/>LSB to MSB (Backwards)"] end
PF0 --> PF1 --> PF2 ```
- **`PF0` (4 blocks):** Only the upper 4 bits are read (the lower 4 bits are discarded). These bits render backwards, reading from least significant bit (LSB) to most significant bit (MSB). - **`PF1` (8 blocks):** All 8 bits are read forwards, progressing from most significant bit (MSB) to least significant bit (LSB). - **`PF2` (8 blocks):** All 8 bits are read backwards, moving from least significant bit (LSB) to most significant bit (MSB).
Once the first 20 blocks conclude, the console determines the second half based on mode flags. In repeat mode, the sequence runs identical to the first half (`PF0` $\to$ `PF1` $\to$ `PF2`). In mirror mode, the entire 20-bit sequence is drawn in reverse order to balance the line symmetrically. When aiming for a completely unique 40-block line, developers had to sequentially refresh each register while avoiding drawing collisions: `PF0`, `PF1`, and `PF2` had to be rewritten at precise intervals after their corresponding left-hand blocks finished drawing, but strictly before the beam arrived to read them for the right-hand blocks. Writing while a block was being actively rendered caused visual garbage on screen.
[07:30](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=450s) Machine Cycles, Clock Timing, and the Real Cost of Asymmetry
The feasibility of updating these registers comes down to the tight relationship between the console's internal processor and the television's electron beam. The Atari 2600 is driven by a MOS 6507 CPU clocked at approximately 1.19 MHz. Because the visual hardware derives its timing from the same master clock, the math locks into a fixed ratio: exactly one CPU machine cycle elapses for every 3 horizontal screen pixels drawn by the beam.
| Metric | Machine Cycles | Equivalent Pixels / Description | |:--- |:--- |:--- | | **Total Line Duration** | 76 cycles | Includes active display and Horizontal Blanking (H-Blank) | | **1 CPU Machine Cycle** | 1 cycle | Exactly 3 horizontal pixels drawn | | **Zero-Page Memory Write** | 3 cycles | 9 pixels (equivalent to $2 \frac{1}{4}$ playfield blocks) | | **Latest Initial `PF0` Write** | 19 cycles | Maximum cycles after H-Blank begins for the first playfield write |
```mermaid flowchart LR accTitle: Scanline CPU Cycle Budget for Playfield Updates accDescr: Timing timeline showing machine cycle consumption across a single 76-cycle scanline.
subgraph LineBudget["Single Scanline: 76 Machine Cycles Total"] direction LR HB["H-Blank to PF0: 19c"] W1["Writes (3c each): 18c"] Gaps["Execution Windows / Gaps: 28c"] Tail["Final Slack to Next H-Blank: 11c"] end
HB --> W1 --> Gaps --> Tail ```
Forcing an asymmetrical playfield required six distinct writes per scanline (three registers written twice). At 3 machine cycles per zero-page store instruction, the writes alone ate up 18 of the line's 76 cycles: - Write `PF0` (Left) at cycle 19 (takes 3 cycles) - 3-cycle gap, then write `PF1` (Left) (takes 3 cycles) - Execution gaps of 7, 8, 2, and 8 cycles between the subsequent writes for `PF2` (Left), `PF0` (Right), `PF1` (Right), and `PF2` (Right) - An 11-cycle buffer remaining after the final `PF2` write before the next line's H-blank begins
Crucially, those 18 cycles accounted *only* for transferring bytes into the registers—they did not include retrieving data from ROM or RAM, calculating sprite positioning, or running actual game logic. Consequently, fully custom backgrounds were rarely practical, leading developers to rely heavily on mirrored or repeated layouts to keep CPU time free for other tasks.
[10:31](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=631s) Sprite Hardware Limitations and Repositioning
Sprited elements operate under constraints just as severe as the playfield. The hardware provides two player sprites, two missile sprites, and a single ball sprite. Each player sprite possesses only 8 bits of graphic data per scanline, while the missiles and the ball possess a single bit (on or off). Because the system only holds graphic values for a single scanline, any vertical dimension requires manual intervention: “In fact, the only way to disable a player sprite is to just set its graphics to all zeros, so you will need to update it on the fly anyway, unless your player sprite is going to be a big vertical column spanning the entire screen.”
Built-in hardware registers allow player and missile sprites to be scaled up or duplicated across a single scanline. Even then, the backing storage remains locked at 8 bits per player and 1 bit per missile. Skilled developers bypassed this limit by overwriting the 8-bit player register during the microsecond gap between the primary sprite and its hardware duplicate, synthesizing the illusion of two completely unique sprites on a single line out of a single register.
However, placing a sprite on screen exposes the system's most counterintuitive hurdle. If a developer wants to render an $8 \times 8$ graphic for Player 0 at line 40 from the top and 60 pixels from the left, standard coordinates do not exist. There are no X and Y position registers where a programmer can simply write `60` and `40`. Positioning sprites requires an entirely different, timing-based approach.
[12:02](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=722s) Horizontal Sprite Positioning via Cycle Counting and WSYNC
Positioning sprites on the Atari 2600 requires manual calculation because the Television Interface Adapter (TIA) lacks dedicated X and Y coordinate registers. To position an object horizontally, programmers must interact with one of five reset sprite strobe registers—such as `RESP0`—where any write triggers the sprite's horizontal origin to latch to the scanning beam's current location. Due to internal hardware delay, the sprite actually appears slightly to the right of the beam's position when the store instruction concludes; for an unstretched player sprite, this offset is exactly five pixels. Therefore, placing a sprite 60 pixels from the screen's left edge requires timing the CPU so that the write to the reset register finishes precisely as the beam scans the 55th visible pixel.
Determining the scanning beam's location at any given instant presents a fundamental challenge. Retro Game Mechanics Explained emphasizes that “There is no register to read the current position of the beam.” Instead, developers must synchronize the CPU with the beam at a fixed point and tally machine cycles for every subsequent instruction. Writing to the special `WSYNC` register halts the CPU until the beam finishes the active scanline, guaranteeing that the following instruction executes at the start of horizontal blanking (H-blank). H-blank spans a duration equivalent to 68 color clocks, or "pixels," before the visible scanline begins. Factoring in the desired 55-pixel visible offset yields 123 total pixels. Since one CPU machine cycle corresponds to three color clocks, 123 pixels equates to 41 machine cycles. Subtracting the 3 cycles consumed by the final `STA RESP0` instruction leaves 38 cycles that must be filled.
A programmer can spend these 38 cycles loading sprite graphics and idling. Loading graphic data via an immediate `LDA` (2 cycles) and an `STA` to the graphics register (3 cycles) absorbs 5 cycles, leaving 33 cycles remaining. Because the 6502 architecture lacks a single-cycle instruction, this exact timing is achieved by pairing a dummy 3-cycle `STA` instruction with fifteen 2-cycle `NOP` instructions. After storing to `RESP0` and following up with an `STA WSYNC`, the sprite's horizontal coordinate remains persistent across future scanlines until another reset strobe occurs. Consequently, rendering the remaining seven scanlines of an eight-line sprite only demands loading graphics, storing them to the graphics register, and strobing `WSYNC`.
```mermaid flowchart TD accTitle: Horizontal Alignment Cycle Budget accDescr: Breakdown of CPU machine cycles required to position a sprite at pixel 60 from H-blank.
subgraph Sync["Synchronization Phase"] W["STA WSYNC<br/>(Halts CPU until line end)"] --> HB["Start of H-Blank<br/>(Target: 123 pixels / 41 cycles)"] end
subgraph Timing["Cycle-Burning Work (38 Cycles)"] HB --> G["LDA #imm + STA GRP0<br/>(Graphics load: 5 cycles)"] G --> D["Dummy STA (3 cycles)<br/>+ 15 NOPs (30 cycles)"] end
subgraph Latch["Strobe Phase"] D --> R["STA RESP0 (3 cycles)<br/>(Latches position at pixel 55)"] R --> S["Hardware Delay (+5 px)<br/>Sprite positioned at visible pixel 60"] end ```
- Horizontal reset registers: five strobe registers (`RESP0` through `RESP4`), one for each sprite object. - Unstretched player sprite hardware delay: 5 color clocks (pixels) after instruction execution. - H-blank duration: 68 color clocks before active video display. - Cycle-to-pixel ratio: 1 CPU machine cycle equals 3 TIA color clocks/pixels. - Timing breakdown for 60-pixel placement: 68 H-blank pixels + 55 pixel offset = 123 pixels = 41 machine cycles. - Cycle allocation: 5 cycles (graphic load) + 3 cycles (dummy `STA`) + 30 cycles (15 `NOP`s) + 3 cycles (`STA RESP0`) = 41 total machine cycles.
---
[15:40](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=940s) Vertical Timing, VSYNC, and the VBLANK Period
While horizontal alignment can be tied directly to hardware scanlines via `WSYNC`, vertical alignment presents a far more bare-bones environment. Retro Game Mechanics Explained explains: “The Atari actually has no hardware for controlling vertical syncing or blanking at all; that has to be done in the game's code as well.” Television receivers depend on a vertical synchronization signal—a brief period where the video voltage drops into a "darker than black" negative range—to recognize the boundary between frames. On the Atari 2600, software must manually turn this signal on and off using the `VSYNC` register. To maintain a stable image on standard displays, code must assert `VSYNC` for precisely three scanlines and then clear it back to zero, typically coordinating each transition by strobing `WSYNC`.
Immediately following the vertical sync pulses, the vertical blanking (V-blank) interval begins. The programmer enables blanking by writing a 1 to the `VBLANK` register, disabling video output while the CRT's electron beam sweeps from the bottom of the screen back up to the top. Standard NTSC format allocates 37 scanlines to V-blank (compared to 45 lines on PAL consoles). Because the electron beam is inactive during this interval, game engines are freed from rendering duties. This makes V-blank the primary processing window for game logic, physics, and controller input routines.
If a game has no logic to run, it simply burns these lines by writing to `WSYNC` repeatedly. Displaying a sprite 40 scanlines below the top of the visible screen requires waiting out the 37 lines of NTSC V-blank plus the 40 visible lines, totaling 77 scanlines after vertical sync concludes. Once the initial 37 lines elapse, the game clears `VBLANK` by writing zero, counts out 40 more `WSYNC` strobes, and finally executes the cycle-counted horizontal rendering routines prepared for the graphic data.
- `VSYNC` signal: held active for exactly 3 scanlines to establish a standard frame transition. - NTSC vs. PAL vertical blanking: NTSC uses 37 V-blank lines; PAL systems use 45 V-blank lines. - Total pre-sprite wait: 37 blanking lines + 40 visible lines = 77 scanlines before sprite data is drawn. - Hardware absence: no vertical counters or automatic blanking timers exist in the TIA silicon.
---
[18:19](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1099s) Coarse and Fine Horizontal Positioning in E.T.
Real-world games rarely rely solely on raw delay loops for every coordinate because counting CPU cycles only provides a coarse positioning resolution. Since a single machine cycle takes three color clocks, strobing `RESPx` alone can only place an object in increments of three pixels. Retro Game Mechanics Explained notes: “To locate sprites horizontally down to the pixel, you need to use the horizontal movement registers.” The TIA solves this by dividing horizontal placement into coarse positioning (strobe registers) and fine adjustments (horizontal motion registers `HMP0`–`HMP4` and `HMOVE`).
```mermaid flowchart LR accTitle: Coarse and Fine Horizontal Positioning Architecture accDescr: Flow diagram showing how coarse positioning loops and fine motion values combine via HMOVE.
subgraph Coarse["Coarse Timing"] A["WSYNC Strobe"] --> B["Inner Delay Loop<br/>(5 cycles / 15 px steps)"] B --> C["RESPx Strobe<br/>(Sets rough position)"] end
subgraph Fine["Fine Offset"] D["Load Nibble to HMPx<br/>(-7 to +8 pixel shift)"] --> E["Strobe HMOVE<br/>(Applies fine offset to all sprites)"] end
C -.-> E ```
The title screen of *E.T. the Extra-Terrestrial* provides a clear demonstration of this dual mechanism. The title graphic displays "E.T." where the "E" is Player 0, the "T" is Player 1, and the two trailing periods are Missile 0 and Missile 1. To position all elements, a unified setup loop iterates five times (covering all sprites, while disabling the ball). Each pass begins with a `WSYNC` so that one sprite is configured per scanline. Inside the loop, the CPU loads a byte from ROM: the upper nibble supplies the fine positioning value written to the corresponding `HMPx` register, while the lower nibble acts as a loop counter for a small cycle-wasting inner loop. Each pass of this inner loop consumes 5 CPU cycles, advancing the beam by 15 color clocks. Because the fine motion registers accommodate a range of 16 pixels (from 7 pixels left to 8 pixels right), this 15-pixel coarse step size is completely bridged.
For the capital "E", the game retrieves the byte `$52` from ROM. The upper nibble (`$5`) commands a fine shift of 5 pixels to the left. The lower nibble (`$2`) causes the inner branch loop to execute twice. By the time the code strobes `RESP0`, 36 machine cycles have elapsed since the start of H-blank. Multiplying 36 cycles by 3 yields 108 pixels; subtracting the 68 pixels of H-blank and adding a 6-pixel delay (offset by one extra pixel because the sprite is stretched) establishes a coarse position at pixel 46. Once all five sprites receive their coarse strobes and fine values, a single strobe to `HMOVE` applies the queued offsets simultaneously. This shifts Player 0 leftward by 5 pixels, placing the "E" at its final destination: exactly 41 pixels from the left border.
- Resolution limit: direct `RESPx` writes have a 3-pixel granularity due to the 1:3 CPU-to-clock ratio. - Fine adjustment limits: `HMPx` values shift sprites from -7 pixels (left) to +8 pixels (right). - ROM data packing: byte `$52` encodes both fine displacement (upper `$5`) and loop count (lower `$2`). - Delay scaling: each inner loop iteration costs 5 machine cycles (15 color clocks/pixels). - "E" sprite coarse calculation: 36 cycles × 3 = 108 pixels; 108 − 68 H-blank + 6 delay = 46 pixels. - Final position: 46 coarse pixels − 5 fine pixels (`HMOVE`) = 41 pixels from screen edge.
---
[22:38](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1358s) Rendering the Title Graphics and Multi-Line Units
Once positioning registers and horizontal offsets are latched, execution shifts to the scanline-by-scanline rendering loop. On the scanline immediately preceding the text display, an initialization routine establishes sprite properties: selecting colors, disabling horizontal reflection, and defining sizes. The player sprites forming the "E" and "T" are configured to stretch at 4 pixels per bit, while the missiles forming the periods are set to a width of 2 pixels.
The main text block is 16 units high, meaning the display loop runs 16 times, pulling bitmap bytes from ROM and storing them directly into the graphic registers (`GRP0` and `GRP1`). The missile registers are updated only during the final two iterations to render the periods at the base of the letters. Every unit of graphic text is two scanlines tall, resulting in "pixels" that measure 4 clocks wide by 2 scanlines high. To maintain this aspect ratio, the loop contains two separate writes to `WSYNC`. Retro Game Mechanics Explained highlights an efficient aspect of the TIA architecture: “Recall that if nothing changes from one scanline to the next, no code needs to be written.” Because sprite graphics persist in TIA registers across scanlines, the CPU spends the second scanline doing virtually nothing—executing only a single `DEX` instruction to decrement the loop counter before waiting for the next line.
After completing all 16 iterations, the routine yields CPU time to downstream display routines. These subsequent routines handle the remainder of the frame, including drawing the large rendering of E.T.'s face located further down the screen—a graphic spanning 48 pixels in width and 40 scanlines in height, assembled entirely using the two player sprites.
- Graphic dimensions: player letters stretched to 4 color clocks per bit; missile dots set to 2 pixels wide. - Virtual pixel aspect: each letter unit covers 4 horizontal pixels and 2 vertical scanlines. - Loop mechanics: 16 iterations total, containing two `WSYNC` strobes per loop pass. - Inactive scanline economy: second scanline executes only a 2-cycle `DEX` instruction while the TIA re-displays latched data. - Subsequent frame element: E.T.'s face measures 48×40 pixels, multiplexing only the two hardware player sprites.
[24:26](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1466s) Setting Up a 48-Pixel Sprite Across Three Scanlines
Displaying a large sprite on the Atari 2600 requires chaining hardware tricks together. A 48-pixel wide graphic can be constructed by configuring the TIA to repeat each player sprite three times per scanline with 8-pixel gaps between copies. By offsetting player sprite 0 and player sprite 1 by exactly 8 pixels, the two sets of three interleaved 8-pixel segments cover the entire 48-pixel span seamlessly. The full sprite image comprises 6 columns across 40 rows (a $6 \times 40$ array of bytes), where each byte defines an $8 \times 1$ pixel strip stored in a ROM lookup table.
Because of the heavy register preparation involved, the execution routine must start three scanlines ahead of the actual render. On the first preparatory scanline, sizing, reflection, and positioning registers are primed:
```mermaid flowchart LR accTitle: Two-Player Interleaving for 48-Pixel Sprite accDescr: Interleaving copies of Player 0 and Player 1 across a 48-pixel span.
subgraph Combined["Combined 48-Pixel Span"] P0_A["P0 (Byte A)"] --> P1_B["P1 (Byte B)"] P1_B --> P0_C["P0 (Byte C)"] P0_C --> P1_D["P1 (Byte D)"] P1_D --> P0_E["P0 (Byte E)"] P0_E --> P1_F["P1 (Byte F)"] end ```
The accumulator is loaded with immediate value `#$03`, and the Y index register is cleared with `#$00`. Reflection registers are zeroed so the sprite appears unflipped, while the sizing registers are written with `#$03` to trigger the "three copies at 8-pixel intervals" mode.
Crucially, this setup introduces the graphics delay registers, `VDELP0` and `VDELP1`: “When VDELP0 is set, the graphics for player sprite 0 won't fully go through until the graphics for player sprite 1 is written.” Similarly, when `VDELP1` is enabled, player 1's graphic is held until player 0's register receives a write. Enabling both registers simultaneously creates internal temporary holding buffers. As the narrator explains, “This is used for timing reasons--it allows an extra position value to be queued up which lets this method of writing 6 bytes worth of graphics data quickly to work at all.”
After double-writing zeroes to clear the delayed graphics registers and inserting a single 2-cycle `NOP` instruction, the CPU writes to the reset position registers (`RESP0` and `RESP1`) at cycles 39 and 42. Multiplied by 3 color clocks per cycle, minus 68 clocks for horizontal blank, plus 5 clocks of internal beam delay, this yields horizontal screen positions of 54 and 63. Because a standard 3-cycle `STA` instruction spaces the writes 9 pixels apart rather than 8, the hardware horizontal motion register (`HMM0`) must be loaded to nudge player 0 one pixel to the right (to position 55), perfectly closing the gap to an 8-pixel offset.
- **Initial Register Values**: `#$03` loaded into Accumulator; `#$00` loaded into Y index register. - **Position Calculation**: Cycle 39 $\to 39 \times 3 - 68 + 5 = 54$; Cycle 42 $\to 42 \times 3 - 68 + 5 = 63$ (an initial spacing of 9 pixels). - **HMOVE Adjustment**: `HMM0` shifts player 0 from horizontal position 54 to 55 to establish the exact 8-pixel offset.
---
[27:45](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1665s) RAM Pointer Initialization and the First Queued Byte
The second setup scanline prepares the pointers needed to retrieve graphic data from ROM into zero-page RAM. The very first instruction is an immediate write to `HMOVE`, adhering to the strict rule that horizontal motion strobes should execute right after writing to `WSYNC`. The remainder of this scanline populates six 16-bit pointers corresponding to the six vertical columns of the 48-pixel sprite. This abstraction enables the underlying draw logic to function as a general-purpose subroutine callable for any arbitrary 48-pixel image.
The third setup scanline configures the sprite color registers and initializes the loop counter to `#$27` (decimal 39), allowing a 40-scanline routine to execute. The CPU then executes a `JSR` jump to the general drawing subroutine. Before this final preparatory scanline finishes, the graphic data for the leftmost column (byte A) is loaded into `GRP0`. Because `VDELP0` is active, byte A sits latched inside the temporary buffer rather than immediately displaying. The scanline concludes by strobing `WSYNC` to halt the CPU until horizontal blanking completes, awaiting the scanline where the sprite actually becomes visible.
---
[29:18](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1758s) The Beam-Racing Bottleneck: 3 Registers vs. 6 Bytes
Once the scanline rendering loop begins, the system races against the electron beam across the 48-pixel display area. The fundamental rule of the TIA graphics registers is that the internal hardware latches data during the final machine cycle of an instruction: “So a write instruction needs to completely finish executing before the beam even begins drawing that part of the sprite.” Because the write only lands on the final cycle, an instruction may begin executing while the CRT beam is still drawing the trailing edge of a previous sprite copy. This overlap is vital because an absolute `STA` takes 3 CPU cycles (equivalent to 9 color clocks/pixels), which exceeds the 8-pixel interval between adjacent sprite copies.
Analyzing a naive approach demonstrates why unbuffered writes fail:
```mermaid flowchart TD accTitle: Data Bottleneck Flow accDescr: Sequence showing CPU register limitations when writing six bytes across one scanline.
subgraph CPU["6507 Internal Registers"] RegA["Accumulator (A)"] RegX["Index (X)"] RegY["Index (Y)"] end
subgraph Target["Required Sprite Bytes"] B1["Byte A"] B2["Byte B"] B3["Byte C"] B4["Byte D"] B5["Byte E"] B6["Byte F"] end
CPU -->|"Can only hold 3 values simultaneously"| Bottleneck{"Mid-line reload impossible<br/>without TIA delay latch"} Target --> Bottleneck ```
On a grid where instructions can only align to 3-pixel boundaries, working backwards reveals that writing bytes C, D, E, and F in direct succession would demand unbroken writes to `GRP0` and `GRP1`. However, the 6507 processor contains only three general-purpose data registers: A, X, and Y. “We can't have six write instructions in a row, especially if all six data values are different,” the narrator emphasizes. “The 6507 CPU only has three internal registers for general-use data. So it can only hold three values at a time.”
While the first two writes can easily be pulled forward into the scanline before the beam reaches the sprite area, that still leaves four bytes to update on the fly. Preloading A, X, and Y handles three of those values, but the fourth cannot be retrieved during rendering: - A ROM read consumes too many cycles to squeeze between active sprite copies. - Even offloading data to the stack pointer register (`S`) with a 2-cycle transfer instruction cannot avoid colliding with byte A as it renders. - Writing byte C directly without a delay mechanism corrupts the ongoing display of byte A.
---
[33:35](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=2015s) Exploiting VDEL to Cycle Six Bytes in Real Time
Enabling `VDELP0` and `VDELP1` resolves the hardware register shortage by turning the TIA's internal holding latches into a fourth temporary staging register. Writing byte A into `GRP0` leaves it dormant in the staging register. When the CPU subsequently writes byte B into `GRP1`, two actions occur at once: byte B enters player 1's holding latch, and byte A is pushed through into player 0's active graphics register. Next, writing byte C to `GRP0` commits byte B to player 1 while caching byte C in player 0's buffer.
This delay mechanism reshuffles the execution schedule: 1. Byte A is pre-written to `GRP0` at the tail end of the preceding scanline. 2. At the start of the active scanline, byte B is written to `GRP1` (pushing byte A live), and byte C is written to `GRP0` (pushing byte B live while byte C waits). 3. With byte C safely parked in the TIA hardware latch, the CPU has enough cycle headroom to reload A, X, and Y with bytes D, E, and F via indirect ROM reads (each taking 5 cycles). 4. Because the Y index register is needed to index through the indirect pointers, it must be clobbered last, and a temporary memory byte temporarily buffers the value destined for the accumulator. 5. Bytes D, E, and F are written to their respective registers in succession. 6. A final dummy write to `GRP0` forces byte F out of the internal temporary latch and onto the screen just as the beam arrives.
After the dummy write, the scanline loop counter decrements and branches back to render the next scanline. Once all 40 lines finish, execution drops out of the loop, clearing the graphics, size, and delay registers back to zero.
---
[36:26](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=2186s) Dynamic Mid-Scanline Playfield Palette Changes (Q*bert)
The playfield can be manipulated on the fly just like player sprites. In the Atari 2600 port of *Q\*bert*, the isometric pyramid is rendered via playfield graphics. The layout is noticeably asymmetric due to the single center cubes on odd-numbered rows, and visually the pyramid displays three or four colors on the same scanline—seemingly breaking the rule that the playfield possesses only a single color register (`COLUPF`).
This visual variety is accomplished by racing the beam to update color registers mid-scanline: “Just like how the graphics data for things can be changed mid-scanline, so can the colors. Only one color can be used at a time, but that one color can change over time.” While the TIA cannot output yellow and blue playfield pixels concurrently on the exact same pixel clock, the CPU can write to `COLUPF` right as the beam moves across different cube facets. By synchronizing register writes with the beam's sweep from left to right, the playfield shifts its palette from blue to yellow against a shared black background across a single horizontal scanline.
[37:13](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=2233s) Mid-Scanline Playfield Updates and Visual Artifacts
The coloration of the visual elements on the display is managed through the `COLUPF` (Color-Luminance Playfield) register. In the scanline under examination, three cubes are rendered across the screen. Because the specific color values assigned to these cubes already exist in memory, the processor's task during the active scanline is simply to fetch those pre-stored values and move them directly into the appropriate hardware register at precisely timed intervals.
However, rendering these cubes correctly introduces significant architectural friction due to their unconventional dimensions. To keep the proportions accurate, the playfield registers—most notably `PF2`—must be modified in the middle of an active scanline. The narrator explains the engineering compromise required for this display trick: “This is just so that the center cube doesn't show up larger than the others--however it does result in this large zig-zag shaped gap.” By executing an exactingly timed mid-line write, the two halves of the center cube are able to render cleanly despite sharing the exact same underlying bits of data.
At the same time, relying on this mirrored playfield technique introduces distinct visual side effects beyond the jagged central seam. Because the hardware mirrors the data across the screen, the apparent light direction and shading across the three cubes end up inconsistent, serving as a direct visual footprint of the Atari 2600’s hardware constraints and raster-racing tricks.
```mermaid flowchart TD accTitle: Mid-Scanline Playfield Update Flow accDescr: Diagram showing how pre-stored color values and timed mid-scanline writes to PF2 prevent the center cube from distorting while causing visual artifacts.
subgraph Memory ["Pre-stored Data"] A["Color Values in Memory"] end
subgraph TIA ["TIA Registers"] B["COLUPF Register"] C["PF2 Register (Updated Mid-Scanline)"] end
subgraph Output ["Screen Display Output"] D["Three Balanced Cubes"] E["Zig-Zag Gap Artifact"] F["Inconsistent Cube Shading"] end
A -->|"Timed write"| B B --> D C -->|"Prevents oversized center"| D C -->|"Side effect"| E C -->|"Mirroring effect"| F ```
- The `COLUPF` register directly determines the active color of the playfield graphics on a given scanline. - Mid-scanline writes to `PF2` prevent the center cube from appearing visibly wider than the outer cubes. - A prominent zig-zag gap down the screen's vertical center is an unavoidable artifact of the mid-line register timing. - Hardware-level playfield mirroring prevents symmetrical lighting across all three rendered cubes.
---
[38:01](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=2281s) Channel Outro and Community Support
Wrapping up the technical breakdown, the narrator expresses appreciation to the audience for watching the video. Viewers who wish to provide financial backing to Retro Game Mechanics Explained are directed to the channel's crowdfunding platforms on Patreon and SubscribeStar. For those unable or not wishing to pledge monetary support, the narrator emphasizes that standard engagement—watching videos to completion, leaving comments, and sharing the content with friends—is an equally valuable way to support the project. Viewers interested in further deep dives into the Atari 2600 hardware and its unique programming hurdles are encouraged to leave a comment requesting future coverage.
## Notable quotes > [02:10](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=130s) If you write the second half of the screen's data too quickly, the first half gets corrupted because the beam is still drawing that half. — Retro Game Mechanics Explained
> [15:40](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=940s) The Atari actually has no hardware for controlling vertical syncing or blanking at all; that has to be done in the game's code as well. — Retro Game Mechanics Explained
> [36:26](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=2186s) Only one color can be used at a time, but that one color can change over time. — Retro Game Mechanics Explained
> [22:38](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1358s) If nothing changes from one scanline to the next, no code needs to be written. — Retro Game Mechanics Explained
> [29:18](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1758s) A write instruction needs to completely finish executing before the beam even begins drawing that part of the sprite. — Retro Game Mechanics Explained
> [18:19](https://www.youtube.com/watch?v=sJFnWZH5FXc&t=1099s) To locate sprites horizontally down to the pixel, you need to use the horizontal movement registers. — Retro Game Mechanics Explained
## Mentioned Atari 2600, MOS 6507, Television Interface Adapter (TIA), Cathode-Ray Tube (CRT), NTSC, PAL, E.T. the Extra-Terrestrial, Q*bert, Retro Game Mechanics Explained, Patreon, SubscribeStar
## Video description How does the Atari 2600 display images to the screen with so little memory dedicated to video graphics? It's all explained right here. LINKS Twitter (updates): https://twitter.com/RetroGameMechEx Patreon (support): https://www.patreon.com/rgmechex SubscribeStar (support): https://www.subscribestar.com/rgmechex Discord (discussion): http://discord.rgmechex.com PATRONS Special thanks to everyone who makes this channel possible! Anthony Losego, Dan Salvato, F. Murmel, Jonathan Aldrich, Ange Albertini, Mark Canlas, Avi Drissman, Steven, Mike Gerow, Larry Koubiak, Tina Wuest, Owen Christensen, Buddy, Brandan Lennox, Jason Hughes, Diamond Ice, Chris Post, Cypher Signal, Rupix, krivx, Xkeeper, Walter Huf, David Spalding, Acceleration Shark, Rory Kelly, Joe Mecca, Ginger Spice, Kefen, Heptonion, leftler, Scott Beca, 19day, Michael Dragone, John Losego, Travis, Arthur Kunkle, Jordan Wright, Stephan Hennion, node1729, Andrea, Michael Cafarelli, KieferSkunk, James Church, Rad, John Kreitlow, Vladimir Kirov, Tromodolo, Zhongchao Qian, Cobalt, Kang Seonghoon, Exani Momeo, Nudelreaktor, tripper, Sten, 333Rich333, The Revd. Juli Mallett, Glenn hEADcRASH Sugden, David Mazarro, null, Ryan, Corey Ogburn, Martin Trozell, Garret Kelly, Joel Kuhn, Dan Shedd, Sembiance, Xander479, 4F Panda, David, Alec Johnson, Brian Henriquez, Travis Nellor, Zach Hugethanks, Oxygen Chen, RetroReversing.com, Ceres, Diego Santos Leão, Jeremiah, Chris Connett, Mark, Matthew, sapslaj, Bjoern Hansen, ers35, Pixy011, Daniel Bernard (ReckedCat), Lukas Kalbertodt, Vier Ladair, Bwangry, iPaq, Metlwing, David Johnson, Matt Shepard, Felix Freiberger, Sypwn, Niles Rogoff, Reflet, Eugene Bulkin, Walter Weaver, Articate, Julien Oster, Steve Losh, Samuel Stoddard, HattyJetty, Paige ? Hex, Yeero, Cruz Godar, Linh Pham, Noah Greenberg, Nick Rogers, Sean Nelson (audiohacked), Bryce, Andrew Yukhymchak, Sean Bryant, John Gabriel, BazBadger, Kyle, Master Knight DH, Tim Romero, Michael B., Eric Loewenthal, Adrian Haslinger, Proxy, Nolan Varani, Daniel Robinson, Hans Jorgensen, Daniel A.A. Pelsmaeker, Aaron, Max Gartung, Nicolas Dohrendorf, ei, Chaz Serir, derHinek, Myriad, samfu, Dominic Wehrmann, André Greubel, Urda, AA, Joseph Torres, Daniel Hug., Audio, Rodrigo Monteiro, Punchmaster, Stephen Bank, Dasterin, Matthew Yu, Alice Hartley, Marcus N., Agoaj, Nicholas Carhuff, Epsilion, bob johnson, Permian Strata, supergtt, Alex Berliner, Sean Coates, Eniplay, iWasHere, Alex, Dominic Cerquetti, Adam Streeter, Eric Romero, Clyde Shaffer, Oast, Esme, FinalQuark, Stefan Eisenreich, pagetable.com, Anthony McNamara (ekinsthepokemon), Keithius, Marius Schoof, Joe Sewell, Rafael De Los Santos, orgamecha, Brandon Stansbury, Steven Foley, ???? ????, & John Bruce!
[Source](https://www.youtube.com/watch?v=sJFnWZH5FXc)