Sonic 3's Object Manager in Sonic 1

Discussion in 'Discussion and Q&A Archive' started by ProjectFM, Mar 5, 2016.

  1. ProjectFM

    ProjectFM Optimistic and self-dependent Member

    Joined:
    Oct 4, 2014
    Messages:
    912
    Location:
    Orono, Maine
    I've just spent a good amount of time porting Sonic 3's Object Manager to my Hivebrain disassembly using this guide. Unfortunately, starting any level with objects that spawn when the level loads (i.e. Sonic and boss objects) will result in multiple address errors and I assume the cause is the object manager code I'm using so here it is:
    Code:
    ; ---------------------------------------------------------------------------
    ; Objects Manager
    ; Subroutine to load objects whenever they are close to the screen. Unlike in
    ; normal s2, in this version every object gets an entry in the respawn table.
    ; This is necessary to get the additional y-range checks to work.
    ;
    ; input variables:
    ;  -none-
    ;
    ; writes:
    ;  d0, d1, d2
    ;  d3 = upper boundary to load object
    ;  d4 = lower boundary to load object
    ;  d5 = #$FFF, used to filter out object's y position
    ;  d6 = camera position
    ;
    ;  a0 = address in object placement list
    ;  a3 = address in object respawn table
    ;  a6 = object loading routine
    ; ---------------------------------------------------------------------------
    ; loc_17AA4
    ObjPosLoad:
            moveq    #0,d0
            move.b    ($FFFFF76C).w,d0
            jmp    ObjPosLoad_States(pc,d0.w)
    ; ============== JUMP TABLE    =============================================
    ObjPosLoad_States:
            bra.w    ObjPosLoad_Init        ; 0
            bra.w    ObjPosLoad_Main        ; 2
            bra.w    ObjPosLoad_Main        ; 4
            rts
    ; ============== END JUMP TABLE    =============================================
    ObjPosLoad_Init:
            addq.b    #4,($FFFFF76C).w
    
            lea     ($FFFFF000).w,a0
            moveq   #0,d0
            move.w  #$1F,d1 ; set loop counter
    OPL1:
            move.l  d0,(a0)+
            dbf     d1,OPL1
            move.w    ($FFFFFE10).w,d0
    ;
    ;    ror.b    #1,d0            ; this is from s3k
    ;    lsr.w    #5,d0
    ;    lea    (ObjPos_Index).l,a0
    ;    movea.l    (a0,d0.w),a0
    ;
            lsl.b    #6,d0
            lsr.w    #4,d0
            lea    (ObjPos_Index).l,a0    ; load the first pointer in the object layout list pointer index,
            adda.w    (a0,d0.w),a0        ; load the pointer to the current object layout
            ; initialize each object load address with the first object in the layout
            move.l    a0,($FFFFF770).w
            move.l    a0,($FFFFF774).w
            adda.w    2(a1,d0.w),a1
            lea    ($FFFFF000).w,a3
            move.w    ($FFFFF700).w,d6
            subi.w    #$80,d6    ; look one chunk to the left
            bcc.s    OPL2    ; if the result was negative,
            moveq    #0,d6    ; cap at zero
    OPL2:    andi.w    #$FF80,d6    ; limit to increments of $80 (width of a chunk)
            movea.l    ($FFFFF770).w,a0    ; get first object in layout
    OPL3:    ; at the beginning of a level this gives respawn table entries to any object that is one chunk
            ; behind the left edge of the screen that needs to remember its state (Monitors, Badniks, etc.)
            cmp.w    (a0),d6        ; is object's x position >= d6?
            bls.s    OPL4        ; if yes, branch
            addq.w    #6,a0    ; next object
            addq.w    #1,a3    ; respawn index of next object going right
            bra.s    OPL3
    ; ===========================================================================
    OPL4:    move.l    a0,($FFFFF770).w    ; remember rightmost object that has been processed, so far (we still need to look forward)
            move.w    a3,($FFFFF774).w    ; and its respawn table index
            lea    ($FFFFF000).w,a3    ; reset a3
            movea.l    ($FFFFF774).w,a0    ; reset a0
            subi.w    #$80,d6        ; look even farther left (any object behind this is out of range)
            bcs.s    OPL6        ; branch, if camera position would be behind level's left boundary
    OPL5:    ; count how many objects are behind the screen that are not in range and need to remember their state
            cmp.w    (a0),d6        ; is object's x position >= d6?
            bls.s    OPL6        ; if yes, branch
            addq.w    #6,a0
            addq.w    #1,a3    ; respawn index of next object going left
            bra.s    OPL5    ; continue with next object
    ; ===========================================================================
    OPL6:    move.l    a0,($FFFFF774).w    ; remember current object from the left
            move.w    a3,($FFFFF778).w    ; and its respawn table index
            move.w    #-1,($FFFFF76E).w    ; make sure ObjPosLoad_GoingForward is run
            move.w    ($FFFFF704).w,d0
            andi.w    #$FF80,d0
            move.w    d0,($FFFFFE2A).w    ; make sure the Y check isn't run unnecessarily during initialization
    ; ===========================================================================
    ObjPosLoad_Main:
            ; get coarse camera position
    
            move.w    ($FFFFF700).w,d1
            subi.w    #$80,d1
            andi.w    #$FF80,d1
            move.w    d1,($FFFFFFEA).w
            tst.w    ($FFFFF72E).w    ; does this level y-wrap?
            bpl.s    ObjMan_Main_NoYWrap    ; if not, branch
            lea    (ChkLoadObj_YWrap).l,a6    ; set object loading routine
            move.w    ($FFFFF704).w,d3
            andi.w    #$FF80,d3    ; get coarse value
            move.w    d3,d4
            addi.w    #$200,d4    ; set lower boundary
            subi.w    #$80,d3        ; set upper boundary
            bpl.s    OPL7        ; branch, if upper boundary > 0
            andi.w    #$7FF,d3    ; wrap value
            bra.s    ObjMan_Main_Cont
    ; ===========================================================================
    OPL7:    move.w    #$7FF,d0
            addq.w    #1,d0
            cmp.w    d0,d4
            bls.s    OPL8        ; branch, if lower boundary < $7FF
            andi.w    #$7FF,d4    ; wrap value
            bra.s    ObjMan_Main_Cont
    ; ===========================================================================
    ObjMan_Main_NoYWrap:
            move.w    ($FFFFF704).w,d3
            andi.w    #$FF80,d3    ; get coarse value
            move.w    d3,d4
            addi.w    #$200,d4    ; set lower boundary
            subi.w    #$80,d3        ; set upper boundary
            bpl.s    OPL8
            moveq    #0,d3    ; no negative values allowed
    OPL8:    lea    (ChkLoadObj).l,a6    ; set object loading routine
    ObjMan_Main_Cont:
            move.w    #$FFF,d5    ; this will be used later when we load objects
            move.w    ($FFFFF700).w,d6
            andi.w    #$FF80,d6
            cmp.w    ($FFFFF76E).w,d6    ; is the X range the same as last time?
            beq.w    ObjPosLoad_SameXRange    ; if yes, branch
            bge.s    ObjPosLoad_GoingForward    ; if new pos is greater than old pos, branch
            ; if the player is moving back
            move.w    d6,($FFFFF76E).w    ; remember current position for next time
            movea.l    ($FFFFF774).w,a0    ; get current object going left
            movea.w    ($FFFFF778).w,a3    ; and its respawn table index
            subi.w    #$80,d6            ; look one chunk to the left
            bcs.s    ObjMan_GoingBack_Part2    ; branch, if camera position would be behind level's left boundary
            jsr    (SingleObjLoad).l        ; find an empty object slot
            bne.s    ObjMan_GoingBack_Part2        ; branch, if there are none
    OPL9:    ; load all objects left of the screen that are now in range
            cmp.w    -6(a0),d6        ; is the previous object's X pos less than d6?
            bge.s    ObjMan_GoingBack_Part2    ; if it is, branch
            subq.w    #6,a0        ; get object's address
            subq.w    #1,a3        ; and respawn table index
            jsr    (a6)        ; load object
            bne.s    OPL10        ; branch, if SST is full
            subq.w    #6,a0
            bra.s    OPL9    ; continue with previous object
    ; ===========================================================================
    OPL10:    ; undo a few things, if the object couldn't load
            addq.w    #6,a0    ; go back to last object
            addq.w    #1,a3    ; since we didn't load the object, undo last change
    ObjMan_GoingBack_Part2:
            move.l    a0,($FFFFF774).w    ; remember current object going left
            move.w    a3,($FFFFF778).w    ; and its respawn table index
            movea.l    ($FFFFF770).w,a0    ; get next object going right
            movea.w    ($FFFFF774).w,a3    ; and its respawn table index
            addi.w    #$300,d6    ; look two chunks beyond the right edge of the screen
    OPL11:    ; subtract number of objects that have been moved out of range (from the right side)
            cmp.w    -6(a0),d6    ; is the previous object's X pos less than d6?
            bgt.s    OPL12        ; if it is, branch
            subq.w    #6,a0        ; get object's address
            subq.w    #1,a3        ; and respawn table index
            bra.s    OPL11    ; continue with previous object
    ; ===========================================================================
    OPL12:    move.l    a0,($FFFFF770).w    ; remember next object going right
            move.w    a3,($FFFFF774).w    ; and its respawn table index
            bra.s    ObjPosLoad_SameXRange
    ; ===========================================================================
    ObjPosLoad_GoingForward:
            move.w    d6,($FFFFF76E).w
            movea.l    ($FFFFF770).w,a0    ; get next object from the right
            movea.w ($FFFFF774).w,a3    ; and its respawn table index
            addi.w    #$280,d6    ; look two chunks forward
            jsr    (SingleObjLoad).l        ; find an empty object slot
            bne.s    ObjMan_GoingForward_Part2    ; branch, if there are none
    OPL13:    ; load all objects right of the screen that are now in range
            cmp.w    (a0),d6                ; is object's x position >= d6?
            bls.s    ObjMan_GoingForward_Part2    ; if yes, branch
            jsr    (a6)        ; load object (and get address of next object)
            addq.w    #1,a3        ; respawn index of next object to the right
            beq.s    OPL13    ; continue loading objects, if the SST isn't full
    ObjMan_GoingForward_Part2:
            move.l    a0,($FFFFF770).w    ; remember next object from the right
            move.w    a3,($FFFFF774).w    ; and its respawn table index
            movea.l    ($FFFFF774).w,a0    ; get current object from the left
            movea.w    ($FFFFF778).w,a3    ; and its respawn table index
            subi.w    #$300,d6        ; look one chunk behind the left edge of the screen
            bcs.s    ObjMan_GoingForward_End    ; branch, if camera position would be behind level's left boundary
    OPL14:    ; subtract number of objects that have been moved out of range (from the left)
            cmp.w    (a0),d6            ; is object's x position >= d6?
            bls.s    OPLXX    ; if yes, branch
            addq.w    #6,a0    ; next object
            addq.w    #1,a3    ; respawn index of next object to the left
            bra.s    OPL14    ; continue with next object
    ; ===========================================================================
    
    OPLXX:
            tst.b    $04(a0)            ; MJ: was this object a remember state?
            bpl.s    ObjMan_GoingForward_End       ; MJ: if not, branch
            subq.b    #$01,(a2)        ; MJ: move right counter back
    
    ObjMan_GoingForward_End:
            move.l    a0,($FFFFF774).w    ; remember current object from the left
            move.w    a3,($FFFFF778).w    ; and its respawn table index
    ObjPosLoad_SameXRange:
            move.w    ($FFFFF704).w,d6
            andi.w    #$FF80,d6
            move.w    d6,d3
            cmp.w    ($FFFFFE2A).w,d6    ; is the y range the same as last time?
            beq.w    ObjPosLoad_SameYRange    ; if yes, branch
            bge.s    ObjPosLoad_GoingDown    ; if the player is moving down
            ; if the player is moving up
            tst.w    ($FFFFF72E).w    ; does the level y-wrap?
            bpl.s    ObjMan_GoingUp_NoYWrap    ; if not, branch
            tst.w    d6
            bne.s    ObjMan_GoingUp_YWrap
            cmpi.w    #$80,($FFFFFE2A).w
            bne.s    ObjMan_GoingDown_YWrap
    ObjMan_GoingUp_YWrap:
            subi.w    #$80,d3            ; look one chunk up
            bpl.s    ObjPosLoad_YCheck    ; go to y check, if camera y position >= $80
            andi.w    #$7FF,d3        ; else, wrap value
            bra.s    ObjPosLoad_YCheck
    ; ===========================================================================
    ObjMan_GoingUp_NoYWrap:
            subi.w    #$80,d3                ; look one chunk up
            bmi.w    ObjPosLoad_SameYRange    ; don't do anything if camera y position is < $80
            bra.s    ObjPosLoad_YCheck
    ; ===========================================================================
    ObjPosLoad_GoingDown:
            tst.w    ($FFFFF72E).w        ; does the level y-wrap?
            bpl.s    ObjMan_GoingDown_NoYWrap    ; if not, branch
            tst.w    ($FFFFFE2A).w
            bne.s    ObjMan_GoingDown_YWrap
            cmpi.w    #$80,d6
            bne.s    ObjMan_GoingUp_YWrap
    ObjMan_GoingDown_YWrap:
            addi.w    #$180,d3        ; look one chunk down
            cmpi.w    #$7FF,d3
            bcs.s    ObjPosLoad_YCheck    ; go to  check, if camera y position < $7FF
            andi.w    #$7FF,d3        ; else, wrap value
            bra.s    ObjPosLoad_YCheck
    ; ===========================================================================
    ObjMan_GoingDown_NoYWrap:
            addi.w    #$180,d3            ; look one chunk down
            cmpi.w    #$7FF,d3
            bhi.s    ObjPosLoad_SameYRange    ; don't do anything, if camera is too close to bottom
    ObjPosLoad_YCheck:
            jsr    (SingleObjLoad).l        ; get an empty object slot
            bne.s    ObjPosLoad_SameYRange    ; branch, if there are none
            move.w    d3,d4
            addi.w    #$80,d4
            move.w    #$FFF,d5    ; this will be used later when we load objects
            movea.l    ($FFFFF774).w,a0    ; get next object going left
            movea.w    ($FFFFF778).w,a3    ; and its respawn table index
            move.l    ($FFFFF770).w,d7    ; get next object going right
            sub.l    a0,d7    ; d7 = number of objects between the left and right boundaries * 6
            beq.s    ObjPosLoad_SameYRange    ; branch if there are no objects inbetween
            addq.w    #2,a0    ; align to object's y position
    OPL15:    ; check, if current object needs to be loaded
            tst.b    (a3)    ; is object already loaded?
            bmi.s    OPL16    ; if yes, branch
            move.w    (a0),d1
            and.w    d5,d1    ; get object's y position
            cmp.w    d3,d1
            bcs.s    OPL16    ; branch, if object is out of range from the top
            cmp.w    d4,d1
            bhi.s    OPL16    ; branch, if object is out of range from the bottom
            bset    #7,(a3)    ; mark object as loaded
            ; load object
            move.w    -4(a0),8(a1)
            move.w    (a0),d1
            move.w    d1,d2
            and.w    d5,d1    ; get object's y position
            move.w    d1,$C(a1)
            rol.w    #3,d2
            andi.w    #3,d2    ; get object's render flags and status
            move.b    d2,1(a1)
            move.b    d2,$22(a1)
            move.b    4(a0),0(a1)
            move.b    3(a0),$28(a1)
            move.w    a3,$1E(a1)
            jsr    (SingleObjLoad).l    ; find new object slot
            bne.s    ObjPosLoad_SameYRange    ; brach, if there are none left
    OPL16:
            addq.w    #6,a0    ; address of next object
            addq.w    #1,a3    ; and its respawn index
            subq.w    #6,d7    ; subtract from size of remaining objects
            bne.s    OPL15    ; branch, if there are more
    ObjPosLoad_SameYRange:
            move.w    d6,($FFFFFE2A).w
            rts   
    ; ===========================================================================
    ; ---------------------------------------------------------------------------
    ; Subroutines to check if an object needs to be loaded,
    ; with and without y-wrapping enabled.
    ;
    ; input variables:
    ;  d3 = upper boundary to load object
    ;  d4 = lower boundary to load object
    ;  d5 = #$FFF, used to filter out object's y position
    ;
    ;  a0 = address in object placement list
    ;  a1 = object
    ;  a3 = address in object respawn table
    ;
    ; writes:
    ;  d1, d2, d7
    ; ---------------------------------------------------------------------------
    ChkLoadObj_YWrap:
            tst.b    (a3)    ; is object already loaded?
            bpl.s    OPL17    ; if not, branch
            addq.w    #6,a0    ; address of next object
            moveq    #0,d1    ; let the objects manager know that it can keep going
            rts
    ; ===========================================================================
    OPL17:    move.w    (a0)+,d7    ; x_pos
            move.w    (a0)+,d1    ; there are three things stored in this word
            move.w    d1,d2    ; does this object skip y-Checks?
            bmi.s    OPL18    ; if yes, branch
            and.w    d5,d1    ; y_pos
            cmp.w    d3,d1
            bcc.s    LoadObj_YWrap
            cmp.w    d4,d1
            bls.s    LoadObj_YWrap
            addq.w    #2,a0    ; address of next object
            moveq    #0,d1    ; let the objects manager know that it can keep going
            rts
    ; ===========================================================================
    OPL18:    and.w    d5,d1    ; y_pos
    LoadObj_YWrap:
            bset    #7,(a3)    ; mark object as loaded
            move.w    d7,8(a1)
            move.w    d1,$C(a1)
            rol.w    #3,d2    ; adjust bits
            andi.w    #3,d2    ; get render flags and status
            move.b    d2,1(a1)
            move.b    d2,$22(a1)
            move.b    (a0)+,0(a1)    ; load obj
            move.b    (a0)+,$28(a1)
            move.w    a3,$1E(a1)
            bra.s    SingleObjLoad    ; find new object slot
    ;loc_17F36
    ChkLoadObj:
            tst.b    (a3)    ; is object already loaded?
            bpl.s    OPL19    ; if not, branch
            addq.w    #6,a0    ; address of next object
            moveq    #0,d1    ; let the objects manager know that it can keep going
            rts
    ; ===========================================================================
    OPL19:    move.w    (a0)+,d7    ; x_pos
            move.w    (a0)+,d1    ; there are three things stored in this word
            move.w    d1,d2    ; does this object skip y-Checks?    ;*6
            bmi.s    OPL21    ; if yes, branch
            and.w    d5,d1    ; y_pos
            cmp.w    d3,d1
            bcs.s    OPL20    ; branch, if object is out of range from the top
            cmp.w    d4,d1
            bls.s    LoadObj    ; branch, if object is in range from the bottom
    OPL20:
            addq.w    #2,a0    ; address of next object
            moveq    #0,d1
            rts   
    ; ===========================================================================
    OPL21:    and.w    d5,d1    ; y_pos
    LoadObj:
            bset    #7,(a3)    ; mark object as loaded
            move.w    d7,8(a1)
            move.w    d1,$C(a1)
            rol.w    #3,d2    ; adjust bits
            andi.w    #3,d2    ; get render flags and status
            move.b    d2,1(a1)
            move.b    d2,$22(a1)
            move.b    (a0)+,0(a1)    ; load obj
            move.b    (a0)+,$28(a1)
            move.w    a3,$1E(a1)
            ; continue straight to SingleObjLoad
    ; End of function ChkLoadObj
    What do you think might be the problem?
    Also, $FFFFF000 is the location respawn table which was freed up when I ported Sonic 3's sound driver and $1E is the SST address used for respawning.

    This may or may be related, but I don't understand Step 5 of the tutorial. Both addresses it says to expend to word values are already longwords in both Sonic 1 and Sonic 2.
    Another thing is that first move.w uses a macro that I can't use in ASM68k so I don't know how many times for it to loop.
     
    Last edited: Mar 6, 2016