Thanks to Matt Jernigan for compiling and contributing this page.
(Adapted by Duane Alan Hahn, a.k.a. Random Terrain.)
As an Amazon Associate I earn from qualifying purchases.
For those looking for the missing part 2, here is Matt Jernigan's interpretation of what it was set to cover (more or less).
Fine Tuning Horizontal Position
This section is taken from the Stella Programmer's Guide by Steve Wright 12/03/79.
| Address | Name | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Function |
| 20 | HMP0 | 1 | 1 | 1 | 1 | . | . | . | . | Horizontal Motion Player 0 |
| 21 | HMP1 | 1 | 1 | 1 | 1 | . | . | . | . | Horizontal Motion Player 1 |
| 22 | HMM0 | 1 | 1 | 1 | 1 | . | . | . | . | Horizontal Motion Missile 0 |
| 23 | HMM1 | 1 | 1 | 1 | 1 | . | . | . | . | Horizontal Motion Missile 1 |
| 24 | HMBL | 1 | 1 | 1 | 1 | . | . | . | . | Horizontal Motion Ball |
Horizontal motion allows the programmer to move any of the 5 graphics objects relative to their current horizontal position. Each object has a 4 bit horizontal motion register (HMP0, HMP1, HMM0, HMM1, HMBL) that can be loaded with a value in the range of +7 to -8 (negative values are expressed in two's complement from). This motion is not executed until the HMOVE register is written to, at which time all motion registers move their respective objects. Objects can be moved repeatedly by simply executing HMOVE. Any object that is not to move must have a 0 in its motion register. With the horizontal positioning command confined to positioning objects at 15 color clock intervals, the motion registers fills in the gaps by moving objects +7 to -8 color clocks. Objects can not be placed at any color clock position across the screen. All 5 motion registers can be set to zero simultaneously by writing to the horizontal motion clear register (HMCLR).
There are timing constraints for the HMOVE command. The HMOVE command must immediately follow a WSYNC (Wait for SYNC) to insure the HMOVE operation occurs during horizontal blanking. This is to allow sufficient time for the motion registers to do their thing before the electron beam starts drawing the next scan line. Also, for mysterious internal hardware considerations, the motion registers should not be modified for at least 24 machine cycles after an HMOVE command.
These addresses write data (horizontal motion values) into the horizontal motion registers. These registers will cause horizontal motion only when commanded to do so by the horiz. move command HMOVE. The motion values are coded as shown below:
| D7 | D6 | D5 | D4 | Clocks | Effect |
| 0 | 1 | 1 | 1 | +7 | Move left indicated number of clocks |
| 0 | 1 | 1 | 0 | +6 | |
| 0 | 1 | 0 | 1 | +5 | |
| 0 | 1 | 0 | 0 | +4 | |
| 0 | 0 | 1 | 1 | +3 | |
| 0 | 0 | 1 | 0 | +2 | |
| 0 | 0 | 0 | 1 | +1 | |
| 0 | 0 | 0 | 0 | 0 | No Motion |
| 1 | 1 | 1 | 1 | -1 | Move right indicated number of clocks |
| 1 | 1 | 1 | 0 | -2 | |
| 1 | 1 | 0 | 1 | -3 | |
| 1 | 1 | 0 | 0 | -4 | |
| 1 | 0 | 1 | 1 | -5 | |
| 1 | 0 | 1 | 0 | -6 | |
| 1 | 0 | 0 | 1 | -7 | |
| 1 | 0 | 0 | 0 | -8 |
Warning: These motion registers should not be modified during the 24 computer cycles immediately following an HMOVE command. Unpredictable motion values may result.
Not all objects position exactly the same. Double-wide and quad-wide player sprites shift +1 color clock. Missiles and the ball shift -1 color clock.
For example, to compensate for this, the quad-wide bridge sprite in River Raid is curiously set to an x-position of 63 (instead of 64) before its fine-positioning subroutine is called. This quirk will shift the bridge +1 color clock and draw it at the intended x-position of 64.
This quirk is discussed in more detail here:
RESPx, NUSIZx, and Player Positioning
Below is example code from River Raid with comments from Matt Jernigan and Thomas Jentzsch:
INX ; 2 x = 0! (player jet)
...
LDY playerX ; 3 y = player's x-pos
...
TYA ; 2 a = y
JSR SetPosX ; 6 x-position player jet
INX ; 2 x = 1 (enemy object)
...
INX ; 2 x = 2 (player missile)
LDA missileX ; 3 a = missile's x-pos
JSR SetPosX ; 6 x-position missile
JSR DoHMove ; 6 do HMOVE
...
SetPosX SUBROUTINE
; calculates the values and positions objects:
; INPUT:
; A = x-position
; X = object to position (0=P0, 1=P1, 2=M0, 3=M1, 4=BL)
JSR CalcPosX ; 6 $FAEF
SetPosX2:
STA HMP0,X ; 4 fine position the object specified in X
INY ; 2 get past the 68 pixels of horizontal blank
INY ; 2
INY ; 2
STA WSYNC ; 3 wait for sync; wait for TIA to finish line
.waitPos:
DEY ; 2
BPL .waitPos ; 2/3 5-cycle loop = 15 TIA color clocks (pixels)
STA RESP0,X ; 4
RTS ; 6
...
CalcPosX SUBROUTINE
; calculates values for fine x-positioning:
;
; Basically, divides pos by 15 and stores the int result of that.
; Then, the mod of that is adjusted to equal 6 + HMP1.
; HMPx bits 4..7: Offset value:
; 0000 ($00): No offset
; 0001 ($10): Left 1 clock
; 0010 ($20): Left 2 clocks
; 0011 ($30): Left 3 clocks
; 0100 ($40): Left 4 clocks
; 0101 ($50): Left 5 clocks
; 0110 ($60): Left 6 clocks
; 0111 ($70): Left 7 clocks
; 1000 ($80): Right 8 clocks
; 1001 ($90): Right 7 clocks
; 1010 ($A0): Right 6 clocks
; 1011 ($B0): Right 5 clocks
; 1100 ($C0): Right 4 clocks
; 1101 ($D0): Right 3 clocks
; 1110 ($E0): Right 2 clocks
; 1111 ($F0): Right 1 clock
;
; INPUT:
; A = x-position
; RETURN:
; Y = coarse value for delay loop (shifted to lower 4 bits), 1 loop = 5 clocks = 15 pixels
; A = fine value for HMPx, HMMx, or HMBL
TAY ; 2 $FDD8 Y = A
INY ; 2 Y++ (setup for div 15; causes 15 to overflow)
TYA ; 2 A = Y
AND #$0F ; 2 A chopped to lower 4 bits 00001111 (fine pos value + 1)
STA temp2 ; 3 $ED temp2 = A (fine pos value + 1)
TYA ; 2 A = Y
LSR ; 2 A chopped to upper 4 bits and shifted to lower 4 bits (A div 16)
LSR ; 2
LSR ; 2
LSR ; 2
TAY ; 2 Y = A, backup shifted bits to Y
CLC ; 2 CF = 0
ADC temp2 ; 3 A=A+temp2 (shifted bits added to change this from div 16 to div 15)
CMP #15 ; 2 is A < 15? (look for div 15 overflow)
BCC .skipIny ; 2 yes, skip to EOR (no div 15 overflow)
SBC #15 ; 2 no, A = A - 15 (CF = 1 but not needed here)
INY ; 2 Y++ (yes on the div 15 overflow so one-up Y)
.skipIny:
EOR #$07 ; 2 A = A XOR 00000111, 7's complement, sets offset for 6 + HMPx
; NOTE: Any JSR here will use 20 cycles instead of 8
; but saves 1 byte of code overall (3 bytes instead of 4).
Mult16:
ASL ; 2 A's lower 4 bits shifted to upper 4
ASL ; 2
ASL ; 2
ASL ; 2
Wait12:
RTS ; 6
...
DoHMove:
STA WSYNC ; 3 wait for sync; wait for TIA to finish line
STA HMOVE ; 3 must follow WSYNC if horizontal motion desired
RTS ; 6
Hopefully, after this, Session 24 will make more sense, which discusses other algorithms for horizontal positioning.
Other Assembly Language Tutorials
Be sure to check out the other assembly language tutorials and the general programming pages on this web site.
Amazon: Atari 2600 Programming (#ad)
Amazon: 6502 Assembly Language Programming (#ad)
Atari 2600 Programming for Newbies (#ad)
|
|
|
Session 2: Television Display Basics
Sessions 3 & 6: The TIA and the 6502
Session 5: Memory Architecture
Session 7: The TV and our Kernel
Session 9: 6502 and DASM - Assembling the Basics
Session 14: Playfield Weirdness
Session 15: Playfield Continued
Session 16: Letting the Assembler do the Work
Sessions 17 & 18: Asymmetrical Playfields (Parts 1 & 2)
Session 20: Asymmetrical Playfields (Part 3)
Session 22: Sprites, Horizontal Positioning (Part 1)
Session 22: Sprites, Horizontal Positioning (Part 2)
Session 23: Moving Sprites Vertically
Session 25: Advanced Timeslicing
Disclaimer
View this page and any external web sites at your own risk. I am not responsible for any possible spiritual, emotional, physical, financial or any other damage to you, your friends, family, ancestors, or descendants in the past, present, or future, living or dead, in this dimension or any other.
Use any example programs at your own risk. I am not responsible if they blow up your computer or melt your Atari 2600. Use assembly language at your own risk. I am not responsible if assembly language makes you cry or gives you brain damage.