Atari 2600 TIA HMOVE: Motion Nibbles, Color Clocks, and the Left Comb
Reproduce TIA horizontal motion by separating coarse reset strobes, signed motion-register clocks, HMOVE timing, and the scanline's visible comb artifact.
The Atari 2600 TIA has no framebuffer and no general-purpose sprite coordinate registers. Software positions players, missiles, and the ball by resetting hardware counters at precise points in the horizontal television line, then applying a fine-motion value through the HMOVE strobe. That combination is a timing contract: the same register values can place an object differently if the write occurs a few color clocks earlier or later.
The fine motion path also creates one of the console’s best-known artifacts. An HMOVE strobe during horizontal blank can extend the blanked portion into the visible area, producing a dark vertical “comb” at the left edge of the next scanline. A renderer that changes an object’s x coordinate but paints a perfectly clean line has not reproduced the complete TIA behavior.
Coarse positioning and fine motion are different operations
RESP0 and RESP1 reset player horizontal position counters. RESM0/RESM1 and RESBL handle missiles and the ball. These strobes are issued at a CPU cycle chosen by a kernel; the TIA’s horizontal phase maps that event onto the beam position. Fine motion is stored separately in HMP0, HMP1, HMM0, HMM1, and HMBL. Writing those motion registers does not immediately move an object. HMOVE applies their values to the corresponding position counters during the horizontal blank interval.
The motion values use the upper nibble of the CPU write. Their signed encoding covers a small movement in either direction, conventionally described as up to seven color clocks left and eight right, with zero representing no motion. The programmer’s guide’s sign convention is positive for leftward movement and negative for rightward movement. The precise hardware path is not a general arithmetic x += signed_nibble operation: it injects or suppresses clock pulses in the object’s horizontal counter. A correct emulator can expose an equivalent displacement to the renderer, but it should preserve the cycle at which the pulses occur and the counter’s wrap behavior.
The five moving objects must remain independent. Players, missiles, and the ball can each have different motion values and counters. The TIA can also lock a missile to its player, which changes which positioning signals apply. A single shared “sprite x offset” loses the relationship between reset timing, motion latches, object lock state, and counter phase.
HMOVE is a strobe with a timing window
HMOVE is write-only and its data byte is ignored; the write itself is the action. The Atari programming guide recommends issuing it at the beginning of horizontal blank, typically immediately after WSYNC. If software writes it too late, the TIA may not have the full blanking interval in which to apply all motion pulses. The result can be a movement that differs from the stored nibble or a partially applied shift.
Horizontal timing is measured in TIA color clocks. A nominal scanline contains 228 color clocks; the horizontal blank occupies the initial part of the line, and the visible playfield follows it. The 6507 CPU runs at a related slower clock, so each CPU bus access spans multiple color clocks. The emulator must translate the exact CPU write phase into the TIA’s color-clock domain. Rounding all TIA writes to CPU-cycle boundaries can erase the sub-cycle distinctions used by kernels.
The motion registers have a settling restriction after HMOVE. The programmer’s guide warns against modifying them during the following 24 CPU cycles because unpredictable movement values can result. Treat this as documented timing behavior and test the relevant register writes around the boundary. Do not reinterpret the warning as a guaranteed “ignore writes for 24 cycles” rule unless a hardware test establishes that exact behavior.
HMCLR is a separate strobe that clears all motion registers. It changes the stored fine-motion state but does not itself move the position counters. Confusing HMCLR with HMOVE causes a common bug: the emulator clears object offsets at the wrong time or applies a movement when the game intended only to reset the pending values.
The HMOVE comb is generated by blanking behavior
When HMOVE is strobed in the expected horizontal blank interval, the TIA’s timing path can blank the first eight color clocks of the visible region on the next line. The effect is often called the HMOVE bar or comb. Many games mask it with a black background or draw it in overscan; others expose it as a thin strip. It is not part of the object’s sprite data and should not be painted as a renderer-level cosmetic overlay disconnected from the beam.
The bar’s color and exact visibility depend on the TIA’s video output state and the strobe timing. Treat the object motion and blanking artifact as sibling effects of the HMOVE event. A game may issue HMOVE on every line to maintain stable object positioning, which can turn the single-line artifact into a persistent vertical bar. It may also issue it at a carefully selected later phase to exploit a different motion/blanking interaction; such techniques should be modeled only when the targeted TIA revision and timing are supported.
The playfield and movable-object counters do not necessarily react identically to the extra blanking. The visible background is generated through its own shift and counter path, while player/missile counters receive the motion clocks. If the implementation shifts every pixel layer eight places to reproduce a bar, it may move the playfield incorrectly. Instead track the TIA’s blanking state and gate the output pixels for the affected color clocks while leaving the underlying object timing intact.
A signed motion decode fixture
This Python helper decodes the four meaningful high bits of an HMP register into the programmer’s signed motion convention. It keeps the CPU write byte separate from the horizontal event itself. A full TIA core should convert the decoded movement into timed counter pulses when HMOVE is strobed and should retain the HMOVE blanking state independently.
def decode_motion_register(value):
"""Decode HMPx/HMMx/HMBL's high nibble as signed 4-bit motion."""
nibble = (value >> 4) & 0x0F
return nibble - 16 if nibble & 0x08 else nibble
def apply_horizontal_motion(position, register_value, modulus=160):
if modulus <= 0:
raise ValueError("position modulus must be positive")
motion = decode_motion_register(register_value)
return (position - motion) % modulus
assert decode_motion_register(0x00) == 0
assert decode_motion_register(0x70) == 7
assert decode_motion_register(0x80) == -8
assert decode_motion_register(0xF0) == -1
assert apply_horizontal_motion(100, 0x70) == 93
This arithmetic fixture intentionally describes only the logical displacement convention, not the electrical direction of injected clock pulses or the position-reset phase. Verify sign orientation against the primary programmer reference and hardware tests; keep direction in one documented coordinate system so “positive is left” does not get inverted in one register path and not another.
Reproduce the beam-level sequence
Build a test kernel that issues WSYNC, resets one player, writes a nonzero motion nibble, then strobes HMOVE. Render an object with an asymmetric bit pattern so horizontal displacement can be distinguished from reflection or a graphics-register error. Repeat at each supported offset. Record CPU cycle, TIA color clock, object position before and after HMOVE, visible pixel coordinates, and blanking state.
Then move the HMOVE write through the horizontal blank interval one CPU/color-clock phase at a time. Confirm when the complete movement applies, when it becomes partial or misses the intended line, and when the comb appears. Test all five object types, a missile locked to a player, both directions, zero motion, and HMCLR before and after a move. Include wraparound near the left and right visible boundaries rather than clamping positions to 0..159.
The same ROM should be run under known TIA revisions if the emulator supports revision selection. Record which results are documented by the Atari guide, which are reproduced by Stella or another cycle-level emulator, and which are only confirmed on one revision. If a test uses a raster trick outside the supported revision, state that limitation instead of claiming universal behavior.
Acceptance criteria
A trustworthy HMOVE implementation separates coarse reset strobes, five fine-motion latches, strobe timing, per-object counters, and visible blanking. It applies motion in emulated color-clock time, preserves the HMOVE comb artifact, and reproduces the same pixels and counter trace after save-state restore. This gives kernels a consistent beam model without resorting to arbitrary per-game x-coordinate corrections.
Related:
- Atari 2600 TIA Collisions: Sticky Latches, Object Pixels, and Beam Timing
- Sprite Limits and Scanlines: Why Accurate Emulators Preserve Flicker and Dropout
Sources: