How to optimize Shield Art loading in Sonic 1

Discussion in 'Engineering & Reverse Engineering' started by Super Egg, Apr 30, 2014.

  1. Super Egg

    Super Egg

    Master of MS Paint. Member
    311
    0
    16
    Tomball, TEXAS
    Sonic 2 beta 3 hoax
    Have you ever been working on a hack in Sonic 1 and realized you don't have enough VRAM to load more art? I know I have, and the idea of not having enough room is quite annoying. Well no more, because with this tutorial,you nice folks will now have 31 free 8x8 blocks of VRAM.

    As a side note, this tutorial is written specifically with the 2005 disassembly in mind. I'm assuming that this tutorial is simple enough to port to the HG disassembly, but as I have little time to actually do it myself, or mental enough to try to waddle my way around the damn thing, port to your own risk. Also, this code should work in all of the Sonic 128 disassemblies as well, in fact this code was written is said disassembly.

    Enjoy

    Step One: Adding the DMA Queue

    If you haven't added this piece of code and it's fixes into your disassembly, go ahead and add it. You may have it already if you've added in the Spin Dash from this tutorial. If that is the case, go down to Step two.

    Look for "PalCycle_SS" and add this piece of code right before it. This will speed things up for later.

    [68k]
    ; ---------------------------------------------------------------------------
    ; Subroutine for queueing VDP commands (seems to only queue transfers to VRAM),
    ; to be issued the next time ProcessDMAQueue is called.
    ; Can be called a maximum of 18 times before the buffer needs to be cleared
    ; by issuing the commands (this subroutine DOES check for overflow)
    ; ---------------------------------------------------------------------------
    ; In case you wish to use this queue system outside of the spin dash, this is the
    ; registers in which it expects data in:
    ; d1.l: Address to data (In 68k address space)
    ; d2.w: Destination in VRAM
    ; d3.w: Length of data
    ; ---------------------------------------------------------------------------

    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

    ; sub_144E: DMA_68KtoVRAM: QueueCopyToVRAM: QueueVDPCommand: Add_To_DMA_Queue:
    QueueDMATransfer:
    movea.l ($FFFFC8FC).w,a1
    cmpa.w #$C8FC,a1
    beq.s QueueDMATransfer_Done ; return if there's no more room in the buffer

    ; piece together some VDP commands and store them for later...
    move.w #$9300,d0 ; command to specify DMA transfer length & $00FF
    move.b d3,d0
    move.w d0,(a1)+ ; store command

    move.w #$9400,d0 ; command to specify DMA transfer length & $FF00
    lsr.w #8,d3
    move.b d3,d0
    move.w d0,(a1)+ ; store command

    move.w #$9500,d0 ; command to specify source address & $0001FE
    lsr.l #1,d1
    move.b d1,d0
    move.w d0,(a1)+ ; store command

    move.w #$9600,d0 ; command to specify source address & $01FE00
    lsr.l #8,d1
    move.b d1,d0
    move.w d0,(a1)+ ; store command

    move.w #$9700,d0 ; command to specify source address & $FE0000
    lsr.l #8,d1
    move.b d1,d0
    move.w d0,(a1)+ ; store command

    andi.l #$FFFF,d2 ; command to specify destination address and begin DMA
    lsl.l #2,d2
    lsr.w #2,d2
    swap d2
    ori.l #$40000080,d2 ; set bits to specify VRAM transfer
    move.l d2,(a1)+ ; store command

    move.l a1,($FFFFC8FC).w ; set the next free slot address
    cmpa.w #$C8FC,a1
    beq.s QueueDMATransfer_Done ; return if there's no more room in the buffer
    move.w #0,(a1) ; put a stop token at the end of the used part of the buffer
    ; return_14AA:
    QueueDMATransfer_Done:
    rts
    ; End of function QueueDMATransfer


    ; ---------------------------------------------------------------------------
    ; Subroutine for issuing all VDP commands that were queued
    ; (by earlier calls to QueueDMATransfer)
    ; Resets the queue when it's done
    ; ---------------------------------------------------------------------------

    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

    ; sub_14AC: CopyToVRAM: IssueVDPCommands: Process_DMA: Process_DMA_Queue:
    ProcessDMAQueue:
    lea ($C00004).l,a5
    lea ($FFFFC800).w,a1
    ; loc_14B6:
    ProcessDMAQueue_Loop:
    move.w (a1)+,d0
    beq.s ProcessDMAQueue_Done ; branch if we reached a stop token
    ; issue a set of VDP commands...
    move.w d0,(a5) ; transfer length
    move.w (a1)+,(a5) ; transfer length
    move.w (a1)+,(a5) ; source address
    move.w (a1)+,(a5) ; source address
    move.w (a1)+,(a5) ; source address
    move.w (a1)+,(a5) ; destination
    move.w (a1)+,(a5) ; destination
    cmpa.w #$C8FC,a1
    bne.s ProcessDMAQueue_Loop ; loop if we haven't reached the end of the buffer
    ; loc_14CE:
    ProcessDMAQueue_Done:
    move.w #0,($FFFFC800).w
    move.l #$FFFFC800,($FFFFC8FC).w
    rts
    ; End of function ProcessDMAQueue[/68k]

    Once you've finished, go to "loc_CD4", "loc_DAE", "loc_EEE", and loc_FAE"

    Replace this piece of code.
    [68k] tst.b ($FFFFF767).w
    beq.s loc_D50
    lea ($C00004).l,a5
    move.l #$94019370,(a5)
    move.l #$96E49500,(a5)
    move.w #$977F,(a5)
    move.w #$7000,(a5)
    move.w #$83,($FFFFF640).w
    move.w ($FFFFF640).w,(a5)
    move.b #0,($FFFFF767).w[/68k]
    With this.
    [68k] jsr (ProcessDMAQueue).l [/68k]

    Next, go to "Level_ClrVars3". After this piece of code
    [68k]
    move.w #$8ADF,($FFFFF624).w
    move.w ($FFFFF624).w,(a6)
    [/68k]

    Add this
    [68k]
    clr.w ($FFFFC800).w
    move.l #$FFFFC800,($FFFFC8FC).w
    [/68k]

    Next, go to "loc_47D4". After
    [68k]
    jsr Hud_Base
    [/68k]

    Add

    [68k] clr.w ($FFFFC800).w
    move.l #$FFFFC800,($FFFFC8FC).w
    [/68k]

    The last fix will be fixing the Sonic Object.

    Go to "LoadSonicDynPLC". Replace this.

    [68k]
    ​ moveq #0,d0
    move.b $1A(a0),d0 ; load frame number
    cmp.b ($FFFFF766).w,d0
    beq.s locret_13C96
    move.b d0,($FFFFF766).w
    lea (SonicDynPLC).l,a2
    add.w d0,d0
    adda.w (a2,d0.w),a2
    moveq #0,d1
    move.b (a2)+,d1 ; read "number of entries" value
    subq.b #1,d1
    bmi.s locret_13C96
    lea ($FFFFC800).w,a3
    move.b #1,($FFFFF767).w

    SPLC_ReadEntry:
    moveq #0,d2
    move.b (a2)+,d2
    move.w d2,d0
    lsr.b #4,d0
    lsl.w #8,d2
    move.b (a2)+,d2
    lsl.w #5,d2
    lea (Art_Sonic).l,a1
    adda.l d2,a1

    SPLC_LoadTile:
    movem.l (a1)+,d2-d6/a4-a6
    movem.l d2-d6/a4-a6,(a3)
    lea $20(a3),a3 ; next tile
    dbf d0,SPLC_LoadTile ; repeat for number of tiles

    dbf d1,SPLC_ReadEntry ; repeat for number of entries

    locret_13C96:
    rts [/68k]

    With this.

    [68k] moveq #0,d0
    move.b $1A(a0),d0 ; load frame number
    cmp.b ($FFFFF766).w,d0
    beq.s locret_13C96
    move.b d0,($FFFFF766).w
    lea (SonicDynPLC).l,a2
    add.w d0,d0
    adda.w (a2,d0.w),a2
    moveq #0,d5
    move.b (a2)+,d5
    subq.w #1,d5
    bmi.s locret_13C96
    move.w #$F000,d4
    move.l #Art_Sonic,d6

    SPLC_ReadEntry:
    moveq #0,d1
    move.b (a2)+,d1
    lsl.w #8,d1
    move.b (a2)+,d1
    move.w d1,d3
    lsr.w #8,d3
    andi.w #$F0,d3
    addi.w #$10,d3
    andi.w #$FFF,d1
    lsl.l #5,d1
    add.l d6,d1
    move.w d4,d2
    add.w d3,d4
    add.w d3,d4
    jsr (QueueDMATransfer).l
    dbf d5,SPLC_ReadEntry ; repeat for number of entries

    locret_13C96:
    rts
    [/68k]
    At this point, your ROM should be safe to build, so go ahead and build. Go play a level and see if Sonic appears on screen. If so, that means you did good and you deserve a cookie.

    Step Two: Separating the Shield and Invincibility objects

    This step is probably the most difficult step, but once it is outta the way, it'll make things easier.

    First, go to Object 38, you should see this.

    [68k]
    Obj38:
    moveq #0,d0
    move.b $24(a0),d0
    move.w Obj38_Index(pc,d0.w),d1
    jmp Obj38_Index(pc,d1.w)
    ; ===========================================================================
    ; More more code.
    [/68k]

    Delete the whole object. In it's place put this in.

    [68k]
    Obj38: ; XREF: Obj_Index
    move.l #UnC_Shield,d1 ; Call for Regular Shield Art
    move.w #$A820,d2 ; Load Art from this location (VRAM location*20)
    ; In this case, VRAM = $541*20
    move.w #$200,d3
    jsr (QueueDMATransfer).l
    ; ---------------------------------------------------------------------------
    ShieldObj_Main:
    moveq #0,d0
    move.b $24(a0),d0
    move.w Shield_Index(pc,d0.w),d1
    jmp Shield_Index(pc,d1.w)
    ; ===========================================================================
    Shield_Index:
    dc.w Shield_Init-Shield_Index
    dc.w ShieldChecks-Shield_Index
    ; ===========================================================================
    Shield_Init:
    addq.b #2,$24(a0)
    move.l #Map_Obj38, $0004(A0) ; Load Shield Map into place
    move.b #4,1(a0)
    move.b #1,$18(a0)
    move.b #$18,$19(a0)
    move.w #$541,2(a0) ; Set VRAM location
    btst #7,($FFFFD002).w
    beq.s ShieldChecks
    bset #7,2(a0)
    ; ---------------------------------------------------------------------------
    ShieldChecks:
    tst.b ($FFFFFE2D).w ; Test if Sonic has a shield
    bne.s SonicHasShield ; If so, branch to do nothing
    tst.b ($FFFFFE2C).w ; Test if Sonic got invisibility
    beq.s jmp_DeleteObj38 ; If so, delete object temporarily
    ShieldProperties:
    move.w ($FFFFD008).w,8(a0) ; Load Main Character X-position
    move.w ($FFFFD00C).w,$C(a0) ; Load Main Character Y-position
    move.b ($FFFFD022).w,$22(a0) ; Something about Character status
    lea (Ani_obj38).l, a1 ; Load Animation Scripts into a1
    jsr AnimateSprite
    jmp DisplaySprite
    SonicHasShield:
    rts
    jmp_DeleteObj38: ; loc_12648:
    jmp DeleteObject
    [/68k]

    This object is now just the shield object. Now, let's go add in the invincibility object.

    Find Obj4A, you should see this.

    [68k]
    ; ---------------------------------------------------------------------------
    ; Object 4A - special stage entry from beta
    ; ---------------------------------------------------------------------------

    Obj4A: ; XREF: Obj_Index
    moveq #0,d0
    move.b $24(a0),d0
    move.w Obj4A_Index(pc,d0.w),d1
    jmp Obj4A_Index(pc,d1.w)
    ; More code after this
    [/68k]

    Replace the whole object with this.

    [68k]
    ; ---------------------------------------------------------------------------
    ; Object 4A - New Invincibility Object
    ; ---------------------------------------------------------------------------

    Obj4A: ; XREF: Obj_Index
    move.l #UnC_Stars,d1
    move.w #$A820,d2
    move.w #$200,d3
    jsr (QueueDMATransfer).l
    Invincibility_Main:
    moveq #0,d0
    move.b $24(a0),d0
    Invincibility_Init:
    addq.b #2,$24(a0)
    move.l #Map_obj38,4(a0) ; loads mapping
    move.b #4,1(a0)
    move.b #1,$18(a0)
    move.b #$10,$19(a0)
    move.w #$541,2(a0) ; shield specific code
    ; ===========================================================================

    Obj4A_Stars: ; XREF: Obj38_Index
    tst.b ($FFFFFE2D).w ; does Sonic have invincibility?
    beq.s Obj4A_Delete2 ; if not, branch
    move.w ($FFFFF7A8).w,d0
    move.b $1C(a0),d1
    subq.b #1,d1
    bra.s Obj4A_StarTrail
    ; ===========================================================================
    lsl.b #4,d1
    addq.b #4,d1
    sub.b d1,d0
    move.b $30(a0),d1
    sub.b d1,d0
    addq.b #4,d1
    andi.b #$F,d1
    move.b d1,$30(a0)
    bra.s Obj4A_StarTrail2a
    ; ===========================================================================

    Obj4A_StarTrail: ; XREF: Obj4A_Stars
    lsl.b #3,d1
    move.b d1,d2
    add.b d1,d1
    add.b d2,d1
    addq.b #4,d1
    sub.b d1,d0
    move.b $30(a0),d1
    sub.b d1,d0
    addq.b #4,d1
    cmpi.b #$18,d1
    bcs.s Obj4A_StarTrail2
    moveq #0,d1

    Obj4A_StarTrail2:
    move.b d1,$30(a0)

    Obj4A_StarTrail2a:
    lea ($FFFFCB00).w,a1
    lea (a1,d0.w),a1
    move.w (a1)+,8(a0)
    move.w (a1)+,$C(a0)
    move.b ($FFFFD022).w,$22(a0)
    lea (Ani_obj38).l,a1
    jsr (AnimateSprite).l
    jmp (DisplaySprite).l
    ; ===========================================================================

    Obj4A_Delete2: ; XREF: Obj4A_Stars
    jmp (DeleteObject).l
    [/68k]

    This completes the object replacement portion of the tutorial, let's move on. If you tried to build at this point, you'll notice it won't as there is a few errors. This next step will remedy that.

    Step Three: Adding and fixing the Art

    This step is the second most easiest step of this tutorial. First, you need to download these two files and save them into the "artunc" folder.

    Uncompressed Invicibility Stars Art
    Uncompressed Shield Art

    (btw, click on the names, they are hyperlinks straight to the art downloads.)

    [68k]
    Once that has been taken care of, go to "nem_Shield". You should see this.
    Nem_Shield: binclude artnem/shield.bin ; shield
    align 2
    Nem_Stars: binclude artnem/invstars.bin ; invincibility stars
    align 2 [/68k]

    Now, assuming you've already placed the art into the correct folder, go ahead and replace that with this.

    [68k]
    UnC_Shield: binclude artunc/shield.bin
    align 2
    UnC_Stars: binclude artunc/stars.bin ; invincibility stars
    align 2 [/68k]

    Now your objects will load the art. But before you go off and build, go open your "Pattern Load Cues.asm" file. You'll need to fix just one portion of the cues.

    Go to "PLC_Main2", and you should see this.

    [68k]
    PLC_Main2: dc.w 2
    dc.l Nem_Monitors ; monitors
    dc.w $D000
    dc.l Nem_Shield ; shield
    dc.w $A820
    dc.l Nem_Stars ; invincibility stars
    dc.w $AB80
    [/68k]

    Replace it with this.

    [68k]
    PLC_Main2: dc.w 0
    dc.l Nem_Monitors ; monitors
    dc.w $D000
    [/68k]

    This should fix all the art issues. Now, for those wondering why I'm making you load uncompressed art as opposed to Nemesis compressed art, a simple explanation shall be given.

    1. The DMA queue can not load Nemesis compressed art.
    2. Even if it could, you still need to remove the art from the PLC. The reason is that now your art between Invincibility and Shield now shares the same VRAM location, and will simply overwrite each other when the one of the objects is in use.

    Now, there is one last step to finish, and that is....

    Step Four: Object loading from the Monitors

    For those who have noticed, the shield and Invincibility objects are now separate objects. As such, the way that the monitor loads said objects must also be updated. Luckily for us, since only the Invincibility object is the only one that changed, this last step will be the easiest step of all.

    Go to "Obj2E_ChkInvinc". Replace this.

    [68k]
    Obj2E_ChkInvinc:
    cmpi.b #5,d0 ; does monitor contain invincibility?
    bne.s Obj2E_ChkRings
    tst.b ($FFFFFE19).w
    bne.s Obj2E_NoMusic
    move.b #1,($FFFFFE2D).w ; make Sonic invincible
    move.w #$4B0,($FFFFD032).w ; time limit for the power-up
    move.b #$38,($FFFFD200).w ; load stars object ($3801)
    move.b #1,($FFFFD21C).w
    move.b #$38,($FFFFD240).w ; load stars object ($3802)
    move.b #2,($FFFFD25C).w
    move.b #$38,($FFFFD280).w ; load stars object ($3803)
    move.b #3,($FFFFD29C).w
    move.b #$38,($FFFFD2C0).w ; load stars object ($3804)
    move.b #4,($FFFFD2DC).w
    tst.b ($FFFFF7AA).w ; is boss mode on?
    bne.s Obj2E_NoMusic ; if yes, branch
    move.w #$87,d0
    jmp (PlaySound).l ; play invincibility music
    [/68k]

    With this

    [68k]
    Obj2E_ChkInvinc:
    cmpi.b #5,d0 ; does monitor contain invincibility?
    bne.s Obj2E_ChkRings
    move.b #1,($FFFFFE2D).w ; Set Invisibility to 1
    move.w #$4B0,($FFFFD032).w ; Set Invisibility timer to 4B0
    move.b #$4A,($FFFFD200).w ; load stars object ($3801)
    move.b #1,($FFFFD21C).w
    move.b #$4A,($FFFFD240).w ; load stars object ($3802)
    move.b #2,($FFFFD25C).w
    move.b #$4A,($FFFFD280).w ; load stars object ($3803)
    move.b #3,($FFFFD29C).w
    move.b #$4A,($FFFFD2C0).w ; load stars object ($3804)
    move.b #4,($FFFFD2DC).w
    tst.b ($FFFFF7AA).w ; is boss mode on?
    bne.s DontPlayMusic ; If so, don't play music
    cmpi.w #$C,($FFFFFE14).w ; Check if Sonic has air left
    bls.s DontPlayMusic ; If so, don't play music
    move.w #$87,d0 ; Load Invisibility music
    jmp (PlaySound).l
    DontPlayMusic:
    rts
    [/68k]

    Once you have finished, save and build. If you've done everything correctly, your ROM will build.

    Special Thanks to Shockwave for double checking and testing this code.
    A thanks to shobiz, who already had the first part already done in a different tutorial. Nevertheless, saved me a bunch of time.
     
  2. Clownacy

    Clownacy

    Tech Member
    795
    24
    18
    Step Five: Introducing a DPLC system to the Shield =P

    At least, if you want to be more true to "Make your Shields load art like S3K."

    I apologise, but I can't find much to praise about this guide: Keeping the usage of the 2005 disasm aside, it's great to see something about freeing up VRAM, for the longest time I had to use that early post in Classic Heroes' thread as a basic guideline, figuring out the rest with no additional help (the experience was worth it though), but, to be blunt, I see too much of a copy-and-paste formula to your guide.

    You have general explanations here and there, but on a code level, there aren't enough. I can understand this in the case of the more vague, somewhat irrelevant code - I can't imagine anyone really wanting to be told how the entire DMA Queue works in a guide on dynamically loading uncompressed art - but areas such as Step Four, sure you talk of what is to be changed and provide a copy of the changed code, but there were no specifics; you didn't even change the comments in the code detailing the object that's loaded. There's also the borderline vital explanation of the data moved to d3 before jumping to QueueDMATransfer, it's the number of bytes to transfer (divided by 2, so, words?)! In fact, your values are incorrect: the Shield's should be $1B0, and the Invincibility Stars', $240. Because of this, the Shield unnecessarily loads some Invincibility Stars art, and the the Invincibility Stars don't even load fully!

    I'm capable of navigating this guide and seeing where and what changes have been made, but can a noob? I know it can be seen as rude to say that guides have to cater to the newbies, but as you've seen, sometimes we don't need the guides, they do. You've said it yourself, you pulled off the Art Loader port without some guide, so could any of the fellas around here that know their stuff. But I'm sure it was enough to give some insight on Sonic 1's art loading process to at least someone.
     
  3. RetroKoH

    RetroKoH

    Member
    1,642
    6
    18
    Project Sonic 8x16
    I myself have done this for my hack a while back... With that said, I'm gonna point out some things:

    1. You don't need to separate the Shield and Invincibility objects... I've been able to have this work just fine without splitting those up. Seems like an extra, unnecessary step IMO. That said, I'm really only nitpicking.

    2. As Clownacy pointed out above. DPLCs??? Without using DPLC's its all but pointless to use Uncompressed art. You want absolutely as FEW tiles in VRAM as possible, and I'll tell you, DPLC's are the way to go with this one. In fact, I was able to cram in that Beta Special stage warp object into my hack, WITH DPLCs... they simply share the same space that the Shield and Invincibility uses... overwriting them when it is visible, much like the Warp Effect in Sonic CD does.

    This is a step in the right direction.... but it could be better. I'll find some time tomorrow to post code from my hack and how I did it.... (with a lil bit of help from a certain Caverns4). PS It'll be in the HG Disassembly format. YAY!

    Otherwise, nice stuff. VRAM management is something not very well done in Sonic 1, and we could all use plenty of tips and tricks on how to optimize VRAM usage in the game.
     
  4. Super Egg

    Super Egg

    Master of MS Paint. Member
    311
    0
    16
    Tomball, TEXAS
    Sonic 2 beta 3 hoax
    Honestly, I agree with Clownacy, it could be done better, and I'm working on it right now. Don't add anything to that disasm just yet.

    Fine. I'll do the the DPLC's.

    Also, what is up with you people and the S1 HG disasm? it like you get hard on's from it like the S2 clone Driver...

    *shot*
     
  5. Clownacy

    Clownacy

    Tech Member
    795
    24
    18
    I didn't bother dwelling on the topic of the better S1 disasm because I'm not too fond of the HG one either, probably for different reasons though. I find it to not be 'HG' enough: a bunch of unequated RAM addresses, the JP1 code is very messy, uncommented/undocumented components that are unchanged yet fully documented in the S2 HG diasam. I just see it as the Hivebrain disasm with some-but-not-enough improvements and incredibly difficult navigation, which rubs off on me as "One step forward, two steps back."

    KingofHarts should be right on the combined Shield/Invin Stars usage of dynamic art. Simply relocate the jump to QueueDMATransfer and associated code to the beginning of the individual 'Init' routines. The Shield's would go under Obj38_Main, and the Stars' would go under Obj38_DoStars, which would have its write to 2(a0) commented out. Though, I haven't tested this.
     
  6. RetroKoH

    RetroKoH

    Member
    1,642
    6
    18
    Project Sonic 8x16
    Really the HG thing is more a matter of preference more than anything else... I just think it's a bit better labeled... plus in the case of Sonic 1... the bug fixes and changes made in Revision 01 are as simple as a switch of a variable... whereas you've gotta put them in yourself in the old version (Not a difficult task, so long as you possess basic reading abilities) That... and the HG disasm's are what I started out with.

    Now... that being said, I do have the Hivebrain and Xenowhirl disasm's on my computer, and do look to them for reference from time to time... so I'm not completely ignorant to them... but I so much more prefer the better labeling, and the existence of macros and constants, and the like.

    Below is my code for invincibility and stars... I added a fix from Mercury's ReadySonic, and also my own addition for taking the Bonus Stage entry into account. Credit for the DPLC implementation goes to myself and Caverns4 in a joint effort.
    As for the actual DPLC script itself, I'll leave it to you and the other readers to figure out. Shouldn't be too hard.

    Code (Text):
    1.  
    2.  
    3. ; ---------------------------------------------------------------------------
    4. ; Object 38 - shield and invincibility stars
    5. ; ---------------------------------------------------------------------------
    6.  
    7. ShieldItem:             ; XREF: Obj_Index
    8.         moveq   #0,d0
    9.         move.b  routine(a0),d0
    10.         move.w  Shi_Index(pc,d0.w),d1
    11.         jmp Shi_Index(pc,d1.w)
    12. ; ===========================================================================
    13. Shi_Index:  dc.w Shi_Main-Shi_Index
    14.         dc.w Shi_Shield-Shi_Index
    15.         dc.w Shi_Stars-Shi_Index
    16. ; ===========================================================================
    17.  
    18. Shi_Main:   ; Routine 0
    19.         addq.b  #2,routine(a0)
    20.         move.l  #Map_Shield,mappings(a0)
    21.         move.b  #4,render_flags(a0)
    22.         move.w  #$80,priority(a0)     ; REV C EDIT - S3K PRIORITY
    23.         move.b  #$10,width_pixels(a0)
    24.         move.w  #$2550,art_tile(a0)
    25.         tst.b   anim(a0)    ; is object a shield?
    26.         beq.s   @shield     ; if yes, branch
    27.         addq.b  #2,routine(a0) ; Stars specific code: goto Shi_Stars next
    28. @shield
    29.                 rts
    30. ; ===========================================================================
    31.  
    32. Shi_Shield: ; Routine 2
    33.         btst    #1,(v_2ndstatus).w  ; does Sonic have invincibility?
    34.         bne.s   @remove     ; if yes, branch
    35.         btst    #0,(v_2ndstatus).w  ; does Sonic have shield?
    36.         beq.s   @delete     ; if not, branch
    37.         move.w  (v_player+x_pos).w,x_pos(a0)
    38.         move.w  (v_player+y_pos).w,y_pos(a0)
    39.         move.b  (v_player+status).w,status(a0)
    40.         ; Mercury's Balance Positioning fix - REV C EDIT
    41.                 move.b  status(a0),d0
    42.         move.w  #$A,d1
    43.         cmpi.b  #id_Balance,(v_player+anim).w
    44.         bne.s   @noshift
    45.     @shift:
    46.         sub.w   d1,x_pos(a0)
    47.         btst    #staFacing,d0
    48.         beq.s   @noshift
    49.         add.w   d1,d1
    50.         add.w   d1,x_pos(a0)
    51.     @noshift:
    52.         ; End of Mercury's Balance Positioning fix - REV C EDIT
    53.        
    54.                 tst.b   ($FFFFD3C0).w       ; Check for Bonus Stage entry
    55.         beq.s   @novanish       ; if Sonic is not entering a Bonus Stage, skip
    56.         rts             ; return, and don't display shield
    57.     @novanish:
    58.                 lea (Ani_Shield).l,a1
    59.         jsr AnimateSprite
    60.         bsr.w   Shield_LoadGfx
    61.         jmp DisplaySprite
    62.     @remove:
    63.         rts
    64.     @delete:
    65.         jmp DeleteObject
    66. ; ===========================================================================
    67.  
    68. Shi_Stars:  ; Routine 4
    69.         btst    #1,(v_2ndstatus).w  ; does Sonic have invincibility?
    70.         beq.s   Shi_Start_Delete    ; if not, branch
    71.         move.w  (v_trackpos).w,d0 ; get index value for tracking data
    72.         move.b  anim(a0),d1
    73.         subq.b  #1,d1
    74.         bra.s   @trail
    75. ; ===========================================================================
    76.         lsl.b   #4,d1
    77.         addq.b  #4,d1
    78.         sub.b   d1,d0
    79.         move.b  $30(a0),d1
    80.         sub.b   d1,d0
    81.         addq.b  #4,d1
    82.         andi.b  #$F,d1
    83.         move.b  d1,$30(a0)
    84.         bra.s   @b
    85. ; ===========================================================================
    86.  
    87. @trail:
    88.         lsl.b   #3,d1       ; multiply animation number by 8
    89.         move.b  d1,d2
    90.         add.b   d1,d1
    91.         add.b   d2,d1       ; multiply by 3
    92.         addq.b  #4,d1
    93.         sub.b   d1,d0
    94.         move.b  $30(a0),d1
    95.         sub.b   d1,d0       ; use earlier tracking data to create trail
    96.         addq.b  #4,d1
    97.         cmpi.b  #$18,d1
    98.         bcs.s   @a
    99.         moveq   #0,d1
    100.  
    101.     @a:
    102.         move.b  d1,$30(a0)
    103.  
    104.     @b:
    105.         lea (v_tracksonic).w,a1
    106.         lea (a1,d0.w),a1
    107.         move.w  (a1)+,x_pos(a0)
    108.         move.w  (a1)+,y_pos(a0)
    109.         move.b  (v_player+status).w,status(a0)
    110.  
    111.         tst.b   ($FFFFD3C0).w       ; Check for Bonus Stage entry
    112.         beq.s   @novanish       ; if Sonic is not entering a Bonus Stage, skip
    113.         rts             ; return, and don't display invincibility stars
    114.     @novanish:
    115.         lea (Ani_Shield).l,a1
    116.         jsr AnimateSprite
    117.         bsr.s   Stars_LoadGfx
    118.         jmp DisplaySprite
    119. ; ===========================================================================
    120.  
    121. Shi_Start_Delete:
    122.         jmp DeleteObject
    123.  
    124. ; ---------------------------------------------------------------------------
    125. ; Shield and Stars dynamic pattern loading subroutine
    126. ; ---------------------------------------------------------------------------
    127.  
    128. Stars_LoadGfx:
    129.         moveq   #0,d0
    130.         move.b  ($FFFFD21A).w,d0    ; load frame number
    131.         move.l  #Art_Stars,d6
    132.         bra.s   ShieldPLC_Cont
    133.  
    134. Shield_LoadGfx:
    135.                 moveq   #0,d0
    136.         move.b  ($FFFFD19A).w,d0    ; load frame number
    137.         move.l  #Art_Shield,d6
    138.  
    139. ShieldPLC_Cont:
    140.             lea (ShieldDynPLC).l,a2
    141.         add.w   d0,d0
    142.         adda.w  (a2,d0.w),a2
    143.         moveq   #0,d5
    144.         move.b  (a2)+,d5          ; read "number of entries" value
    145.         subq.w  #1,d5
    146.         bmi.s   ShieldDPLC_Return ; if zero, branch
    147.         move.w  #$AA00,d4
    148.  
    149. ShieldPLC_ReadEntry:
    150.         moveq   #0,d1
    151.         move.b  (a2)+,d1
    152.         lsl.w   #8,d1
    153.         move.b  (a2)+,d1
    154.         move.w  d1,d3
    155.         lsr.w   #8,d3
    156.         andi.w  #$F0,d3
    157.         addi.w  #$10,d3
    158.         andi.w  #$FFF,d1
    159.         lsl.l   #5,d1
    160.         add.l   d6,d1
    161.         move.w  d4,d2
    162.         add.w   d3,d4
    163.         add.w   d3,d4
    164.         jsr (QueueDMATransfer).l
    165.         dbf d5,ShieldPLC_ReadEntry  ; repeat for number of entries
    166.  
    167. ShieldDPLC_Return:
    168.         rts
    169.  
     
  7. Clownacy

    Clownacy

    Tech Member
    795
    24
    18
    You can be better off by replacing your modified LoadSonicDynPLC with Sonic 3&K's PLCLoad_Shields. It's mainly for only if you have multiple DPLC-using objects, like in S3K's case of the Elemental Shields. Instead of hardcoding the address of the art, DPLC file, and the sort; you simply load the addresses into some unused SSTs in the 'Init' routine, and then later branch to PLCLoad_Shields as you would with LoadSonicDynPLC. Apparently, it accepts a different DPLC format: S3K's "Object DPLC" instead of the usual "Player DPLC". The latter following S2's format.
     
  8. RetroKoH

    RetroKoH

    Member
    1,642
    6
    18
    Project Sonic 8x16
    You know what... you make a perfectly good point. Thanks, I shall do just that, and suggest the same to SuperEgg as well.
     
  9. Clownacy

    Clownacy

    Tech Member
    795
    24
    18
    Just finished adding PLCLoad_Shields to the Shield and Invincibility Stars, and have some things to report on it.

    It looks like I was wrong on it using Object DPLCs, looking at the routine and the stock S3K Elemental Shields DPLCs, the routine uses Player DPLCs. Makes me wonder what does use Object DPLCs if not these.

    DPLC usage reduces the Invincibility Stars' VRAM footprint to half, only $12 tiles are ever loaded at once. The Shield does not see as great an optimisation. However, the maximum is still $12, with 9 tiles shaven off.

    Further optimisation can be done by using some more complex mappings to eliminate some blank tiles; two can be found in the Shield and four in the Stars. Leaving the maximum tiles loaded at $11 for the Shield and $10 for the Stars.

    Going back to the current guide, I'm not familiar with the Object Manager, but typically, in objects that use a routine system, there's a block of code labelled 'Init' (ObjXX_Init, etc). It's designed to be ran only the once, at object initialisation, unlike 'Main' (ObjXX_Main, etc), which appears to be ran every frame without the use of a manual loop ('WaitForVInt, then dbf' for example). For Main to run every frame, the code that branches to it also has to run every frame, right? Say, anyone noticed how the QueueDMATransfer jump is in the code that uses the object's routine counter to branch to an entry of the object's index? Said code being one of the only potential branches to the 'Main' code?

    Uh oh, you know what this means? The objects art is being transferred on every single frame of the object's presence.

    Despite the lack of this routine-index code in the S3K objects that dynamically load their art in the manner of this DPLC-less guide (Obj_Invincibility, Obj_SuperSonicKnux_Stars), the code that loads the art is still located within 'Init' code territory: it is only processed the once due to S3K's unique ID (now simply an address) system.
     
  10. MarkeyJester

    MarkeyJester

    ♡ ! Resident Jester
    I'd like to play Devil's advocate here for a moment.

    You're now reaching the point in optimisation where sacrifices are to be made in order to optimise in one form. That is to say you're sacrificing processing time and sprite count in favour of VRAM memory space. Don't get me wrong, I'm all for the whole "saving VRAM by loading individual frames of art", but when you start to go excessively one sided and forget the downfalls such as sprite limits, perhaps it'd be prudent to accept a fair compromise between memory usage, sprite usage, and processing time usage.
     
  11. Clownacy

    Clownacy

    Tech Member
    795
    24
    18
    Of course, my own hack doesn't go as far as to have saved those few tiles. The Invincibility Stars cause enough flickering as they are. But I'd hardly forgotten about the Sprite Limit, I just thought everyone here knew about it. My wording was to hint at it ("complex" over "better"), though it was subtle.

    S3K's developers appear to have had come to a similar conclusion: Objects such as the Elemental Shields are riddled with blank tiles.
     
  12. RetroKoH

    RetroKoH

    Member
    1,642
    6
    18
    Project Sonic 8x16
    If I'm understanding this point you are making correctly, This is something to also keep in mind when porting the spindash... I.E. Look how Mercury does it in ReadySonic compared to how it's done in S3K