Jump to content
IGNORED

Cycle 74 Hmoves


Recommended Posts

In my game I just needed to enable/disable a missile, and position an object with a cycle 73 or cycle 74 hmove all in 1 scanline. Playing around with how to do this I came up with some fairly economical code. I then made the demo posted below showing the yellow object positioned by a cycle 74 hmove, and the green object positioned by a traditional hmove.

 

If you are wondering cycle 73 or 74 hmoves hide the black "comb" lines on in an Atari game. The consequence of using cycle 73 or 74 hmoves is that you can only fine position the object leftward.

 

 

hmove74.zip

Link to comment
Share on other sites

I made a commenting error in my code saying X is bounded 1-10. When X is used its value is $FF, which causes the page boundary to be crossed hence why the 5 cycles delay works.

 

 

I was playing around more and it seems easy enough to do any positioning by using Y for the delay and X for the index, like:

 

 

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; prep to position top object
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

   lda    quickPos
   sta    HMP0
   and    #$0F
   tay                      ; delay count
   ldx    #0                ; 0 = GRP0, 1 = GRP1, etc..

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; position top object in 1 scanline
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

   sta    WSYNC
;---------------------------------------
   nop                               ;2  @2
   nop                               ;2  @4
   nop                               ;2  @6
   nop                               ;3  @8
.waitPos:
   dey                               ;2  @10
   bpl    .waitPos                   ;2³ @12/13
   jmp.ind (indirectAddress)         ;5  @17    jump into table below

position_3:
   sta   RESP0,X
   .byte $9D       ;  sta  $1095,X (rom space), skip next two bytes
position_15:
   sta   RESP0,X
   .byte $9D
position_30:
   sta   RESP0,X
   .byte $9D
position_45:
   sta   RESP0,X
   .byte $9D
position_60:
   sta   RESP0,X
   .byte $9D
position_75:
   sta   RESP0,X
   .byte $9D
position_90:
   sta   RESP0,X
   .byte $9D
position_105:
   sta   RESP0,X
   .byte $9D
position_120:
   sta   RESP0,X
   .byte $9D
position_135:
   sta   RESP0,X
   .byte $9D
position_150:
   sta   RESP0,X
   sta   HMOVE       ;3  @74
   nop               ;2  @76   free time

 

Opcode $9D always seem to take 5 cycles regardless of X's value.

Edited by Omegamatrix
Link to comment
Share on other sites

Thinking about it some more, and it might be better to do illegal NOP's to avoid superchip areas. Maybe something like this:

 

 

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; prep to position top object
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; -5 = RESP0
; -4 = RESP1
; -3 = RESM0
; -2 = RESM1
; -1 = RESBL
   ldx    #-5
   lda    quickPos
   sta    HMP0+5,X
   and    #$0F
   tay                      ; delay count

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; position top object in 1 scanline
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

   sta    WSYNC
;---------------------------------------
   nop                               ;2  @2
   nop                               ;2  @4
   nop                               ;2  @6
   nop                               ;3  @8
.waitPos:
   dey                               ;2  @10
   bpl    .waitPos                   ;2³ @12/13
   jmp.ind (indirectAddress)         ;5  @17    jump into table below

postion_3:
   sta   RESP0+5,X
   .byte $1C       ;  nop  $1595,X (rom space), skip next two bytes
postion_15:
   sta   RESP0+5,X
   .byte $1C
postion_30:
   sta   RESP0+5,X
   .byte $1C
postion_45:
   sta   RESP0+5,X
   .byte $1C
postion_60:
   sta   RESP0+5,X
   .byte $1C
postion_75:
   sta   RESP0+5,X
   .byte $1C
postion_90:
   sta   RESP0+5,X
   .byte $1C
postion_105:
   sta   RESP0+5,X
   .byte $1C
postion_120:
   sta   RESP0+5,X
   .byte $1C
postion_135:
   sta   RESP0+5,X
   .byte $1C
postion_150:
   sta   RESP0+5,X
   sta   HMOVE       ;3  @74
   nop               ;2  @76   free time
;---------------------------------------

 

Where using the negative values for X will wrap the page boundary and add a cycle. This could easily turned into a subroutine as well. :)

Link to comment
Share on other sites

The old routine used a register that held a horizontal position value (0-159) and a second register for the "quickPos" value. This was a waste of ram, so I made a little routine that just uses "quickPos" values for location, and updates them with joystick movement. This way you free up ram! Find all your new values in overscan and then quickly load the "quickPos" object you want mid kernel.

 

 

L_EDGE_VAL  = $71
R_EDGE_VAL  = $C0
LEFT_ADJ    = $9A
RIGHT_ADJ   = $50

   lax    quickPos
   and    #$0F
   sta    tempOne
   txa                      ; quickPos
   lsr
   lsr
   lsr
   lsr
   tay                      ; HMxx >> 4, use Y to find next HMxx value
   bit    SWCHA             ; test joystick
   bvc    .moveLeft
   bmi    .exitRoutine      ; if no change

;else move right...
   cpx    #R_EDGE_VAL
   beq    .alignLeft
   cpx    #LEFT_ADJ
   beq    .fixRight

   lda    RightTab,Y
   cpy    #9
   beq    .addDelayCount
   bne    .prepForAddition  ; always branch

.alignLeft:
   lda    #L_EDGE_VAL
   .byte $0C
.alignRight:
   lda    #R_EDGE_VAL
   .byte $0C
.fixLeft:
   lda    #LEFT_ADJ
   .byte $0C
.fixRight:
   lda    #RIGHT_ADJ
   bne    .storeQuickPos     ; always branch

LeftTab:
   .byte $10
   .byte $20
   .byte $30
   .byte $40
   .byte $50
   .byte $60
   .byte $70
   .byte $90
   nop
   .byte $A0
   .byte $B0
   .byte $C0
   .byte $D0
   .byte $E0
RightTab:
   .byte $F0 ; shared
   .byte $00 ; shared
   .byte $10
   .byte $20
   .byte $30
   .byte $40
   .byte $50
   .byte $60
   nop
   .byte $70
   .byte $90
   .byte $A0
   .byte $B0
   .byte $C0
   .byte $D0
;    .byte $E0    shared with CPX #immediate opcode

.moveLeft:
   cpx    #L_EDGE_VAL
   beq    .alignRight
   cpx    #RIGHT_ADJ
   beq    .fixLeft
   lda    LeftTab,Y
   cpy    #7
   bne    .prepForAddition
   dec    tempOne
.prepForAddition:
   clc
.addDelayCount:
   adc    tempOne
.storeQuickPos:
   sta    quickPos
.exitRoutine:

 

 

 

I almost forgot, you do have to initialize the horizontal value one time, i.e.:

 

lda #$71 ; this is position "0", in a range of 0-159

sta quickPos

Edited by Omegamatrix
Link to comment
Share on other sites

Then again it makes it a lot easier just to spend the bytes and do a big old table of values. It is by far the fastest, and makes the code easier to understand and manage, and it easier to calculate new positions for objects when they jump more than 1 pixel.

 

 

   ldy    objHpos
   lda    PositionTab,Y
   sta    HMP0+5,X
   and    #$0F
   tay
;......



PositionTab:
   .byte $71,$61,$51,$41,$31,$21,$11,$01,$F1,$E1,$D1,$C1,$B1,$A1,$91
   .byte $72,$62,$52,$42,$32,$22,$12,$02,$F2,$E2,$D2,$C2,$B2,$A2,$92
   .byte $73,$63,$53,$43,$33,$23,$13,$03,$F3,$E3,$D3,$C3,$B3,$A3,$93
   .byte $74,$64,$54,$44,$34,$24,$14,$04,$F4,$E4,$D4,$C4,$B4,$A4,$94
   .byte $75,$65,$55,$45,$35,$25,$15,$05,$F5,$E5,$D5,$C5,$B5,$A5,$95
   .byte $76,$66,$56,$46,$36,$26,$16,$06,$F6,$E6,$D6,$C6,$B6,$A6,$96
   .byte $77,$67,$57,$47,$37,$27,$17,$07,$F7,$E7,$D7,$C7,$B7,$A7,$97
   .byte $78,$68,$58,$48,$38,$28,$18,$08,$F8,$E8,$D8,$C8,$B8,$A8,$98
   .byte $79,$69,$59,$49,$39,$29,$19,$09,$F9,$E9,$D9,$C9,$B9,$A9,$99
   .byte $7A,$6A,$5A,$4A,$3A,$2A,$1A,$0A,$FA,$EA,$DA,$CA,$BA,$AA,$9A
   .byte $50,$40,$30,$20,$10,$00,$F0,$E0,$D0,$C0

Link to comment
Share on other sites

Newbie question (maybe ;-) ): Looking at the code it seems that the preparation code results in so many cycles that it cannot be applied in consequtive scanlines and due to the STA RESPx there the object will not be present in the line itself right? So the "only" advantage is not to have the black "comb" (which is sure worth the effort in some situations like blue background).

Link to comment
Share on other sites

>The consequence of using cycle 73 or 74 hmoves is that you can only fine position the object leftward

And are the valid HM values also restricted is you only use HMOVE and no RESPx ? (Why are they restricted at all?)

Link to comment
Share on other sites

>The consequence of using cycle 73 or 74 hmoves is that you can only fine position the object leftward

And are the valid HM values also restricted is you only use HMOVE and no RESPx ? (Why are they restricted at all?)

The HM values are still the same, but their *effects* are different. With a normal (cycle 3) HMOVE, the HM values let you shift an object's position between 7 pixels to the left and 8 pixels to the right. When you do a cycle 73 or cycle 74 HMOVE, the same HM values shift an object's position between 15 pixels to the left and 0 pixels to the right. This is due to technical reasons related to the way HMOVE works, as well as the way the position counters for the players, missiles, and ball work. Basically, HMOVE by itself can move objects only to the left, by feeding extra pulses to the position counters so the objects end up being drawn sooner than they otherwise would have been. Since there's no way to *subtract* pulses from the position counters to make the objects get drawn later (i.e., moved to the right), HMOVE turns on a latch that causes the horizontal blank to be extended 8 pixels-- creating the "HMOVE bar"-- and that causes the objects to be shifted to the right by 8 pixels. Strobing HMOVE at cycle 73 or 74 sets the latch, but then the latch gets released by the leading edge of the horizontal blank, so there's no "HMOVE bar," consequently there's also no way to move the objects to the right.

 

Michael

Link to comment
Share on other sites

MiniDig has a list of HMOVE Timing values. Normal cycle 3 HMOVE is listed as cycle 79 in the list:

 

     HMPx values
    0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f 
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

Link to comment
Share on other sites

Thanks to the good explanations. Now I also see why the bar is always black, no matter what the COLUBK is. For my current effect I need right moves, but it's good to know about this anyway.

 

Peter.

Link to comment
Share on other sites

Thinking about it some more, and it might be better to do illegal NOP's to avoid superchip areas.

I am not 100% sure, but IIRC even a NOP would trigger the hotspots.

You are correct, NOP absolute instead of BIT will not save you from wayward accesses. However, this particular case will only access Superchip read areas and so it should be safe.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...