Jump to content
IGNORED

Fine player positioning: Stella 'Pixel Pos' value is different from P0 Pos. at RESP0


lucienEn

Recommended Posts

Hi,

 

Question: in Stella debugger I don't see the player position correspond to the pixel position when strobing RESP0.

See below: pixel position is 136 but the P0 position is 141. I thought maybe that's the HMP0 value but that shows as 0 below. Could that be related to VDel which is set here?

 

Also I noticed that "Pos#" is changing very often even if it doesn't go over a RESP0 strobe. For example after: STA CXCLR, STA COLUP0

 

image.thumb.jpeg.d5db9f60b718a16895d49a29159303d8.jpeg

 

Thanks,

Lucien

Link to comment
Share on other sites

Did you have a look at Andrew Towers' notes? There you will find the answer to your RESPx question.

 

Can you give an example, where Pos is changing due to e.g. CXCLR?

Note: If you put a breakpoint after it, Pos might be in red, because it has changed since the last debugger interrupt, no because of CXCLR changing it.

  • Like 1
Link to comment
Share on other sites

8 hours ago, Thomas Jentzsch said:

Did you have a look at Andrew Towers' notes? There you will find the answer to your RESPx question.

 

Can you give an example, where Pos is changing due to e.g. CXCLR?

Note: If you put a breakpoint after it, Pos might be in red, because it has changed since the last debugger interrupt, no because of CXCLR changing it.

 

Thanks that explains it! Seems most docs ignore these details. The other mystery is how blanking first 8 pixels works. Someone mentioned WSYNC+HMOVE blanks first 8 pixels but not sure why and I don't always see that working as expected (seems it must be on every line to take effect). But I believe that doc might explain it, reading that now:-).

 

Regarding changing Pos# in debugger: I'm debugging Pitfall and the position for P0 is changing a lot. For example see below.

The other weird thing is that in Pitfall I never see HMOVE set for P0 so I wonder how that's accomplished without?

 

P0 pos. before instruction:

f235: 132 (WSYNC)

f237: 132 (HMOVE)

f239: 132 (STA COLUP0)

f23b: 131 (dex)

f23c: 130 (bpl LF221)

 

image.thumb.jpeg.fa436f9dc81606b08dd7bc317616660f.jpeg

Edited by lucienEn
Link to comment
Share on other sites

13 minutes ago, lucienEn said:

Someone mentioned WSYNC+HMOVE blanks first 8 pixels but not sure why and I don't always see that working as expected (seems it must be on every line to take effect).

HMOVE hides/blanks the first eight pixels only on the lines that it is used.

 

So, IF you want to use the WSYNC+HMOVE to hide the first eight pixel column, THEN it must be done on every line.

 

Your other option to blank the first eight pixels is to set the color of one of the Player/Missiles to black and position it there.

 

Why do you want to hide the first eight pixels?  Are you just trying to avoid the HMOVE "comb" OR are you trying to use that as a clipping area (hide sprite graphics that wrap-around)?

 

Link to comment
Share on other sites

53 minutes ago, splendidnut said:

HMOVE hides/blanks the first eight pixels only on the lines that it is used.

 

So, IF you want to use the WSYNC+HMOVE to hide the first eight pixel column, THEN it must be done on every line.

 

Your other option to blank the first eight pixels is to set the color of one of the Player/Missiles to black and position it there.

 

Why do you want to hide the first eight pixels?  Are you just trying to avoid the HMOVE "comb" OR are you trying to use that as a clipping area (hide sprite graphics that wrap-around)?

 

David Crane referred to the left black lines in Air Sea Battle as a chipset bug and they avoided that issue so I'm curious proper way to do that and what's exactly causing this. You see it also in Space Invaders but different spacing.

 

In Pitfall I can see they always have WSYNC + HMOVE and that clearly works but not clear how that works since HMOVE is just described as applying all offsets. And why is it exactly 8 pixels? Does HMOVE just happen to delay drawing by 8 pixels always?

 

Regarding the jumping of Pos# in debugger, I noticed it's always outside the visible space. And 2nd why HM is always 0 might be that the debugger does not repeat HM once it is applied and instead adjust the Pos# on the next line and resets HM. At least that's the only explanation I can think off.

 

I did find the subroutine in Pitall! that computes both coarse (15 cycle loop) and fine calculations. So that seems all straightforward at least:

 

F388: 
Input: A = xPos
Output:A = fine Offset. Y = Coarse pos. (x 15 cycles)

Link to comment
Share on other sites

1 hour ago, lucienEn said:

Regarding changing Pos# in debugger: I'm debugging Pitfall and the position for P0 is changing a lot. For example see below.

The other weird thing is that in Pitfall I never see HMOVE set for P0 so I wonder how that's accomplished without?

That's because, while the coarse positioning (RESP0) is immediate, the fine positioning (HMOVE) isn't.

 

The "Tia Hardware notes" document linked above explains this in detail, but in short, each moveable object has a position counter that's only incremented during the visible part of the scanline, while it stays "dormant" during the Horizontal Blank part.

The counter wraps around in exactly 160 TIA clocks, and when it does, the object is displayed. So normally each object will show up exactly on the same horizontal position on each line.

If you write to RESP0, the counter for P0 is reset immediately, and it will show up on the new position starting from the next scanline (starting drawing isn't immediate, actually, so there's an offset of 4 pixels for Missile and Balls, 5 pixels for single size Player and 6 pixels for double and quad size players, which accounts for the differences you noted in the first post).


HMOVE works differently. When you strobe that register, the position counter receives some extra clocks during the horizontal blank (the amount of which corresponds to the value stored into the high nybble of HMP0 after inverting bit 7, and so ranges from 0 to 15 extra clocks).

Each one of these extra clocks will cause the counter to wrap around 1 pixel earlier, so it will move the object 1 pixel to the left. In fact this mechanism alone would only allow for movement to the left. That's why when you strobe HMOVE, in addition to providing the extra clocks, the Horizontal blank is extended by 8 pixels, which cause all the objects to move to the right by the same amount.

The combination of the 0 to 15 pixels to the left caused by the extra clocks, and 8 pixels to the right for the extended HBLANK results in the -7 to +8 pixels fine positioning.

 

During HBLANK, the estimated position in the debugger TIA tab is updated as the counter receives the extra clocks. (for each clock, the position is decreased by one, and each clock happens every 4 TIA clks, or 1.33 CPU cycles) and at the end of HBLANK, the position is increased by 8 again because of the extended Blank period. that's why you see it change, even when HMP0 is set to 0 (which corresponds to 8 extra clocks).

 

Edited by alex_79
  • Like 1
Link to comment
Share on other sites

1 minute ago, lucienEn said:

David Crane referred to the left black lines in Air Sea Battle as a chipset bug

I wouldn't call it a bug. It's just one of the many compromises done while designing the TIA to obtain the functionality required as efficiently and cost effectively as possible.

  • Like 1
Link to comment
Share on other sites

1 hour ago, alex_79 said:

That's because, while the coarse positioning (RESP0) is immediate, the fine positioning (HMOVE) isn't.

 

The "Tia Hardware notes" document linked above explains this in detail, but in short, each moveable object has a position counter that's only incremented during the visible part of the scanline, while it stays "dormant" during the Horizontal Blank part.

The counter wraps around in exactly 160 TIA clocks, and when it does, the object is displayed. So normally each object will show up exactly on the same horizontal position on each line.

If you write to RESP0, the counter for P0 is reset immediately, and it will show up on the new position starting from the next scanline (starting drawing isn't immediate, actually, so there's an offset of 4 pixels for Missile and Balls, 5 pixels for single size Player and 6 pixels for double and quad size players, which accounts for the differences you noted in the first post).


HMOVE works differently. When you strobe that register, the position counter receives some extra clocks during the horizontal blank (the amount of which corresponds to the value stored into the high nybble of HMP0 after inverting bit 7, and so ranges from 0 to 15 extra clocks).

Each one of these extra clocks will cause the counter to wrap around 1 pixel earlier, so it will move the object 1 pixel to the left. In fact this mechanism alone would only allow for movement to the left. That's why when you strobe HMOVE, in addition to providing the extra clocks, the Horizontal blank is extended by 8 pixels, which cause all the objects to move to the right by the same amount.

The combination of the 0 to 15 pixels to the left caused by the extra clocks, and 8 pixels to the right for the extended HBLANK results in the -7 to +8 pixels fine positioning.

 

During HBLANK, the estimated position in the debugger TIA tab is updated as the counter receives the extra clocks. (for each clock, the position is decreased by one, and each clock happens every 4 TIA clks, or 1.33 CPU cycles) and at the end of HBLANK, the position is increased by 8 again because of the extended Blank period. that's why you see it change, even when HMP0 is set to 0 (which corresponds to 8 extra clocks).

 

Thanks very much, those details I just missed in few books and docs I've seen. I found Pitfall! code where the positioning is done and looking at Stella debugger it applies the HMOVE offset at the next scanline to the P0 pos (and HM stays but is reset later in this code):

 

Lf1cc
    sta     WSYNC                   ;3   =   3  * Kernel loop set sprite x, calc. in f388 and stored in 95/98 for Harry
    sta     HMOVE                   ;3         * Example: x=38 (ram_E1); fine=E0 (+2 in ram_95), coarse=2 (ram_98)
    ldy     #$00                    ;2         * Pos#=34;PixelPos=-59   
    sty     ram_F6,x                ;4         * temp memory
    ldy     ram_98,x                ;4         * Pos#=31;PixelPos Load coarse position (x=0: harry)
    bne     Lf1e2                   ;2/3       * Pos#=28; if pos = 0 then reset
;---------------- RESET POSITION IF pos. = 0
    ldy     #$60                    ;2         * Reset position
    sty     ram_F6,x                ;4         *
    sta     RESP0,x                 ;4         * Start pos. new screen
    sta     HMBL                    ;3         *
    bne     Lf1ea                   ;2/3 =  30 *
;---------------- SET COARSE POSITION
Lf1e2		; 15 cycle loop to set coarse position (here: 2 loops). PixelPos start: -20
    dey                             ;2         * Pos#=26 (2x +6)
    bne     Lf1e2                   ;2/3       * Pos#=34 (+9 +6 for last)
    sta.w   HMBL                    ;4         * PixelPos=7 (-20+6+6+9+6)   Filler +12?
    sta     RESP0,x                 ;4   =  12 * PixelPos=19
;---------------- SET FINE POSITION (next scanline)
Lf1ea
    sta     WSYNC                   ;3   =   3 * Pos#=36 (PixelPos=31 + 5 for P0)
    sta     HMOVE                   ;3        
    dex                             ;2        
    bpl     Lf1b0                   ;2/3      
    jsr     Lf3a6                   ;6        
    lda     ram_95                  ;3         * Pos#=36; Harry fine position  
    sta     HMP0                    ;3        
    lda     ram_96                  ;3        
    sta     HMP1                    ;3         * Pos#=36;HM=E0     
    sta     WSYNC                   ;3   =  28
;---------------------------------------
    sta     HMOVE                   ;3         *
    jsr     Lf3a6                   ;6         * After: Pos#=33;HM=E0 (outside of visible scanline, settles at 38 at PixelPos=16)
    lda     ram_F6                  ;3         * Pos#=38
    sta     HMP0                    ;3        
    lda     ram_F7                  ;3        
    sta     HMP1                    ;3        
    lda     ram_E9                  ;3        
    clc                             ;2        
    adc     ram_F2                  ;3        
    adc     #$15                    ;2        
    tay                             ;2        
    lda     ram_E5                  ;3        
    sta     REFP0                   ;3        
    sta     WSYNC                   ;3   =  42
    sta     HMOVE                   ;3        


 

Edited by lucienEn
Added comments values
Link to comment
Share on other sites

There's a much simpler way to do this:

SetHorizPos
    sta WSYNC    ; start a new line
    sec        ; set carry flag
DivideLoop
    sbc #15        ; subtract 15
    bcs DivideLoop    ; branch until negative
    eor #7        ; calculate fine offset
    asl
    asl
    asl
    asl
    sta RESP0,x    ; fix coarse position
    sta HMP0,x    ; set fine offset
    rts        ; return to caller

 

Set X to the sprite you want to set the position for, and A to the actual XPOS, and it will do it for you with a simple JSR after doing those.

  • Thanks 1
Link to comment
Share on other sites

25 minutes ago, Ecernosoft said:

There's a much simpler way to do this:

SetHorizPos
    sta WSYNC    ; start a new line
    sec        ; set carry flag
DivideLoop
    sbc #15        ; subtract 15
    bcs DivideLoop    ; branch until negative
    eor #7        ; calculate fine offset
    asl
    asl
    asl
    asl
    sta RESP0,x    ; fix coarse position
    sta HMP0,x    ; set fine offset
    rts        ; return to caller

 

Set X to the sprite you want to set the position for, and A to the actual XPOS, and it will do it for you with a simple JSR after doing those.

That's nice and compact code. Only thing missing might be HMOVE after WSYNC but I assume not needed for that scanline since it's not drawing anything? I'll give that a try, thanks.

 

In Pitfall! the logic for calculating and setting is split. The calculation is every 2-3 frames only for P0 & P1. See below code in Pitfall! for that calc.

Haven't analyzed that but it's doing some extra stuff it seems (although end result should be same so not sure why it's doing the modulus 15):

 

; subroutine to compute fine and coarse offsets for x in Pitfall!

Lf388                                ; A = X position player
    tay                               ;2        
    iny                               ;2        
    tya                               ;2        
    and     #$0f                  ;2        
    sta     ram_F6               ;3        
    tya                               ;2        
    lsr                                ;2        
    lsr                                ;2        
    lsr                                ;2        
    lsr                                ;2        
    tay                               ;2        
    clc                                ;2        
    adc     ram_F6               ;3        
    cmp     #$0f                  ;2        
    bcc     Lf39f                   ;2/3      
    sbc     #$0f                    ;2        
    iny                                ;2   =  36
Lf39f
    eor     #$07                   ;2        
    asl                                ;2        
    asl                                ;2        
    asl                                ;2        
    asl                                ;2        
    rts                                ;6   =  16
 
Edited by lucienEn
Link to comment
Share on other sites

3 minutes ago, lucienEn said:

Only thing missing might be HMOVE after WSYNC

 

That routine's often used to set all the object positions, after which a single HMOVE is done.  From my tutorial:

 

;===============================================================================
; 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

 

 

You might find the tutorial useful as I put in way more comments that I normally would, such as:

 

;===============================================================================
; 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.
;
; Note: The X position differs based on the object, for player0 and player1
;       0 is the leftmost pixel while for missile0, missile1 and ball 1 is
;       the leftmost pixel:
;           players     - X range is 0-159
;           missiles    - X range is 1-160
;           ball        - X range is 1-160
; Note: Setting players to double or quad size will affect the position of
;       the players.
;===============================================================================
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

 

 

  • Thanks 1
Link to comment
Share on other sites

40 minutes ago, lucienEn said:

I understand HMOVE is needed after that but wasn't sure if every line needs to have it to get the blanking of first 8 pixels. I'm seeing some odd behavior when I don't have it on every scanline.

 

If you want those pixels blanked then do an HMOVE on every scanline.  You can see that in Pitfall by switching to Debug Colors - the 8 pixels hidden by HMOVE are drawn in white:

 

image.thumb.png.42e0e569c5de4226cd32a5f34cdf993e.png

 

 

In Boxing they also used the BALL object to hide the 8 pixels on some scanlines:

 

910380479_ScreenShot2022-12-23at3_42_39PM.thumb.png.649b670467a2fc924dea1b7c976bca7b.png

 

If you disable the BALL you can see what it's hiding:

 

47106521_ScreenShot2022-12-23at3_42_46PM.thumb.png.23d0cc68a83ceda282becb66e3fe2639.png

 

Link to comment
Share on other sites

4 hours ago, lucienEn said:

That's nice and compact code. Only thing missing might be HMOVE after WSYNC but I assume not needed for that scanline since it's not drawing anything? I'll give that a try, thanks.

 

that can be fixed with STA WSYNC.

 

By the way, anyone have a good PF + Multisprite kernal? In case I make any more 2600 games.

Link to comment
Share on other sites

10 minutes ago, glurk said:

A commented Pitfall! disassembly (by Thomas Jentzsch) is available here:

 

http://www.bjars.com/source/Pitfall.asm

 

It might be worth studying, as the game has many (9) kernel sections, and the positioning is done in various ways to suit the needs of the specific sections.

 

Yes I looked at that earlier. For some reason that one is not running correctly for me (no jumping and no collision detection). But good to understand what the variables mean.

Link to comment
Share on other sites

4 minutes ago, Ecernosoft said:

that can be fixed with STA WSYNC.

 

By the way, anyone have a good PF + Multisprite kernal? In case I make any more 2600 games.

I don't but I'm thinking there must be a way to create a nice UI tool that can automate the kernels generation. Where you can designate display bands and set properties like static (playfield+background editor)/dynamic sprites etc. If I look at Circus Convoy I'm thinking there must have been some tools as well to help with the numerous multi-colored overlapping sprites.

Edited by lucienEn
Link to comment
Share on other sites

10 minutes ago, SpiceWare said:

 

If you want those pixels blanked then do an HMOVE on every scanline.  You can see that in Pitfall by switching to Debug Colors - the 8 pixels hidden by HMOVE are drawn in white:

 

image.thumb.png.42e0e569c5de4226cd32a5f34cdf993e.png

 

 

In Boxing they also used the BALL object to hide the 8 pixels on some scanlines:

 

 

I've seen that but finally understanding now why some lines don't show HM and some do (in Pitfall it's for all). It's simply those lines where HMOVE was applied for the scanline.

Link to comment
Share on other sites

52 minutes ago, alex_79 said:

Add the line

TIA_BASE_READ_ADDRESS = $30

at the beginning of the disassembly, so it will assemble correctly with the "vcs.h" file from a current version of dasm.

I just figured reason why it didn't work for me correctly. I can't just add HMOVE on every line, I also need to make sure I do HMCLR unless there is really a fine positioning needed.

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...