lucienEn Posted February 5 Share Posted February 5 Hi, I'm looking into setting position for player objects and I used the sethorizpos subroutine but it's not behaving as I expected close to both boundaries. See code below. With the joystick you can move left or right and for some reason it's jumping when < 14 and > 135. Related questions that might explain some of this: What happens when you strobe RESP while in HBLANK? TIA notes you can only set it in the visible area but then how would this work for small X numbers? If only in visible area, how could you position P0/P1 at position 1 since the STA RESP takes already 9 + 5 color clocks? The offset is max -7 so then minimum position is really 7 (which is still ok with HMOVE)? Can you position P0/P1 at position 152? Seems you would run out of color clocks for the scanline and you get an extra scanline. Or is there a trick? What is the best place to reset HMCLR? I think it needs to be reset before the next scanline but also cannot happen to soon after HMOVE. I added lots of NOP but is there a better way? Thanks! Lucien processor 6502 include "vcs.h" include "macro.h" seg.u Variables org $80 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Variables and Constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Player0_X byte Player0_X_Fine byte OVERSCAN_LINES equ 30 X_Fine_Res equ 10 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; seg Code org $f000 Start CLEAN_START lda #20 sta Player0_X lda #1 sta Player0_X_Fine ; ----------------------------------------------- NextFrame VERTICAL_SYNC ; 3 lines ; ----------------------------------------------- ; 0. Vertical Blank Area. Add game logic here ; ----------------------------------------------- ldx #36 lda #$0 ; mask last line when we turn on again WaitVBlank sta WSYNC sta COLUBK dex bne WaitVBlank lda #0 sta VBLANK ; ; we are now at scanline = 36, pixel pos = -32 (next kernel will close this scanline) Kernel1_SetX ; the next two scanlines position the player horizontally lda Player0_X ; get X coordinate ldx #0 ; player 0 jsr SetHorizPos ; set coarse offset sta WSYNC ; sync w/ scanline sta HMOVE ; apply fine offsets lda #$0E sta COLUBK nop nop nop nop nop nop nop nop sta HMCLR ; reset fine position ; we are now at scanline = 38 Kernel2_DrawP0 ldy #8 P0Loop lda P0_Bitmap-1,y ; lookup color sta WSYNC ; sync w/ scanline sta HMOVE ; apply fine offsets sta GRP0 ; store bitmap lda P0_Color-1,y ; lookup color sta COLUP0 ; store color dey ; decrement X bne P0Loop ; we are now at scanline = 46 Kernel3_Setup ldx #228-46 Kernel3_Loop sta WSYNC sta HMOVE dex bne Kernel3_Loop JoystickMovePlayer sec ldx Player0_X_Fine bit SWCHA bvs .CheckRight dex bmi .DecPlayer0_X jmp .CheckRight .DecPlayer0_X dec Player0_X ldx #X_Fine_Res-1 .CheckRight stx Player0_X_Fine bit SWCHA bmi .SkipMoveRight clc inx cpx #X_Fine_Res beq .IncrPlayer0_X jmp .SkipMoveRight .IncrPlayer0_X inc Player0_X ldx #0 .SkipMoveRight stx Player0_X_Fine ;inc Player0_X ;lda Player0_X ;cmp #120 ;beq resetPlayer0_X jmp OverScanArea resetPlayer0_X lda #0 sta Player0_X OverScanArea ; ----------------------------------------------- ; Overscan area. Add game logic here ; ----------------------------------------------- sta WSYNC ; close previous kernel lda #2 ; turn on VBLANK sta VBLANK ldx #OVERSCAN_LINES NextOverscanLine sta WSYNC dex bne NextOverscanLine jmp NextFrame ; ----------------------------------------------- ; SUBROUTINE SECTION ; ----------------------------------------------- ; SetHorizPos - Sets the horizontal position of an object. ; The X register contains the index of the desired object: ; X=0: player 0 ; X=1: player 1 ; X=2: missile 0 ; X=3: missile 1 ; X=4: ball ; This routine does a WSYNC and HMOVE before executing, ; so whatever you do here will not take effect until you ; call the routine again or do your own WSYNC and HMOVE. SetHorizPos sta WSYNC sta HMOVE sec SetHorizPosLoop sbc #15 bcs SetHorizPosLoop eor #7 ; calculate fine offset asl asl asl asl sta RESP0,x ; fix coarse position sta HMP0,x ; set fine offset rts P0_Bitmap .byte #0 .byte #%11111111 .byte #%10000001 .byte #%10000001 .byte #%11111111 .byte #%10000001 .byte #%10000001 .byte #%11111111 P0_Color .byte #0 .byte #$AE .byte #$AC .byte #$A8 .byte #$AC .byte #$8E .byte #$8E .byte #$98 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Epilogue org $fffc .word Start ; reset vector .word Start ; BRK vector Quote Link to comment Share on other sites More sharing options...
DEBRO Posted February 6 Share Posted February 6 Hi there, This might help. https://forums.atariage.com/topic/308513-a-working-horizontal-positioning-routine/?do=findComment&comment=4578560 Quote Link to comment Share on other sites More sharing options...
lucienEn Posted February 6 Author Share Posted February 6 (edited) Your spreadsheet is linear and for some reason I'm not seeing that. Seems a bug somewhere in the code. Need to look at some other sample code but so far haven't seen any code where x corresponds to full 0..159 range. Edited February 6 by lucienEn Quote Link to comment Share on other sites More sharing options...
DEBRO Posted February 6 Share Posted February 6 Hi there, 10 hours ago, lucienEn said: Your spreadsheet is linear and for some reason I'm not seeing that. Seems a bug somewhere in the code. Need to look at some other sample code but so far haven't seen any code where x corresponds to full 0..159 range. Here is a link to my github with numerous ROM reverse engineered that may help. There is also code posted here on AA from fellow homebrew authors that may help as well. I haven't run your example so hopefully I'm following you. A value less than 14 should position your sprite in the color clock range of 63 - 76. A value of 5 should place you at color clock 68. The problem I see with anything greater than 134 is the RTS. Anything greater than 134 will hit the RESPx at cycle 68. You would only have 8 cycles left before the next scanline. The HMPx and RTS push this into the next scanline, returning at cycle 2 which then a WSYNC is called to wait for the next scanline. If you are going to use this in a subroutine; the lowest you can use is 134 which will place your sprite to color clock 197. If you want full range then I would suggest not using the subroutine or doing a table lookup for the fine motion adjustment. Using a table lookup for the fine motion would save ~6 cycles. 1 1 Quote Link to comment Share on other sites More sharing options...
lucienEn Posted February 6 Author Share Posted February 6 (edited) 2 hours ago, DEBRO said: Hi there, Here is a link to my github with numerous ROM reverse engineered that may help. There is also code posted here on AA from fellow homebrew authors that may help as well. I haven't run your example so hopefully I'm following you. A value less than 14 should position your sprite in the color clock range of 63 - 76. A value of 5 should place you at color clock 68. The problem I see with anything greater than 134 is the RTS. Anything greater than 134 will hit the RESPx at cycle 68. You would only have 8 cycles left before the next scanline. The HMPx and RTS push this into the next scanline, returning at cycle 2 which then a WSYNC is called to wait for the next scanline. If you are going to use this in a subroutine; the lowest you can use is 134 which will place your sprite to color clock 197. If you want full range then I would suggest not using the subroutine or doing a table lookup for the fine motion adjustment. Using a table lookup for the fine motion would save ~6 cycles. Thanks. Yes > 134 is clear why that causes extra scanline. Regarding smaller values it seems it's because the SetHorizPos is not strobing RESP in the visible area (and in my case I have HMOVE in SetHorizPos). See below. in that case you get wrong results and not 100% why it then adds extra 5. But that means I'd need another subroutine indeed if I wanted full range. It seems lowest contiguous x value would be to strobe at pixelpos=0 (68 colorclk) with HM=-7 whch results then in x = 0 + 3x3 (STA RESP) +5 -7 = 7. X RESP Start End PixelPos HMP Position Shown 03 -11 1 (+5) -3 8? Instead of 3 .. 05 -11 1 (+5) -1 10 .. 13 -11 (+5) 1 (+5) +7 18 ?? Shouldn't this be 13? 14 -11 1 (+5) +8 19 ?? Shouldn't this be 14? 15 4 16 (+5) -6 15 Edited February 6 by lucienEn Quote Link to comment Share on other sites More sharing options...
DEBRO Posted February 6 Share Posted February 6 1 hour ago, lucienEn said: Regarding smaller values it seems it's because the SetHorizPos is not strobing RESP in the visible area (and in my case I have HMOVE in SetHorizPos). Oh my...I missed that! Yes, the HMOVE appears to be affecting this. You can do a SLEEP 3 to avoid the HMOVE or if you need it; adjust your horizontal minimum so the RESPx hits within the visible screen. Quote Link to comment Share on other sites More sharing options...
lucienEn Posted February 6 Author Share Posted February 6 Just now, DEBRO said: Oh my...I missed that! Yes, the HMOVE appears to be affecting this. You can do a SLEEP 3 to avoid the HMOVE or if you need it; adjust your horizontal minimum so the RESPx hits within the visible screen. Thanks! Yes that causes the visible area to be smaller and not yet understanding the effect of strobing in the HBLANK area. I got this working now but not the prettiest code perhaps with the x adj. and it takes now 2 scanlines for positioning: processor 6502 include "vcs.h" include "macro.h" seg.u Variables org $80 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Variables and Constants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Player0_X byte Player0_X_Fine byte OVERSCAN_LINES equ 30 X_Fine_Res equ 5 adjSetHorizPos equ 6 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; seg Code org $f000 Start CLEAN_START lda #120 sta Player0_X lda #1 sta Player0_X_Fine ; ----------------------------------------------- NextFrame VERTICAL_SYNC ; 3 lines ; ----------------------------------------------- ; 0. Vertical Blank Area. Add game logic here ; ----------------------------------------------- ldx #36 lda #$0 ; mask last line when we turn on again WaitVBlank sta WSYNC sta COLUBK dex bne WaitVBlank lda #0 sta VBLANK ; ; we are now at scanline = 36, pixel pos = -32 sta WSYNC sta HMOVE lda #$0E sta COLUBK ; we are now at scanline = 37 Kernel1_SetX ; the next two scanlines position the player horizontally lda Player0_X ; get X coordinate ldx #0 ; player 0 jsr SetHorizPos ; set coarse offset ; wait at least 24-9 (HMOVE) cycles to finish fine positioning nop nop nop nop nop nop nop sta HMCLR ; reset fine position for next scanline ; we are now at scanline = 39 Kernel2_DrawP0 ldy #8 P0Loop lda P0_Bitmap-1,y ; lookup color sta WSYNC ; sync w/ scanline sta HMOVE ; apply fine offsets sta GRP0 ; store bitmap lda P0_Color-1,y ; lookup color sta COLUP0 ; store color dey ; decrement X bne P0Loop ; we are now at scanline = 47 Kernel3_Setup ldx #228-36-2-1-8 Kernel3_Loop sta WSYNC sta HMOVE dex bne Kernel3_Loop JoystickMovePlayer sec ldx Player0_X_Fine bit SWCHA bvs .CheckRight dex bmi .DecPlayer0_X jmp .CheckRight .DecPlayer0_X dec Player0_X ldx #X_Fine_Res-1 .CheckRight stx Player0_X_Fine bit SWCHA bmi .SkipMoveRight clc inx cpx #X_Fine_Res beq .IncrPlayer0_X jmp .SkipMoveRight .IncrPlayer0_X inc Player0_X ldx #0 .SkipMoveRight stx Player0_X_Fine jmp OverScanArea resetPlayer0_X lda #0 sta Player0_X OverScanArea ; ----------------------------------------------- ; Overscan area. Add game logic here ; ----------------------------------------------- sta WSYNC ; close previous kernel lda #2 ; turn on VBLANK sta VBLANK ldx #OVERSCAN_LINES NextOverscanLine sta WSYNC dex bne NextOverscanLine jmp NextFrame ; ----------------------------------------------- ; SUBROUTINE SECTION ; ----------------------------------------------- SetHorizPos sec sbc #adjSetHorizPos sta WSYNC sta HMOVE sec SetHorizPosLoop sbc #15 bcs SetHorizPosLoop eor #7 ; calculate fine offset asl asl asl asl sta HMP0 ; set fine offset sta RESP0 ; fix coarse position sta WSYNC sta HMOVE rts P0_Bitmap .byte #0 .byte #%11111111 .byte #%10000001 .byte #%10000001 .byte #%11111111 .byte #%10000001 .byte #%10000001 .byte #%11111111 P0_Color .byte #0 .byte #$AE .byte #$AC .byte #$A8 .byte #$AC .byte #$8E .byte #$8E .byte #$98 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; org $fffc .word Start ; reset vector .word Start ; BRK vector Quote Link to comment Share on other sites More sharing options...
lucienEn Posted February 6 Author Share Posted February 6 Here's a modified SetHorizPos routine that works for any object for full 0..160 range but it will require 2 scanlines: adjSetHorizPos equ 6 ; ----------------------------------------------- ; SUBROUTINE SECTION ; ----------------------------------------------- ; SetHorizPos - Sets the horizontal position of an object. ; The X register contains the index of the desired object: ; X=0: player 0 ; X=1: player 1 ; X=2: missile 0 ; X=3: missile 1 ; X=4: ball ; This routine does a WSYNC and HMOVE before executing, ; so whatever you do here will not take effect until you ; call the routine again or do your own WSYNC and HMOVE. SetHorizPos sec sbc #adjSetHorizPos sec sta WSYNC sta HMOVE SetHorizPosLoop sbc #15 bcs SetHorizPosLoop eor #7 ; calculate fine offset asl asl asl asl sta HMP0,X ; set fine offset sta RESP0,X ; fix coarse position sta WSYNC sta HMOVE ; wait at least 24-3 (HMOVE) cycles to finish fine positioning nop nop nop nop nop nop nop nop nop nop nop sta HMCLR ; reset fine position for next scanline rts Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted February 6 Share Posted February 6 ; ----------------------------------------------- ; SUBROUTINE SECTION ; ----------------------------------------------- SetHorizPos sec sbc #adjSetHorizPos sta WSYNC sta HMOVE ; 3 3 sec ; 2 5 SetHorizPosLoop sbc #15 ; 2 7 bcs SetHorizPosLoop ; 2 9 (3 10 if taken) eor #7 ; 2 11 calculate fine offset asl ; 2 13 asl ; 2 15 asl ; 2 17 asl ; 2 19 sta HMP0 ; 3 21 set fine offset sta RESP0 ; 3 24 fix coarse position sta WSYNC sta HMOVE rts From 8.0 Horizontal Motion in the Stella Programmer's Guide: This is probably your issue for positions < 14 as RESP0 would be strobed 21 cycles after your first HMOVE. Quote Link to comment Share on other sites More sharing options...
+splendidnut Posted February 6 Share Posted February 6 Why are you doing STA HMOVE both before and after positioning? You only need to do it after. Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted February 6 Share Posted February 6 You might like to check out my tutorial, which covers writing a complete 2K game from scratch. Step 4 of the tutorial is when object positioning comes into play. It uses this subroutine: ;=============================================================================== ; PosObject ;---------- ; subroutine for setting the X position of any TIA object ; when called, set the following registers: ; A - holds the X position of the object ; X - holds which object to position ; 0 = player0 ; 1 = player1 ; 2 = missile0 ; 3 = missile1 ; 4 = ball ; the routine will set the coarse X position of the object, as well as the ; fine-tune register that will be used when HMOVE is used. ;=============================================================================== PosObject: sec sta WSYNC DivideLoop sbc #15 ; 2 2 - each time thru this loop takes 5 cycles, which is bcs DivideLoop ; 2 4 - the same amount of time it takes to draw 15 pixels eor #7 ; 2 6 - The EOR & ASL statements convert the remainder asl ; 2 8 - of position/15 to the value needed to fine tune asl ; 2 10 - the X position asl ; 2 12 asl ; 2 14 sta.wx HMP0,X ; 5 19 - store fine tuning of X sta RESP0,X ; 4 23 - set coarse X position of object rts ; 6 29 Which is called by this loop in Step 4 to position the players: ;=============================================================================== ; PositionObjects ; -------------- ; Updates TIA for X position of all objects ; Updates Kernel variables for Y position of all objects ;=============================================================================== PositionObjects: ldx #1 ; position objects 0-1: player0 and player1 POloop lda ObjectX,x ; get the object's X position jsr PosObject ; set coarse X position and fine-tune amount dex ; DEcrement X bpl POloop ; Branch PLus so we position all objects sta WSYNC ; wait for end of scanline sta HMOVE ; use fine-tune values to set final X positions Later on in the tutorial that loop becomes this as missiles and ball are added into the mix: ;=============================================================================== ; PositionObjects ; -------------- ; Updates TIA for X position of all objects ; Updates Kernel variables for Y position of all objects ;=============================================================================== PositionObjects: ldx #4 ; position all objects POloop lda ObjectX,x ; get the object's X position jsr PosObject ; set coarse X position and fine-tune amount dex ; DEcrement X bpl POloop ; Branch PLus so we position all objects sta WSYNC ; wait for end of scanline sta HMOVE ; use fine-tune values to set final X positions Quote Link to comment Share on other sites More sharing options...
lucienEn Posted February 6 Author Share Posted February 6 1 hour ago, SpiceWare said: You might like to check out my tutorial, which covers writing a complete 2K game from scratch. Step 4 of the tutorial is when object positioning comes into play. It uses this subroutine: ;=============================================================================== ; PosObject ;---------- ; subroutine for setting the X position of any TIA object ; when called, set the following registers: ; A - holds the X position of the object ; X - holds which object to position ; 0 = player0 ; 1 = player1 ; 2 = missile0 ; 3 = missile1 ; 4 = ball ; the routine will set the coarse X position of the object, as well as the ; fine-tune register that will be used when HMOVE is used. ;=============================================================================== PosObject: sec sta WSYNC DivideLoop sbc #15 ; 2 2 - each time thru this loop takes 5 cycles, which is bcs DivideLoop ; 2 4 - the same amount of time it takes to draw 15 pixels eor #7 ; 2 6 - The EOR & ASL statements convert the remainder asl ; 2 8 - of position/15 to the value needed to fine tune asl ; 2 10 - the X position asl ; 2 12 asl ; 2 14 sta.wx HMP0,X ; 5 19 - store fine tuning of X sta RESP0,X ; 4 23 - set coarse X position of object rts ; 6 29 Which is called by this loop in Step 4 to position the players: ;=============================================================================== ; PositionObjects ; -------------- ; Updates TIA for X position of all objects ; Updates Kernel variables for Y position of all objects ;=============================================================================== PositionObjects: ldx #1 ; position objects 0-1: player0 and player1 POloop lda ObjectX,x ; get the object's X position jsr PosObject ; set coarse X position and fine-tune amount dex ; DEcrement X bpl POloop ; Branch PLus so we position all objects sta WSYNC ; wait for end of scanline sta HMOVE ; use fine-tune values to set final X positions Later on in the tutorial that loop becomes this as missiles and ball are added into the mix: ;=============================================================================== ; PositionObjects ; -------------- ; Updates TIA for X position of all objects ; Updates Kernel variables for Y position of all objects ;=============================================================================== PositionObjects: ldx #4 ; position all objects POloop lda ObjectX,x ; get the object's X position jsr PosObject ; set coarse X position and fine-tune amount dex ; DEcrement X bpl POloop ; Branch PLus so we position all objects sta WSYNC ; wait for end of scanline sta HMOVE ; use fine-tune values to set final X positions That's the code where I get the subroutine from, except I wanted to add HMOVE to blank out left side throughout the entire screen (to make it easier to remove comb effect). With HMOVE added I needed to adjust the routine timing to make it work. The issue it failed < 14, was that it was strobing RESP in the invisible screen area and that causes incorrect positioning (not sure what then really happens). The extra CPU cycles needed for HMCLR is explained in TIA HW notes but that wasn't the issue here as I had enough NOP in there. Quote Link to comment Share on other sites More sharing options...
lucienEn Posted February 6 Author Share Posted February 6 1 hour ago, splendidnut said: Why are you doing STA HMOVE both before and after positioning? You only need to do it after. Easy way to blank out entire left side of the screen without requiring to draw some ball object or other. Unless there's an even easier way. Quote Link to comment Share on other sites More sharing options...
+splendidnut Posted February 6 Share Posted February 6 3 minutes ago, lucienEn said: Easy way to blank out entire left side of the screen without requiring to draw some ball object or other. Unless there's an even easier way. Yeah, I just saw that in your other response. That makes sense. Quote Link to comment Share on other sites More sharing options...
+SpiceWare Posted February 6 Share Posted February 6 32 minutes ago, lucienEn said: With HMOVE added I needed to adjust the routine timing to make it work. The issue it failed < 14, was that it was strobing RESP in the invisible screen area and that causes incorrect positioning (not sure what then really happens). That'd be due to strobing RESP0 too soon after HMOVE was strobed. You might be able add 160 to the position for anything < 14, that would wrap around to the position you want. You'll probably need to use multiple reposition routines. An example would be here, though do note this example is using early HMOVE (cycle 73) which eliminates the HMOVE bars. That might also be an option - use early HMOVE, then you don't have to worry about hiding HMOVE bars on the left. Of course early HMOVe wouldn't help help if you were planning to use those 8 pixels to implement smooth on/off screen movement of objects. Quote Link to comment Share on other sites More sharing options...
lucienEn Posted February 6 Author Share Posted February 6 4 minutes ago, SpiceWare said: That'd be due to strobing RESP0 too soon after HMOVE was strobed. You might be able add 160 to the position for anything < 14, that would wrap around to the position you want. You'll probably need to use multiple reposition routines. An example would be here, though do note this example is using early HMOVE (cycle 73) which eliminates the HMOVE bars. That might also be an option - use early HMOVE, then you don't have to worry about hiding HMOVE bars on the left. Of course early HMOVe wouldn't help help if you were planning to use those 8 pixels to implement smooth on/off screen movement of objects. Thanks, yes that's what happened. But I fixed it, see my sample code above and the code you quoted. That works for 0..159 and for any object. Not prettiest code with the -6 adj. but otherwise does the job. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.