mirror of
https://github.com/peterdell/wudsn-ide.git
synced 2024-12-22 09:29:44 +00:00
712 lines
28 KiB
Plaintext
712 lines
28 KiB
Plaintext
**************************************************************
|
|
**************************************************************
|
|
* *
|
|
* ATARI 2600 ADVANCED PROGRAMMING GUIDE *
|
|
* *
|
|
* Updated 05-21-04 *
|
|
* Compiled and edited by Paul Slocum *
|
|
* Written by the Atari 2600 programming community *
|
|
* *
|
|
* version 1.0 / 05-19-04 / first release *
|
|
* version 1.1 / 05-21-04 / added multi-sprite trick *
|
|
* *
|
|
**************************************************************
|
|
**************************************************************
|
|
|
|
This guide is intended to be a supplement to the standard Stella Programmer's
|
|
Guide. Some of the sections assume a working knowledge of 6502 assembly and
|
|
Atari 2600 registers.
|
|
|
|
If you would like to write new sections or update existing sections of this
|
|
document, contact me: paul at treewave dot com
|
|
|
|
|
|
|
|
**************************************************************
|
|
**************************************************************
|
|
* *
|
|
* TABLE OF CONTENTS *
|
|
* *
|
|
**************************************************************
|
|
**************************************************************
|
|
|
|
- Bankswitching
|
|
- BRK Subroutine Trick
|
|
- Checking the Number of Scanlines
|
|
- Constant Cycle Count to Avoid WSYNC
|
|
- Counting Down when Looping
|
|
- Illegal Opcodes
|
|
- Insurance Against Too Many VBlank/Overscan Cycles
|
|
- Multi-Sprite Trick
|
|
- Paddles
|
|
- Showing Missiles/Ball using PHP
|
|
- Skipdraw
|
|
- Sound and Music
|
|
- Using BRK with RESXX
|
|
- Wasting Cycles
|
|
- HMOVE Timing Chart
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Bankswitching
|
|
==============================================================
|
|
==============================================================
|
|
|
|
For a bankswitching reference you'll want Kevin Horton's sizes.txt reference:
|
|
http://www.tripoint.org/kevtris/files/sizes.txt
|
|
|
|
One thing you'll probably want to know for any kind of bankswitching is the RORG
|
|
assembler directive. RORG is like ORG except it only affects the way label
|
|
addresses are handled, not where the code is placed in the ROM. So let's say
|
|
you're working on an 8K ROM. The first bank will start with ORG $1000 and all
|
|
the code and data for the first bank will follow. At the start of the second 4K
|
|
bank, you'll want:
|
|
|
|
ORG $2000
|
|
RORG $1000
|
|
|
|
If you don't use RORG, all your label addresses in the second bank will be in
|
|
the $2000-$2FFF range which won't work. With RORG, they will continue to
|
|
address the $1000-$1FFF range.
|
|
|
|
Here's a basic template for doing F8 bankswitching. This can easily be modified
|
|
to work with similar bankswitching methods (F4,F6,etc). This code allows you to
|
|
call "Bank2Subroutine" from bank 1 using jsr CallBank2Subroutine.
|
|
|
|
;------------------------------------
|
|
;This code in bank 1
|
|
;Switches to bank 2 where the subroutine it called.
|
|
org $1FE0
|
|
CallBank2Subroutine
|
|
ldx $1FF9 ; switch to bank 2
|
|
nop ; 1FE3 jsr Bank2Subroutine
|
|
nop ; .
|
|
nop ; .
|
|
nop ; 1FE6 lda $1FF8 (Switch back to bank 1)
|
|
nop ; .
|
|
nop ; .
|
|
rts
|
|
|
|
;------------------------------------
|
|
;This code in bank 2:
|
|
;Calls the subroutine and returns to bank 1
|
|
org $2FE3
|
|
rorg $1FE3
|
|
|
|
jsr Bank2Subroutine
|
|
ldx $1FF8 ;(Switch back to bank 1)
|
|
|
|
|
|
It's good practice to assume that your multi-bank ROM could start up in any bank
|
|
. In each bank, set up the startup vector so it points to code that switches to
|
|
the correct startup bank and then jumps to the start of your program.
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
BRK Subroutine Trick
|
|
--------------------------------------------------------------
|
|
Mark Lesser, Thomas Jentzch
|
|
==============================================================
|
|
==============================================================
|
|
|
|
Thomas found this trick in Mark Lesser's Lord of the Rings prototype: You can
|
|
use BRK to call a subroutine that needs to be called often and save ROM space.
|
|
If you aren't familiar with BRK, it pushes the flags and PC on the stack and
|
|
jumps to wherever the vector $FFFE is pointing.
|
|
|
|
Thomas found BRK commands like this scattered through Lord of the Rings:
|
|
|
|
brk
|
|
.byte $0e ; id-byte
|
|
lda $e3 ; <- here we will continue
|
|
ora #$04
|
|
|
|
And the BRK vector was pointing to this routine:
|
|
|
|
BrkRoutine:
|
|
plp ; remove flags from stack (not needed)
|
|
tsx ; load x with stackpointer
|
|
inx ; x++
|
|
dec $00,x ; adjust return address
|
|
lda ($00,x) ; read break-id...
|
|
tay ; ...and store in y
|
|
Subroutine:
|
|
[subroutine code...]
|
|
rts
|
|
|
|
So it ended up being the equivalent of passing a value to a subroutine similar
|
|
to this:
|
|
|
|
ldy #value
|
|
jsr Subroutine
|
|
|
|
But it saves 3 bytes with each call and the overhead is only 8 bytes.
|
|
After only 3 subroutine calls (Lord of the Rings has about 20) you are saving
|
|
ROM space.
|
|
|
|
In the case of Lord of the Rings, the subroutine was sound related code that
|
|
selected the sound effect to be played based on a priority system.
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Checking the Number of Scanlines
|
|
--------------------------------------------------------------
|
|
Eckhard Strolberg
|
|
==============================================================
|
|
==============================================================
|
|
|
|
You'll want to verify that your program is drawing the desired number of
|
|
scanlines (around 262 NTSC and 312 PAL) and is not varying that number while
|
|
running. To do this, use the Z26 emulator in video modeo 9. While Z26 is
|
|
running, press ALT-9 to enter this mode which will display the number of
|
|
scanlines in the upper right corner. The -v9 switch will start Z26 in this
|
|
mode.
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Constant Cycle Count to Avoid WSYNC
|
|
--------------------------------------------------------------
|
|
==============================================================
|
|
==============================================================
|
|
|
|
Normally you use STA WSYNC towards the end of each scanline in the kernal to
|
|
stay in sync with the TV. But by carefully programming your kernal so each line
|
|
consistently takes exactly 76 cycles, you can avoid using STA WSYNC at all in
|
|
your kernal loop. This will save you at least 3 cycles per scanline.
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Counting Down When Looping
|
|
--------------------------------------------------------------
|
|
==============================================================
|
|
==============================================================
|
|
|
|
Have loops count down whenever possible since this can save a lot of cycles and
|
|
a little bit of ROM too.
|
|
|
|
------------------------------------------
|
|
|
|
Counting up requires a compare:
|
|
|
|
ldx #0
|
|
Loop
|
|
[your code...]
|
|
inx
|
|
cpx #20
|
|
bne Loop
|
|
|
|
------------------------------------------
|
|
|
|
Counting down allows you to get rid of the compare:
|
|
|
|
ldx #20
|
|
Loop
|
|
[your code...]
|
|
dex
|
|
bne Loop
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Illegal Opcodes
|
|
--------------------------------------------------------------
|
|
Thomas Jentzch
|
|
==============================================================
|
|
==============================================================
|
|
|
|
These are the most commonly used illegal opcodes in 2600 programming and are
|
|
considered safe.
|
|
|
|
Decrement and Compare:
|
|
Decrements the memory location and compares it to the accumulator.
|
|
DCP Opcode:$C3 M <- (M)-1, (A-M) -> NZC (Ind,X) 2/8
|
|
|
|
Double NOP:
|
|
No operation command that takes 3 cycles and uses two bytes.
|
|
DOP Opcode:$04 [no operation] (Z-Page) 2/3
|
|
|
|
Load A and X:
|
|
Loads both A and X with the memory.
|
|
LAX Opcode:$AF A <- M, X <- M (Absolute) 3/4
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Insurance Against Too Many VBLANK/Overscan Cycles
|
|
--------------------------------------------------------------
|
|
Paul Slocum
|
|
==============================================================
|
|
==============================================================
|
|
|
|
It's difficult to make sure that there is no unusual case where your game logic
|
|
in VBlank and Overscan will use too many cycles and cause the number of
|
|
scanlines in a frame to fluctuate. To insure against this problem, pad your
|
|
VBlank and Overscan with a few STA WSYNCs to add extra cycles during development
|
|
. Optimize your game so that it runs fine with these in. Then when the game is
|
|
finished and ready for release, comment out those extra WSYNCs. This is also an
|
|
easy way to estimate how much time you have left in VBlank and Overscan: keep
|
|
adding WSYNCs (each line is 76 cycles) until the screen jumps.
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
The Multi-Sprite Trick
|
|
--------------------------------------------------------------
|
|
Christopher Tumber
|
|
Todo: Add ASCII Images
|
|
==============================================================
|
|
==============================================================
|
|
|
|
The multi-sprite trick is a technique which allows a programmer to put more sprites on a scanline than would normally be
|
|
allowable. Using this trick, up to 18 [?] sprites may be displayed on a scanline.
|
|
|
|
This trick may be used with Player0, Player1, Missile0 and/or Missile1 in any combination. The most common use is with
|
|
P0 and P1 to create a row of sprites.
|
|
|
|
There are important limitations with this trick. This trick essentially allows you to create more than the normal limit
|
|
of 3 copies of a sprite. However, in doing so you probably will not have time to change the bitmap, colour or size of the
|
|
sprite (as applicable). So the kinds of displays created with this technique will usually be repetitive patterns.
|
|
|
|
The trick is accomplished by first setting NUSIZ0 and/or NUSIZ1 to display 2 or more copies of the sprites you want to
|
|
display [Is the exact setting significant?]. Your program then must strobe RESP0, RESP1, RESM0 and/or RESM1 repeatedly.
|
|
If this is timed correctly so that it occurs after the first copy is drawn but before the second then the TIA is tricked
|
|
into thinking it's just started drawing sprites and contiues drawing the sprite as if it's the first copy. You just
|
|
continue this for every copy you need.
|
|
|
|
The tightest formation of sprites possible with this trick is:
|
|
|
|
[image]
|
|
|
|
which is done by the following code:
|
|
|
|
|
|
sta RESP0
|
|
sta RESP1
|
|
sta RESP0
|
|
sta RESP1
|
|
sta RESP0
|
|
sta RESP1
|
|
sta RESP0
|
|
sta RESP1
|
|
sta RESP0
|
|
sta RESP1
|
|
|
|
However, this formation has one problem in that the last sprite on a row is shifted one pixel to the left. There is no
|
|
known soloution to this problem. If you can work this "glitch" into your game (for example as part of an asynchronous
|
|
display) then you can use this layout. If not, the closest "stable" formation is:
|
|
|
|
[image]
|
|
|
|
which is done by the following code:
|
|
|
|
sta RESP0,x
|
|
sta RESP1,x
|
|
sta RESP0,x
|
|
sta RESP1,x
|
|
sta RESP0,x
|
|
sta RESP1,x
|
|
sta RESP0,x
|
|
sta RESP1,x
|
|
sta RESP0,x
|
|
sta RESP1,x
|
|
|
|
or
|
|
|
|
sta.w RESP0
|
|
sta.w RESP1
|
|
sta.w RESP0
|
|
sta.w RESP1
|
|
sta.w RESP0
|
|
sta.w RESP1
|
|
sta.w RESP0
|
|
sta.w RESP1
|
|
sta.w RESP0
|
|
sta.w RESP1
|
|
|
|
The ,x and .w in this example are "dummies". They're only used to increased the length of time taken by each instruction
|
|
by 1 cycle. The tradeoff being that the former requires the X register be set to zero and the latter results in slightly
|
|
larger code.
|
|
|
|
If you need to turn off some of the sprites, you can do this by simply skipping their spot by inserting a dummy command.
|
|
For example:
|
|
|
|
[image]
|
|
|
|
sta RESP0
|
|
sta RESP1
|
|
sta Dummy
|
|
sta RESP1
|
|
sta RESP0
|
|
sta Dummy
|
|
sta RESP0
|
|
sta RESP1
|
|
sta RESP0
|
|
sta RESP1
|
|
|
|
Where Dummy is an unsued (or scratch or temporary) RAM location.
|
|
|
|
This trick is quite easy to use to generate static displays. However, if you want a fully dynamic display things get
|
|
considerably more complicated.
|
|
|
|
Since there is no time available while drawing the sprites to do any calculations, if you want a variable number of
|
|
sprites on and off you must predetermine which sprites to display. A simple way to do this is to create a subroutine
|
|
for every possible combination of on/off sprites. Then your program just needs to call the appropriate subroutine.
|
|
The problem with this approach is that if you have a lot of sprites, the number of subroutines becomes very large.
|
|
An alternative method is to place you drawing routine in RAM, adjusted for the current display - In the above example,
|
|
copy all the STA RESP0 and STA RESP1 commands into RAM and then where sprites don't appear, substitue in a dummy RAM
|
|
location for the relevant RESP0 or RESP1.
|
|
|
|
One thing you must be aware of if you are allowing individual sprites to be switched on and off. There are a number
|
|
of cominations which you must treat as an exception. They cannot be displayed using the general display as above.
|
|
For example, if either RESP0 or RESP1 needs to display only 1 copy of a sprite (because all other copies are off)
|
|
then you must reset NUSIZ0/NUSIZ1. This would also be the case when two copies of a sprite are set too far apart
|
|
for the second STA RESPn to occur before a "normal" copy is drawn.
|
|
|
|
In addition, these sprites are not positioned vertically like normal sprites. Rather their position is determined by which
|
|
display cycle the first STA RESPn or RESPMn occurs. So if you want to be able to reposition your sprites verically,
|
|
you will either need to add more subroutines (as above) or adjust your RAM routine further. Or some combination of the
|
|
two. Space Instigators uses a different subroutine for each possible vertical position, copies that routine into RAM
|
|
and then modifies the STA RESPn commands to turn off dead Instigators. The new, single scanline repositioning routine may
|
|
be of help here, however the multi-sprite trick tends to use up so much of your scanline time that if you're
|
|
trying to do other things (ie: display other sprites) on that scanline you may not have the luxury of enough
|
|
cycles for general purpose positioning code.
|
|
|
|
The multi-sprite trick has a side effect in that the formation of sprites is shifted [?exact number?] pixels right
|
|
as compared to where a normal sprite would appear with an STA RESPn at that cycle. This results in a left margin
|
|
that's not at the left edge of the screen. "Illegal" HMOVE/HMMn combination tricks may be used to fix this but with a
|
|
corresponding increase in complexity.
|
|
|
|
[Some more example code here]
|
|
|
|
References: The multi-sprite trick was originally used in Galaxian and was pioneered by Eckhard Stolberg, John
|
|
Saeger, Erik Mooney and Thomas Jentzsch. Search Stella List under "Grid demo","trick18","trick12" and "inv3".
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Paddles
|
|
--------------------------------------------------------------
|
|
Thomas Jentzch
|
|
Todo: Explain and include how to discharge cap
|
|
==============================================================
|
|
==============================================================
|
|
|
|
Assumes Y is your kernal line counter.
|
|
|
|
lda INPT0 ;3
|
|
bmi paddles1 ;2 or 3
|
|
.byte $2c ;1 bit abs opcode
|
|
paddles1:
|
|
sty padVal1 ;3
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Showing Missiles/Ball using PHP
|
|
==============================================================
|
|
==============================================================
|
|
|
|
This trick is originally from Combat and is probably the most efficient way to
|
|
display the missiles and/or ball. This trick just requires that you don't use
|
|
the stack during your kernal. Recall that:
|
|
|
|
ENABL = $1F
|
|
ENAM1 = $1E
|
|
ENAM0 = $1D
|
|
|
|
In this example I'll show how to use the trick for both missiles. You can
|
|
easily adapt it for the ball too. To set the trick up, before your kernal save
|
|
the stack pointer and set the top of the stack to ENAM1+1.
|
|
|
|
tsx ; Transfer stack pointer to X
|
|
stx SavedStackPointer ; Store it in RAM
|
|
ldx #ENAM1+1
|
|
txs ; Set the top of the stack to ENAM1+1
|
|
|
|
Now during the kernal you can compare your scanline counter to your missile
|
|
position register and this will set the zero flag in the processor. Then to
|
|
enable/disable the missile for that scanline, just push the processor flags onto
|
|
the stack. The ENAxx registers use bit 1 to enable/disable which corresponds
|
|
with the zero flag in the processor, so the enable/disable will be automatic.
|
|
It takes few cycles and doesn't vary the number of cycles depending on the
|
|
result like branching usually does.
|
|
|
|
; On each line of your the kernal...
|
|
cpy MissilePos1 ; Assumes Y is your kernal line counter
|
|
php
|
|
cpy MissilePos0
|
|
php
|
|
|
|
Then before you do it again, somewhere on each scanline you need to pull off the
|
|
stack again using two PLA's or PLP's, or you can manually reset the stack
|
|
pointer with ldx #ENAM1+1, txs.
|
|
|
|
After your kernal, restore the stack pointer:
|
|
|
|
ldx SavedStackPointer
|
|
txs
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Skipdraw
|
|
--------------------------------------------------------------
|
|
Thomas Jentzch
|
|
Todo: Explain and clean up
|
|
==============================================================
|
|
==============================================================
|
|
|
|
The best way, i knew until now, was (if y contains linecounter):
|
|
|
|
tya ; 2
|
|
; sec ; 2) <- this can sometimes be avoided
|
|
sbc SpriteEnd ; 3
|
|
adc #SPRITEHEIGHT ; 2
|
|
bcx .skipDraw ; 2 = 9-11 cycles
|
|
...
|
|
|
|
---------- or ---------
|
|
|
|
If you like using illegal opcodes, you can use dcp (dec,cmp) here:
|
|
|
|
lda #SPRITEHEIGHT ; 2
|
|
dcp SpriteEnd ; 5 initial value has to be adjusted
|
|
bcx .skipDraw ; 2 = 9
|
|
...
|
|
|
|
Advantages:
|
|
- state of carry flag doesn't matter anymore (may save 2 cycles)
|
|
- a remains constant, could be useful for a 2nd sprite
|
|
- you could use the content of SpriteEnd instead of y for accesing sprite data
|
|
|
|
;==================================
|
|
;An Example:
|
|
;
|
|
; skipDraw routine for right player
|
|
TXA ; 2 A-> Current scannline
|
|
SEC ; 2 Set Carry
|
|
SBC slowP1YCoordFromBottom+1 ; 3
|
|
ADC #SPRITEHEIGHT+1 ; 2 calc if sprite is drawn
|
|
BCC skipDrawRight ; 2/3 To skip or not to skip?
|
|
TAY ; 2
|
|
lda P1Graphic,y ; 4
|
|
continueRight:
|
|
STA GRP0
|
|
|
|
;----- this part outside of kernel
|
|
|
|
skipDrawRight ; 3 from BCC
|
|
LDA #0 ; 2
|
|
BEQ continueRight ; 3 Return...
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Sound and Music
|
|
--------------------------------------------------------------
|
|
==============================================================
|
|
==============================================================
|
|
|
|
Atari 2600 Music Programming Guide and music driver code:
|
|
|
|
http://qotile.net/sequencer.html
|
|
|
|
Eckhard Strolberg's Frequency and Waveform Guide:
|
|
|
|
http://buerger.metropolis.de/estolberg
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Using BRK with RESXX
|
|
--------------------------------------------------------------
|
|
Eckhard Strolberg
|
|
==============================================================
|
|
==============================================================
|
|
|
|
(I'm not sure what you'd do with this trick but it's pretty interesting. Maybe
|
|
somebody will figure out how to use it.)
|
|
|
|
Pole Position puts the stack pointer over the RESxx registers and then does a
|
|
BRK. There are three write cycles in a BRK instruction, so the three position
|
|
registers for the objects that make up the road in PP, get accessed in three
|
|
consecutive cycles. This is how PP managed to get the road to meet so closely in
|
|
the horizon.
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
Wasting Cycles
|
|
--------------------------------------------------------------
|
|
Christopher Tumbler, Chris Wilkson, Andrew Davie
|
|
==============================================================
|
|
==============================================================
|
|
|
|
These are the most efficient ways to waste processor cycles.
|
|
|
|
Note that locations $2D-$3F do nothin and aren't decoded, and so they are used
|
|
often here. In some bankswitching schemes this could cause problems though.
|
|
|
|
-----------------
|
|
1 Cycle (0 or 1 byte)
|
|
.w (Change a zero page instruction to absolute, adds 1 byte of code)
|
|
,x (Change a zero page or absolute instruction to an indexed instruction.
|
|
Make sure x=0. Can also use Y)
|
|
-----------------
|
|
2 Cycles (1 byte)
|
|
nop
|
|
-----------------
|
|
3 Cycles (2 bytes)
|
|
sta $2D
|
|
- or -
|
|
lda $2D
|
|
- or -
|
|
dop (Double NOP illegal opcode)
|
|
-----------------
|
|
4 Cycles (2 bytes)
|
|
nop
|
|
nop
|
|
-----------------
|
|
5 Cycles (2 bytes)
|
|
dec $2D
|
|
- or -
|
|
sta $1800,X ; asssumes you can write to ROM without problems
|
|
-----------------
|
|
6 Cycles (2 bytes)
|
|
lda ($80,X) ; assumes possible reads from 0-$7f have no effect
|
|
|
|
6 Cycles (3 bytes)
|
|
nop
|
|
nop
|
|
nop
|
|
-----------------
|
|
7 Cycles (2 bytes, need 1 byte free on stack)
|
|
pha
|
|
pla
|
|
-----------------
|
|
8 Cycles (3 bytes)
|
|
lda ($80,X) ; assumes possible reads from 0-$7f have no effect
|
|
nop
|
|
-----------------
|
|
9 Cycles (3 bytes, need 1 byte free on stack)
|
|
pha
|
|
pla
|
|
nop
|
|
|
|
9 Cycles (4 bytes)
|
|
dec $2D
|
|
nop
|
|
nop
|
|
-----------------
|
|
10 Cycles (4 bytes)
|
|
dec $2D
|
|
dec $2D
|
|
- or -
|
|
rol $80
|
|
rol $80 ; leaves $80 unchanged
|
|
-----------------
|
|
11 Cycles (4 bytes)
|
|
ASSUMING we can safely write to ROM and have nothing disasterous...
|
|
STA $8000,X
|
|
LDA ($80,X) ; assumes possible reads from 0-$7f have no effect
|
|
-----------------
|
|
12 Cycles (3 bytes, need 2 bytes free on stack)
|
|
jsr return
|
|
; somewhere else
|
|
return:
|
|
rts
|
|
-----------------
|
|
12 Cycles (4 bytes)
|
|
LDA ($80,X) ; assumes possible reads from 0-$7f have no effect
|
|
LDA ($80,X) ; assumes possible reads from 0-$7f have no effect
|
|
|
|
Also:
|
|
You can use PHA/PHP (1 byte 3 cycles) or PLA/PLP (1 byte 4 cycles) alone but
|
|
you have to be carefull not to mess up your stack (PLP/PHA would be usefull if
|
|
you have no stack!
|
|
|
|
|
|
|
|
==============================================================
|
|
==============================================================
|
|
HMOVE Timing Chart
|
|
--------------------------------------------------------------
|
|
Brad Mott
|
|
==============================================================
|
|
==============================================================
|
|
|
|
Typically HMOVE is executed right after WSYNC, but hitting HMOVE at other times
|
|
during the line has effects that can sometimes be useful. It's possible to move
|
|
objects without the black HMOVE bars and/or move objects farther than would
|
|
normally be possible with HMPx registers. This test was performed using player
|
|
graphics, but will probably work with the ball and missiles as well.
|
|
|
|
|
|
HMPx values
|
|
0 1 2 3 4 5 6 7 8 9 a b c d e f
|
|
Cyc
|
|
10 0 -1 -2 -2 -2 -2 -2 -2 8 7 6 5 4 3 2 1 ** HBLANK
|
|
11 0 -1 -1 -1 -1 -1 -1 -1 8 7 6 5 4 3 2 1 HBLANK
|
|
12 0 0 0 0 0 0 0 0 8 7 6 5 4 3 2 1 HBLANK
|
|
13 1 1 1 1 1 1 1 1 8 7 6 5 4 3 2 1 HBLANK
|
|
14 1 1 1 1 1 1 1 1 8 7 6 5 4 3 2 1 ** HBLANK
|
|
15 2 2 2 2 2 2 2 2 8 7 6 5 4 3 2 2 HBLANK
|
|
16 3 3 3 3 3 3 3 3 8 7 6 5 4 3 3 3 HBLANK
|
|
17 4 4 4 4 4 4 4 4 8 7 6 5 4 4 4 4 HBLANK
|
|
18 4 4 4 4 4 4 4 4 8 7 6 5 4 4 4 4 ** HBLANK
|
|
19 5 5 5 5 5 5 5 5 8 7 6 5 5 5 5 5 HBLANK
|
|
20 6 6 6 6 6 6 6 6 8 7 6 6 6 6 6 6 HBLANK
|
|
21 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
22 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
.
|
|
.
|
|
53 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
54 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
55 0 0 0 0 0 0 0 -1 0 0 0 0 0 0 0 0
|
|
56 0 0 0 0 0 0 -1 -2 0 0 0 0 0 0 0 0
|
|
57 0 0 0 0 0 -1 -2 -3 0 0 0 0 0 0 0 0
|
|
58 0 0 0 0 0 -1 -2 -3 0 0 0 0 0 0 0 0 **
|
|
59 0 0 0 0 -1 -2 -3 -4 0 0 0 0 0 0 0 0
|
|
60 0 0 0 -1 -2 -3 -4 -5 0 0 0 0 0 0 0 0
|
|
61 0 0 -1 -2 -3 -4 -5 -6 0 0 0 0 0 0 0 0
|
|
62 0 0 -1 -2 -3 -4 -5 -6 0 0 0 0 0 0 0 0 **
|
|
63 0 -1 -2 -3 -4 -5 -6 -7 0 0 0 0 0 0 0 0
|
|
64 -1 -2 -3 -4 -5 -6 -7 -8 0 0 0 0 0 0 0 0
|
|
65 -2 -3 -4 -5 -6 -7 -8 -9 0 0 0 0 0 0 0 -1
|
|
66 -2 -3 -4 -5 -6 -7 -8 -9 0 0 0 0 0 0 0 -1 **
|
|
67 -3 -4 -5 -6 -7 -8 -9 -10 0 0 0 0 0 0 -1 -2
|
|
68 -4 -5 -6 -7 -8 -9 -10 -11 0 0 0 0 0 -1 -2 -3
|
|
69 -5 -6 -7 -8 -9 -10 -11 -12 0 0 0 0 -1 -2 -3 -4
|
|
70 -5 -6 -7 -8 -9 -10 -11 -12 0 0 0 0 -1 -2 -3 -4 **
|
|
71 -6 -7 -8 -9 -10 -11 -12 -13 0 0 0 -1 -2 -3 -4 -5
|
|
72 -7 -8 -9 -10 -11 -12 -13 -14 0 0 -1 -2 -3 -4 -5 -6
|
|
73 -8 -9 -10 -11 -12 -13 -14 -15 0 -1 -2 -3 -4 -5 -6 -7
|
|
74 -8 -9 -10 -11 -12 -13 -14 -15 0 -1 -2 -3 -4 -5 -6 -7 **
|
|
75 0 -1 -2 -3 -4 -5 -6 -7 8 7 6 5 4 3 2 1 HBLANK
|
|
76 0 -1 -2 -3 -4 -5 -6 -7 8 7 6 5 4 3 2 1 HBLANK
|
|
77 0 -1 -2 -3 -4 -5 -6 -7 8 7 6 5 4 3 2 1 HBLANK
|
|
78 0 -1 -2 -3 -4 -5 -6 -7 8 7 6 5 4 3 2 1 HBLANK
|
|
79 0 -1 -2 -3 -4 -5 -6 -7 8 7 6 5 4 3 2 1 HBLANK
|
|
80 0 -1 -2 -3 -4 -5 -6 -6 8 7 6 5 4 3 2 1 HBLANK
|
|
81 0 -1 -2 -3 -4 -5 -5 -5 8 7 6 5 4 3 2 1 HBLANK
|
|
82 0 -1 -2 -3 -4 -5 -5 -5 8 7 6 5 4 3 2 1 ** HBLANK
|
|
83 0 -1 -2 -3 -4 -4 -4 -4 8 7 6 5 4 3 2 1 HBLANK
|
|
84 0 -1 -2 -3 -3 -3 -3 -3 8 7 6 5 4 3 2 1 HBLANK
|
|
85 0 -1 -2 -2 -2 -2 -2 -2 8 7 6 5 4 3 2 1 HBLANK
|
|
( table repeats at this point) |